diff --git a/Android.bp b/Android.bp index e5d4b8b87c88781a4d0f0b0ee84517a80d365eac..d0bee2890fb767f955952dbb6a16e6e42ca80fd2 100644 --- a/Android.bp +++ b/Android.bp @@ -113,11 +113,12 @@ java_library { "core/java/android/content/IOnPrimaryClipChangedListener.aidl", "core/java/android/content/IRestrictionsManager.aidl", "core/java/android/content/ISyncAdapter.aidl", + "core/java/android/content/ISyncAdapterUnsyncableAccountCallback.aidl", "core/java/android/content/ISyncContext.aidl", "core/java/android/content/ISyncServiceAdapter.aidl", "core/java/android/content/ISyncStatusObserver.aidl", "core/java/android/content/om/IOverlayManager.aidl", - "core/java/android/content/pm/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", @@ -147,9 +148,10 @@ java_library { "core/java/android/hardware/display/IDisplayManager.aidl", "core/java/android/hardware/display/IDisplayManagerCallback.aidl", "core/java/android/hardware/display/IVirtualDisplayCallback.aidl", + "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl", + "core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl", "core/java/android/hardware/fingerprint/IFingerprintService.aidl", "core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl", - "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl", "core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl", "core/java/android/hardware/hdmi/IHdmiControlCallback.aidl", "core/java/android/hardware/hdmi/IHdmiControlService.aidl", @@ -177,6 +179,8 @@ java_library { "core/java/android/hardware/location/IContextHubClientCallback.aidl", "core/java/android/hardware/location/IContextHubService.aidl", "core/java/android/hardware/location/IContextHubTransactionCallback.aidl", + "core/java/android/hardware/radio/IAnnouncementListener.aidl", + "core/java/android/hardware/radio/ICloseHandle.aidl", "core/java/android/hardware/radio/IRadioService.aidl", "core/java/android/hardware/radio/ITuner.aidl", "core/java/android/hardware/radio/ITunerCallback.aidl", @@ -208,6 +212,11 @@ java_library { "core/java/android/nfc/INfcUnlockHandler.aidl", "core/java/android/nfc/INfcDta.aidl", "core/java/android/nfc/ITagRemovedCallback.aidl", + "core/java/android/se/omapi/ISecureElementService.aidl", + "core/java/android/se/omapi/ISecureElementListener.aidl", + "core/java/android/se/omapi/ISecureElementChannel.aidl", + "core/java/android/se/omapi/ISecureElementReader.aidl", + "core/java/android/se/omapi/ISecureElementSession.aidl", "core/java/android/os/IBatteryPropertiesListener.aidl", "core/java/android/os/IBatteryPropertiesRegistrar.aidl", "core/java/android/os/ICancellationSignal.aidl", @@ -230,6 +239,7 @@ java_library { "core/java/android/os/ISchedulingPolicyService.aidl", "core/java/android/os/IStatsCompanionService.aidl", "core/java/android/os/IStatsManager.aidl", + "core/java/android/os/ISystemUpdateManager.aidl", "core/java/android/os/IThermalEventListener.aidl", "core/java/android/os/IThermalService.aidl", "core/java/android/os/IUpdateLock.aidl", @@ -239,9 +249,11 @@ java_library { "core/java/android/os/storage/IStorageEventListener.aidl", "core/java/android/os/storage/IStorageShutdownObserver.aidl", "core/java/android/os/storage/IObbActionListener.aidl", + "core/java/android/security/IConfirmationPromptCallback.aidl", "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", @@ -324,6 +336,10 @@ java_library { "core/java/android/view/IOnKeyguardExitResult.aidl", "core/java/android/view/IPinnedStackController.aidl", "core/java/android/view/IPinnedStackListener.aidl", + "core/java/android/view/IRemoteAnimationRunner.aidl", + "core/java/android/view/IRecentsAnimationController.aidl", + "core/java/android/view/IRecentsAnimationRunner.aidl", + "core/java/android/view/IRemoteAnimationFinishedCallback.aidl", "core/java/android/view/IRotationWatcher.aidl", "core/java/android/view/IWallpaperVisibilityListener.aidl", "core/java/android/view/IWindow.aidl", @@ -403,9 +419,9 @@ java_library { "location/java/android/location/IGpsGeofenceHardware.aidl", "location/java/android/location/INetInitiatedListener.aidl", "location/java/com/android/internal/location/ILocationProvider.aidl", - "media/java/android/media/IAudioService.aidl", "media/java/android/media/IAudioFocusDispatcher.aidl", "media/java/android/media/IAudioRoutesObserver.aidl", + "media/java/android/media/IAudioService.aidl", "media/java/android/media/IMediaHTTPConnection.aidl", "media/java/android/media/IMediaHTTPService.aidl", "media/java/android/media/IMediaResourceMonitor.aidl", @@ -414,6 +430,7 @@ java_library { "media/java/android/media/IMediaScannerListener.aidl", "media/java/android/media/IMediaScannerService.aidl", "media/java/android/media/IPlaybackConfigDispatcher.aidl", + "media/java/android/media/ISessionTokensListener.aidl", ":libaudioclient_aidl", "media/java/android/media/IRecordingConfigDispatcher.aidl", "media/java/android/media/IRemoteDisplayCallback.aidl", @@ -464,6 +481,8 @@ java_library { "telecomm/java/com/android/internal/telecom/IInCallService.aidl", "telecomm/java/com/android/internal/telecom/ITelecomService.aidl", "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl", + "telephony/java/android/telephony/data/IDataService.aidl", + "telephony/java/android/telephony/data/IDataServiceCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl", @@ -471,17 +490,16 @@ java_library { "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", - "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", + "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", "telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl", "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl", "telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl", "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl", + "telephony/java/android/telephony/INetworkService.aidl", + "telephony/java/android/telephony/INetworkServiceCallback.aidl", "telephony/java/com/android/ims/internal/IImsCallSession.aidl", "telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl", "telephony/java/com/android/ims/internal/IImsConfig.aidl", @@ -492,10 +510,13 @@ java_library { "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl", "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl", "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl", + "telephony/java/com/android/ims/internal/IImsRegistration.aidl", + "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl", "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl", "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", "telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl", + "telephony/java/com/android/ims/internal/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", @@ -519,9 +540,31 @@ java_library { "telephony/java/com/android/internal/telephony/ITelephony.aidl", "telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl", "telephony/java/com/android/internal/telephony/IWapPushManager.aidl", + "telephony/java/com/android/internal/telephony/euicc/IAuthenticateServerCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/ICancelSessionCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IDeleteProfileCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IDisableProfileCallback.aidl", "telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl", "telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl", "telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetDefaultSmdpAddressCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetEuiccChallengeCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo1Callback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetEuiccInfo2Callback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetProfileCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetRulesAuthTableCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IGetSmdsAddressCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IListNotificationsCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/ILoadBoundProfilePackageCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IPrepareDownloadCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IRemoveNotificationFromListCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IResetMemoryCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/IRetrieveNotificationListCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/ISetDefaultSmdpAddressCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl", + "telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl", + "wifi/java/android/net/wifi/ISoftApCallback.aidl", "wifi/java/android/net/wifi/IWifiManager.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl", @@ -558,6 +601,7 @@ java_library { "core/java/android/speech/tts/EventLogTags.logtags", "core/java/android/net/EventLogTags.logtags", "core/java/android/webkit/EventLogTags.logtags", + "core/java/com/android/internal/app/EventLogTags.logtags", "core/java/com/android/internal/logging/EventLogTags.logtags", "core/java/com/android/server/DropboxLogTags.logtags", "core/java/org/chromium/arc/EventLogTags.logtags", @@ -599,6 +643,11 @@ java_library { ], }, + // See comment on framework-oahl-backward-compatibility module below + exclude_srcs: [ + "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java", + ], + no_framework_libs: true, libs: [ "conscrypt", @@ -624,7 +673,9 @@ java_library { ], // Loaded with System.loadLibrary by android.view.textclassifier - required: ["libtextclassifier"], + required: [ + "libtextclassifier", + "libmedia2_jni",], javac_shard_size: 150, @@ -634,6 +685,18 @@ java_library { ], } +// A temporary build target that is conditionally included on the bootclasspath if +// org.apache.http.legacy library has been removed and which provides support for +// maintaining backwards compatibility for APKs that target pre-P and depend on +// org.apache.http.legacy classes. This is used iff REMOVE_OAHL_FROM_BCP=true is +// specified on the build command line. +java_library { + name: "framework-oahl-backward-compatibility", + srcs: [ + "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java", + ], +} + genrule { name: "framework-statslog-gen", tools: ["stats-log-api-gen"], @@ -661,7 +724,10 @@ gensrcs { " $(in) " + "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)", - srcs: ["core/proto/**/*.proto"], + srcs: [ + "core/proto/**/*.proto", + "libs/incident/**/*.proto", + ], output_extension: "srcjar", } @@ -771,7 +837,9 @@ java_library { srcs: [ "core/java/android/os/HidlSupport.java", + "core/java/android/annotation/IntDef.java", "core/java/android/annotation/NonNull.java", + "core/java/android/annotation/SystemApi.java", "core/java/android/os/HwBinder.java", "core/java/android/os/HwBlob.java", "core/java/android/os/HwParcel.java", diff --git a/Android.mk b/Android.mk index 3c6dd37acefe1cb547cac46cb0946b2cac0f9d54..35b5f92be3107180849d52bfa30d8b46f4ee2244 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) \ @@ -339,6 +331,8 @@ LOCAL_DROIDDOC_OPTIONS:=\ $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \ -referenceonly \ -api $(INTERNAL_PLATFORM_API_FILE) \ + -privateApi $(INTERNAL_PLATFORM_PRIVATE_API_FILE) \ + -privateDexApi $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \ -removedApi $(INTERNAL_PLATFORM_REMOVED_API_FILE) \ -nodocs @@ -348,7 +342,9 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -$(INTERNAL_PLATFORM_API_FILE): $(full_target) +$(full_target): .KATI_IMPLICIT_OUTPUTS := $(INTERNAL_PLATFORM_API_FILE) \ + $(INTERNAL_PLATFORM_PRIVATE_API_FILE) \ + $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) # ==== the system api stubs =================================== @@ -373,6 +369,8 @@ LOCAL_DROIDDOC_OPTIONS:=\ -referenceonly \ -showAnnotation android.annotation.SystemApi \ -api $(INTERNAL_PLATFORM_SYSTEM_API_FILE) \ + -privateApi $(INTERNAL_PLATFORM_SYSTEM_PRIVATE_API_FILE) \ + -privateDexApi $(INTERNAL_PLATFORM_SYSTEM_PRIVATE_DEX_API_FILE) \ -removedApi $(INTERNAL_PLATFORM_SYSTEM_REMOVED_API_FILE) \ -exactApi $(INTERNAL_PLATFORM_SYSTEM_EXACT_API_FILE) \ -nodocs @@ -383,7 +381,9 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -$(INTERNAL_PLATFORM_SYSTEM_API_FILE): $(full_target) +$(full_target): .KATI_IMPLICIT_OUTPUTS := $(INTERNAL_PLATFORM_SYSTEM_API_FILE) \ + $(INTERNAL_PLATFORM_SYSTEM_PRIVATE_API_FILE) \ + $(INTERNAL_PLATFORM_SYSTEM_PRIVATE_DEX_API_FILE) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE)) # ==== the test api stubs =================================== @@ -789,6 +789,7 @@ LOCAL_PROTOC_FLAGS := \ LOCAL_SOURCE_FILES_ALL_GENERATED := true LOCAL_SRC_FILES := \ cmds/am/proto/instrumentation_data.proto \ + cmds/statsd/src/perfetto/perfetto_config.proto \ $(call all-proto-files-under, core/proto) \ $(call all-proto-files-under, libs/incident/proto) \ $(call all-proto-files-under, cmds/statsd/src) @@ -805,7 +806,8 @@ LOCAL_PROTO_JAVA_OUTPUT_PARAMS := \ store_unknown_fields = true LOCAL_JAVA_LIBRARIES := core-oj core-libart LOCAL_SRC_FILES := \ - $(call all-proto-files-under, core/proto) + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto/android/os) include $(BUILD_STATIC_JAVA_LIBRARY) @@ -817,9 +819,42 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := lite LOCAL_PROTOC_FLAGS := \ -Iexternal/protobuf/src LOCAL_SRC_FILES := \ - $(call all-proto-files-under, core/proto) + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto/android/os) include $(BUILD_STATIC_JAVA_LIBRARY) +# ==== hiddenapi lists ======================================= + +# Copy blacklist and dark greylist over into the build folder. +# This is for ART buildbots which need to mock these lists and have alternative +# rules for building them. Other rules in the build system should depend on the +# files in the build folder. + +$(eval $(call copy-one-file,frameworks/base/config/hiddenapi-blacklist.txt,\ + $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST))) +$(eval $(call copy-one-file,frameworks/base/config/hiddenapi-dark-greylist.txt,\ + $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST))) + +# Generate light greylist as private API minus (blacklist plus dark greylist). + +$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): PRIVATE_API := $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) +$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): BLACKLIST := $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) +$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): DARK_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST) +$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \ + $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) \ + $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST) + if [ ! -z "`comm -12 <(sort $(BLACKLIST)) <(sort $(DARK_GREYLIST))`" ]; then \ + echo "There should be no overlap between $(BLACKLIST) and $(DARK_GREYLIST)" 1>&2; \ + exit 1; \ + elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST))`" ]; then \ + echo "$(BLACKLIST) must be a subset of $(PRIVATE_API)" 1>&2; \ + exit 2; \ + elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(DARK_GREYLIST))`" ]; then \ + echo "$(DARK_GREYLIST) must be a subset of $(PRIVATE_API)" 1>&2; \ + exit 3; \ + fi + comm -23 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST) $(DARK_GREYLIST)) > $@ + # Include subdirectory makefiles # ============================================================ @@ -829,4 +864,5 @@ ifeq (,$(ONE_SHOT_MAKEFILE)) include $(call first-makefiles-under,$(LOCAL_PATH)) endif -endif # ANDROID_BUILD_EMBEDDED \ No newline at end of file +endif # ANDROID_BUILD_EMBEDDED + diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml index bd0b944e12339ce03174b2584f6810b20cdeb616..132a2f9b2d0c589921ae549f2ab306e9ba35761c 100644 --- a/apct-tests/perftests/core/AndroidManifest.xml +++ b/apct-tests/perftests/core/AndroidManifest.xml @@ -10,7 +10,11 @@ - + + + + + diff --git a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java new file mode 100644 index 0000000000000000000000000000000000000000..145fbcd2d5d7cb351101ac25e292425becd86acf --- /dev/null +++ b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.os; + +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PackageManagerPerfTest { + private static final String PERMISSION_NAME_EXISTS = + "com.android.perftests.core.TestPermission"; + private static final String PERMISSION_NAME_DOESNT_EXIST = + "com.android.perftests.core.TestBadPermission"; + private static final ComponentName TEST_ACTIVITY = + new ComponentName("com.android.perftests.core", "android.perftests.utils.StubActivity"); + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Test + public void testCheckPermissionExists() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); + final String packageName = TEST_ACTIVITY.getPackageName(); + + while (state.keepRunning()) { + int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName); + } + } + + @Test + public void testCheckPermissionDoesntExist() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); + final String packageName = TEST_ACTIVITY.getPackageName(); + + while (state.keepRunning()) { + int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName); + } + } + + @Test + public void testQueryIntentActivities() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); + final Intent intent = new Intent("com.android.perftests.core.PERFTEST"); + + while (state.keepRunning()) { + pm.queryIntentActivities(intent, 0); + } + } + + @Test + public void testGetPackageInfo() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); + final String packageName = TEST_ACTIVITY.getPackageName(); + + while (state.keepRunning()) { + pm.getPackageInfo(packageName, 0); + } + } + + @Test + public void testGetApplicationInfo() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); + final String packageName = TEST_ACTIVITY.getPackageName(); + + while (state.keepRunning()) { + pm.getApplicationInfo(packageName, 0); + } + } + + @Test + public void testGetActivityInfo() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); + + while (state.keepRunning()) { + pm.getActivityInfo(TEST_ACTIVITY, 0); + } + } +} diff --git a/apct-tests/perftests/core/src/android/os/PermissionTest.java b/apct-tests/perftests/core/src/android/os/PermissionTest.java deleted file mode 100644 index d292e7dc3b96641956109c860a9bbfb1caedda74..0000000000000000000000000000000000000000 --- a/apct-tests/perftests/core/src/android/os/PermissionTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package android.os; - -import static android.content.pm.PackageManager.PERMISSION_DENIED; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import android.content.Context; -import android.perftests.utils.BenchmarkState; -import android.perftests.utils.PerfStatusReporter; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class PermissionTest { - private static final String PERMISSION_HAS_NAME = "com.android.perftests.core.TestPermission"; - private static final String PERMISSION_DOESNT_HAVE_NAME = - "com.android.perftests.core.TestBadPermission"; - private static final String THIS_PACKAGE_NAME = "com.android.perftests.core"; - - @Rule - public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - - @Test - public void testHasPermission() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final Context context = InstrumentationRegistry.getTargetContext(); - while (state.keepRunning()) { - int ret = context.getPackageManager().checkPermission(PERMISSION_HAS_NAME, - THIS_PACKAGE_NAME); - } - } - - @Test - public void testDoesntHavePermission() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final Context context = InstrumentationRegistry.getTargetContext(); - - while (state.keepRunning()) { - int ret = context.getPackageManager().checkPermission(PERMISSION_DOESNT_HAVE_NAME, - THIS_PACKAGE_NAME); - } - } - -} diff --git a/apct-tests/perftests/core/src/android/os/PssPerfTest.java b/apct-tests/perftests/core/src/android/os/PssPerfTest.java new file mode 100644 index 0000000000000000000000000000000000000000..400115deeeb8e9cc83690302952905c075cad847 --- /dev/null +++ b/apct-tests/perftests/core/src/android/os/PssPerfTest.java @@ -0,0 +1,41 @@ +/* + * 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.os; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PssPerfTest { + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Test + public void testPss() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + Debug.getPss(); + } + } +} diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fc6302ea93942d30532e9d5ae185ead48eb31d78 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java @@ -0,0 +1,132 @@ +/* + * 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.text; + +import static android.text.TextDirectionHeuristics.LTR; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import android.app.Activity; +import android.os.Bundle; +import android.support.test.InstrumentationRegistry; +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.CharBuffer; +import java.util.Random; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class MeasuredTextMemoryUsageTest { + private static final int WORD_LENGTH = 9; // Random word has 9 characters. + private static final boolean NO_STYLE_TEXT = false; + + private static TextPaint PAINT = new TextPaint(); + + private static int TRIAL_COUNT = 100; + + public MeasuredTextMemoryUsageTest() {} + + private TextPerfUtils mTextUtil = new TextPerfUtils(); + + @Before + public void setUp() { + mTextUtil.resetRandom(0 /* seed */); + } + + private void reportMemoryUsage(int memoryUsage, String key) { + Bundle status = new Bundle(); + status.putInt(key + "_median", memoryUsage); + InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status); + } + + private int median(int[] values) { + return values.length % 2 == 0 ? + (values[values.length / 2] + values[values.length / 2 - 1]) / 2: + values[values.length / 2]; + } + + @Test + public void testMemoryUsage_NoHyphenation() { + int[] memories = new int[TRIAL_COUNT]; + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build().getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation"); + } + + @Test + public void testMemoryUsage_Hyphenation() { + int[] memories = new int[TRIAL_COUNT]; + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build().getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation"); + } + + @Test + public void testMemoryUsage_NoHyphenation_WidthOnly() { + int[] memories = new int[TRIAL_COUNT]; + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(false /* width only */).getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation_WidthOnly"); + } + + @Test + public void testMemoryUsage_Hyphenatation_WidthOnly() { + int[] memories = new int[TRIAL_COUNT]; + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(false /* width only */).getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation_WidthOnly"); + } +} diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java new file mode 100644 index 0000000000000000000000000000000000000000..98f2bd5e573652d1feba4220546293bef82d1865 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java @@ -0,0 +1,185 @@ +/* + * 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.text; + +import static android.text.TextDirectionHeuristics.LTR; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.CharBuffer; +import java.util.Random; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class MeasuredTextPerfTest { + private static final int WORD_LENGTH = 9; // Random word has 9 characters. + private static final int WORDS_IN_LINE = 8; // Roughly, 8 words in a line. + private static final boolean NO_STYLE_TEXT = false; + private static final boolean STYLE_TEXT = true; + + private static TextPaint PAINT = new TextPaint(); + private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize(); + + public MeasuredTextPerfTest() {} + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private TextPerfUtils mTextUtil = new TextPerfUtils(); + + @Before + public void setUp() { + mTextUtil.resetRandom(0 /* seed */); + } + + @Test + public void testCreate_NoStyled_Hyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(true /* do full layout */); + } + } + + @Test + public void testCreate_NoStyled_NoHyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(true /* do full layout */); + } + } + + @Test + public void testCreate_NoStyled_Hyphenation_WidthOnly() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(false /* width only */); + } + } + + @Test + public void testCreate_NoStyled_NoHyphenation_WidthOnly() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(false /* width only */); + } + } + + @Test + public void testCreate_Styled_Hyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(true /* do full layout */); + } + } + + @Test + public void testCreate_Styled_NoHyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(true /* do full layout */); + } + } + + @Test + public void testCreate_Styled_Hyphenation_WidthOnly() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(false /* width only */); + } + } + + @Test + public void testCreate_Styled_NoHyphenation_WidthOnly() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(false /* width only */); + } + } +} diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 5653a039a9ed5120a18480a26902bf7324b40bb1..bab2a859698cb59f77c82b39b125b6e1e6eb8277 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -25,10 +25,14 @@ import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; import android.content.res.ColorStateList; +import android.graphics.Canvas; import android.graphics.Typeface; import android.text.Layout; import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,69 +43,30 @@ import java.util.Random; @LargeTest @RunWith(AndroidJUnit4.class) public class StaticLayoutPerfTest { - - public StaticLayoutPerfTest() {} - - @Rule - public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - private static final int WORD_LENGTH = 9; // Random word has 9 characters. private static final int WORDS_IN_LINE = 8; // Roughly, 8 words in a line. - private static final int PARA_LENGTH = 500; // Number of characters in a paragraph. - private static final boolean NO_STYLE_TEXT = false; private static final boolean STYLE_TEXT = true; - private final Random mRandom = new Random(31415926535L); - - private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - private static final int ALPHABET_LENGTH = ALPHABET.length(); - - private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000); - private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" }; - private static final int[] STYLES = { - Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC - }; - - private final char[] mBuffer = new char[PARA_LENGTH]; - private static TextPaint PAINT = new TextPaint(); private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize(); - private CharSequence generateRandomParagraph(int wordLen, boolean applyRandomStyle) { - for (int i = 0; i < PARA_LENGTH; i++) { - if (i % (wordLen + 1) == wordLen) { - mBuffer[i] = ' '; - } else { - mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH)); - } - } - - CharSequence cs = CharBuffer.wrap(mBuffer); - if (!applyRandomStyle) { - return cs; - } + public StaticLayoutPerfTest() {} - SpannableStringBuilder ssb = new SpannableStringBuilder(cs); - for (int i = 0; i < ssb.length(); i += WORD_LENGTH) { - final int spanStart = i; - final int spanEnd = (i + WORD_LENGTH) > ssb.length() ? ssb.length() : i + WORD_LENGTH; + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - final TextAppearanceSpan span = new TextAppearanceSpan( - FAMILIES[mRandom.nextInt(FAMILIES.length)], - STYLES[mRandom.nextInt(STYLES.length)], - 24 + mRandom.nextInt(32), // text size. min 24 max 56 - TEXT_COLOR, TEXT_COLOR); + private TextPerfUtils mTextUtil = new TextPerfUtils(); - ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - return ssb; + @Before + public void setUp() { + mTextUtil.resetRandom(0 /* seed */); } @Test public void testCreate_FixedText_NoStyle_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); while (state.keepRunning()) { StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) @@ -115,7 +80,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -130,7 +95,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -145,7 +110,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -160,7 +125,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -175,7 +140,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -190,8 +155,11 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(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 +174,11 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(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 +193,11 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(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 +212,11 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(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 +231,11 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( - generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(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) @@ -264,4 +244,157 @@ public class StaticLayoutPerfTest { .build(); } } + + @Test + public void testDraw_FixedText_NoStyled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_Styled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_NoStyled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_Styled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_NoStyled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_Styled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_NoStyled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_Styled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_NoStyled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + } diff --git a/apct-tests/perftests/core/src/android/text/TextPerfUtils.java b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..dccb34be9d079420396477e52dd05a865e661bb3 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java @@ -0,0 +1,93 @@ +/* + * 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.text; + +import static android.text.TextDirectionHeuristics.LTR; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.CharBuffer; +import java.util.Random; + +public class TextPerfUtils { + + private static final int PARA_LENGTH = 500; // Number of characters in a paragraph. + + private Random mRandom = new Random(0); + + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final int ALPHABET_LENGTH = ALPHABET.length(); + + private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000); + private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" }; + private static final int[] STYLES = { + Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC + }; + + private final char[] mBuffer = new char[PARA_LENGTH]; + + public void resetRandom(long seed) { + mRandom = new Random(seed); + } + + public CharSequence nextRandomParagraph(int wordLen, boolean applyRandomStyle) { + for (int i = 0; i < PARA_LENGTH; i++) { + if (i % (wordLen + 1) == wordLen) { + mBuffer[i] = ' '; + } else { + mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH)); + } + } + + CharSequence cs = CharBuffer.wrap(mBuffer); + if (!applyRandomStyle) { + return cs; + } + + SpannableStringBuilder ssb = new SpannableStringBuilder(cs); + for (int i = 0; i < ssb.length(); i += wordLen) { + final int spanStart = i; + final int spanEnd = (i + wordLen) > ssb.length() ? ssb.length() : i + wordLen; + + final TextAppearanceSpan span = new TextAppearanceSpan( + FAMILIES[mRandom.nextInt(FAMILIES.length)], + STYLES[mRandom.nextInt(STYLES.length)], + 24 + mRandom.nextInt(32), // text size. min 24 max 56 + TEXT_COLOR, TEXT_COLOR); + + ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + return ssb; + } +} diff --git a/api/current.txt b/api/current.txt index 7bdac7abfb312a550e1d1298a800a0dce6275363..800412bd2e1279bd85d27bf636a24e0011fbe23f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6,6 +6,7 @@ package android { public static final class Manifest.permission { ctor public Manifest.permission(); + field public static final java.lang.String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"; field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES"; field public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; field public static final java.lang.String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"; @@ -93,6 +94,7 @@ package android { field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS"; field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"; field public static final java.lang.String NFC = "android.permission.NFC"; + field public static final java.lang.String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT"; field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field public static final deprecated java.lang.String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY"; field public static final java.lang.String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; @@ -211,6 +213,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 @@ -602,6 +605,7 @@ package android { field public static final int fingerprintAuthDrawable = 16844008; // 0x10104e8 field public static final int finishOnCloseSystemDialogs = 16843431; // 0x10102a7 field public static final int finishOnTaskLaunch = 16842772; // 0x1010014 + field public static final int firstBaselineToTopHeight = 16844157; // 0x101057d field public static final int firstDayOfWeek = 16843581; // 0x101033d field public static final int fitsSystemWindows = 16842973; // 0x10100dd field public static final int flipInterval = 16843129; // 0x1010179 @@ -798,6 +802,7 @@ package android { field public static final int largeHeap = 16843610; // 0x101035a field public static final int largeScreens = 16843398; // 0x1010286 field public static final int largestWidthLimitDp = 16843622; // 0x1010366 + field public static final int lastBaselineToBottomHeight = 16844158; // 0x101057e field public static final int launchMode = 16842781; // 0x101001d field public static final int launchTaskBehindSourceAnimation = 16843922; // 0x1010492 field public static final int launchTaskBehindTargetAnimation = 16843921; // 0x1010491 @@ -855,6 +860,7 @@ package android { field public static final int left = 16843181; // 0x10101ad field public static final int letterSpacing = 16843958; // 0x10104b6 field public static final int level = 16844032; // 0x1010500 + field public static final int lineHeight = 16844159; // 0x101057f field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 field public static final int lines = 16843092; // 0x1010154 @@ -1805,6 +1811,7 @@ package android { public static final class R.id { ctor public R.id(); field public static final int accessibilityActionContextClick = 16908348; // 0x102003c + field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045 field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042 field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039 @@ -1813,6 +1820,7 @@ package android { field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038 field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036 + field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044 field public static final int addToDictionary = 16908330; // 0x102002a field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 @@ -1895,6 +1903,7 @@ package android { field public static final int decelerate_cubic = 17563651; // 0x10c0003 field public static final int decelerate_quad = 17563649; // 0x10c0001 field public static final int decelerate_quint = 17563653; // 0x10c0005 + field public static final int fast_out_extra_slow_in = 17563674; // 0x10c001a field public static final int fast_out_linear_in = 17563663; // 0x10c000f field public static final int fast_out_slow_in = 17563661; // 0x10c000d field public static final int linear = 17563659; // 0x10c000b @@ -2792,6 +2801,7 @@ package android.accessibilityservice { field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6 field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5 field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3 + field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9 field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7 field public static final java.lang.String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice"; @@ -3759,6 +3769,7 @@ package android.app { method public final void requestShowKeyboardShortcuts(); method public deprecated boolean requestVisibleBehind(boolean); method public final boolean requestWindowFeature(int); + method public final T requireViewById(int); method public final void runOnUiThread(java.lang.Runnable); method public void setActionBar(android.widget.Toolbar); method public void setContentTransitionManager(android.transition.TransitionManager); @@ -4453,6 +4464,7 @@ package android.app { method public void openOptionsMenu(); method public void registerForContextMenu(android.view.View); method public final boolean requestWindowFeature(int); + method public final T requireViewById(int); method public void setCancelMessage(android.os.Message); method public void setCancelable(boolean); method public void setCanceledOnTouchOutside(boolean); @@ -5216,6 +5228,7 @@ package android.app { field public static final java.lang.String EXTRA_PROGRESS = "android.progress"; field public static final java.lang.String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; field public static final java.lang.String EXTRA_PROGRESS_MAX = "android.progressMax"; + field public static final java.lang.String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; field public static final java.lang.String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; field public static final deprecated java.lang.String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; field public static final java.lang.String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; @@ -5688,6 +5701,7 @@ package android.app { method public final void setInterruptionFilter(int); method public void setNotificationPolicy(android.app.NotificationManager.Policy); method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule); + field public static final java.lang.String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; @@ -5856,11 +5870,15 @@ package android.app { method public java.lang.CharSequence getLabel(); method public java.lang.String getResultKey(); method public static android.os.Bundle getResultsFromIntent(android.content.Intent); + method public static int getResultsSource(android.content.Intent); method public boolean isDataOnly(); + method public static void setResultsSource(android.content.Intent, int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results"; + field public static final int SOURCE_CHOICE = 1; // 0x1 + field public static final int SOURCE_FREE_FORM_INPUT = 0; // 0x0 } public static final class RemoteInput.Builder { @@ -6335,9 +6353,13 @@ package android.app.admin { method public void onReceive(android.content.Context, android.content.Intent); method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); + method public void onTransferAffiliatedProfileOwnershipComplete(android.content.Context, android.os.UserHandle); method public void onTransferOwnershipComplete(android.content.Context, android.os.PersistableBundle); method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle); + method public void onUserStarted(android.content.Context, android.content.Intent, android.os.UserHandle); + method public void onUserStopped(android.content.Context, android.content.Intent, android.os.UserHandle); + method public void onUserSwitched(android.content.Context, android.content.Intent, android.os.UserHandle); field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; @@ -6353,7 +6375,8 @@ package android.app.admin { field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin"; field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING"; field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE"; - field public static final java.lang.String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE"; + field public static final java.lang.String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE"; + field public static final java.lang.String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = "android.app.support_transfer_ownership"; } public class DeviceAdminService extends android.app.Service { @@ -6364,10 +6387,11 @@ package android.app.admin { public class DevicePolicyManager { method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int); method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); + method public int addOverrideApn(android.content.ComponentName, android.telephony.data.ApnSetting); method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); - method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener, java.util.concurrent.Executor); + method public void clearApplicationUserData(android.content.ComponentName, java.lang.String, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener); method public void clearCrossProfileIntentFilters(android.content.ComponentName); method public deprecated void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); @@ -6397,16 +6421,20 @@ package android.app.admin { method public java.util.List getDelegatePackages(android.content.ComponentName, java.lang.String); method public java.util.List getDelegatedScopes(android.content.ComponentName, java.lang.String); method public java.lang.CharSequence getDeviceOwnerLockScreenInfo(); + method public java.lang.CharSequence getEndUserSessionMessage(android.content.ComponentName); method public java.util.List getInstalledCaCerts(android.content.ComponentName); method public java.util.List getKeepUninstalledPackages(android.content.ComponentName); method public int getKeyguardDisabledFeatures(android.content.ComponentName); method public int getLockTaskFeatures(android.content.ComponentName); method public java.lang.String[] getLockTaskPackages(android.content.ComponentName); method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName); + method public android.content.ComponentName getMandatoryBackupTransport(); method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); method public long getMaximumTimeToLock(android.content.ComponentName); + method public java.util.List getMeteredDataDisabled(android.content.ComponentName); method public int getOrganizationColor(android.content.ComponentName); method public java.lang.CharSequence getOrganizationName(android.content.ComponentName); + method public java.util.List getOverrideApns(android.content.ComponentName); method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); method public java.lang.String getPasswordBlacklistName(android.content.ComponentName); method public long getPasswordExpiration(android.content.ComponentName); @@ -6431,9 +6459,11 @@ package android.app.admin { method public boolean getScreenCaptureDisabled(android.content.ComponentName); method public java.util.List getSecondaryUsers(android.content.ComponentName); method public java.lang.CharSequence getShortSupportMessage(android.content.ComponentName); + method public java.lang.CharSequence getStartUserSessionMessage(android.content.ComponentName); method public boolean getStorageEncryption(android.content.ComponentName); method public int getStorageEncryptionStatus(); method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy(); + method public android.os.PersistableBundle getTransferOwnershipBundle(); method public java.util.List getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName); method public android.os.Bundle getUserRestrictions(android.content.ComponentName); method public java.lang.String getWifiMacAddress(android.content.ComponentName); @@ -6457,7 +6487,9 @@ package android.app.admin { method public boolean isManagedProfile(android.content.ComponentName); method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isNetworkLoggingEnabled(android.content.ComponentName); + method public boolean isOverrideApnEnabled(android.content.ComponentName); method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public boolean isPrintingEnabled(); method public boolean isProfileOwnerApp(java.lang.String); method public boolean isProvisioningAllowed(java.lang.String); method public boolean isResetPasswordTokenActive(android.content.ComponentName); @@ -6471,6 +6503,7 @@ package android.app.admin { method public void removeActiveAdmin(android.content.ComponentName); method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public boolean removeKeyPair(android.content.ComponentName, java.lang.String); + method public boolean removeOverrideApn(android.content.ComponentName, int); method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean requestBugreport(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); @@ -6493,6 +6526,7 @@ package android.app.admin { method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean); method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List); method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence); + method public void setEndUserSessionMessage(android.content.ComponentName, java.lang.CharSequence); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List); method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List, boolean); @@ -6502,12 +6536,15 @@ package android.app.admin { method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException; method public void setLogoutEnabled(android.content.ComponentName, boolean); method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence); + method public void setMandatoryBackupTransport(android.content.ComponentName, android.content.ComponentName); method public void setMasterVolumeMuted(android.content.ComponentName, boolean); method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int); method public void setMaximumTimeToLock(android.content.ComponentName, long); + method public java.util.List setMeteredDataDisabled(android.content.ComponentName, java.util.List); method public void setNetworkLoggingEnabled(android.content.ComponentName, boolean); method public void setOrganizationColor(android.content.ComponentName, int); method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence); + method public void setOverrideApnsEnabled(android.content.ComponentName, boolean); method public java.lang.String[] setPackagesSuspended(android.content.ComponentName, java.lang.String[], boolean); method public boolean setPasswordBlacklist(android.content.ComponentName, java.lang.String, java.util.List); method public void setPasswordExpirationTimeout(android.content.ComponentName, long); @@ -6525,6 +6562,7 @@ package android.app.admin { method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List); method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List); method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List); + method public void setPrintingEnabled(android.content.ComponentName, boolean); method public void setProfileEnabled(android.content.ComponentName); method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); @@ -6535,6 +6573,7 @@ package android.app.admin { method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public void setSecurityLoggingEnabled(android.content.ComponentName, boolean); method public void setShortSupportMessage(android.content.ComponentName, java.lang.CharSequence); + method public void setStartUserSessionMessage(android.content.ComponentName, java.lang.CharSequence); method public boolean setStatusBarDisabled(android.content.ComponentName, boolean); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void setSystemSetting(android.content.ComponentName, java.lang.String, java.lang.String); @@ -6544,11 +6583,13 @@ package android.app.admin { method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap); + method public boolean startUserInBackground(android.content.ComponentName, android.os.UserHandle); method public boolean stopUser(android.content.ComponentName, android.os.UserHandle); method public boolean switchUser(android.content.ComponentName, android.os.UserHandle); method public void transferOwnership(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void uninstallAllUserCaCerts(android.content.ComponentName); method public void uninstallCaCert(android.content.ComponentName, byte[]); + method public boolean updateOverrideApn(android.content.ComponentName, int, android.telephony.data.ApnSetting); method public void wipeData(int); method public void wipeDataWithReason(int, java.lang.CharSequence); field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN"; @@ -6653,10 +6694,10 @@ package android.app.admin { field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0 field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera"; field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture"; + field public static final java.lang.String POLICY_MANDATORY_BACKUPS = "policy_mandatory_backups"; field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 - field public static final int START_USER_IN_BACKGROUND = 8; // 0x8 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 } @@ -6683,20 +6724,46 @@ package android.app.admin { public class SecurityLog { ctor public SecurityLog(); + field public static final int LEVEL_ERROR = 3; // 0x3 + field public static final int LEVEL_INFO = 1; // 0x1 + field public static final int LEVEL_WARNING = 2; // 0x2 field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452 field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451 field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455 + field public static final int TAG_CERT_AUTHORITY_INSTALLED = 210029; // 0x3346d + field public static final int TAG_CERT_AUTHORITY_REMOVED = 210030; // 0x3346e + field public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET = 210021; // 0x33465 field public static final int TAG_KEYGUARD_DISMISSED = 210006; // 0x33456 field public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = 210007; // 0x33457 field public static final int TAG_KEYGUARD_SECURED = 210008; // 0x33458 + field public static final int TAG_KEY_DESTRUCTION = 210026; // 0x3346a + field public static final int TAG_KEY_GENERATED = 210024; // 0x33468 + field public static final int TAG_KEY_IMPORT = 210025; // 0x33469 + field public static final int TAG_LOGGING_STARTED = 210011; // 0x3345b + field public static final int TAG_LOGGING_STOPPED = 210012; // 0x3345c + field public static final int TAG_LOG_BUFFER_SIZE_CRITICAL = 210015; // 0x3345f + field public static final int TAG_MAX_PASSWORD_ATTEMPTS_SET = 210020; // 0x33464 + field public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = 210019; // 0x33463 + field public static final int TAG_MEDIA_MOUNT = 210013; // 0x3345d + field public static final int TAG_MEDIA_UNMOUNT = 210014; // 0x3345e + field public static final int TAG_OS_SHUTDOWN = 210010; // 0x3345a + field public static final int TAG_OS_STARTUP = 210009; // 0x33459 + field public static final int TAG_PASSWORD_COMPLEXITY_SET = 210017; // 0x33461 + field public static final int TAG_PASSWORD_EXPIRATION_SET = 210016; // 0x33460 + field public static final int TAG_PASSWORD_HISTORY_LENGTH_SET = 210018; // 0x33462 + field public static final int TAG_REMOTE_LOCK = 210022; // 0x33466 field public static final int TAG_SYNC_RECV_FILE = 210003; // 0x33453 field public static final int TAG_SYNC_SEND_FILE = 210004; // 0x33454 + field public static final int TAG_USER_RESTRICTION_ADDED = 210027; // 0x3346b + field public static final int TAG_USER_RESTRICTION_REMOVED = 210028; // 0x3346c + field public static final int TAG_WIPE_FAILURE = 210023; // 0x33467 } public static final class SecurityLog.SecurityEvent implements android.os.Parcelable { method public int describeContents(); method public java.lang.Object getData(); method public long getId(); + method public int getLogLevel(); method public int getTag(); method public long getTimeNanos(); method public void writeToParcel(android.os.Parcel, int); @@ -6719,9 +6786,11 @@ package android.app.admin { method public static android.app.admin.SystemUpdatePolicy createPostponeInstallPolicy(); method public static android.app.admin.SystemUpdatePolicy createWindowedInstallPolicy(int, int); method public int describeContents(); + method public java.util.List> getFreezePeriods(); method public int getInstallWindowEnd(); method public int getInstallWindowStart(); method public int getPolicyType(); + method public android.app.admin.SystemUpdatePolicy setFreezePeriods(java.util.List>); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int TYPE_INSTALL_AUTOMATIC = 1; // 0x1 @@ -6729,6 +6798,18 @@ package android.app.admin { field public static final int TYPE_POSTPONE = 3; // 0x3 } + public static final class SystemUpdatePolicy.ValidationFailedException extends java.lang.IllegalArgumentException implements android.os.Parcelable { + method public int describeContents(); + method public int getErrorCode(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5; // 0x5 + field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4; // 0x4 + field public static final int ERROR_DUPLICATE_OR_OVERLAP = 1; // 0x1 + field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3; // 0x3 + field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2; // 0x2 + } + } package android.app.assist { @@ -6854,6 +6935,8 @@ package android.app.backup { method public void onRestore(android.app.backup.BackupDataInput, long, android.os.ParcelFileDescriptor) throws java.io.IOException; method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException; method public void onRestoreFinished(); + field public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; // 0x1 + field public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2; // 0x2 field public static final int TYPE_DIRECTORY = 2; // 0x2 field public static final int TYPE_FILE = 1; // 0x1 } @@ -6881,6 +6964,7 @@ package android.app.backup { public class BackupDataOutput { method public long getQuota(); + method public int getTransportFlags(); method public int writeEntityData(byte[], int) throws java.io.IOException; method public int writeEntityHeader(java.lang.String, int) throws java.io.IOException; } @@ -6895,7 +6979,7 @@ package android.app.backup { ctor public BackupManager(android.content.Context); method public void dataChanged(); method public static void dataChanged(java.lang.String); - method public int requestRestore(android.app.backup.RestoreObserver); + method public deprecated int requestRestore(android.app.backup.RestoreObserver); } public class FileBackupHelper implements android.app.backup.BackupHelper { @@ -6906,6 +6990,7 @@ package android.app.backup { public class FullBackupDataOutput { method public long getQuota(); + method public int getTransportFlags(); } public abstract class RestoreObserver { @@ -6977,6 +7062,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); @@ -7066,8 +7152,8 @@ package android.app.slice { public final class Slice implements android.os.Parcelable { ctor protected Slice(android.os.Parcel); - method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List); - method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List); + method public static deprecated android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List); + method public static deprecated android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List); method public int describeContents(); method public java.util.List getHints(); method public java.util.List getItems(); @@ -7075,26 +7161,30 @@ package android.app.slice { method public android.net.Uri getUri(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final java.lang.String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; field public static final java.lang.String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; field public static final java.lang.String HINT_ACTIONS = "actions"; + field public static final java.lang.String HINT_CALLER_NEEDED = "caller_needed"; field public static final java.lang.String HINT_HORIZONTAL = "horizontal"; field public static final java.lang.String HINT_LARGE = "large"; field public static final java.lang.String HINT_LIST = "list"; field public static final java.lang.String HINT_LIST_ITEM = "list_item"; - field public static final java.lang.String HINT_MAX = "max"; field public static final java.lang.String HINT_NO_TINT = "no_tint"; field public static final java.lang.String HINT_PARTIAL = "partial"; + field public static final java.lang.String HINT_SEE_MORE = "see_more"; field public static final java.lang.String HINT_SELECTED = "selected"; field public static final java.lang.String HINT_SHORTCUT = "shortcut"; field public static final java.lang.String HINT_SUMMARY = "summary"; field public static final java.lang.String HINT_TITLE = "title"; field public static final java.lang.String SUBTYPE_COLOR = "color"; field public static final java.lang.String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; + field public static final java.lang.String SUBTYPE_MAX = "max"; field public static final java.lang.String SUBTYPE_MESSAGE = "message"; field public static final java.lang.String SUBTYPE_PRIORITY = "priority"; field public static final java.lang.String SUBTYPE_SLIDER = "slider"; field public static final java.lang.String SUBTYPE_SOURCE = "source"; field public static final java.lang.String SUBTYPE_TOGGLE = "toggle"; + field public static final java.lang.String SUBTYPE_VALUE = "value"; } public static class Slice.Builder { @@ -7104,8 +7194,6 @@ package android.app.slice { method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String); method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, java.lang.String, java.lang.String...); method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, java.lang.String, java.util.List); - method public deprecated android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...); - method public deprecated android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List); method public android.app.slice.Slice.Builder addHints(java.lang.String...); method public android.app.slice.Slice.Builder addHints(java.util.List); method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...); @@ -7128,7 +7216,6 @@ package android.app.slice { method public int describeContents(); method public android.app.PendingIntent getAction(); method public android.os.Bundle getBundle(); - method public deprecated int getColor(); method public java.lang.String getFormat(); method public java.util.List getHints(); method public android.graphics.drawable.Icon getIcon(); @@ -7143,7 +7230,6 @@ package android.app.slice { field public static final android.os.Parcelable.Creator CREATOR; field public static final java.lang.String FORMAT_ACTION = "action"; field public static final java.lang.String FORMAT_BUNDLE = "bundle"; - field public static final deprecated java.lang.String FORMAT_COLOR = "color"; field public static final java.lang.String FORMAT_IMAGE = "image"; field public static final java.lang.String FORMAT_INT = "int"; field public static final java.lang.String FORMAT_REMOTE_INPUT = "input"; @@ -7153,11 +7239,15 @@ package android.app.slice { } public class SliceManager { + method public android.app.slice.Slice bindSlice(android.net.Uri, java.util.List); + method public android.app.slice.Slice bindSlice(android.content.Intent, java.util.List); method public java.util.List getPinnedSpecs(android.net.Uri); + method public java.util.Collection getSliceDescendants(android.net.Uri); method public void pinSlice(android.net.Uri, java.util.List); - method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List); - method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List, android.os.Handler); - method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List, java.util.concurrent.Executor); + method public deprecated void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List); + method public deprecated void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List, java.util.concurrent.Executor); + method public void registerSliceCallback(android.net.Uri, java.util.List, android.app.slice.SliceManager.SliceCallback); + method public void registerSliceCallback(android.net.Uri, java.util.List, java.util.concurrent.Executor, android.app.slice.SliceManager.SliceCallback); method public void unpinSlice(android.net.Uri); method public void unregisterSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback); } @@ -7169,10 +7259,11 @@ package android.app.slice { public abstract class SliceProvider extends android.content.ContentProvider { ctor public SliceProvider(); method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); + method public final java.lang.String getBindingPackage(); method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List); - method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri); + method public java.util.Collection onGetSliceDescendants(android.net.Uri); method public android.net.Uri onMapIntentToUri(android.content.Intent); method public void onSlicePinned(android.net.Uri); method public void onSliceUnpinned(android.net.Uri); @@ -7229,6 +7320,7 @@ package android.app.usage { public static class NetworkStats.Bucket { ctor public NetworkStats.Bucket(); + method public int getDefaultNetwork(); method public long getEndTimeStamp(); method public int getMetered(); method public int getRoaming(); @@ -7240,6 +7332,9 @@ package android.app.usage { method public long getTxBytes(); method public long getTxPackets(); method public int getUid(); + field public static final int DEFAULT_NETWORK_ALL = -1; // 0xffffffff + field public static final int DEFAULT_NETWORK_NO = 1; // 0x1 + field public static final int DEFAULT_NETWORK_YES = 2; // 0x2 field public static final int METERED_ALL = -1; // 0xffffffff field public static final int METERED_NO = 1; // 0x1 field public static final int METERED_YES = 2; // 0x2 @@ -8715,6 +8810,7 @@ package android.content { method public void onSecurityException(android.accounts.Account, android.os.Bundle, java.lang.String, android.content.SyncResult); method public void onSyncCanceled(); method public void onSyncCanceled(java.lang.Thread); + method public boolean onUnsyncableAccount(); field public static final deprecated int LOG_SYNC_DETAILS = 2743; // 0xab7 } @@ -9947,6 +10043,7 @@ package android.content { field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000 field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000 field public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 4096; // 0x1000 + field public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 2048; // 0x800 field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000 field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000 field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000 @@ -10669,6 +10766,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 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); @@ -10834,7 +10938,8 @@ package android.content.pm { field public android.content.pm.ServiceInfo[] services; field public java.lang.String sharedUserId; field public int sharedUserLabel; - field public android.content.pm.Signature[] signatures; + field public deprecated android.content.pm.Signature[] signatures; + field public android.content.pm.Signature[][] signingCertificateHistory; field public java.lang.String[] splitNames; field public int[] splitRevisionCodes; field public deprecated int versionCode; @@ -11037,6 +11142,8 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle); method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); + method public boolean hasSigningCertificate(java.lang.String, byte[], int); + method public boolean hasSigningCertificate(int, byte[], int); method public abstract boolean hasSystemFeature(java.lang.String); method public abstract boolean hasSystemFeature(java.lang.String, int); method public abstract boolean isInstantApp(); @@ -11062,6 +11169,8 @@ package android.content.pm { method public abstract void setInstallerPackageName(java.lang.String, java.lang.String); method public abstract void updateInstantAppCookie(byte[]); method public abstract void verifyPendingInstall(int, int); + field public static final int CERT_INPUT_RAW_X509 = 0; // 0x0 + field public static final int CERT_INPUT_SHA256 = 1; // 0x1 field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0 field public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; // 0x2 field public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4; // 0x4 @@ -11082,6 +11191,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le"; field public static final java.lang.String FEATURE_CAMERA = "android.hardware.camera"; field public static final java.lang.String FEATURE_CAMERA_ANY = "android.hardware.camera.any"; + field public static final java.lang.String FEATURE_CAMERA_AR = "android.hardware.camera.ar"; field public static final java.lang.String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus"; field public static final java.lang.String FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING = "android.hardware.camera.capability.manual_post_processing"; field public static final java.lang.String FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR = "android.hardware.camera.capability.manual_sensor"; @@ -11141,6 +11251,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector"; field public static final java.lang.String FEATURE_SIP = "android.software.sip"; field public static final java.lang.String FEATURE_SIP_VOIP = "android.software.sip.voip"; + field public static final java.lang.String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore"; field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony"; field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; @@ -11154,7 +11265,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host"; field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot"; field public static final java.lang.String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; - field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode"; + field public static final deprecated java.lang.String FEATURE_VR_MODE = "android.software.vr.mode"; field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance"; field public static final java.lang.String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute"; field public static final java.lang.String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level"; @@ -11180,7 +11291,8 @@ package android.content.pm { field public static final int GET_RESOLVED_FILTER = 64; // 0x40 field public static final int GET_SERVICES = 4; // 0x4 field public static final int GET_SHARED_LIBRARY_FILES = 1024; // 0x400 - field public static final int GET_SIGNATURES = 64; // 0x40 + field public static final deprecated int GET_SIGNATURES = 64; // 0x40 + field public static final int GET_SIGNING_CERTIFICATES = 134217728; // 0x8000000 field public static final deprecated int GET_UNINSTALLED_PACKAGES = 8192; // 0x2000 field public static final int GET_URI_PERMISSION_PATTERNS = 2048; // 0x800 field public static final int INSTALL_REASON_DEVICE_RESTORE = 2; // 0x2 @@ -11463,17 +11575,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 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 { @@ -11505,15 +11606,15 @@ package android.content.res { public final class AssetManager implements java.lang.AutoCloseable { method public void close(); - method public final java.lang.String[] getLocales(); - method public final java.lang.String[] list(java.lang.String) throws java.io.IOException; - method public final java.io.InputStream open(java.lang.String) throws java.io.IOException; - method public final java.io.InputStream open(java.lang.String, int) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; - method public final android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; - method public final android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; + method public java.lang.String[] getLocales(); + method public java.lang.String[] list(java.lang.String) throws java.io.IOException; + method public java.io.InputStream open(java.lang.String) throws java.io.IOException; + method public java.io.InputStream open(java.lang.String, int) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; + method public android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; + method public android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; field public static final int ACCESS_BUFFER = 3; // 0x3 field public static final int ACCESS_RANDOM = 1; // 0x1 field public static final int ACCESS_STREAMING = 2; // 0x2 @@ -13478,6 +13579,59 @@ package android.graphics { ctor public EmbossMaskFilter(float[], float, float, float); } + public final class ImageDecoder implements java.lang.AutoCloseable { + method public void close(); + method public static android.graphics.ImageDecoder.Source createSource(android.content.ContentResolver, android.net.Uri); + method public static android.graphics.ImageDecoder.Source createSource(java.nio.ByteBuffer); + method public static android.graphics.ImageDecoder.Source createSource(java.io.File); + method public static android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source, android.graphics.ImageDecoder.OnHeaderDecodedListener) throws java.io.IOException; + method public static android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source) throws java.io.IOException; + method public static android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source, android.graphics.ImageDecoder.OnHeaderDecodedListener) throws java.io.IOException; + method public static android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source) throws java.io.IOException; + method public android.util.Size getSampledSize(int); + method public void setAllocator(int); + method public void setAsAlphaMask(boolean); + method public void setCrop(android.graphics.Rect); + method public void setMutable(boolean); + method public void setOnPartialImageListener(android.graphics.ImageDecoder.OnPartialImageListener); + method public void setPostProcessor(android.graphics.PostProcessor); + method public void setPreferRamOverQuality(boolean); + method public void setRequireUnpremultiplied(boolean); + method public void setResize(int, int); + method public void setResize(int); + field public static final int ALLOCATOR_DEFAULT = 0; // 0x0 + field public static final int ALLOCATOR_HARDWARE = 3; // 0x3 + field public static final int ALLOCATOR_SHARED_MEMORY = 2; // 0x2 + field public static final int ALLOCATOR_SOFTWARE = 1; // 0x1 + field public static final int ERROR_SOURCE_ERROR = 3; // 0x3 + field public static final int ERROR_SOURCE_EXCEPTION = 1; // 0x1 + field public static final int ERROR_SOURCE_INCOMPLETE = 2; // 0x2 + } + + public static abstract class ImageDecoder.Error implements java.lang.annotation.Annotation { + } + + public static class ImageDecoder.ImageInfo { + method public java.lang.String getMimeType(); + method public android.util.Size getSize(); + method public boolean isAnimated(); + } + + public static class ImageDecoder.IncompleteException extends java.io.IOException { + ctor public ImageDecoder.IncompleteException(); + } + + public static abstract interface ImageDecoder.OnHeaderDecodedListener { + method public abstract void onHeaderDecoded(android.graphics.ImageDecoder, android.graphics.ImageDecoder.ImageInfo, android.graphics.ImageDecoder.Source); + } + + public static abstract interface ImageDecoder.OnPartialImageListener { + method public abstract boolean onPartialImage(int, android.graphics.ImageDecoder.Source); + } + + public static abstract class ImageDecoder.Source { + } + public class ImageFormat { ctor public ImageFormat(); method public static int getBitsPerPixel(int); @@ -14048,6 +14202,10 @@ package android.graphics { ctor public PorterDuffXfermode(android.graphics.PorterDuff.Mode); } + public abstract interface PostProcessor { + method public abstract int onPostProcess(android.graphics.Canvas); + } + public class RadialGradient extends android.graphics.Shader { ctor public RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode); ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode); @@ -14327,6 +14485,22 @@ package android.graphics.drawable { method public void onAnimationStart(android.graphics.drawable.Drawable); } + public class AnimatedImageDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable2 { + ctor public AnimatedImageDrawable(); + method public void clearAnimationCallbacks(); + method public void draw(android.graphics.Canvas); + method public int getOpacity(); + method public boolean isRunning(); + method public void registerAnimationCallback(android.graphics.drawable.Animatable2.AnimationCallback); + method public void setAlpha(int); + method public void setColorFilter(android.graphics.ColorFilter); + method public void setLoopCount(int); + method public void start(); + method public void stop(); + method public boolean unregisterAnimationCallback(android.graphics.drawable.Animatable2.AnimationCallback); + field public static final int LOOP_INFINITE = -1; // 0xffffffff + } + public class AnimatedStateListDrawable extends android.graphics.drawable.StateListDrawable { ctor public AnimatedStateListDrawable(); method public void addState(int[], android.graphics.drawable.Drawable, int); @@ -15214,18 +15388,26 @@ package android.hardware { method public void writeToParcel(android.os.Parcel, int); field public static final int BLOB = 33; // 0x21 field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DS_24UI8 = 50; // 0x32 + field public static final int DS_FP32UI8 = 52; // 0x34 + field public static final int D_16 = 48; // 0x30 + field public static final int D_24 = 49; // 0x31 + field public static final int D_FP32 = 51; // 0x33 field public static final int RGBA_1010102 = 43; // 0x2b field public static final int RGBA_8888 = 1; // 0x1 field public static final int RGBA_FP16 = 22; // 0x16 field public static final int RGBX_8888 = 2; // 0x2 field public static final int RGB_565 = 4; // 0x4 field public static final int RGB_888 = 3; // 0x3 + field public static final int S_UI8 = 53; // 0x35 field public static final long USAGE_CPU_READ_OFTEN = 3L; // 0x3L field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L field public static final long USAGE_CPU_WRITE_RARELY = 32L; // 0x20L field public static final long USAGE_GPU_COLOR_OUTPUT = 512L; // 0x200L + field public static final long USAGE_GPU_CUBE_MAP = 33554432L; // 0x2000000L field public static final long USAGE_GPU_DATA_BUFFER = 16777216L; // 0x1000000L + field public static final long USAGE_GPU_MIPMAP_COMPLETE = 67108864L; // 0x4000000L field public static final long USAGE_GPU_SAMPLED_IMAGE = 256L; // 0x100L field public static final long USAGE_PROTECTED_CONTENT = 16384L; // 0x4000L field public static final long USAGE_SENSOR_DIRECT_DATA = 8388608L; // 0x800000L @@ -15540,8 +15722,10 @@ package android.hardware.camera2 { method public T get(android.hardware.camera2.CameraCharacteristics.Key); method public java.util.List> getAvailableCaptureRequestKeys(); method public java.util.List> getAvailableCaptureResultKeys(); + method public java.util.List> getAvailablePhysicalCameraRequestKeys(); method public java.util.List> getAvailableSessionKeys(); method public java.util.List> getKeys(); + method public java.util.List getPhysicalCameraIds(); field public static final android.hardware.camera2.CameraCharacteristics.Key COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AE_AVAILABLE_ANTIBANDING_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AE_AVAILABLE_MODES; @@ -15580,6 +15764,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_POSE_ROTATION; field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_POSE_TRANSLATION; field public static final android.hardware.camera2.CameraCharacteristics.Key LENS_RADIAL_DISTORTION; + field public static final android.hardware.camera2.CameraCharacteristics.Key LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE; field public static final android.hardware.camera2.CameraCharacteristics.Key NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key REPROCESS_MAX_CAPTURE_STALL; field public static final android.hardware.camera2.CameraCharacteristics.Key REQUEST_AVAILABLE_CAPABILITIES; @@ -15620,6 +15805,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraCharacteristics.Key STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES; + field public static final android.hardware.camera2.CameraCharacteristics.Key STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key STATISTICS_INFO_MAX_FACE_COUNT; field public static final android.hardware.camera2.CameraCharacteristics.Key SYNC_MAX_LATENCY; field public static final android.hardware.camera2.CameraCharacteristics.Key TONEMAP_AVAILABLE_TONE_MAP_MODES; @@ -15640,6 +15826,7 @@ package android.hardware.camera2 { public abstract class CameraDevice implements java.lang.AutoCloseable { method public abstract void close(); method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; + method public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSession(java.util.List, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSessionByOutputConfigurations(java.util.List, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; @@ -15711,6 +15898,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 @@ -15809,6 +15997,7 @@ package android.hardware.camera2 { field public static final int HOT_PIXEL_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int HOT_PIXEL_MODE_OFF = 0; // 0x0 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_3 = 3; // 0x3 + field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL = 4; // 0x4 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_FULL = 1; // 0x1 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY = 2; // 0x2 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED = 0; // 0x0 @@ -15824,6 +16013,8 @@ package android.hardware.camera2 { field public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0; // 0x0 field public static final int LENS_STATE_MOVING = 1; // 0x1 field public static final int LENS_STATE_STATIONARY = 0; // 0x0 + field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; // 0x0 + field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED = 1; // 0x1 field public static final int NOISE_REDUCTION_MODE_FAST = 1; // 0x1 field public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int NOISE_REDUCTION_MODE_MINIMAL = 3; // 0x3 @@ -15833,6 +16024,7 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; // 0x6 field public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; // 0x9 field public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; // 0x8 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; // 0xb field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; // 0xa @@ -15882,6 +16074,8 @@ package android.hardware.camera2 { field public static final int STATISTICS_FACE_DETECT_MODE_SIMPLE = 1; // 0x1 field public static final int STATISTICS_LENS_SHADING_MAP_MODE_OFF = 0; // 0x0 field public static final int STATISTICS_LENS_SHADING_MAP_MODE_ON = 1; // 0x1 + field public static final int STATISTICS_OIS_DATA_MODE_OFF = 0; // 0x0 + field public static final int STATISTICS_OIS_DATA_MODE_ON = 1; // 0x1 field public static final int STATISTICS_SCENE_FLICKER_50HZ = 1; // 0x1 field public static final int STATISTICS_SCENE_FLICKER_60HZ = 2; // 0x2 field public static final int STATISTICS_SCENE_FLICKER_NONE = 0; // 0x0 @@ -15964,6 +16158,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureRequest.Key STATISTICS_FACE_DETECT_MODE; field public static final android.hardware.camera2.CaptureRequest.Key STATISTICS_HOT_PIXEL_MAP_MODE; field public static final android.hardware.camera2.CaptureRequest.Key STATISTICS_LENS_SHADING_MAP_MODE; + field public static final android.hardware.camera2.CaptureRequest.Key STATISTICS_OIS_DATA_MODE; field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_CURVE; field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_GAMMA; field public static final android.hardware.camera2.CaptureRequest.Key TONEMAP_MODE; @@ -15974,8 +16169,10 @@ package android.hardware.camera2 { method public void addTarget(android.view.Surface); method public android.hardware.camera2.CaptureRequest build(); method public T get(android.hardware.camera2.CaptureRequest.Key); + method public T getPhysicalCameraKey(android.hardware.camera2.CaptureRequest.Key, java.lang.String); method public void removeTarget(android.view.Surface); method public void set(android.hardware.camera2.CaptureRequest.Key, T); + method public android.hardware.camera2.CaptureRequest.Builder setPhysicalCameraKey(android.hardware.camera2.CaptureRequest.Key, T, java.lang.String); method public void setTag(java.lang.Object); } @@ -16063,6 +16260,10 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_HOT_PIXEL_MAP_MODE; field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_SHADING_CORRECTION_MAP; field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_LENS_SHADING_MAP_MODE; + field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_OIS_DATA_MODE; + field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_OIS_TIMESTAMPS; + field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_OIS_X_SHIFTS; + field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_OIS_Y_SHIFTS; field public static final android.hardware.camera2.CaptureResult.Key STATISTICS_SCENE_FLICKER; field public static final android.hardware.camera2.CaptureResult.Key TONEMAP_CURVE; field public static final android.hardware.camera2.CaptureResult.Key TONEMAP_GAMMA; @@ -16092,6 +16293,7 @@ package android.hardware.camera2 { public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult { method public java.util.List getPartialResults(); + method public T getPhysicalCameraKey(android.hardware.camera2.CaptureResult.Key, java.lang.String); } } @@ -16171,6 +16373,7 @@ package android.hardware.camera2.params { method public int getSurfaceGroupId(); method public java.util.List getSurfaces(); method public void removeSurface(android.view.Surface); + method public void setPhysicalCameraId(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff @@ -16284,6 +16487,20 @@ package android.hardware.display { package android.hardware.fingerprint { + public class FingerprintDialog { + method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback); + method public void authenticate(android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback); + } + + public static class FingerprintDialog.Builder { + ctor public FingerprintDialog.Builder(); + method public android.hardware.fingerprint.FingerprintDialog build(android.content.Context); + method public android.hardware.fingerprint.FingerprintDialog.Builder setDescription(java.lang.CharSequence); + method public android.hardware.fingerprint.FingerprintDialog.Builder setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener); + method public android.hardware.fingerprint.FingerprintDialog.Builder setSubtitle(java.lang.CharSequence); + method public android.hardware.fingerprint.FingerprintDialog.Builder setTitle(java.lang.CharSequence); + } + public class FingerprintManager { method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, int, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, android.os.Handler); method public boolean hasEnrolledFingerprints(); @@ -20882,7 +21099,6 @@ package android.inputmethodservice { method public int getMaxWidth(); method public java.lang.CharSequence getTextForImeAction(int); method public android.app.Dialog getWindow(); - method public void hideSoftInputFromInputMethod(int); method public void hideStatusIcon(); method public void hideWindow(); method public boolean isExtractViewShown(); @@ -20930,6 +21146,7 @@ package android.inputmethodservice { method public void onWindowHidden(); method public void onWindowShown(); method public void requestHideSelf(int); + method public void requestShowSelf(int); method public boolean sendDefaultEditorAction(boolean); method public void sendDownUpKeyEvents(int); method public void sendKeyChar(char); @@ -20942,7 +21159,6 @@ package android.inputmethodservice { method public void setInputMethodAndSubtype(java.lang.String, android.view.inputmethod.InputMethodSubtype); method public void setInputView(android.view.View); method public boolean shouldOfferSwitchingToNextInputMethod(); - method public void showSoftInputFromInputMethod(int); method public void showStatusIcon(int); method public void showWindow(boolean); method public void switchInputMethod(java.lang.String); @@ -21476,6 +21692,7 @@ package android.location { method public android.location.LocationProvider getProvider(java.lang.String); method public java.util.List getProviders(boolean); method public java.util.List getProviders(android.location.Criteria, boolean); + method public boolean isLocationEnabled(); method public boolean isProviderEnabled(java.lang.String); method public boolean registerGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback); method public boolean registerGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback, android.os.Handler); @@ -21663,6 +21880,7 @@ package android.media { method public android.media.AudioFocusRequest.Builder setAcceptsDelayedFocusGain(boolean); method public android.media.AudioFocusRequest.Builder setAudioAttributes(android.media.AudioAttributes); method public android.media.AudioFocusRequest.Builder setFocusGain(int); + method public android.media.AudioFocusRequest.Builder setForceDucking(boolean); method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener); method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler); method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean); @@ -21718,7 +21936,13 @@ package android.media { field public static final int CHANNEL_OUT_STEREO = 12; // 0xc field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c field public static final android.os.Parcelable.Creator CREATOR; + field public static final int ENCODING_AAC_ELD = 15; // 0xf + field public static final int ENCODING_AAC_HE_V1 = 11; // 0xb + field public static final int ENCODING_AAC_HE_V2 = 12; // 0xc + field public static final int ENCODING_AAC_LC = 10; // 0xa + field public static final int ENCODING_AAC_XHE = 16; // 0x10 field public static final int ENCODING_AC3 = 5; // 0x5 + field public static final int ENCODING_AC4 = 17; // 0x11 field public static final int ENCODING_DEFAULT = 1; // 0x1 field public static final int ENCODING_DOLBY_TRUEHD = 14; // 0xe field public static final int ENCODING_DTS = 7; // 0x7 @@ -21726,6 +21950,7 @@ package android.media { field public static final int ENCODING_E_AC3 = 6; // 0x6 field public static final int ENCODING_IEC61937 = 13; // 0xd field public static final int ENCODING_INVALID = 0; // 0x0 + field public static final int ENCODING_MP3 = 9; // 0x9 field public static final int ENCODING_PCM_16BIT = 2; // 0x2 field public static final int ENCODING_PCM_8BIT = 3; // 0x3 field public static final int ENCODING_PCM_FLOAT = 4; // 0x4 @@ -21768,6 +21993,7 @@ package android.media { method public boolean isBluetoothScoOn(); method public boolean isMicrophoneMute(); method public boolean isMusicActive(); + method public boolean isOffloadedPlaybackSupported(android.media.AudioFormat); method public boolean isSpeakerphoneOn(); method public boolean isStreamMute(int); method public boolean isVolumeFixed(); @@ -21937,6 +22163,7 @@ package android.media { method public int getChannelConfiguration(); method public int getChannelCount(); method public android.media.AudioFormat getFormat(); + method public android.os.PersistableBundle getMetrics(); method public static int getMinBufferSize(int, int, int); method public int getNotificationMarkerPosition(); method public int getPositionNotificationPeriod(); @@ -21985,6 +22212,14 @@ package android.media { method public android.media.AudioRecord.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException; } + public static final class AudioRecord.MetricsConstants { + field public static final java.lang.String CHANNELS = "android.media.audiorecord.channels"; + field public static final java.lang.String ENCODING = "android.media.audiorecord.encoding"; + field public static final java.lang.String LATENCY = "android.media.audiorecord.latency"; + field public static final java.lang.String SAMPLERATE = "android.media.audiorecord.samplerate"; + field public static final java.lang.String SOURCE = "android.media.audiorecord.source"; + } + public static abstract interface AudioRecord.OnRecordPositionUpdateListener { method public abstract void onMarkerReached(android.media.AudioRecord); method public abstract void onPeriodicNotification(android.media.AudioRecord); @@ -22044,6 +22279,7 @@ package android.media { method public int getChannelCount(); method public android.media.AudioFormat getFormat(); method public static float getMaxVolume(); + method public android.os.PersistableBundle getMetrics(); method public static int getMinBufferSize(int, int, int); method public static float getMinVolume(); method protected deprecated int getNativeFrameCount(); @@ -22068,6 +22304,7 @@ package android.media { method public int reloadStaticData(); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener); + method public void removeStreamEventCallback(); method public int setAuxEffectSendLevel(float); method public int setBufferSizeInFrames(int); method public int setLoopPoints(int, int, int); @@ -22081,6 +22318,7 @@ package android.media { method public boolean setPreferredDevice(android.media.AudioDeviceInfo); method protected deprecated void setState(int); method public deprecated int setStereoVolume(float, float); + method public void setStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback); method public int setVolume(float); method public void stop() throws java.lang.IllegalStateException; method public int write(byte[], int, int); @@ -22116,11 +22354,20 @@ package android.media { method public android.media.AudioTrack.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException; method public android.media.AudioTrack.Builder setAudioFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException; method public android.media.AudioTrack.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException; + method public android.media.AudioTrack.Builder setOffloadedPlayback(boolean); method public android.media.AudioTrack.Builder setPerformanceMode(int); method public android.media.AudioTrack.Builder setSessionId(int) throws java.lang.IllegalArgumentException; method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException; } + public static final class AudioTrack.MetricsConstants { + field public static final java.lang.String CHANNELMASK = "android.media.audiorecord.channelmask"; + field public static final java.lang.String CONTENTTYPE = "android.media.audiotrack.type"; + field public static final java.lang.String SAMPLERATE = "android.media.audiorecord.samplerate"; + field public static final java.lang.String STREAMTYPE = "android.media.audiotrack.streamtype"; + field public static final java.lang.String USAGE = "android.media.audiotrack.usage"; + } + public static abstract interface AudioTrack.OnPlaybackPositionUpdateListener { method public abstract void onMarkerReached(android.media.AudioTrack); method public abstract void onPeriodicNotification(android.media.AudioTrack); @@ -22131,6 +22378,12 @@ package android.media { method public default void onRoutingChanged(android.media.AudioRouting); } + public static abstract class AudioTrack.StreamEventCallback { + method public void onStreamDataRequest(android.media.AudioTrack); + method public void onStreamPresentationEnd(android.media.AudioTrack); + method public void onTearDown(android.media.AudioTrack); + } + public class CamcorderProfile { method public static android.media.CamcorderProfile get(int); method public static android.media.CamcorderProfile get(int, int); @@ -22183,6 +22436,40 @@ package android.media { field public static final int QUALITY_MEDIUM = 1; // 0x1 } + public final class DataSourceDesc { + method public long getEndPosition(); + method public java.io.FileDescriptor getFileDescriptor(); + method public long getFileDescriptorLength(); + method public long getFileDescriptorOffset(); + method public long getId(); + method public android.media.Media2DataSource getMedia2DataSource(); + method public long getStartPosition(); + method public int getType(); + method public android.net.Uri getUri(); + method public android.content.Context getUriContext(); + method public java.util.List getUriCookies(); + method public java.util.Map getUriHeaders(); + field public static final long LONG_MAX = 576460752303423487L; // 0x7ffffffffffffffL + field public static final int TYPE_CALLBACK = 1; // 0x1 + field public static final int TYPE_FD = 2; // 0x2 + field public static final int TYPE_NONE = 0; // 0x0 + field public static final int TYPE_URI = 3; // 0x3 + } + + public static class DataSourceDesc.Builder { + ctor public DataSourceDesc.Builder(); + ctor public DataSourceDesc.Builder(android.media.DataSourceDesc); + method public android.media.DataSourceDesc build(); + method public android.media.DataSourceDesc.Builder setDataSource(android.media.Media2DataSource); + method public android.media.DataSourceDesc.Builder setDataSource(java.io.FileDescriptor); + method public android.media.DataSourceDesc.Builder setDataSource(java.io.FileDescriptor, long, long); + method public android.media.DataSourceDesc.Builder setDataSource(android.content.Context, android.net.Uri); + method public android.media.DataSourceDesc.Builder setDataSource(android.content.Context, android.net.Uri, java.util.Map, java.util.List); + method public android.media.DataSourceDesc.Builder setEndPosition(long); + method public android.media.DataSourceDesc.Builder setId(long); + method public android.media.DataSourceDesc.Builder setStartPosition(long); + } + public final class DeniedByServerException extends android.media.MediaDrmException { ctor public DeniedByServerException(java.lang.String); } @@ -22459,6 +22746,12 @@ package android.media { method public abstract void onJetUserIdUpdate(android.media.JetPlayer, int, int); } + public abstract class Media2DataSource implements java.io.Closeable { + ctor public Media2DataSource(); + method public abstract long getSize() throws java.io.IOException; + method public abstract int readAt(long, byte[], int, int) throws java.io.IOException; + } + public class MediaActionSound { ctor public MediaActionSound(); method public void load(int); @@ -22913,6 +23206,7 @@ package android.media { public static final class MediaCodecInfo.EncoderCapabilities { method public android.util.Range getComplexityRange(); + method public android.util.Range getQualityRange(); method public boolean isBitrateModeSupported(int); field public static final int BITRATE_MODE_CBR = 2; // 0x2 field public static final int BITRATE_MODE_CQ = 0; // 0x0 @@ -23008,38 +23302,58 @@ package android.media { method public android.media.MediaDescription.Builder setTitle(java.lang.CharSequence); } - public final class MediaDrm { + public final class MediaDrm implements java.lang.AutoCloseable { ctor public MediaDrm(java.util.UUID) throws android.media.UnsupportedSchemeException; + method public void close(); method public void closeSession(byte[]); - method protected void finalize(); + method public int getConnectedHdcpLevel(); method public android.media.MediaDrm.CryptoSession getCryptoSession(byte[], java.lang.String, java.lang.String); method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.HashMap) throws android.media.NotProvisionedException; + method public int getMaxHdcpLevel(); + method public int getMaxSessionCount(); + method public android.os.PersistableBundle getMetrics(); + method public int getOpenSessionCount(); method public byte[] getPropertyByteArray(java.lang.String); method public java.lang.String getPropertyString(java.lang.String); method public android.media.MediaDrm.ProvisionRequest getProvisionRequest(); method public byte[] getSecureStop(byte[]); + method public java.util.List getSecureStopIds(); method public java.util.List getSecureStops(); + method public int getSecurityLevel(byte[]); method public static final boolean isCryptoSchemeSupported(java.util.UUID); method public static final boolean isCryptoSchemeSupported(java.util.UUID, java.lang.String); method public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException; method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException; method public void provideProvisionResponse(byte[]) throws android.media.DeniedByServerException; method public java.util.HashMap queryKeyStatus(byte[]); - method public final void release(); - method public void releaseAllSecureStops(); + method public deprecated void release(); + method public deprecated void releaseAllSecureStops(); method public void releaseSecureStops(byte[]); + method public void removeAllSecureStops(); method public void removeKeys(byte[]); + method public void removeSecureStop(byte[]); method public void restoreKeys(byte[], byte[]); method public void setOnEventListener(android.media.MediaDrm.OnEventListener); method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler); method public void setOnKeyStatusChangeListener(android.media.MediaDrm.OnKeyStatusChangeListener, android.os.Handler); method public void setPropertyByteArray(java.lang.String, byte[]); method public void setPropertyString(java.lang.String, java.lang.String); + method public void setSecurityLevel(byte[], int); field public static final deprecated int EVENT_KEY_EXPIRED = 3; // 0x3 field public static final int EVENT_KEY_REQUIRED = 2; // 0x2 field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1 field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5 field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4 + field public static final int HDCP_LEVEL_UNKNOWN = 0; // 0x0 + field public static final int HDCP_NONE = 1; // 0x1 + field public static final int HDCP_NO_DIGITAL_OUTPUT = 2147483647; // 0x7fffffff + field public static final int HDCP_V1 = 2; // 0x2 + field public static final int HDCP_V2 = 3; // 0x3 + field public static final int HDCP_V2_1 = 4; // 0x4 + field public static final int HDCP_V2_2 = 5; // 0x5 + field public static final int HW_SECURE_ALL = 5; // 0x5 + field public static final int HW_SECURE_CRYPTO = 3; // 0x3 + field public static final int HW_SECURE_DECODE = 4; // 0x4 field public static final int KEY_TYPE_OFFLINE = 2; // 0x2 field public static final int KEY_TYPE_RELEASE = 3; // 0x3 field public static final int KEY_TYPE_STREAMING = 1; // 0x1 @@ -23048,6 +23362,9 @@ package android.media { field public static final java.lang.String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId"; field public static final java.lang.String PROPERTY_VENDOR = "vendor"; field public static final java.lang.String PROPERTY_VERSION = "version"; + field public static final int SECURITY_LEVEL_UNKNOWN = 0; // 0x0 + field public static final int SW_SECURE_CRYPTO = 1; // 0x1 + field public static final int SW_SECURE_DECODE = 2; // 0x2 } public final class MediaDrm.CryptoSession { @@ -23057,13 +23374,18 @@ package android.media { method public boolean verify(byte[], byte[], byte[]); } + public static abstract class MediaDrm.HdcpLevel implements java.lang.annotation.Annotation { + } + public static final class MediaDrm.KeyRequest { method public byte[] getData(); method public java.lang.String getDefaultUrl(); method public int getRequestType(); field public static final int REQUEST_TYPE_INITIAL = 0; // 0x0 + field public static final int REQUEST_TYPE_NONE = 3; // 0x3 field public static final int REQUEST_TYPE_RELEASE = 2; // 0x2 field public static final int REQUEST_TYPE_RENEWAL = 1; // 0x1 + field public static final int REQUEST_TYPE_UPDATE = 4; // 0x4 } public static final class MediaDrm.KeyStatus { @@ -23080,6 +23402,44 @@ package android.media { method public java.lang.String getDiagnosticInfo(); } + public static final class MediaDrm.MetricsConstants { + field public static final java.lang.String CLOSE_SESSION_ERROR_COUNT = "drm.mediadrm.close_session.error.count"; + field public static final java.lang.String CLOSE_SESSION_ERROR_LIST = "drm.mediadrm.close_session.error.list"; + field public static final java.lang.String CLOSE_SESSION_OK_COUNT = "drm.mediadrm.close_session.ok.count"; + field public static final java.lang.String EVENT_KEY_EXPIRED_COUNT = "drm.mediadrm.event.KEY_EXPIRED.count"; + field public static final java.lang.String EVENT_KEY_NEEDED_COUNT = "drm.mediadrm.event.KEY_NEEDED.count"; + field public static final java.lang.String EVENT_PROVISION_REQUIRED_COUNT = "drm.mediadrm.event.PROVISION_REQUIRED.count"; + field public static final java.lang.String EVENT_SESSION_RECLAIMED_COUNT = "drm.mediadrm.event.SESSION_RECLAIMED.count"; + field public static final java.lang.String EVENT_VENDOR_DEFINED_COUNT = "drm.mediadrm.event.VENDOR_DEFINED.count"; + field public static final java.lang.String GET_DEVICE_UNIQUE_ID_ERROR_COUNT = "drm.mediadrm.get_device_unique_id.error.count"; + field public static final java.lang.String GET_DEVICE_UNIQUE_ID_ERROR_LIST = "drm.mediadrm.get_device_unique_id.error.list"; + field public static final java.lang.String GET_DEVICE_UNIQUE_ID_OK_COUNT = "drm.mediadrm.get_device_unique_id.ok.count"; + field public static final java.lang.String GET_KEY_REQUEST_ERROR_COUNT = "drm.mediadrm.get_key_request.error.count"; + field public static final java.lang.String GET_KEY_REQUEST_ERROR_LIST = "drm.mediadrm.get_key_request.error.list"; + field public static final java.lang.String GET_KEY_REQUEST_OK_COUNT = "drm.mediadrm.get_key_request.ok.count"; + field public static final java.lang.String GET_KEY_REQUEST_OK_TIME_MICROS = "drm.mediadrm.get_key_request.ok.average_time_micros"; + field public static final java.lang.String GET_PROVISION_REQUEST_ERROR_COUNT = "drm.mediadrm.get_provision_request.error.count"; + field public static final java.lang.String GET_PROVISION_REQUEST_ERROR_LIST = "drm.mediadrm.get_provision_request.error.list"; + field public static final java.lang.String GET_PROVISION_REQUEST_OK_COUNT = "drm.mediadrm.get_provision_request.ok.count"; + field public static final java.lang.String KEY_STATUS_EXPIRED_COUNT = "drm.mediadrm.key_status.EXPIRED.count"; + field public static final java.lang.String KEY_STATUS_INTERNAL_ERROR_COUNT = "drm.mediadrm.key_status.INTERNAL_ERROR.count"; + field public static final java.lang.String KEY_STATUS_OUTPUT_NOT_ALLOWED_COUNT = "drm.mediadrm.key_status_change.OUTPUT_NOT_ALLOWED.count"; + field public static final java.lang.String KEY_STATUS_PENDING_COUNT = "drm.mediadrm.key_status_change.PENDING.count"; + field public static final java.lang.String KEY_STATUS_USABLE_COUNT = "drm.mediadrm.key_status_change.USABLE.count"; + field public static final java.lang.String OPEN_SESSION_ERROR_COUNT = "drm.mediadrm.open_session.error.count"; + field public static final java.lang.String OPEN_SESSION_ERROR_LIST = "drm.mediadrm.open_session.error.list"; + field public static final java.lang.String OPEN_SESSION_OK_COUNT = "drm.mediadrm.open_session.ok.count"; + field public static final java.lang.String PROVIDE_KEY_RESPONSE_ERROR_COUNT = "drm.mediadrm.provide_key_response.error.count"; + field public static final java.lang.String PROVIDE_KEY_RESPONSE_ERROR_LIST = "drm.mediadrm.provide_key_response.error.list"; + field public static final java.lang.String PROVIDE_KEY_RESPONSE_OK_COUNT = "drm.mediadrm.provide_key_response.ok.count"; + field public static final java.lang.String PROVIDE_KEY_RESPONSE_OK_TIME_MICROS = "drm.mediadrm.provide_key_response.ok.average_time_micros"; + field public static final java.lang.String PROVIDE_PROVISION_RESPONSE_ERROR_COUNT = "drm.mediadrm.provide_provision_response.error.count"; + field public static final java.lang.String PROVIDE_PROVISION_RESPONSE_ERROR_LIST = "drm.mediadrm.provide_provision_response.error.list"; + field public static final java.lang.String PROVIDE_PROVISION_RESPONSE_OK_COUNT = "drm.mediadrm.provide_provision_response.ok.count"; + field public static final java.lang.String SESSION_END_TIMES_MS = "drm.mediadrm.session_end_times_ms"; + field public static final java.lang.String SESSION_START_TIMES_MS = "drm.mediadrm.session_start_times_ms"; + } + public static abstract interface MediaDrm.OnEventListener { method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]); } @@ -23097,6 +23457,9 @@ package android.media { method public java.lang.String getDefaultUrl(); } + public static abstract class MediaDrm.SecurityLevel implements java.lang.annotation.Annotation { + } + public class MediaDrmException extends java.lang.Exception { ctor public MediaDrmException(java.lang.String); } @@ -23116,6 +23479,7 @@ package android.media { method public java.util.Map getPsshInfo(); method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo); method public int getSampleFlags(); + method public long getSampleSize(); method public long getSampleTime(); method public int getSampleTrackIndex(); method public final int getTrackCount(); @@ -23228,6 +23592,7 @@ package android.media { field public static final java.lang.String KEY_PRIORITY = "priority"; field public static final java.lang.String KEY_PROFILE = "profile"; field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; + field public static final java.lang.String KEY_QUALITY = "quality"; field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; field public static final java.lang.String KEY_ROTATION = "rotation-degrees"; field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate"; @@ -23628,6 +23993,169 @@ package android.media { field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1 } + public abstract class MediaPlayer2 implements android.media.AudioRouting java.lang.AutoCloseable { + method public abstract void addPlaylistItem(int, android.media.DataSourceDesc); + method public abstract void attachAuxEffect(int); + method public abstract void clearPendingCommands(); + method public abstract void close(); + method public static final android.media.MediaPlayer2 create(); + method public abstract void deselectTrack(int); + method public abstract android.media.DataSourceDesc editPlaylistItem(int, android.media.DataSourceDesc); + method public abstract int getAudioSessionId(); + method public abstract android.media.DataSourceDesc getCurrentDataSource(); + method public abstract int getCurrentPlaylistItemIndex(); + method public abstract int getCurrentPosition(); + method public abstract android.media.MediaPlayer2.DrmInfo getDrmInfo(); + method public abstract java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract int getDuration(); + method public abstract android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract int getLoopingMode(); + method public abstract android.os.PersistableBundle getMetrics(); + method public abstract android.media.PlaybackParams getPlaybackParams(); + method public abstract java.util.List getPlaylist(); + method public abstract int getSelectedTrack(int); + method public abstract android.media.SyncParams getSyncParams(); + method public abstract android.media.MediaTimestamp getTimestamp(); + method public abstract java.util.List getTrackInfo(); + method public abstract int getVideoHeight(); + method public abstract int getVideoWidth(); + method public abstract boolean isPlaying(); + method public abstract void movePlaylistItem(int, int); + method public abstract void pause(); + method public abstract void play(); + method public abstract void prepareAsync(); + method public abstract void prepareDrm(java.util.UUID) throws android.media.MediaPlayer2.ProvisioningNetworkErrorException, android.media.MediaPlayer2.ProvisioningServerErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; + method public abstract byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract void registerDrmEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.DrmEventCallback); + method public abstract void registerEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.EventCallback); + method public abstract void releaseDrm() throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract android.media.DataSourceDesc removePlaylistItem(int); + method public abstract void reset(); + method public abstract void restoreKeys(byte[]) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract void seekTo(long, int); + method public abstract void selectTrack(int); + method public abstract void setAudioAttributes(android.media.AudioAttributes); + method public abstract void setAudioSessionId(int); + method public abstract void setAuxEffectSendLevel(float); + method public abstract void setCurrentPlaylistItem(int); + method public abstract void setDataSource(android.media.DataSourceDesc) throws java.io.IOException; + method public abstract void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract void setLoopingMode(int); + method public abstract void setNextPlaylistItem(int); + method public abstract void setOnDrmConfigHelper(android.media.MediaPlayer2.OnDrmConfigHelper); + method public abstract void setPlaybackParams(android.media.PlaybackParams); + method public abstract void setPlaylist(java.util.List, int) throws java.io.IOException; + method public abstract void setSurface(android.view.Surface); + method public abstract void setSyncParams(android.media.SyncParams); + method public abstract void setVolume(float, float); + method public abstract void unregisterDrmEventCallback(android.media.MediaPlayer2.DrmEventCallback); + method public abstract void unregisterEventCallback(android.media.MediaPlayer2.EventCallback); + field public static final int LOOPING_MODE_FULL = 1; // 0x1 + field public static final int LOOPING_MODE_NONE = 0; // 0x0 + field public static final int LOOPING_MODE_SHUFFLE = 3; // 0x3 + field public static final int LOOPING_MODE_SINGLE = 2; // 0x2 + field public static final int MEDIA_ERROR_IO = -1004; // 0xfffffc14 + field public static final int MEDIA_ERROR_MALFORMED = -1007; // 0xfffffc11 + field public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; // 0xc8 + field public static final int MEDIA_ERROR_TIMED_OUT = -110; // 0xffffff92 + field public static final int MEDIA_ERROR_UNKNOWN = 1; // 0x1 + field public static final int MEDIA_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e + field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324 + field public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; // 0x4 + field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320 + field public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be + field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd + field public static final int MEDIA_INFO_COMPLETE_CALL_PAUSE = 102; // 0x66 + field public static final int MEDIA_INFO_COMPLETE_CALL_PLAY = 101; // 0x65 + field public static final int MEDIA_INFO_COMPLETE_CALL_SEEK = 103; // 0x67 + field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322 + field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321 + field public static final int MEDIA_INFO_PLAYBACK_COMPLETE = 5; // 0x5 + field public static final int MEDIA_INFO_PLAYLIST_END = 6; // 0x6 + field public static final int MEDIA_INFO_PREPARED = 100; // 0x64 + field public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; // 0x2 + field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386 + field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1 + field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385 + field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325 + field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3 + field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc + field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3 + field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1 + field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2 + field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0 + field public static final int SEEK_CLOSEST = 3; // 0x3 + field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2 + field public static final int SEEK_NEXT_SYNC = 1; // 0x1 + field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0 + field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1 + } + + public static abstract class MediaPlayer2.DrmEventCallback { + ctor public MediaPlayer2.DrmEventCallback(); + method public void onDrmInfo(android.media.MediaPlayer2, android.media.MediaPlayer2.DrmInfo); + method public void onDrmPrepared(android.media.MediaPlayer2, int); + } + + public static abstract class MediaPlayer2.DrmInfo { + ctor public MediaPlayer2.DrmInfo(); + method public abstract java.util.Map getPssh(); + method public abstract java.util.List getSupportedSchemes(); + } + + public static abstract class MediaPlayer2.EventCallback { + ctor public MediaPlayer2.EventCallback(); + method public void onBufferingUpdate(android.media.MediaPlayer2, long, int); + method public void onError(android.media.MediaPlayer2, long, int, int); + method public void onInfo(android.media.MediaPlayer2, long, int, int); + method public void onTimedMetaDataAvailable(android.media.MediaPlayer2, long, android.media.TimedMetaData); + method public void onVideoSizeChanged(android.media.MediaPlayer2, long, int, int); + } + + public static final class MediaPlayer2.MetricsConstants { + field public static final java.lang.String CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String WIDTH = "android.media.mediaplayer.width"; + } + + public static abstract class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException { + ctor protected MediaPlayer2.NoDrmSchemeException(java.lang.String); + } + + public static abstract interface MediaPlayer2.OnDrmConfigHelper { + method public abstract void onDrmConfig(android.media.MediaPlayer2); + } + + public static abstract class MediaPlayer2.ProvisioningNetworkErrorException extends android.media.MediaDrmException { + ctor protected MediaPlayer2.ProvisioningNetworkErrorException(java.lang.String); + } + + public static abstract class MediaPlayer2.ProvisioningServerErrorException extends android.media.MediaDrmException { + ctor protected MediaPlayer2.ProvisioningServerErrorException(java.lang.String); + } + + public static abstract class MediaPlayer2.TrackInfo { + ctor public MediaPlayer2.TrackInfo(); + method public abstract android.media.MediaFormat getFormat(); + method public abstract java.lang.String getLanguage(); + method public abstract int getTrackType(); + method public abstract java.lang.String toString(); + field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2 + field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5 + field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4 + field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0 + field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1 + } + public class MediaRecorder implements android.media.AudioRouting { ctor public MediaRecorder(); method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); @@ -26096,6 +26624,7 @@ package android.net { method public deprecated android.net.NetworkInfo getNetworkInfo(int); method public android.net.NetworkInfo getNetworkInfo(android.net.Network); method public deprecated int getNetworkPreference(); + method public byte[] getNetworkWatchlistConfigHash(); method public static deprecated android.net.Network getProcessDefaultNetwork(); method public int getRestrictBackgroundStatus(); method public boolean isActiveNetworkMetered(); @@ -26218,12 +26747,18 @@ package android.net { } public final class IpSecManager { - method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; - method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; + method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; + method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public void applyTransportModeTransform(java.net.Socket, int, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.net.DatagramSocket, int, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.io.FileDescriptor, int, android.net.IpSecTransform) throws java.io.IOException; method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; - method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; + method public void removeTransportModeTransforms(java.net.Socket) throws java.io.IOException; + method public void removeTransportModeTransforms(java.net.DatagramSocket) throws java.io.IOException; + method public void removeTransportModeTransforms(java.io.FileDescriptor) throws java.io.IOException; + field public static final int DIRECTION_IN = 0; // 0x0 + field public static final int DIRECTION_OUT = 1; // 0x1 } public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { @@ -26246,18 +26781,15 @@ package android.net { public final class IpSecTransform implements java.lang.AutoCloseable { method public void close(); - field public static final int DIRECTION_IN = 0; // 0x0 - field public static final int DIRECTION_OUT = 1; // 0x1 } public static class IpSecTransform.Builder { ctor public IpSecTransform.Builder(android.content.Context); - method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm); - method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); - method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setAuthentication(android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setEncryption(android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); - method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); } public class LinkAddress implements android.os.Parcelable { @@ -26277,7 +26809,9 @@ package android.net { method public android.net.ProxyInfo getHttpProxy(); method public java.lang.String getInterfaceName(); method public java.util.List getLinkAddresses(); + method public java.lang.String getPrivateDnsServerName(); method public java.util.List getRoutes(); + method public boolean isPrivateDnsActive(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -26340,10 +26874,10 @@ package android.net { } public final class MacAddress implements android.os.Parcelable { - method public int addressType(); method public int describeContents(); method public static android.net.MacAddress fromBytes(byte[]); method public static android.net.MacAddress fromString(java.lang.String); + method public int getAddressType(); method public boolean isLocallyAssigned(); method public byte[] toByteArray(); method public java.lang.String toOuiString(); @@ -26353,7 +26887,6 @@ package android.net { field public static final int TYPE_BROADCAST = 3; // 0x3 field public static final int TYPE_MULTICAST = 2; // 0x2 field public static final int TYPE_UNICAST = 1; // 0x1 - field public static final int TYPE_UNKNOWN = 0; // 0x0 } public class MailTo { @@ -27189,14 +27722,14 @@ package android.net.wifi { field public java.lang.String providerFriendlyName; field public long[] roamingConsortiumIds; field public int status; - field public java.lang.String[] wepKeys; - field public int wepTxKeyIndex; + field public deprecated java.lang.String[] wepKeys; + field public deprecated int wepTxKeyIndex; } public static class WifiConfiguration.AuthAlgorithm { field public static final int LEAP = 2; // 0x2 field public static final int OPEN = 0; // 0x0 - field public static final int SHARED = 1; // 0x1 + field public static final deprecated int SHARED = 1; // 0x1 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "auth_alg"; } @@ -27204,8 +27737,8 @@ package android.net.wifi { public static class WifiConfiguration.GroupCipher { field public static final int CCMP = 3; // 0x3 field public static final int TKIP = 2; // 0x2 - field public static final int WEP104 = 1; // 0x1 - field public static final int WEP40 = 0; // 0x0 + field public static final deprecated int WEP104 = 1; // 0x1 + field public static final deprecated int WEP40 = 0; // 0x0 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "group"; } @@ -27214,7 +27747,7 @@ package android.net.wifi { field public static final int IEEE8021X = 3; // 0x3 field public static final int NONE = 0; // 0x0 field public static final int WPA_EAP = 2; // 0x2 - field public static final int WPA_PSK = 1; // 0x1 + field public static final deprecated int WPA_PSK = 1; // 0x1 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "key_mgmt"; } @@ -27222,14 +27755,14 @@ package android.net.wifi { public static class WifiConfiguration.PairwiseCipher { field public static final int CCMP = 2; // 0x2 field public static final int NONE = 0; // 0x0 - field public static final int TKIP = 1; // 0x1 + field public static final deprecated int TKIP = 1; // 0x1 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "pairwise"; } public static class WifiConfiguration.Protocol { field public static final int RSN = 1; // 0x1 - field public static final int WPA = 0; // 0x0 + field public static final deprecated int WPA = 0; // 0x0 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "proto"; } @@ -27322,7 +27855,7 @@ package android.net.wifi { method public int addNetwork(android.net.wifi.WifiConfiguration); method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method public static int calculateSignalLevel(int, int); - method public void cancelWps(android.net.wifi.WifiManager.WpsCallback); + method public deprecated void cancelWps(android.net.wifi.WifiManager.WpsCallback); method public static int compareSignalLevel(int, int); method public android.net.wifi.WifiManager.MulticastLock createMulticastLock(java.lang.String); method public android.net.wifi.WifiManager.WifiLock createWifiLock(int, java.lang.String); @@ -27355,7 +27888,7 @@ package android.net.wifi { method public boolean setWifiEnabled(boolean); method public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler); method public deprecated boolean startScan(); - method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback); + method public deprecated void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback); method public int updateNetwork(android.net.wifi.WifiConfiguration); field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE"; @@ -27385,11 +27918,11 @@ package android.net.wifi { field public static final int WIFI_STATE_ENABLED = 3; // 0x3 field public static final int WIFI_STATE_ENABLING = 2; // 0x2 field public static final int WIFI_STATE_UNKNOWN = 4; // 0x4 - field public static final int WPS_AUTH_FAILURE = 6; // 0x6 - field public static final int WPS_OVERLAP_ERROR = 3; // 0x3 - field public static final int WPS_TIMED_OUT = 7; // 0x7 - field public static final int WPS_TKIP_ONLY_PROHIBITED = 5; // 0x5 - field public static final int WPS_WEP_PROHIBITED = 4; // 0x4 + field public static final deprecated int WPS_AUTH_FAILURE = 6; // 0x6 + field public static final deprecated int WPS_OVERLAP_ERROR = 3; // 0x3 + field public static final deprecated int WPS_TIMED_OUT = 7; // 0x7 + field public static final deprecated int WPS_TKIP_ONLY_PROHIBITED = 5; // 0x5 + field public static final deprecated int WPS_WEP_PROHIBITED = 4; // 0x4 } public static class WifiManager.LocalOnlyHotspotCallback { @@ -27423,11 +27956,11 @@ package android.net.wifi { method public void setWorkSource(android.os.WorkSource); } - public static abstract class WifiManager.WpsCallback { + public static abstract deprecated class WifiManager.WpsCallback { ctor public WifiManager.WpsCallback(); - method public abstract void onFailed(int); - method public abstract void onStarted(java.lang.String); - method public abstract void onSucceeded(); + method public abstract deprecated void onFailed(int); + method public abstract deprecated void onStarted(java.lang.String); + method public abstract deprecated void onSucceeded(); } public class WpsInfo implements android.os.Parcelable { @@ -27869,6 +28402,29 @@ package android.net.wifi.p2p.nsd { package android.net.wifi.rtt { + public final class LocationCivic implements android.os.Parcelable { + method public int describeContents(); + method public byte[] getData(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class LocationConfigurationInformation implements android.os.Parcelable { + method public int describeContents(); + method public double getAltitude(); + method public int getAltitudeType(); + method public double getAltitudeUncertainty(); + method public double getLatitude(); + method public double getLatitudeUncertainty(); + method public double getLongitude(); + method public double getLongitudeUncertainty(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int ALTITUDE_IN_FLOORS = 2; // 0x2 + field public static final int ALTITUDE_IN_METERS = 1; // 0x1 + field public static final int ALTITUDE_UNKNOWN = 0; // 0x0 + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class RangingRequest implements android.os.Parcelable { method public int describeContents(); method public static int getMaxPeers(); @@ -27892,6 +28448,8 @@ package android.net.wifi.rtt { method public android.net.MacAddress getMacAddress(); method public android.net.wifi.aware.PeerHandle getPeerHandle(); method public long getRangingTimestampUs(); + method public android.net.wifi.rtt.LocationCivic getReportedLocationCivic(); + method public android.net.wifi.rtt.LocationConfigurationInformation getReportedLocationConfigurationInformation(); method public int getRssi(); method public int getStatus(); method public void writeToParcel(android.os.Parcel, int); @@ -31493,7 +32051,7 @@ package android.os { } public final class Debug { - method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException; + method public static void attachJvmtiAgent(java.lang.String, java.lang.String, java.lang.ClassLoader) throws java.io.IOException; method public static deprecated void changeDebugPort(int); method public static void dumpHprofData(java.lang.String) throws java.io.IOException; method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]); @@ -32206,6 +32764,7 @@ package android.os { field public static final int THREAD_PRIORITY_MORE_FAVORABLE = -1; // 0xffffffff field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8 + field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6 } public abstract class ProxyFileDescriptorCallback { @@ -32449,6 +33008,7 @@ package android.os { field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_AIRPLANE_MODE = "no_airplane_mode"; + field public static final java.lang.String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill"; field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth"; @@ -32461,6 +33021,7 @@ package android.os { field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale"; field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode"; field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; + field public static final java.lang.String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout"; field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn"; field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi"; @@ -33967,9 +34528,10 @@ package android.provider { field public static final java.lang.String DURATION = "duration"; field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER"; field public static final java.lang.String FEATURES = "features"; + field public static final int FEATURES_ASSISTED_DIALING_USED = 16; // 0x10 field public static final int FEATURES_HD_CALL = 4; // 0x4 field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2 - field public static final int FEATURES_RTT = 16; // 0x10 + field public static final int FEATURES_RTT = 32; // 0x20 field public static final int FEATURES_VIDEO = 1; // 0x1 field public static final int FEATURES_WIFI = 8; // 0x8 field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; @@ -35675,7 +36237,6 @@ package android.provider { field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS"; field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS"; field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS"; - field public static final java.lang.String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS"; field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS"; @@ -35716,6 +36277,7 @@ package android.provider { field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS"; field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO"; field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS"; + field public static final java.lang.String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS"; field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS"; field public static final java.lang.String ACTION_USAGE_ACCESS_SETTINGS = "android.settings.USAGE_ACCESS_SETTINGS"; field public static final java.lang.String ACTION_USER_DICTIONARY_SETTINGS = "android.settings.USER_DICTIONARY_SETTINGS"; @@ -35736,7 +36298,6 @@ package android.provider { field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE"; field public static final java.lang.String EXTRA_AUTHORITIES = "authorities"; field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; - field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes"; @@ -35848,11 +36409,11 @@ package android.provider { field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy"; field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility"; field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; - field public static final java.lang.String LOCATION_MODE = "location_mode"; - field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2 - field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3 - field public static final int LOCATION_MODE_OFF = 0; // 0x0 - field public static final int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1 + field public static final deprecated java.lang.String LOCATION_MODE = "location_mode"; + field public static final deprecated int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2 + field public static final deprecated int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3 + field public static final deprecated int LOCATION_MODE_OFF = 0; // 0x0 + field public static final deprecated int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1 field public static final deprecated java.lang.String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; field public static final deprecated java.lang.String LOCK_PATTERN_ENABLED = "lock_pattern_autolock"; field public static final deprecated java.lang.String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED = "lock_pattern_tactile_feedback_enabled"; @@ -36022,6 +36583,18 @@ package android.provider { field public static final deprecated java.lang.String WINDOW_ANIMATION_SCALE = "window_animation_scale"; } + public class SettingsSlicesContract { + field public static final java.lang.String AUTHORITY = "android.settings.slices"; + field public static final android.net.Uri BASE_URI; + field public static final java.lang.String KEY_AIRPLANE_MODE = "airplane_mode"; + field public static final java.lang.String KEY_BATTERY_SAVER = "battery_saver"; + field public static final java.lang.String KEY_BLUETOOTH = "bluetooth"; + field public static final java.lang.String KEY_LOCATION = "location"; + field public static final java.lang.String KEY_WIFI = "wifi"; + field public static final java.lang.String PATH_SETTING_ACTION = "action"; + field public static final java.lang.String PATH_SETTING_INTENT = "intent"; + } + public class SyncStateContract { ctor public SyncStateContract(); } @@ -36268,6 +36841,7 @@ package android.provider { field public static final int RESULT_SMS_HANDLED = 1; // 0x1 field public static final int RESULT_SMS_OUT_OF_MEMORY = 3; // 0x3 field public static final int RESULT_SMS_UNSUPPORTED = 4; // 0x4 + field public static final java.lang.String SECRET_CODE_ACTION = "android.provider.Telephony.SECRET_CODE"; field public static final java.lang.String SIM_FULL_ACTION = "android.provider.Telephony.SIM_FULL"; field public static final java.lang.String SMS_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_CB_RECEIVED"; field public static final java.lang.String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER"; @@ -37558,6 +38132,59 @@ package android.sax { } +package android.se.omapi { + + public class Channel { + method public void close(); + method public byte[] getSelectResponse(); + method public android.se.omapi.Session getSession(); + method public boolean isBasicChannel(); + method public boolean isClosed(); + method public boolean selectNext() throws java.io.IOException; + method public byte[] transmit(byte[]) throws java.io.IOException; + } + + public abstract interface ISecureElementListener implements android.os.IInterface { + method public abstract void serviceConnected() throws android.os.RemoteException; + } + + public static abstract class ISecureElementListener.Stub extends android.os.Binder implements android.se.omapi.ISecureElementListener { + ctor public ISecureElementListener.Stub(); + method public android.os.IBinder asBinder(); + method public static android.se.omapi.ISecureElementListener asInterface(android.os.IBinder); + method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException; + } + + public class Reader { + method public void closeSessions(); + method public java.lang.String getName(); + method public android.se.omapi.SEService getSEService(); + method public boolean isSecureElementPresent(); + method public android.se.omapi.Session openSession() throws java.io.IOException; + } + + public class SEService { + ctor public SEService(android.content.Context, android.se.omapi.ISecureElementListener); + method public android.se.omapi.Reader[] getReaders(); + method public java.lang.String getVersion(); + method public boolean isConnected(); + method public void shutdown(); + } + + public class Session { + method public void close(); + method public void closeChannels(); + method public byte[] getATR(); + method public android.se.omapi.Reader getReader(); + method public boolean isClosed(); + method public android.se.omapi.Channel openBasicChannel(byte[], byte) throws java.io.IOException; + method public android.se.omapi.Channel openBasicChannel(byte[]) throws java.io.IOException; + method public android.se.omapi.Channel openLogicalChannel(byte[], byte) throws java.io.IOException; + method public android.se.omapi.Channel openLogicalChannel(byte[]) throws java.io.IOException; + } + +} + package android.security { public final class AttestedKeyPair { @@ -37670,6 +38297,8 @@ package android.security.keystore { method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); method public boolean isRandomizedEncryptionRequired(); + method public boolean isStrongBoxBacked(); + method public boolean isTrustedUserPresenceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); } @@ -37687,6 +38316,7 @@ package android.security.keystore { method public android.security.keystore.KeyGenParameterSpec.Builder setDigests(java.lang.String...); method public android.security.keystore.KeyGenParameterSpec.Builder setEncryptionPaddings(java.lang.String...); method public android.security.keystore.KeyGenParameterSpec.Builder setInvalidatedByBiometricEnrollment(boolean); + method public android.security.keystore.KeyGenParameterSpec.Builder setIsStrongBoxBacked(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setKeySize(int); method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityEnd(java.util.Date); method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date); @@ -37694,6 +38324,7 @@ package android.security.keystore { method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date); method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); + method public android.security.keystore.KeyGenParameterSpec.Builder setTrustedUserPresenceRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int); @@ -37714,6 +38345,7 @@ package android.security.keystore { method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isInsideSecureHardware(); method public boolean isInvalidatedByBiometricEnrollment(); + method public boolean isTrustedUserPresenceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware(); method public boolean isUserAuthenticationValidWhileOnBody(); @@ -37747,6 +38379,7 @@ package android.security.keystore { field public static final java.lang.String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding"; field public static final java.lang.String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding"; field public static final java.lang.String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding"; + field public static final deprecated java.lang.String KEY_ALGORITHM_3DES = "DESede"; field public static final java.lang.String KEY_ALGORITHM_AES = "AES"; field public static final java.lang.String KEY_ALGORITHM_EC = "EC"; field public static final java.lang.String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1"; @@ -37757,11 +38390,13 @@ package android.security.keystore { field public static final java.lang.String KEY_ALGORITHM_RSA = "RSA"; field public static final int ORIGIN_GENERATED = 1; // 0x1 field public static final int ORIGIN_IMPORTED = 2; // 0x2 + field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8 field public static final int ORIGIN_UNKNOWN = 4; // 0x4 field public static final int PURPOSE_DECRYPT = 2; // 0x2 field public static final int PURPOSE_ENCRYPT = 1; // 0x1 field public static final int PURPOSE_SIGN = 4; // 0x4 field public static final int PURPOSE_VERIFY = 8; // 0x8 + field public static final int PURPOSE_WRAP_KEY = 32; // 0x20 field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1"; field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS"; } @@ -37801,12 +38436,29 @@ package android.security.keystore { method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int); } + public class StrongBoxUnavailableException extends java.security.ProviderException { + } + public class UserNotAuthenticatedException extends java.security.InvalidKeyException { ctor public UserNotAuthenticatedException(); ctor public UserNotAuthenticatedException(java.lang.String); ctor public UserNotAuthenticatedException(java.lang.String, java.lang.Throwable); } + public class UserPresenceUnavailableException extends java.security.InvalidAlgorithmParameterException { + ctor public UserPresenceUnavailableException(); + ctor public UserPresenceUnavailableException(java.lang.String); + ctor public UserPresenceUnavailableException(java.lang.String, java.lang.Throwable); + } + + public class WrappedKeyEntry implements java.security.KeyStore.Entry { + ctor public WrappedKeyEntry(byte[], java.lang.String, java.lang.String, java.security.spec.AlgorithmParameterSpec); + method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec(); + method public java.lang.String getTransformation(); + method public byte[] getWrappedKeyBytes(); + method public java.lang.String getWrappingKeyAlias(); + } + } package android.service.autofill { @@ -37884,7 +38536,6 @@ package android.service.autofill { } public static final class FieldClassification.Match { - method public java.lang.String getAlgorithm(); method public java.lang.String getRemoteId(); method public float getScore(); } @@ -38221,6 +38872,7 @@ package android.service.dreams { method public void onWindowFocusChanged(boolean); method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback); method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); + method public final T requireViewById(int); method public void setContentView(int); method public void setContentView(android.view.View); method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); @@ -39036,11 +39688,6 @@ package android.system { field public final int errno; } - public class Int32Ref { - ctor public Int32Ref(int); - field public int value; - } - public class Int64Ref { ctor public Int64Ref(long); field public long value; @@ -39140,7 +39787,6 @@ package android.system { method public static int umask(int); method public static android.system.StructUtsname uname(); method public static void unsetenv(java.lang.String) throws android.system.ErrnoException; - method public static int waitpid(int, android.system.Int32Ref, int) throws android.system.ErrnoException; method public static int write(java.io.FileDescriptor, java.nio.ByteBuffer) throws android.system.ErrnoException, java.io.InterruptedIOException; method public static int write(java.io.FileDescriptor, byte[], int, int) throws android.system.ErrnoException, java.io.InterruptedIOException; method public static int writev(java.io.FileDescriptor, java.lang.Object[], int[], int[]) throws android.system.ErrnoException, java.io.InterruptedIOException; @@ -39818,6 +40464,7 @@ package android.telecom { field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800 field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 @@ -39964,6 +40611,7 @@ package android.telecom { method public void onCallEvent(java.lang.String, android.os.Bundle); method public void onDisconnect(); method public void onExtrasChanged(android.os.Bundle); + method public void onHandoverComplete(); method public void onHold(); method public void onPlayDtmfTone(char); method public void onPostDialContinue(boolean); @@ -40038,6 +40686,7 @@ package android.telecom { field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT"; field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS"; field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER"; + field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20 field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10 field public static final int PROPERTY_IS_RTT = 256; // 0x100 @@ -40454,12 +41103,14 @@ package android.telecom { field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL"; field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED"; field public static final java.lang.String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED"; + field public static final java.lang.String ACTION_SHOW_ASSISTED_DIALING_SETTINGS = "android.telecom.action.SHOW_ASSISTED_DIALING_SETTINGS"; field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS"; field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS"; field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"; field public static final java.lang.String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS"; field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ',' field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';' + field public static final java.lang.String EXTRA_ASSISTED_DIALING_TRANSFORMATION_INFO = "android.telecom.extra.ASSISTED_DIALING_TRANSFORMATION_INFO"; field public static final java.lang.String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"; field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE"; field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE"; @@ -40475,6 +41126,7 @@ package android.telecom { field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT"; field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; + field public static final java.lang.String EXTRA_USE_ASSISTED_DIALING = "android.telecom.extra.USE_ASSISTED_DIALING"; field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS"; field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE"; field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS"; @@ -40487,6 +41139,18 @@ package android.telecom { field public static final int PRESENTATION_UNKNOWN = 3; // 0x3 } + public final class TransformationInfo implements android.os.Parcelable { + ctor public TransformationInfo(java.lang.String, java.lang.String, java.lang.String, java.lang.String, int); + method public int describeContents(); + method public java.lang.String getOriginalNumber(); + method public java.lang.String getTransformedNumber(); + method public int getTransformedNumberCountryCallingCode(); + method public java.lang.String getUserHomeCountryCode(); + method public java.lang.String getUserRoamingCountryCode(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public class VideoProfile implements android.os.Parcelable { ctor public VideoProfile(int); ctor public VideoProfile(int, int); @@ -40536,6 +41200,7 @@ package android.telephony { field public static final int EUTRAN = 3; // 0x3 field public static final int GERAN = 1; // 0x1 field public static final int IWLAN = 5; // 0x5 + field public static final int UNKNOWN = 0; // 0x0 field public static final int UTRAN = 2; // 0x2 } @@ -40640,6 +41305,8 @@ package android.telephony { method public void notifyConfigChangedForSubId(int); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe + field public static final java.lang.String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; + field public static final java.lang.String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -40650,6 +41317,7 @@ package android.telephony { field public static final java.lang.String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool"; field public static final java.lang.String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; field public static final java.lang.String KEY_APN_EXPAND_BOOL = "apn_expand_bool"; + field public static final java.lang.String KEY_ASSISTED_DIALING_ENABLED_BOOL = "assisted_dialing_enabled_bool"; field public static final java.lang.String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool"; field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool"; @@ -40682,6 +41350,7 @@ package android.telephony { field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string"; field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string"; field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string"; + field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string"; field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool"; field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; @@ -40820,6 +41489,7 @@ package android.telephony { } public final class CellIdentityLte extends android.telephony.CellIdentity { + method public int getBandwidth(); method public int getCi(); method public int getEarfcn(); method public deprecated int getMcc(); @@ -40863,8 +41533,13 @@ package android.telephony { public abstract class CellInfo implements android.os.Parcelable { method public int describeContents(); + method public int getCellConnectionStatus(); method public long getTimeStamp(); method public boolean isRegistered(); + field public static final int CONNECTION_NONE = 0; // 0x0 + field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1 + field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2 + field public static final int CONNECTION_UNKNOWN = 2147483647; // 0x7fffffff field public static final android.os.Parcelable.Creator CREATOR; } @@ -41177,6 +41852,9 @@ package android.telephony { ctor public ServiceState(android.os.Parcel); method protected void copyFrom(android.telephony.ServiceState); method public int describeContents(); + method public int[] getCellBandwidths(); + method public int getChannelNumber(); + method public int getDuplexMode(); method public boolean getIsManualSelection(); method public int getNetworkId(); method public java.lang.String getOperatorAlphaLong(); @@ -41193,6 +41871,9 @@ package android.telephony { method public void setStateOutOfService(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DUPLEX_MODE_FDD = 1; // 0x1 + field public static final int DUPLEX_MODE_TDD = 2; // 0x2 + field public static final int DUPLEX_MODE_UNKNOWN = 0; // 0x0 field public static final int STATE_EMERGENCY_ONLY = 2; // 0x2 field public static final int STATE_IN_SERVICE = 0; // 0x0 field public static final int STATE_OUT_OF_SERVICE = 1; // 0x1 @@ -41375,10 +42056,16 @@ package android.telephony { method public static int getDefaultSmsSubscriptionId(); method public static int getDefaultSubscriptionId(); method public static int getDefaultVoiceSubscriptionId(); + method public java.util.List getSubscriptionPlans(int); method public boolean isNetworkRoaming(int); method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); + method public void setSubscriptionOverrideCongested(int, boolean, long); + method public void setSubscriptionOverrideUnmetered(int, boolean, long); + method public void setSubscriptionPlans(int, java.util.List); field public static final java.lang.String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED"; field public static final java.lang.String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED"; + 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"; field public static final int DATA_ROAMING_DISABLE = 0; // 0x0 field public static final int DATA_ROAMING_ENABLE = 1; // 0x1 field public static final java.lang.String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; @@ -41390,11 +42077,45 @@ package android.telephony { method public void onSubscriptionsChanged(); } + public final class SubscriptionPlan implements android.os.Parcelable { + method public java.util.Iterator> cycleIterator(); + method public int describeContents(); + method public int getDataLimitBehavior(); + method public long getDataLimitBytes(); + method public long getDataUsageBytes(); + method public long getDataUsageTime(); + method public java.lang.CharSequence getSummary(); + method public java.lang.CharSequence getTitle(); + method public void writeToParcel(android.os.Parcel, int); + field public static final long BYTES_UNKNOWN = -1L; // 0xffffffffffffffffL + field public static final long BYTES_UNLIMITED = 9223372036854775807L; // 0x7fffffffffffffffL + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int LIMIT_BEHAVIOR_BILLED = 1; // 0x1 + field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0 + field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2 + field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff + field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL + } + + public static class SubscriptionPlan.Builder { + method public android.telephony.SubscriptionPlan build(); + method public static android.telephony.SubscriptionPlan.Builder createNonrecurring(java.time.ZonedDateTime, java.time.ZonedDateTime); + method public static android.telephony.SubscriptionPlan.Builder createRecurringDaily(java.time.ZonedDateTime); + method public static android.telephony.SubscriptionPlan.Builder createRecurringMonthly(java.time.ZonedDateTime); + method public static android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime); + method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int); + method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long); + method public android.telephony.SubscriptionPlan.Builder setSummary(java.lang.CharSequence); + method public android.telephony.SubscriptionPlan.Builder setTitle(java.lang.CharSequence); + } + public class TelephonyManager { method public boolean canChangeDtmfToneLength(); method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle); method public android.telephony.TelephonyManager createForSubscriptionId(int); method public java.util.List getAllCellInfo(); + method public int getAndroidCarrierIdForSubscription(); + method public java.lang.CharSequence getAndroidCarrierNameForSubscription(); method public int getCallState(); method public android.os.PersistableBundle getCarrierConfig(); method public deprecated android.telephony.CellLocation getCellLocation(); @@ -41432,8 +42153,6 @@ package android.telephony { method public int getSimState(); method public int getSimState(int); method public java.lang.String getSubscriberId(); - method public int getSubscriptionCarrierId(); - method public java.lang.String getSubscriptionCarrierName(); method public java.lang.String getVisualVoicemailPackageName(); method public java.lang.String getVoiceMailAlphaTag(); method public java.lang.String getVoiceMailNumber(); @@ -41651,6 +42370,7 @@ package android.telephony.data { method public java.net.InetAddress getMmsProxy(); method public java.net.URL getMmsc(); method public java.lang.String getMvnoType(); + method public int getNetworkTypeBitmask(); method public java.lang.String getOperatorNumeric(); method public java.lang.String getPassword(); method public int getPort(); @@ -41694,11 +42414,11 @@ package android.telephony.data { method public android.telephony.data.ApnSetting.Builder setAuthType(int); method public android.telephony.data.ApnSetting.Builder setCarrierEnabled(boolean); method public android.telephony.data.ApnSetting.Builder setEntryName(java.lang.String); - method public android.telephony.data.ApnSetting.Builder setId(int); method public android.telephony.data.ApnSetting.Builder setMmsPort(int); method public android.telephony.data.ApnSetting.Builder setMmsProxy(java.net.InetAddress); method public android.telephony.data.ApnSetting.Builder setMmsc(java.net.URL); method public android.telephony.data.ApnSetting.Builder setMvnoType(java.lang.String); + method public android.telephony.data.ApnSetting.Builder setNetworkTypeBitmask(int); method public android.telephony.data.ApnSetting.Builder setOperatorNumeric(java.lang.String); method public android.telephony.data.ApnSetting.Builder setPassword(java.lang.String); method public android.telephony.data.ApnSetting.Builder setPort(int); @@ -42295,22 +43015,11 @@ package android.text { method public boolean isAllowed(char); } - public abstract interface NoCopySpan { - } - - public static class NoCopySpan.Concrete implements android.text.NoCopySpan { - ctor public NoCopySpan.Concrete(); - } - - public abstract interface ParcelableSpan implements android.os.Parcelable { - method public abstract int getSpanTypeId(); - } - - public class PremeasuredText implements android.text.Spanned { - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); + public class MeasuredText implements android.text.Spanned { 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); @@ -42327,6 +43036,26 @@ 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 { + } + + public static class NoCopySpan.Concrete implements android.text.NoCopySpan { + ctor public NoCopySpan.Concrete(); + } + + public abstract interface ParcelableSpan implements android.os.Parcelable { + method public abstract int getSpanTypeId(); + } + public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); @@ -43026,9 +43755,13 @@ package android.text.style { ctor public BulletSpan(); ctor public BulletSpan(int); ctor public BulletSpan(int, int); + ctor public BulletSpan(int, int, int); ctor public BulletSpan(android.os.Parcel); method public int describeContents(); method public void drawLeadingMargin(android.graphics.Canvas, android.graphics.Paint, int, int, int, int, int, java.lang.CharSequence, int, int, boolean, android.text.Layout); + method public int getBulletRadius(); + method public int getColor(); + method public int getGapWidth(); method public int getLeadingMargin(boolean); method public int getSpanTypeId(); method public void writeToParcel(android.os.Parcel, int); @@ -43178,13 +43911,19 @@ package android.text.style { public class QuoteSpan implements android.text.style.LeadingMarginSpan android.text.ParcelableSpan { ctor public QuoteSpan(); ctor public QuoteSpan(int); + ctor public QuoteSpan(int, int, int); ctor public QuoteSpan(android.os.Parcel); method public int describeContents(); method public void drawLeadingMargin(android.graphics.Canvas, android.graphics.Paint, int, int, int, int, int, java.lang.CharSequence, int, int, boolean, android.text.Layout); method public int getColor(); + method public int getGapWidth(); method public int getLeadingMargin(boolean); method public int getSpanTypeId(); + method public int getStripeWidth(); method public void writeToParcel(android.os.Parcel, int); + field public static final int STANDARD_COLOR = -16776961; // 0xff0000ff + field public static final int STANDARD_GAP_WIDTH_PX = 2; // 0x2 + field public static final int STANDARD_STRIPE_WIDTH_PX = 2; // 0x2 } public class RelativeSizeSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan { @@ -44052,6 +44791,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); } @@ -44351,42 +45102,42 @@ package android.util { method public void previousMonth(); } - public final class MutableBoolean { + public final deprecated class MutableBoolean { ctor public MutableBoolean(boolean); field public boolean value; } - public final class MutableByte { + public final deprecated class MutableByte { ctor public MutableByte(byte); field public byte value; } - public final class MutableChar { + public final deprecated class MutableChar { ctor public MutableChar(char); field public char value; } - public final class MutableDouble { + public final deprecated class MutableDouble { ctor public MutableDouble(double); field public double value; } - public final class MutableFloat { + public final deprecated class MutableFloat { ctor public MutableFloat(float); field public float value; } - public final class MutableInt { + public final deprecated class MutableInt { ctor public MutableInt(int); field public int value; } - public final class MutableLong { + public final deprecated class MutableLong { ctor public MutableLong(long); field public long value; } - public final class MutableShort { + public final deprecated class MutableShort { ctor public MutableShort(short); field public short value; } @@ -45459,6 +46210,7 @@ package android.view { field public static final int KEYCODE_PROG_YELLOW = 185; // 0xb9 field public static final int KEYCODE_Q = 45; // 0x2d field public static final int KEYCODE_R = 46; // 0x2e + field public static final int KEYCODE_REFRESH = 285; // 0x11d field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48 field public static final int KEYCODE_RO = 217; // 0xd9 field public static final int KEYCODE_S = 47; // 0x2f @@ -46625,6 +47377,7 @@ package android.view { method public boolean requestRectangleOnScreen(android.graphics.Rect); method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean); method public final void requestUnbufferedDispatch(android.view.MotionEvent); + method public final T requireViewById(int); method public static int resolveSize(int, int); method public static int resolveSizeAndState(int, int, int); method public boolean restoreDefaultFocus(); @@ -47627,6 +48380,7 @@ package android.view { method protected final int getLocalFeatures(); method public android.media.session.MediaController getMediaController(); method public abstract int getNavigationBarColor(); + method public int getNavigationBarDividerColor(); method public android.transition.Transition getReenterTransition(); method public android.transition.Transition getReturnTransition(); method public android.transition.Transition getSharedElementEnterTransition(); @@ -47659,6 +48413,7 @@ package android.view { method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int); method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener); method public boolean requestFeature(int); + method public final T requireViewById(int); method public abstract void restoreHierarchyState(android.os.Bundle); method public abstract android.os.Bundle saveHierarchyState(); method public void setAllowEnterTransitionOverlap(boolean); @@ -47695,6 +48450,7 @@ package android.view { method public void setLogo(int); method public void setMediaController(android.media.session.MediaController); method public abstract void setNavigationBarColor(int); + method public void setNavigationBarDividerColor(int); method public void setReenterTransition(android.transition.Transition); method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable); method public final void setRestrictedCaptionAreaListener(android.view.Window.OnRestrictedCaptionAreaChangedListener); @@ -47888,7 +48644,6 @@ package android.view { field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1 field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8 field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0 - field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L field public static final int FLAGS_CHANGED = 4; // 0x4 field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1 field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000 @@ -47926,6 +48681,9 @@ package android.view { field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7 field public static final int LAYOUT_CHANGED = 1; // 0x1 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; // 0x1 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2 field public static final int MEMORY_TYPE_CHANGED = 256; // 0x100 field public static final deprecated int MEMORY_TYPE_GPU = 2; // 0x2 field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1 @@ -47983,11 +48741,11 @@ package android.view { field public float buttonBrightness; field public float dimAmount; field public int flags; - field public long flags2; field public int format; field public int gravity; field public float horizontalMargin; field public float horizontalWeight; + field public int layoutInDisplayCutoutMode; field public deprecated int memoryType; field public java.lang.String packageName; field public int preferredDisplayModeId; @@ -48035,6 +48793,8 @@ package android.view.accessibility { method public void setPackageName(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4 + field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10 + field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20 field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8 field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1 field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2 @@ -48152,6 +48912,7 @@ package android.view.accessibility { method public java.lang.CharSequence getText(); method public int getTextSelectionEnd(); method public int getTextSelectionStart(); + method public java.lang.CharSequence getTooltipText(); method public android.view.accessibility.AccessibilityNodeInfo getTraversalAfter(); method public android.view.accessibility.AccessibilityNodeInfo getTraversalBefore(); method public java.lang.String getViewIdResourceName(); @@ -48168,6 +48929,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(); @@ -48211,6 +48973,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); @@ -48237,6 +49000,7 @@ package android.view.accessibility { method public void setSource(android.view.View, int); method public void setText(java.lang.CharSequence); method public void setTextSelection(int, int); + method public void setTooltipText(java.lang.CharSequence); method public void setTraversalAfter(android.view.View); method public void setTraversalAfter(android.view.View, int); method public void setTraversalBefore(android.view.View); @@ -48306,6 +49070,7 @@ package android.view.accessibility { field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS; + field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY; @@ -48325,6 +49090,7 @@ package android.view.accessibility { field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN; + field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP; } public static final class AccessibilityNodeInfo.CollectionInfo { @@ -48344,7 +49110,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); @@ -49043,6 +49809,7 @@ package android.view.inputmethod { method public abstract boolean performEditorAction(int); method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle); method public abstract boolean reportFullscreenMode(boolean); + method public default void reportLanguageHint(android.os.LocaleList); method public abstract boolean requestCursorUpdates(int); method public abstract boolean sendKeyEvent(android.view.KeyEvent); method public abstract boolean setComposingRegion(int, int); @@ -49261,14 +50028,13 @@ package android.view.textclassifier { method public android.graphics.drawable.Drawable getSecondaryIcon(int); method public android.content.Intent getSecondaryIntent(int); method public java.lang.CharSequence getSecondaryLabel(int); - method public android.view.View.OnClickListener getSecondaryOnClickListener(int); method public java.lang.String getSignature(); method public java.lang.String getText(); } public static final class TextClassification.Builder { ctor public TextClassification.Builder(); - method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener); + method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable); method public android.view.textclassifier.TextClassification build(); method public android.view.textclassifier.TextClassification.Builder clearSecondaryActions(); method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float); @@ -49276,15 +50042,20 @@ package android.view.textclassifier { method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent); method public android.view.textclassifier.TextClassification.Builder setLabel(java.lang.String); method public android.view.textclassifier.TextClassification.Builder setOnClickListener(android.view.View.OnClickListener); - method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener); + method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable); method public android.view.textclassifier.TextClassification.Builder setSignature(java.lang.String); method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String); } - public static final class TextClassification.Options { + public static final class TextClassification.Options implements android.os.Parcelable { ctor public TextClassification.Options(); + method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); + method public java.util.Calendar getReferenceTime(); method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList); + method public android.view.textclassifier.TextClassification.Options setReferenceTime(java.util.Calendar); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } public final class TextClassificationManager { @@ -49307,23 +50078,32 @@ package android.view.textclassifier { field public static final int ENTITY_PRESET_NONE = 1; // 0x1 field public static final android.view.textclassifier.TextClassifier NO_OP; field public static final java.lang.String TYPE_ADDRESS = "address"; + field public static final java.lang.String TYPE_DATE = "date"; + field public static final java.lang.String TYPE_DATE_TIME = "datetime"; field public static final java.lang.String TYPE_EMAIL = "email"; + field public static final java.lang.String TYPE_FLIGHT_NUMBER = "flight"; field public static final java.lang.String TYPE_OTHER = "other"; field public static final java.lang.String TYPE_PHONE = "phone"; field public static final java.lang.String TYPE_UNKNOWN = ""; field public static final java.lang.String TYPE_URL = "url"; } - public static final class TextClassifier.EntityConfig { + public static final class TextClassifier.EntityConfig implements android.os.Parcelable { ctor public TextClassifier.EntityConfig(int); + method public int describeContents(); method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...); method public java.util.List getEntities(android.view.textclassifier.TextClassifier); method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } - public final class TextLinks { + public final class TextLinks implements android.os.Parcelable { method public boolean apply(android.text.SpannableString, java.util.function.Function); + method public int describeContents(); method public java.util.Collection getLinks(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } public static final class TextLinks.Builder { @@ -49332,21 +50112,27 @@ package android.view.textclassifier { method public android.view.textclassifier.TextLinks build(); } - public static final class TextLinks.Options { + public static final class TextLinks.Options implements android.os.Parcelable { ctor public TextLinks.Options(); + method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig(); method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList); method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } - public static final class TextLinks.TextLink { + public static final class TextLinks.TextLink implements android.os.Parcelable { ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map); + method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public int getEnd(); method public java.lang.String getEntity(int); method public int getEntityCount(); method public int getStart(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } public final class TextSelection { @@ -49365,10 +50151,13 @@ package android.view.textclassifier { method public android.view.textclassifier.TextSelection.Builder setSignature(java.lang.String); } - public static final class TextSelection.Options { + public static final class TextSelection.Options implements android.os.Parcelable { ctor public TextSelection.Options(); + method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextSelection.Options setDefaultLocales(android.os.LocaleList); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } } @@ -50054,6 +50843,7 @@ package android.webkit { method public android.graphics.Bitmap getFavicon(); method public android.webkit.WebView.HitTestResult getHitTestResult(); method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String); + method public android.os.Looper getLooper(); method public java.lang.String getOriginalUrl(); method public int getProgress(); method public boolean getRendererPriorityWaivedWhenNotVisible(); @@ -50065,6 +50855,7 @@ package android.webkit { method public java.lang.String getTitle(); method public java.lang.String getUrl(); method public android.webkit.WebChromeClient getWebChromeClient(); + method public static java.lang.ClassLoader getWebViewClassLoader(); method public android.webkit.WebViewClient getWebViewClient(); method public void goBack(); method public void goBackOrForward(int); @@ -52486,6 +53277,7 @@ package android.widget { method public int getExtendedPaddingBottom(); method public int getExtendedPaddingTop(); method public android.text.InputFilter[] getFilters(); + method public int getFirstBaselineToTopHeight(); method public java.lang.String getFontFeatureSettings(); method public java.lang.String getFontVariationSettings(); method public boolean getFreezesText(); @@ -52503,6 +53295,7 @@ package android.widget { method public int getInputType(); method public int getJustificationMode(); method public final android.text.method.KeyListener getKeyListener(); + method public int getLastBaselineToBottomHeight(); method public final android.text.Layout getLayout(); method public float getLetterSpacing(); method public int getLineBounds(int, android.graphics.Rect); @@ -52550,6 +53343,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(); @@ -52572,6 +53366,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); @@ -52599,6 +53394,7 @@ package android.widget { method public void setExtractedText(android.view.inputmethod.ExtractedText); method public void setFallbackLineSpacing(boolean); method public void setFilters(android.text.InputFilter[]); + method public void setFirstBaselineToTopHeight(int); method public void setFontFeatureSettings(java.lang.String); method public boolean setFontVariationSettings(java.lang.String); method protected boolean setFrame(int, int, int, int); @@ -52620,7 +53416,9 @@ package android.widget { method public void setInputType(int); method public void setJustificationMode(int); method public void setKeyListener(android.text.method.KeyListener); + method public void setLastBaselineToBottomHeight(int); method public void setLetterSpacing(float); + method public void setLineHeight(int); method public void setLineSpacing(float, float); method public void setLines(int); method public final void setLinkTextColor(int); @@ -72370,197 +73168,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 errors(); - method public synchronized int failureCount(); - method public synchronized java.util.Enumeration 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 fErrors; - field protected java.util.Vector fFailures; - field protected java.util.Vector 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, java.lang.String); - ctor public TestSuite(java.lang.String); - ctor public TestSuite(java.lang.Class...); - ctor public TestSuite(java.lang.Class[], java.lang.String); - method public void addTest(junit.framework.Test); - method public void addTestSuite(java.lang.Class); - 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 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/removed.txt b/api/removed.txt index 77088e5bdab7338f73c9118af4616192eea72935..2aab223ed281d88936a26f30fa4c4c2cd2b83248 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -265,7 +265,6 @@ package android.os { method public android.graphics.drawable.Drawable getBadgedDrawableForUser(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int); method public android.graphics.drawable.Drawable getBadgedIconForUser(android.graphics.drawable.Drawable, android.os.UserHandle); method public java.lang.CharSequence getBadgedLabelForUser(java.lang.CharSequence, android.os.UserHandle); - method public deprecated boolean trySetQuietModeEnabled(boolean, android.os.UserHandle); } } diff --git a/api/system-current.txt b/api/system-current.txt index b47304a41bc2a8f18593a6f6088cdf8045d568e9..396ff1a9aa334b1938a50c3fae5fcf0ad0a80029 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -33,6 +33,7 @@ package android { field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; field public static final java.lang.String BRICK = "android.permission.BRICK"; + field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final deprecated java.lang.String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED"; field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED"; field public static final java.lang.String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; @@ -46,6 +47,7 @@ package android { field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION"; field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; + field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL"; field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"; field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; @@ -73,6 +75,7 @@ package android { field public static final java.lang.String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"; field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES"; + field public static final java.lang.String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES"; field public static final java.lang.String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES"; field public static final java.lang.String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT"; field public static final java.lang.String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS"; @@ -129,6 +132,7 @@ package android { field public static final java.lang.String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final java.lang.String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES"; field public static final java.lang.String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES"; + field public static final java.lang.String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO"; field public static final java.lang.String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; field public static final java.lang.String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL"; field public static final java.lang.String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS"; @@ -249,6 +253,7 @@ package android.app { public class AppOpsManager { method public static java.lang.String[] getOpStrs(); method public void setUidMode(java.lang.String, int, int); + field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; @@ -305,8 +310,10 @@ package android.app { ctor public InstantAppResolverService(); method public final void attachBaseContext(android.content.Context); method public final android.os.IBinder onBind(android.content.Intent); - method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public deprecated void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppIntentFilter(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public deprecated void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppResolveInfo(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); } public static final class InstantAppResolverService.InstantAppResolutionCallback { @@ -350,6 +357,20 @@ package android.app { method public org.json.JSONObject toJson() throws org.json.JSONException; } + public final class StatsManager { + method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); + method public byte[] getData(long); + method public byte[] getMetadata(); + method public boolean removeConfiguration(long); + method public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); + field public static final java.lang.String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; + field public static final java.lang.String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; + field public static final java.lang.String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; + field public static final java.lang.String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE"; + field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID"; + field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; + } + public class VrManager { method public void setAndBindVrCompositor(android.content.ComponentName); method public void setPersistentVrModeEnabled(boolean); @@ -428,10 +449,11 @@ package android.app.backup { method public java.lang.String getCurrentTransport(); method public boolean isAppEligibleForBackup(java.lang.String); method public boolean isBackupEnabled(); + method public boolean isBackupServiceActive(android.os.UserHandle); method public java.lang.String[] listAllTransports(); method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver); method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int); - method public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor); + method public deprecated int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor); method public deprecated java.lang.String selectBackupTransport(java.lang.String); method public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback); method public void setAutoRestore(boolean); @@ -508,6 +530,7 @@ package android.app.backup { field public static final int LOG_EVENT_ID_SIGNATURE_MISMATCH = 29; // 0x1d field public static final int LOG_EVENT_ID_SYSTEM_APP_NO_AGENT = 38; // 0x26 field public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50; // 0x32 + field public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51; // 0x33 field public static final int LOG_EVENT_ID_UNKNOWN_VERSION = 44; // 0x2c field public static final int LOG_EVENT_ID_VERSIONS_MATCH = 35; // 0x23 field public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36; // 0x24 @@ -549,6 +572,7 @@ package android.app.backup { method public long getCurrentRestoreSet(); method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor); method public int getRestoreData(android.os.ParcelFileDescriptor); + method public int getTransportFlags(); method public int initializeDevice(); method public boolean isAppEligibleForBackup(android.content.pm.PackageInfo, boolean); method public java.lang.String name(); @@ -564,9 +588,12 @@ package android.app.backup { method public java.lang.String transportDirName(); field public static final int AGENT_ERROR = -1003; // 0xfffffc15 field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14 + field public static final int FLAG_INCREMENTAL = 2; // 0x2 + field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4 field public static final int FLAG_USER_INITIATED = 1; // 0x1 field public static final int NO_MORE_DATA = -1; // 0xffffffff field public static final int TRANSPORT_ERROR = -1000; // 0xfffffc18 + field public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006; // 0xfffffc12 field public static final int TRANSPORT_NOT_INITIALIZED = -1001; // 0xfffffc17 field public static final int TRANSPORT_OK = 0; // 0x0 field public static final int TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16 @@ -756,7 +783,9 @@ package android.content { field public static final java.lang.String NETWORK_SCORE_SERVICE = "network_score"; field public static final java.lang.String OEM_LOCK_SERVICE = "oem_lock"; field public static final java.lang.String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; + field public static final java.lang.String SECURE_ELEMENT_SERVICE = "secure_element"; field public static final java.lang.String STATS_MANAGER = "stats"; + field public static final java.lang.String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final java.lang.String VR_SERVICE = "vrmanager"; field public static final java.lang.String WIFI_RTT_SERVICE = "rttmanager"; field public static final java.lang.String WIFI_SCANNING_SERVICE = "wifiscanner"; @@ -787,18 +816,20 @@ 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 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"; field public static final java.lang.String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS"; field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET"; + field public static final java.lang.String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES"; field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID"; field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON"; field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; + field public static final java.lang.String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; } public class IntentFilter implements android.os.Parcelable { @@ -841,6 +872,7 @@ package android.content.pm { ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, int); ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, long, android.os.Bundle); ctor public InstantAppResolveInfo(java.lang.String, java.lang.String, java.util.List); + ctor public InstantAppResolveInfo(android.os.Bundle); method public int describeContents(); method public byte[] getDigestBytes(); method public int getDigestPrefix(); @@ -849,6 +881,7 @@ package android.content.pm { method public long getLongVersionCode(); method public java.lang.String getPackageName(); method public deprecated int getVersionCode(); + method public boolean shouldLetInstallerDecide(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -860,6 +893,7 @@ package android.content.pm { method public int[] getDigestPrefix(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.content.pm.InstantAppResolveInfo.InstantAppDigest UNDEFINED; } public final class IntentFilterVerificationInfo implements android.os.Parcelable { @@ -1018,8 +1052,10 @@ package android.content.pm { package android.content.pm.dex { public class ArtManager { - method public boolean isRuntimeProfilingEnabled(); - method public void snapshotRuntimeProfile(java.lang.String, java.lang.String, android.content.pm.dex.ArtManager.SnapshotRuntimeProfileCallback, android.os.Handler); + method public boolean isRuntimeProfilingEnabled(int); + method public void snapshotRuntimeProfile(int, java.lang.String, java.lang.String, java.util.concurrent.Executor, android.content.pm.dex.ArtManager.SnapshotRuntimeProfileCallback); + field public static final int PROFILE_APPS = 0; // 0x0 + field public static final int PROFILE_BOOT_IMAGE = 1; // 0x1 field public static final int SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND = 1; // 0x1 field public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2; // 0x2 field public static final int SNAPSHOT_FAILED_PACKAGE_NOT_FOUND = 0; // 0x0 @@ -1092,8 +1128,38 @@ package android.hardware.camera2.params { package android.hardware.display { + public final class BrightnessChangeEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public final float batteryLevel; + field public final float brightness; + field public final int colorTemperature; + field public final float lastBrightness; + field public final long[] luxTimestamps; + field public final float[] luxValues; + field public final boolean nightMode; + field public final java.lang.String packageName; + field public final long timeStamp; + } + + public final class BrightnessConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public android.util.Pair getCurve(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class BrightnessConfiguration.Builder { + ctor public BrightnessConfiguration.Builder(float[], float[]); + method public android.hardware.display.BrightnessConfiguration build(); + method public android.hardware.display.BrightnessConfiguration.Builder setDescription(java.lang.String); + } + public final class DisplayManager { + method public java.util.List getBrightnessEvents(); method public android.graphics.Point getStableDisplaySize(); + method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); } } @@ -1701,6 +1767,60 @@ package android.hardware.location { package android.hardware.radio { + public final class Announcement implements android.os.Parcelable { + method public int describeContents(); + method public android.hardware.radio.ProgramSelector getSelector(); + method public int getType(); + method public java.util.Map getVendorInfo(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int TYPE_EMERGENCY = 1; // 0x1 + field public static final int TYPE_EVENT = 6; // 0x6 + field public static final int TYPE_MISC = 8; // 0x8 + field public static final int TYPE_NEWS = 5; // 0x5 + field public static final int TYPE_SPORT = 7; // 0x7 + field public static final int TYPE_TRAFFIC = 3; // 0x3 + field public static final int TYPE_WARNING = 2; // 0x2 + field public static final int TYPE_WEATHER = 4; // 0x4 + } + + public static abstract interface Announcement.OnListUpdatedListener { + method public abstract void onListUpdated(java.util.Collection); + } + + public final class ProgramList implements java.lang.AutoCloseable { + method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener); + method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); + method public void close(); + method public android.hardware.radio.RadioManager.ProgramInfo get(android.hardware.radio.ProgramSelector.Identifier); + method public void registerListCallback(java.util.concurrent.Executor, android.hardware.radio.ProgramList.ListCallback); + method public void registerListCallback(android.hardware.radio.ProgramList.ListCallback); + method public void removeOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); + method public java.util.List toList(); + method public void unregisterListCallback(android.hardware.radio.ProgramList.ListCallback); + } + + public static final class ProgramList.Filter implements android.os.Parcelable { + ctor public ProgramList.Filter(java.util.Set, java.util.Set, boolean, boolean); + method public boolean areCategoriesIncluded(); + method public boolean areModificationsExcluded(); + method public int describeContents(); + method public java.util.Set getIdentifierTypes(); + method public java.util.Set getIdentifiers(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static abstract class ProgramList.ListCallback { + ctor public ProgramList.ListCallback(); + method public void onItemChanged(android.hardware.radio.ProgramSelector.Identifier); + method public void onItemRemoved(android.hardware.radio.ProgramSelector.Identifier); + } + + public static abstract interface ProgramList.OnCompleteListener { + method public abstract void onComplete(); + } + public final class ProgramSelector implements android.os.Parcelable { ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]); method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int); @@ -1709,9 +1829,10 @@ package android.hardware.radio { method public android.hardware.radio.ProgramSelector.Identifier[] getAllIds(int); method public long getFirstId(int); method public android.hardware.radio.ProgramSelector.Identifier getPrimaryId(); - method public int getProgramType(); + method public deprecated int getProgramType(); method public android.hardware.radio.ProgramSelector.Identifier[] getSecondaryIds(); - method public long[] getVendorIds(); + method public deprecated long[] getVendorIds(); + method public android.hardware.radio.ProgramSelector withSecondaryPreferred(android.hardware.radio.ProgramSelector.Identifier); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1 @@ -1719,25 +1840,31 @@ package android.hardware.radio { field public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; // 0x8 field public static final int IDENTIFIER_TYPE_DAB_SCID = 7; // 0x7 field public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5 + field public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5 field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa - field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb + field public static final deprecated int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 - field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 + field public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; // 0x2714 + field public static final deprecated int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 + field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc - field public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf - field public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8 - field public static final int PROGRAM_TYPE_AM = 1; // 0x1 - field public static final int PROGRAM_TYPE_AM_HD = 3; // 0x3 - field public static final int PROGRAM_TYPE_DAB = 5; // 0x5 - field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6 - field public static final int PROGRAM_TYPE_FM = 2; // 0x2 - field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4 - field public static final int PROGRAM_TYPE_SXM = 7; // 0x7 - field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf - field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 + field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf + field public static final deprecated int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf + field public static final deprecated int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8 + field public static final int IDENTIFIER_TYPE_VENDOR_START = 1000; // 0x3e8 + field public static final deprecated int PROGRAM_TYPE_AM = 1; // 0x1 + field public static final deprecated int PROGRAM_TYPE_AM_HD = 3; // 0x3 + field public static final deprecated int PROGRAM_TYPE_DAB = 5; // 0x5 + field public static final deprecated int PROGRAM_TYPE_DRMO = 6; // 0x6 + field public static final deprecated int PROGRAM_TYPE_FM = 2; // 0x2 + field public static final deprecated int PROGRAM_TYPE_FM_HD = 4; // 0x4 + field public static final deprecated int PROGRAM_TYPE_INVALID = 0; // 0x0 + field public static final deprecated int PROGRAM_TYPE_SXM = 7; // 0x7 + field public static final deprecated int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf + field public static final deprecated int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 } public static final class ProgramSelector.Identifier implements android.os.Parcelable { @@ -1752,12 +1879,15 @@ package android.hardware.radio { public static abstract class ProgramSelector.IdentifierType implements java.lang.annotation.Annotation { } - public static abstract class ProgramSelector.ProgramType implements java.lang.annotation.Annotation { + public static abstract deprecated class ProgramSelector.ProgramType implements java.lang.annotation.Annotation { } public class RadioManager { + method public void addAnnouncementListener(java.util.Set, android.hardware.radio.Announcement.OnListUpdatedListener); + method public void addAnnouncementListener(java.util.concurrent.Executor, java.util.Set, android.hardware.radio.Announcement.OnListUpdatedListener); method public int listModules(java.util.List); method public android.hardware.radio.RadioTuner openTuner(int, android.hardware.radio.RadioManager.BandConfig, boolean, android.hardware.radio.RadioTuner.Callback, android.os.Handler); + method public void removeAnnouncementListener(android.hardware.radio.Announcement.OnListUpdatedListener); field public static final int BAND_AM = 0; // 0x0 field public static final int BAND_AM_HD = 3; // 0x3 field public static final int BAND_FM = 1; // 0x1 @@ -1887,12 +2017,15 @@ package android.hardware.radio { public static class RadioManager.ProgramInfo implements android.os.Parcelable { method public int describeContents(); method public deprecated int getChannel(); + method public android.hardware.radio.ProgramSelector.Identifier getLogicallyTunedTo(); method public android.hardware.radio.RadioMetadata getMetadata(); + method public android.hardware.radio.ProgramSelector.Identifier getPhysicallyTunedTo(); + method public java.util.Collection getRelatedContent(); method public android.hardware.radio.ProgramSelector getSelector(); method public int getSignalStrength(); method public deprecated int getSubChannel(); method public java.util.Map getVendorInfo(); - method public boolean isDigital(); + method public deprecated boolean isDigital(); method public boolean isLive(); method public boolean isMuted(); method public boolean isStereo(); @@ -1952,20 +2085,21 @@ package android.hardware.radio { method public abstract int cancel(); method public abstract void cancelAnnouncement(); method public abstract void close(); - method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); + method public abstract deprecated int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); + method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter); method public abstract boolean getMute(); method public java.util.Map getParameters(java.util.List); - method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); - method public abstract java.util.List getProgramList(java.util.Map); + method public abstract deprecated int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); + method public abstract deprecated java.util.List getProgramList(java.util.Map); method public abstract boolean hasControl(); method public abstract deprecated boolean isAnalogForced(); - method public abstract boolean isAntennaConnected(); + method public abstract deprecated boolean isAntennaConnected(); method public boolean isConfigFlagSet(int); method public boolean isConfigFlagSupported(int); method public abstract int scan(int, boolean); method public abstract deprecated void setAnalogForced(boolean); method public void setConfigFlag(int, boolean); - method public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig); + method public abstract deprecated int setConfiguration(android.hardware.radio.RadioManager.BandConfig); method public abstract int setMute(boolean); method public java.util.Map setParameters(java.util.Map); method public abstract boolean startBackgroundScan(); @@ -1974,13 +2108,13 @@ package android.hardware.radio { method public abstract void tune(android.hardware.radio.ProgramSelector); field public static final int DIRECTION_DOWN = 1; // 0x1 field public static final int DIRECTION_UP = 0; // 0x0 - field public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6 - field public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5; // 0x5 - field public static final int ERROR_CANCELLED = 2; // 0x2 - field public static final int ERROR_CONFIG = 4; // 0x4 - field public static final int ERROR_HARDWARE_FAILURE = 0; // 0x0 - field public static final int ERROR_SCAN_TIMEOUT = 3; // 0x3 - field public static final int ERROR_SERVER_DIED = 1; // 0x1 + field public static final deprecated int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6 + field public static final deprecated int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5; // 0x5 + field public static final deprecated int ERROR_CANCELLED = 2; // 0x2 + field public static final deprecated int ERROR_CONFIG = 4; // 0x4 + field public static final deprecated int ERROR_HARDWARE_FAILURE = 0; // 0x0 + field public static final deprecated int ERROR_SCAN_TIMEOUT = 3; // 0x3 + field public static final deprecated int ERROR_SERVER_DIED = 1; // 0x1 } public static abstract class RadioTuner.Callback { @@ -1988,15 +2122,16 @@ package android.hardware.radio { method public void onAntennaState(boolean); method public void onBackgroundScanAvailabilityChange(boolean); method public void onBackgroundScanComplete(); - method public void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig); + method public deprecated void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig); method public void onControlChanged(boolean); method public void onEmergencyAnnouncement(boolean); - method public void onError(int); + method public deprecated void onError(int); method public deprecated void onMetadataChanged(android.hardware.radio.RadioMetadata); method public void onParametersUpdated(java.util.Map); method public void onProgramInfoChanged(android.hardware.radio.RadioManager.ProgramInfo); method public void onProgramListChanged(); method public void onTrafficAnnouncement(boolean); + method public void onTuneFailed(int, android.hardware.radio.ProgramSelector); } } @@ -2256,11 +2391,15 @@ package android.location { method public deprecated boolean addGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener); method public void flushGnssBatch(); method public int getGnssBatchSize(); + method public boolean isLocationEnabledForUser(android.os.UserHandle); + method public boolean isProviderEnabledForUser(java.lang.String, android.os.UserHandle); method public boolean registerGnssBatchedLocationCallback(long, boolean, android.location.BatchedLocationCallback, android.os.Handler); method public deprecated void removeGpsMeasurementListener(android.location.GpsMeasurementsEvent.Listener); method public deprecated void removeGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener); method public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper); method public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent); + method public void setLocationEnabledForUser(boolean, android.os.UserHandle); + method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle); method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback); } @@ -2464,12 +2603,17 @@ package android.media.audiopolicy { method public void onStatusChange(); } + public static abstract class AudioPolicy.AudioPolicyVolumeCallback { + method public void onVolumeAdjustment(int); + } + public static class AudioPolicy.Builder { ctor public AudioPolicy.Builder(android.content.Context); method public android.media.audiopolicy.AudioPolicy.Builder addMix(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException; method public android.media.audiopolicy.AudioPolicy build(); method public void setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener); method public void setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener); + method public android.media.audiopolicy.AudioPolicy.Builder setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback); method public android.media.audiopolicy.AudioPolicy.Builder setIsAudioFocusPolicy(boolean); method public android.media.audiopolicy.AudioPolicy.Builder setLooper(android.os.Looper) throws java.lang.IllegalArgumentException; } @@ -2789,9 +2933,33 @@ package android.net { method public void onTetheringStarted(); } + public final class IpSecManager { + method public void applyTunnelModeTransform(android.net.IpSecManager.IpSecTunnelInterface, int, android.net.IpSecTransform) throws java.io.IOException; + method public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(java.net.InetAddress, java.net.InetAddress, android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + } + + public static final class IpSecManager.IpSecTunnelInterface implements java.lang.AutoCloseable { + method public void close(); + method public java.lang.String getInterfaceName(); + } + + public final class IpSecTransform implements java.lang.AutoCloseable { + method public void startNattKeepalive(android.net.IpSecTransform.NattKeepaliveCallback, int, android.os.Handler) throws java.io.IOException; + method public void stopNattKeepalive(); + } + public static class IpSecTransform.Builder { - method public android.net.IpSecTransform.Builder setNattKeepalive(int); - method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network); + method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + } + + public static class IpSecTransform.NattKeepaliveCallback { + ctor public IpSecTransform.NattKeepaliveCallback(); + method public void onError(int); + method public void onStarted(); + method public void onStopped(); + field public static final int ERROR_HARDWARE_ERROR = 3; // 0x3 + field public static final int ERROR_HARDWARE_UNSUPPORTED = 2; // 0x2 + field public static final int ERROR_INVALID_NETWORK = 1; // 0x1 } public class NetworkKey implements android.os.Parcelable { @@ -3359,11 +3527,138 @@ package android.os { field public static final java.lang.String ACTION_UPDATE_CT_LOGS = "android.intent.action.UPDATE_CT_LOGS"; field public static final java.lang.String ACTION_UPDATE_INTENT_FIREWALL = "android.intent.action.UPDATE_INTENT_FIREWALL"; field public static final java.lang.String ACTION_UPDATE_LANG_ID = "android.intent.action.UPDATE_LANG_ID"; + field public static final java.lang.String ACTION_UPDATE_NETWORK_WATCHLIST = "android.intent.action.UPDATE_NETWORK_WATCHLIST"; field public static final java.lang.String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS"; field public static final java.lang.String ACTION_UPDATE_SMART_SELECTION = "android.intent.action.UPDATE_SMART_SELECTION"; field public static final java.lang.String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES"; } + public class HidlSupport { + method public static boolean deepEquals(java.lang.Object, java.lang.Object); + method public static int deepHashCode(java.lang.Object); + method public static int getPidIfSharable(); + method public static boolean interfacesEqual(android.os.IHwInterface, java.lang.Object); + } + + public abstract class HwBinder implements android.os.IHwBinder { + method public static final void configureRpcThreadpool(long, boolean); + method public static void enableInstrumentation(); + method public static final android.os.IHwBinder getService(java.lang.String, java.lang.String) throws java.util.NoSuchElementException, android.os.RemoteException; + method public static final android.os.IHwBinder getService(java.lang.String, java.lang.String, boolean) throws java.util.NoSuchElementException, android.os.RemoteException; + method public static final void joinRpcThreadpool(); + method public final void transact(int, android.os.HwParcel, android.os.HwParcel, int) throws android.os.RemoteException; + } + + public class HwBlob { + ctor public HwBlob(int); + method public final void copyToBoolArray(long, boolean[], int); + method public final void copyToDoubleArray(long, double[], int); + method public final void copyToFloatArray(long, float[], int); + method public final void copyToInt16Array(long, short[], int); + method public final void copyToInt32Array(long, int[], int); + method public final void copyToInt64Array(long, long[], int); + method public final void copyToInt8Array(long, byte[], int); + method public final boolean getBool(long); + method public final double getDouble(long); + method public final float getFloat(long); + method public final short getInt16(long); + method public final int getInt32(long); + method public final long getInt64(long); + method public final byte getInt8(long); + method public final java.lang.String getString(long); + method public final long handle(); + method public final void putBlob(long, android.os.HwBlob); + method public final void putBool(long, boolean); + method public final void putBoolArray(long, boolean[]); + method public final void putDouble(long, double); + method public final void putDoubleArray(long, double[]); + method public final void putFloat(long, float); + method public final void putFloatArray(long, float[]); + method public final void putInt16(long, short); + method public final void putInt16Array(long, short[]); + method public final void putInt32(long, int); + method public final void putInt32Array(long, int[]); + method public final void putInt64(long, long); + method public final void putInt64Array(long, long[]); + method public final void putInt8(long, byte); + method public final void putInt8Array(long, byte[]); + method public final void putString(long, java.lang.String); + method public static java.lang.Boolean[] wrapArray(boolean[]); + method public static java.lang.Long[] wrapArray(long[]); + method public static java.lang.Byte[] wrapArray(byte[]); + method public static java.lang.Short[] wrapArray(short[]); + method public static java.lang.Integer[] wrapArray(int[]); + method public static java.lang.Float[] wrapArray(float[]); + method public static java.lang.Double[] wrapArray(double[]); + } + + public class HwParcel { + ctor public HwParcel(); + method public final void enforceInterface(java.lang.String); + method public final boolean readBool(); + method public final java.util.ArrayList readBoolVector(); + method public final android.os.HwBlob readBuffer(long); + method public final double readDouble(); + method public final java.util.ArrayList readDoubleVector(); + method public final android.os.HwBlob readEmbeddedBuffer(long, long, long, boolean); + method public final float readFloat(); + method public final java.util.ArrayList readFloatVector(); + method public final short readInt16(); + method public final java.util.ArrayList readInt16Vector(); + method public final int readInt32(); + method public final java.util.ArrayList readInt32Vector(); + method public final long readInt64(); + method public final java.util.ArrayList readInt64Vector(); + method public final byte readInt8(); + method public final java.util.ArrayList readInt8Vector(); + method public final java.lang.String readString(); + method public final java.util.ArrayList readStringVector(); + method public final android.os.IHwBinder readStrongBinder(); + method public final void release(); + method public final void releaseTemporaryStorage(); + method public final void send(); + method public final void verifySuccess(); + method public final void writeBool(boolean); + method public final void writeBoolVector(java.util.ArrayList); + method public final void writeBuffer(android.os.HwBlob); + method public final void writeDouble(double); + method public final void writeDoubleVector(java.util.ArrayList); + method public final void writeFloat(float); + method public final void writeFloatVector(java.util.ArrayList); + method public final void writeInt16(short); + method public final void writeInt16Vector(java.util.ArrayList); + method public final void writeInt32(int); + method public final void writeInt32Vector(java.util.ArrayList); + method public final void writeInt64(long); + method public final void writeInt64Vector(java.util.ArrayList); + method public final void writeInt8(byte); + method public final void writeInt8Vector(java.util.ArrayList); + method public final void writeInterfaceToken(java.lang.String); + method public final void writeStatus(int); + method public final void writeString(java.lang.String); + method public final void writeStringVector(java.util.ArrayList); + method public final void writeStrongBinder(android.os.IHwBinder); + field public static final int STATUS_SUCCESS = 0; // 0x0 + } + + public static abstract class HwParcel.Status implements java.lang.annotation.Annotation { + } + + public abstract interface IHwBinder { + method public abstract boolean linkToDeath(android.os.IHwBinder.DeathRecipient, long); + method public abstract android.os.IHwInterface queryLocalInterface(java.lang.String); + method public abstract void transact(int, android.os.HwParcel, android.os.HwParcel, int) throws android.os.RemoteException; + method public abstract boolean unlinkToDeath(android.os.IHwBinder.DeathRecipient); + } + + public static abstract interface IHwBinder.DeathRecipient { + method public abstract void serviceDied(long); + } + + public abstract interface IHwInterface { + method public abstract android.os.IHwBinder asBinder(); + } + public class IncidentManager { method public void reportIncident(android.os.IncidentReportArgs); method public void reportIncident(java.lang.String, byte[]); @@ -3418,6 +3713,51 @@ package android.os { method public abstract void onResult(android.os.Bundle); } + public final class StatsDimensionsValue implements android.os.Parcelable { + method public int describeContents(); + method public boolean getBooleanValue(); + method public int getField(); + method public float getFloatValue(); + method public int getIntValue(); + method public long getLongValue(); + method public java.lang.String getStringValue(); + method public java.util.List getTupleValueList(); + method public int getValueType(); + method public boolean isValueType(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final int BOOLEAN_VALUE_TYPE = 5; // 0x5 + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int FLOAT_VALUE_TYPE = 6; // 0x6 + field public static final int INT_VALUE_TYPE = 3; // 0x3 + field public static final int LONG_VALUE_TYPE = 4; // 0x4 + field public static final int STRING_VALUE_TYPE = 2; // 0x2 + field public static final int TUPLE_VALUE_TYPE = 7; // 0x7 + } + + public class SystemProperties { + method public static java.lang.String get(java.lang.String); + method public static java.lang.String get(java.lang.String, java.lang.String); + method public static boolean getBoolean(java.lang.String, boolean); + method public static int getInt(java.lang.String, int); + method public static long getLong(java.lang.String, long); + } + + public class SystemUpdateManager { + method public android.os.Bundle retrieveSystemUpdateInfo(); + method public void updateSystemUpdateInfo(android.os.PersistableBundle); + field public static final java.lang.String KEY_IS_SECURITY_UPDATE = "is_security_update"; + field public static final java.lang.String KEY_STATUS = "status"; + field public static final java.lang.String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint"; + field public static final java.lang.String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level"; + field public static final java.lang.String KEY_TITLE = "title"; + field public static final int STATUS_IDLE = 1; // 0x1 + field public static final int STATUS_IN_PROGRESS = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + field public static final int STATUS_WAITING_DOWNLOAD = 2; // 0x2 + field public static final int STATUS_WAITING_INSTALL = 4; // 0x4 + field public static final int STATUS_WAITING_REBOOT = 5; // 0x5 + } + public class UpdateEngine { ctor public UpdateEngine(); method public void applyPayload(java.lang.String, long, long, java.lang.String[]); @@ -3488,6 +3828,7 @@ package android.os { method public boolean isRestrictedProfile(); field public static final java.lang.String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED"; field public static final deprecated java.lang.String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; + field public static final java.lang.String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background"; field public static final int RESTRICTION_NOT_SET = 0; // 0x0 field public static final int RESTRICTION_SOURCE_DEVICE_OWNER = 2; // 0x2 field public static final int RESTRICTION_SOURCE_PROFILE_OWNER = 4; // 0x4 @@ -3505,6 +3846,20 @@ package android.os { public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation { } + public class WorkSource implements android.os.Parcelable { + method public android.os.WorkSource.WorkChain createWorkChain(); + } + + public static final class WorkSource.WorkChain implements android.os.Parcelable { + ctor public WorkSource.WorkChain(); + method public android.os.WorkSource.WorkChain addNode(int, java.lang.String); + method public int describeContents(); + method public java.lang.String getAttributionTag(); + method public int getAttributionUid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + } package android.os.storage { @@ -3786,6 +4141,132 @@ package android.security.keystore { } +package android.security.keystore.recovery { + + public class DecryptionFailedException extends java.security.GeneralSecurityException { + ctor public DecryptionFailedException(java.lang.String); + } + + public class InternalRecoveryServiceException extends java.security.GeneralSecurityException { + ctor public InternalRecoveryServiceException(java.lang.String); + ctor public InternalRecoveryServiceException(java.lang.String, java.lang.Throwable); + } + + public final class KeyChainProtectionParams implements android.os.Parcelable { + method public void clearSecret(); + method public int describeContents(); + method public android.security.keystore.recovery.KeyDerivationParams getKeyDerivationParams(); + method public int getLockScreenUiFormat(); + method public byte[] getSecret(); + method public int getUserSecretType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int TYPE_CUSTOM_PASSWORD = 101; // 0x65 + field public static final int TYPE_LOCKSCREEN = 100; // 0x64 + field public static final int UI_FORMAT_PASSWORD = 2; // 0x2 + field public static final int UI_FORMAT_PATTERN = 3; // 0x3 + field public static final int UI_FORMAT_PIN = 1; // 0x1 + } + + public static class KeyChainProtectionParams.Builder { + ctor public KeyChainProtectionParams.Builder(); + method public android.security.keystore.recovery.KeyChainProtectionParams build(); + method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setKeyDerivationParams(android.security.keystore.recovery.KeyDerivationParams); + method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setLockScreenUiFormat(int); + method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setSecret(byte[]); + method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setUserSecretType(int); + } + + public final class KeyChainSnapshot implements android.os.Parcelable { + method public int describeContents(); + method public long getCounterId(); + method public byte[] getEncryptedRecoveryKeyBlob(); + method public java.util.List getKeyChainProtectionParams(); + method public int getMaxAttempts(); + method public byte[] getServerParams(); + method public int getSnapshotVersion(); + method public byte[] getTrustedHardwarePublicKey(); + method public java.util.List getWrappedApplicationKeys(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class KeyDerivationParams implements android.os.Parcelable { + method public static android.security.keystore.recovery.KeyDerivationParams createSha256Params(byte[]); + method public int describeContents(); + method public int getAlgorithm(); + method public byte[] getSalt(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int ALGORITHM_SHA256 = 1; // 0x1 + field public static final android.os.Parcelable.Creator CREATOR; + } + + public class LockScreenRequiredException extends java.security.GeneralSecurityException { + ctor public LockScreenRequiredException(java.lang.String); + } + + public class RecoveryController { + method public byte[] generateAndStoreKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException; + method public java.util.List getAliases(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public static android.security.keystore.recovery.RecoveryController getInstance(android.content.Context); + method public int[] getPendingRecoverySecretTypes() throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public android.security.keystore.recovery.KeyChainSnapshot getRecoveryData() throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public int[] getRecoverySecretTypes() throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public int getRecoveryStatus(java.lang.String, java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public void initRecoveryService(java.lang.String, byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException; + method public void recoverySecretAvailable(android.security.keystore.recovery.KeyChainProtectionParams) throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public void removeKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public void setRecoverySecretTypes(int[]) throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public void setRecoveryStatus(java.lang.String, java.lang.String, int) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.content.pm.PackageManager.NameNotFoundException; + method public void setServerParams(byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException; + method public void setSnapshotCreatedPendingIntent(android.app.PendingIntent) throws android.security.keystore.recovery.InternalRecoveryServiceException; + field public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2; // 0x2 + field public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3; // 0x3 + field public static final int RECOVERY_STATUS_SYNCED = 0; // 0x0 + field public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1; // 0x1 + } + + public class RecoverySession implements java.lang.AutoCloseable { + method public void close(); + method public java.util.Map recoverKeys(byte[], java.util.List) throws android.security.keystore.recovery.DecryptionFailedException, android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.SessionExpiredException; + method public byte[] start(byte[], byte[], byte[], java.util.List) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException; + } + + public class SessionExpiredException extends java.security.GeneralSecurityException { + ctor public SessionExpiredException(java.lang.String); + } + + public final class WrappedApplicationKey implements android.os.Parcelable { + method public int describeContents(); + method public byte[] getAccount(); + method public java.lang.String getAlias(); + method public byte[] getEncryptedKeyMaterial(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class WrappedApplicationKey.Builder { + ctor public WrappedApplicationKey.Builder(); + method public android.security.keystore.recovery.WrappedApplicationKey build(); + method public android.security.keystore.recovery.WrappedApplicationKey.Builder setAccount(byte[]); + method public android.security.keystore.recovery.WrappedApplicationKey.Builder setAlias(java.lang.String); + method public android.security.keystore.recovery.WrappedApplicationKey.Builder setEncryptedKeyMaterial(byte[]); + } + +} + +package android.service.autofill { + + public abstract class AutofillFieldClassificationService extends android.app.Service { + method public android.os.IBinder onBind(android.content.Intent); + method public float[][] onGetScores(java.lang.String, android.os.Bundle, java.util.List, java.util.List); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService"; + field public static final java.lang.String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms"; + field public static final java.lang.String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm"; + } + +} + package android.service.notification { public final class Adjustment implements android.os.Parcelable { @@ -4266,6 +4747,12 @@ package android.telecom { package android.telephony { + public static final class AccessNetworkConstants.TransportType { + ctor public AccessNetworkConstants.TransportType(); + field public static final int WLAN = 2; // 0x2 + field public static final int WWAN = 1; // 0x1 + } + public class CarrierConfigManager { method public static android.os.PersistableBundle getDefaultConfig(); method public void updateConfigForPhoneId(int, java.lang.String); @@ -4280,13 +4767,92 @@ package android.telephony { field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; } + public class NetworkRegistrationState implements android.os.Parcelable { + ctor public NetworkRegistrationState(int, int, int, int, int, boolean, int[], android.telephony.CellIdentity); + ctor protected NetworkRegistrationState(android.os.Parcel); + method public int describeContents(); + method public int getAccessNetworkTechnology(); + method public int[] getAvailableServices(); + method public android.telephony.CellIdentity getCellIdentity(); + method public int getDomain(); + method public int getReasonForDenial(); + method public int getRegState(); + method public int getTransportType(); + method public boolean isEmergencyEnabled(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DOMAIN_CS = 1; // 0x1 + field public static final int DOMAIN_PS = 2; // 0x2 + field public static final int REG_STATE_DENIED = 3; // 0x3 + field public static final int REG_STATE_HOME = 1; // 0x1 + field public static final int REG_STATE_NOT_REG_NOT_SEARCHING = 0; // 0x0 + field public static final int REG_STATE_NOT_REG_SEARCHING = 2; // 0x2 + field public static final int REG_STATE_ROAMING = 5; // 0x5 + field public static final int REG_STATE_UNKNOWN = 4; // 0x4 + field public static final int SERVICE_TYPE_DATA = 2; // 0x2 + field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5 + field public static final int SERVICE_TYPE_SMS = 3; // 0x3 + field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4 + field public static final int SERVICE_TYPE_VOICE = 1; // 0x1 + } + + public abstract class NetworkService extends android.app.Service { + method protected abstract android.telephony.NetworkService.NetworkServiceProvider createNetworkServiceProvider(int); + field public static final java.lang.String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID"; + field public static final java.lang.String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService"; + } + + public class NetworkService.NetworkServiceProvider { + ctor public NetworkService.NetworkServiceProvider(int); + method public void getNetworkRegistrationState(int, android.telephony.NetworkServiceCallback); + method public final int getSlotId(); + method public final void notifyNetworkRegistrationStateChanged(); + method protected void onDestroy(); + } + + public class NetworkServiceCallback { + method public void onGetNetworkRegistrationStateComplete(int, android.telephony.NetworkRegistrationState); + field public static final int RESULT_ERROR_BUSY = 3; // 0x3 + field public static final int RESULT_ERROR_FAILED = 5; // 0x5 + field public static final int RESULT_ERROR_ILLEGAL_STATE = 4; // 0x4 + field public static final int RESULT_ERROR_INVALID_ARG = 2; // 0x2 + field public static final int RESULT_ERROR_UNSUPPORTED = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + } + + public class ServiceState implements android.os.Parcelable { + method public java.util.List getNetworkRegistrationStates(); + method public java.util.List getNetworkRegistrationStates(int); + method public android.telephony.NetworkRegistrationState getNetworkRegistrationStates(int, int); + } + public final class SmsManager { method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List, java.util.List, java.util.List); method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent); + field public static final int RESULT_CANCELLED = 23; // 0x17 + field public static final int RESULT_ENCODING_ERROR = 18; // 0x12 + field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; // 0x6 + field public static final int RESULT_ERROR_NONE = 0; // 0x0 + field public static final int RESULT_INTERNAL_ERROR = 21; // 0x15 + field public static final int RESULT_INVALID_ARGUMENTS = 11; // 0xb + field public static final int RESULT_INVALID_SMSC_ADDRESS = 19; // 0x13 + field public static final int RESULT_INVALID_SMS_FORMAT = 14; // 0xe + field public static final int RESULT_INVALID_STATE = 12; // 0xc + field public static final int RESULT_MODEM_ERROR = 16; // 0x10 + field public static final int RESULT_NETWORK_ERROR = 17; // 0x11 + field public static final int RESULT_NETWORK_REJECT = 10; // 0xa + field public static final int RESULT_NO_MEMORY = 13; // 0xd + field public static final int RESULT_NO_RESOURCES = 22; // 0x16 + field public static final int RESULT_OPERATION_NOT_ALLOWED = 20; // 0x14 + field public static final int RESULT_RADIO_NOT_AVAILABLE = 9; // 0x9 + field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18 + field public static final int RESULT_SYSTEM_ERROR = 15; // 0xf } public class SubscriptionManager { method public java.util.List getSubscriptionPlans(int); + method public void setSubscriptionOverrideCongested(int, boolean, long); + method public void setSubscriptionOverrideUnmetered(int, boolean, long); method public void setSubscriptionPlans(int, java.util.List); 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"; @@ -4368,7 +4934,10 @@ 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 getTelephonyHistograms(); + method public android.telephony.UiccSlotInfo[] getUiccSlotsInfo(); method public android.os.Bundle getVisualVoicemailSettings(); method public int getVoiceActivationState(); method public boolean handlePinMmi(java.lang.String); @@ -4386,6 +4955,8 @@ package android.telephony { method public deprecated void setDataEnabled(int, boolean); method public boolean setRadio(boolean); method public boolean setRadioPower(boolean); + method public void setSimPowerState(int); + method public void setSimPowerStateForSlot(int, int); method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean); method public void setVoiceActivationState(int); method public deprecated void silenceRinger(); @@ -4393,14 +4964,17 @@ package android.telephony { method public int[] supplyPinReportResult(java.lang.String); method public boolean supplyPuk(java.lang.String, java.lang.String); method public int[] supplyPukReportResult(java.lang.String, java.lang.String); + method public boolean switchSlots(int[]); method public void toggleRadioOnOff(); method public void updateServiceLocation(); - method public void setSimPowerState(int); - method public void setSimPowerStateForSlot(int, int); + field public static final java.lang.String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED"; + field public static final java.lang.String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED"; + field public static final java.lang.String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED"; field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe 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 @@ -4408,6 +4982,27 @@ 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 class UiccSlotInfo implements android.os.Parcelable { + ctor public UiccSlotInfo(boolean, boolean, java.lang.String, int); + method public int describeContents(); + method public java.lang.String getCardId(); + method public int getCardStateInfo(); + method public boolean getIsActive(); + method public boolean getIsEuicc(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CARD_STATE_INFO_ABSENT = 1; // 0x1 + field public static final int CARD_STATE_INFO_ERROR = 3; // 0x3 + field public static final int CARD_STATE_INFO_PRESENT = 2; // 0x2 + field public static final int CARD_STATE_INFO_RESTRICTED = 4; // 0x4 + field public static final android.os.Parcelable.Creator CREATOR; + field public final java.lang.String cardId; + field public final int cardStateInfo; + field public final boolean isActive; + field public final boolean isEuicc; } public abstract class VisualVoicemailService extends android.app.Service { @@ -4467,6 +5062,41 @@ package android.telephony.data { field public static final int TYPE_COMMON = 0; // 0x0 } + public abstract class DataService extends android.app.Service { + method public abstract android.telephony.data.DataService.DataServiceProvider createDataServiceProvider(int); + field public static final java.lang.String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID"; + field public static final java.lang.String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService"; + field public static final int REQUEST_REASON_HANDOVER = 3; // 0x3 + field public static final int REQUEST_REASON_NORMAL = 1; // 0x1 + field public static final int REQUEST_REASON_SHUTDOWN = 2; // 0x2 + } + + public class DataService.DataServiceProvider { + ctor public DataService.DataServiceProvider(int); + method public void deactivateDataCall(int, int, android.telephony.data.DataServiceCallback); + method public void getDataCallList(android.telephony.data.DataServiceCallback); + method public final int getSlotId(); + method public final void notifyDataCallListChanged(java.util.List); + method protected void onDestroy(); + method public void setDataProfile(java.util.List, boolean, android.telephony.data.DataServiceCallback); + method public void setInitialAttachApn(android.telephony.data.DataProfile, boolean, android.telephony.data.DataServiceCallback); + method public void setupDataCall(int, android.telephony.data.DataProfile, boolean, boolean, int, android.net.LinkProperties, android.telephony.data.DataServiceCallback); + } + + public class DataServiceCallback { + method public void onDataCallListChanged(java.util.List); + method public void onDeactivateDataCallComplete(int); + method public void onGetDataCallListComplete(int, java.util.List); + method public void onSetDataProfileComplete(int); + method public void onSetInitialAttachApnComplete(int); + method public void onSetupDataCallComplete(int, android.telephony.data.DataCallResponse); + field public static final int RESULT_ERROR_BUSY = 3; // 0x3 + field public static final int RESULT_ERROR_ILLEGAL_STATE = 4; // 0x4 + field public static final int RESULT_ERROR_INVALID_ARG = 2; // 0x2 + field public static final int RESULT_ERROR_UNSUPPORTED = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + } + } package android.telephony.ims { @@ -4477,6 +5107,30 @@ package android.telephony.ims { } +package android.telephony.ims.internal.stub { + + public class SmsImplBase { + ctor public SmsImplBase(); + method public void acknowledgeSms(int, int, int); + method public void acknowledgeSmsReport(int, int, int); + method public java.lang.String getSmsFormat(); + method public void onReady(); + method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException; + method public final void onSmsReceived(int, java.lang.String, byte[]) throws java.lang.RuntimeException; + method public final void onSmsStatusReportReceived(int, int, java.lang.String, byte[]) throws java.lang.RuntimeException; + method public void sendSms(int, int, java.lang.String, java.lang.String, boolean, byte[]); + field public static final int DELIVER_STATUS_ERROR = 2; // 0x2 + field public static final int DELIVER_STATUS_OK = 1; // 0x1 + field public static final int SEND_STATUS_ERROR = 2; // 0x2 + field public static final int SEND_STATUS_ERROR_FALLBACK = 4; // 0x4 + field public static final int SEND_STATUS_ERROR_RETRY = 3; // 0x3 + field public static final int SEND_STATUS_OK = 1; // 0x1 + field public static final int STATUS_REPORT_STATUS_ERROR = 2; // 0x2 + field public static final int STATUS_REPORT_STATUS_OK = 1; // 0x1 + } + +} + package android.telephony.mbms { public final class DownloadRequest implements android.os.Parcelable { @@ -4578,21 +5232,10 @@ package android.util { method public int getUid(); } - public final class StatsManager { - method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); - method public byte[] getData(long); - method public byte[] getMetadata(); - method public boolean removeConfiguration(long); - } - } package android.view { - public abstract class Window { - method public void setDisableWallpaperTouchEvents(boolean); - } - public abstract interface WindowManager implements android.view.ViewManager { method public abstract android.graphics.Region getCurrentImeTouchRegion(); } @@ -4829,6 +5472,7 @@ package android.webkit { method public abstract android.webkit.TracingController getTracingController(); method public abstract android.webkit.WebIconDatabase getWebIconDatabase(); method public abstract android.webkit.WebStorage getWebStorage(); + method public abstract java.lang.ClassLoader getWebViewClassLoader(); method public abstract android.webkit.WebViewDatabase getWebViewDatabase(android.content.Context); } @@ -5017,7 +5661,7 @@ package android.webkit { field public final java.lang.String description; field public final boolean isFallback; field public final java.lang.String packageName; - field public final java.lang.String[] signatures; + field public final android.content.pm.Signature[] signatures; } public final class WebViewUpdateService { diff --git a/api/test-current.txt b/api/test-current.txt index 6ad4f5a8cf1f519ae3928189d2e9fcb4f0cd0ca2..4e8f904b96b7a17d3a2257733a2ee19e3d8f1b63 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -48,6 +48,7 @@ package android.app { public class AppOpsManager { method public static java.lang.String[] getOpStrs(); method public void setMode(int, int, java.lang.String, int); + field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; @@ -295,6 +296,43 @@ package android.hardware.camera2 { } +package android.hardware.display { + + public final class BrightnessChangeEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public final float batteryLevel; + field public final float brightness; + field public final int colorTemperature; + field public final float lastBrightness; + field public final long[] luxTimestamps; + field public final float[] luxValues; + field public final boolean nightMode; + field public final java.lang.String packageName; + field public final long timeStamp; + } + + public final class BrightnessConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public android.util.Pair getCurve(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class BrightnessConfiguration.Builder { + ctor public BrightnessConfiguration.Builder(float[], float[]); + method public android.hardware.display.BrightnessConfiguration build(); + method public android.hardware.display.BrightnessConfiguration.Builder setDescription(java.lang.String); + } + + public final class DisplayManager { + method public java.util.List getBrightnessEvents(); + method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); + } + +} + package android.location { public final class GnssClock implements android.os.Parcelable { @@ -484,6 +522,7 @@ package android.provider { } public static final class Settings.Global extends android.provider.Settings.NameValueTable { + field public static final java.lang.String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch"; field public static final java.lang.String LOW_POWER_MODE = "low_power"; field public static final java.lang.String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; } @@ -526,11 +565,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(); } @@ -929,6 +963,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 } @@ -1000,6 +1037,10 @@ package android.view.autofill { package android.widget { + public abstract class AbsListView extends android.widget.AdapterView implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener { + method public final boolean shouldDrawSelector(); + } + public class CalendarView extends android.widget.FrameLayout { method public boolean getBoundsForDate(long, android.graphics.Rect); } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index d1af71d8886ed4c82afaa4c1a23dde8c0f804fa5..54785ca131859d1e3c4e26221e0af502a601554f 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -66,9 +66,12 @@ namespace android { static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; +static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"; static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; +static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"; static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; +static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip"; static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"; static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; @@ -308,14 +311,20 @@ status_t BootAnimation::readyToRun() { bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt); - if (!mShuttingDown && encryptedAnimation && - (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) { - mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE; - return NO_ERROR; + if (!mShuttingDown && encryptedAnimation) { + static const char* encryptedBootFiles[] = + {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE}; + for (const char* f : encryptedBootFiles) { + if (access(f, R_OK) == 0) { + mZipFileName = f; + return NO_ERROR; + } + } } - static const char* bootFiles[] = {OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; + static const char* bootFiles[] = + {PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; static const char* shutdownFiles[] = - {OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; + {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) { if (access(f, R_OK) == 0) { diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md index 9ea6fea966f2521489c4abade4c1d1b3e3192fc4..5946515aa263f264cfd95d1764e1b4d4495faf41 100644 --- a/cmds/bootanimation/FORMAT.md +++ b/cmds/bootanimation/FORMAT.md @@ -30,7 +30,7 @@ The first line defines the general parameters of the animation: It is followed by a number of rows of the form: - TYPE COUNT PAUSE PATH [#RGBHEX CLOCK] + TYPE COUNT PAUSE PATH [#RGBHEX [CLOCK1 [CLOCK2]]] * **TYPE:** a single char indicating what type of animation segment this is: + `p` -- this part will play unless interrupted by the end of the boot @@ -39,11 +39,38 @@ It is followed by a number of rows of the form: * **PAUSE:** number of FRAMES to delay after this part ends * **PATH:** directory in which to find the frames for this part (e.g. `part0`) * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB` - * **CLOCK:** _(OPTIONAL)_ the y-coordinate at which to draw the current time (for watches) + * **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches): + + If only `CLOCK1` is provided it is the y-coordinate of the clock and the x-coordinate + defaults to `c` + + If both `CLOCK1` and `CLOCK2` are provided then `CLOCK1` is the x-coordinate and `CLOCK2` is + the y-coodinate + + Values can be either a positive integer, a negative integer, or `c` + - `c` -- will centre the text + - `n` -- will position the text n pixels from the start; left edge for x-axis, bottom edge + for y-axis + - `-n` -- will position the text n pixels from the end; right edge for x-axis, top edge + for y-axis + - Examples: + * `-24` or `c -24` will position the text 24 pixels from the top of the screen, + centred horizontally + * `16 c` will position the text 16 pixels from the left of the screen, centred + vertically + * `-32 32` will position the text such that the bottom right corner is 32 pixels above + and 32 pixels left of the edges of the screen There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip` and plays that. +## clock_font.png + +The file used to draw the time on top of the boot animation. The font format is as follows: + * The file specifies glyphs for the ascii characters 32-127 (0x20-0x7F), both regular weight and + bold weight. + * The image is divided into a grid of characters + * There are 16 columns and 6 rows + * Each row is divided in half: regular weight glyphs on the top half, bold glyphs on the bottom + * For a NxM image each character glyph will be N/16 pixels wide and M/(12*2) pixels high + ## loading and playing frames Each part is scanned and loaded directly from the zip archive. Within a part directory, every file diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java index 3ac70d668198e699e5d4b4b7a32571100644f39c..47581e10e937669bcb524eafb6261acca34fdbdd 100644 --- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java +++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java @@ -45,6 +45,7 @@ public final class Dpm extends BaseCommand { private static final String COMMAND_SET_DEVICE_OWNER = "set-device-owner"; private static final String COMMAND_SET_PROFILE_OWNER = "set-profile-owner"; private static final String COMMAND_REMOVE_ACTIVE_ADMIN = "remove-active-admin"; + private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record"; private IDevicePolicyManager mDevicePolicyManager; private int mUserId = UserHandle.USER_SYSTEM; @@ -75,7 +76,11 @@ public final class Dpm extends BaseCommand { "\n" + "dpm remove-active-admin: Disables an active admin, the admin must have declared" + " android:testOnly in the application in its manifest. This will also remove" + - " device and profile owners\n"); + " device and profile owners\n" + + "\n" + + "dpm " + COMMAND_CLEAR_FREEZE_PERIOD_RECORD + ": clears framework-maintained " + + "record of past freeze periods that the device went through. For use during " + + "feature development to prevent triggering restriction on setting freeze periods"); } @Override @@ -101,6 +106,9 @@ public final class Dpm extends BaseCommand { case COMMAND_REMOVE_ACTIVE_ADMIN: runRemoveActiveAdmin(); break; + case COMMAND_CLEAR_FREEZE_PERIOD_RECORD: + runClearFreezePeriodRecord(); + break; default: throw new IllegalArgumentException ("unknown command '" + command + "'"); } @@ -190,6 +198,11 @@ public final class Dpm extends BaseCommand { + mComponent.toShortString() + " for user " + mUserId); } + private void runClearFreezePeriodRecord() throws RemoteException { + mDevicePolicyManager.clearSystemUpdatePolicyFreezePeriodRecord(); + System.out.println("Success"); + } + private ComponentName parseComponentName(String component) { ComponentName cn = ComponentName.unflattenFromString(component); if (cn == null) { diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp index 519852dbe88b8b9336100e76d6376ddb4cb0a0fc..cdec6a01d086d80f489c0ebeacdab173dfd9f060 100644 --- a/cmds/incident/main.cpp +++ b/cmds/incident/main.cpp @@ -148,9 +148,19 @@ find_section(const char* name) static int get_dest(const char* arg) { - if (strcmp(arg, "LOCAL") == 0) return 0; - if (strcmp(arg, "EXPLICIT") == 0) return 1; - if (strcmp(arg, "AUTOMATIC") == 0) return 2; + if (strcmp(arg, "L") == 0 + || strcmp(arg, "LOCAL") == 0) { + return DEST_LOCAL; + } + if (strcmp(arg, "E") == 0 + || strcmp(arg, "EXPLICIT") == 0) { + return DEST_EXPLICIT; + } + if (strcmp(arg, "A") == 0 + || strcmp(arg, "AUTO") == 0 + || strcmp(arg, "AUTOMATIC") == 0) { + return DEST_AUTOMATIC; + } return -1; // return the default value } diff --git a/cmds/incident_helper/src/TextParserBase.h b/cmds/incident_helper/src/TextParserBase.h index c41612de4eb301823876f945e688ed5684100992..166796673e2501404119d47def0651f8ff8ccc94 100644 --- a/cmds/incident_helper/src/TextParserBase.h +++ b/cmds/incident_helper/src/TextParserBase.h @@ -68,4 +68,4 @@ public: virtual status_t Parse(const int in, const int out) const; }; -#endif // TEXT_PARSER_BASE_H \ No newline at end of file +#endif // TEXT_PARSER_BASE_H diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk index 8420bc8f7ac74767721beca3ded0644025a3f57b..2b00d9e6a5962be1c8012865dcd03e62ffd7c162 100644 --- a/cmds/incidentd/Android.mk +++ b/cmds/incidentd/Android.mk @@ -91,9 +91,7 @@ GEN_PROTO:= gen_src_dir:= -ifeq ($(BUILD_WITH_INCIDENTD_RC), true) LOCAL_INIT_RC := incidentd.rc -endif include $(BUILD_EXECUTABLE) @@ -134,6 +132,7 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libincident \ liblog \ + libprotobuf-cpp-lite \ libprotoutil \ libselinux \ libservices \ diff --git a/cmds/incidentd/README.md b/cmds/incidentd/README.md index ad0fa08c7326ea7499552da2776688b76ee36fc2..71c6deb18aac2b90a6273576e44116717e6a80af 100644 --- a/cmds/incidentd/README.md +++ b/cmds/incidentd/README.md @@ -12,8 +12,8 @@ Run the test on a device manually ``` root$ mmm -j frameworks/base/cmds/incidentd && \ -adb push $OUT/data/nativetest/incidentd_test/* /data/nativetest/incidentd_test/ && \ -adb shell /data/nativetest/incidentd_test/incidentd_test 2>/dev/null +adb push $OUT/data/nativetest/incidentd_test/* /data/nativetest/ && \ +adb shell /data/nativetest/incidentd_test 2>/dev/null ``` Run the test via AndroidTest.xml diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc index 66667dca29826ea6bd7cc1a60acc9431b0ed1d35..1bd146850ea9b7e82638edfbee2864b119b5220e 100644 --- a/cmds/incidentd/incidentd.rc +++ b/cmds/incidentd/incidentd.rc @@ -19,4 +19,4 @@ service incidentd /system/bin/incidentd on post-fs-data # Create directory for incidentd - mkdir /data/misc/incidents 0770 root root + mkdir /data/misc/incidents 0770 incidentd incidentd diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index 30dd339a629b6666ec6458fc45b4a30f689ac592..0fff4e6dc4a02577ebc0ba82b164a908f458e098 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -63,12 +63,14 @@ FdBuffer::read(int fd, int64_t timeout) int64_t remainingTime = (mStartTime + timeout) - uptimeMillis(); if (remainingTime <= 0) { + if (DEBUG) ALOGD("timed out due to long read"); mTimedOut = true; break; } int count = poll(&pfds, 1, remainingTime); if (count == 0) { + if (DEBUG) ALOGD("timed out due to block calling poll"); mTimedOut = true; break; } else if (count < 0) { @@ -129,6 +131,7 @@ FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeou int64_t remainingTime = (mStartTime + timeoutMs) - uptimeMillis(); if (remainingTime <= 0) { + if (DEBUG) ALOGD("timed out due to long read"); mTimedOut = true; break; } @@ -136,6 +139,7 @@ FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeou // wait for any pfds to be ready to perform IO int count = poll(pfds, 3, remainingTime); if (count == 0) { + if (DEBUG) ALOGD("timed out due to block calling poll"); mTimedOut = true; break; } else if (count < 0) { diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index c4b54bbbc022f7b1a1bf2ff419e8d81d8053b7ec..1d5ab59f9ba81192697429cb583d97a9693c03d2 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -43,24 +43,44 @@ String16 const DUMP_PERMISSION("android.permission.DUMP"); String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); static Status -checkIncidentPermissions() +checkIncidentPermissions(const IncidentReportArgs& args) { + uid_t callingUid = IPCThreadState::self()->getCallingUid(); + if (callingUid == AID_ROOT || callingUid == AID_SHELL) { + // root doesn't have permission.DUMP if don't do this! + return Status::ok(); + } + + // checking calling permission. if (!checkCallingPermission(DUMP_PERMISSION)) { ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", - IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + IPCThreadState::self()->getCallingPid(), callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission: android.permission.DUMP"); } if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", - IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + IPCThreadState::self()->getCallingPid(), callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission: android.permission.USAGE_STATS"); } + + // checking calling request uid permission. + switch (args.dest()) { + case DEST_LOCAL: + if (callingUid != AID_SHELL || callingUid != AID_ROOT) { + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission to get local data."); + } + case DEST_EXPLICIT: + if (callingUid != AID_SHELL || callingUid != AID_ROOT || + callingUid != AID_STATSD || callingUid != AID_SYSTEM) { + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission to get explicit data."); + } + } return Status::ok(); } - - // ================================================================================ ReportRequestQueue::ReportRequestQueue() { @@ -71,7 +91,7 @@ ReportRequestQueue::~ReportRequestQueue() } void -ReportRequestQueue::addRequest(const sp& request) +ReportRequestQueue::addRequest(const sp& request) { unique_lock lock(mLock); mQueue.push_back(request); @@ -196,7 +216,7 @@ IncidentService::reportIncident(const IncidentReportArgs& args) { ALOGI("reportIncident"); - Status status = checkIncidentPermissions(); + Status status = checkIncidentPermissions(args); if (!status.isOk()) { return status; } @@ -212,7 +232,7 @@ IncidentService::reportIncidentToStream(const IncidentReportArgs& args, { ALOGI("reportIncidentToStream"); - Status status = checkIncidentPermissions(); + Status status = checkIncidentPermissions(args); if (!status.isOk()) { return status; } diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp index 5db2239810e2fb2f5f5a55c83187b77bfbc82531..44adaecfe97f136a1dff4fcc7e99085c8fb02fc7 100644 --- a/cmds/incidentd/src/Privacy.cpp +++ b/cmds/incidentd/src/Privacy.cpp @@ -67,8 +67,14 @@ PrivacySpec::RequireAll() const { return dest == android::os::DEST_LOCAL; } PrivacySpec new_spec_from_args(int dest) { - if (dest < 0) return PrivacySpec(); - return PrivacySpec(dest); + switch (dest) { + case android::os::DEST_AUTOMATIC: + case android::os::DEST_EXPLICIT: + case android::os::DEST_LOCAL: + return PrivacySpec(dest); + default: + return PrivacySpec(); + } } PrivacySpec get_default_dropbox_spec() { return PrivacySpec(android::os::DEST_AUTOMATIC); } \ No newline at end of file diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 34930aa57321d079b56b3562eda2355157c64cc4..bd559d6980f1d04d0c55baf058e91108bb1430b4 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -251,7 +251,7 @@ Reporter::create_file(int* fd) // Override umask. Not super critical. If it fails go on with life. chmod(filename, 0660); - if (chown(filename, AID_SYSTEM, AID_SYSTEM)) { + if (chown(filename, AID_INCIDENTD, AID_INCIDENTD)) { ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); status_t err = -errno; unlink(mFilename.c_str()); diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 61d16f815e65be6565465c1af55d337c741dce82..0827785811b66fe9ae11650bcdaf38b06176bd7a 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -19,6 +19,7 @@ #include "Section.h" #include +#include #include #include @@ -30,7 +31,6 @@ #include #include #include -#include // for AID_NOBODY #include #include "FdBuffer.h" @@ -55,26 +55,20 @@ static pid_t fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe) { const char* ihArgs[] { INCIDENT_HELPER, "-s", String8::format("%d", id).string(), NULL }; - // fork used in multithreaded environment, avoid adding unnecessary code in child process pid_t pid = fork(); if (pid == 0) { - // child process executes incident helper as nobody - if (setgid(AID_NOBODY) == -1) { - ALOGW("%s can't change gid: %s", name, strerror(errno)); - _exit(EXIT_FAILURE); - } - if (setuid(AID_NOBODY) == -1) { - ALOGW("%s can't change uid: %s", name, strerror(errno)); - _exit(EXIT_FAILURE); - } - - if (dup2(p2cPipe.readFd(), STDIN_FILENO) != 0 || !p2cPipe.close() || - dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) { + if (TEMP_FAILURE_RETRY(dup2(p2cPipe.readFd(), STDIN_FILENO)) != 0 + || !p2cPipe.close() + || TEMP_FAILURE_RETRY(dup2(c2pPipe.writeFd(), STDOUT_FILENO)) != 1 + || !c2pPipe.close()) { ALOGW("%s can't setup stdin and stdout for incident helper", name); _exit(EXIT_FAILURE); } + /* make sure the child dies when incidentd dies */ + prctl(PR_SET_PDEATHSIG, SIGKILL); + execv(INCIDENT_HELPER, const_cast(ihArgs)); ALOGW("%s failed in incident helper process: %s", name, strerror(errno)); @@ -87,11 +81,23 @@ fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpi } // ================================================================================ +static status_t statusCode(int status) { + if (WIFSIGNALED(status)) { + ALOGD("return by signal: %s", strerror(WTERMSIG(status))); + return -WTERMSIG(status); + } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) { + ALOGD("return by exit: %s", strerror(WEXITSTATUS(status))); + return -WEXITSTATUS(status); + } + return NO_ERROR; +} + static status_t kill_child(pid_t pid) { int status; + ALOGD("try to kill child process %d", pid); kill(pid, SIGKILL); if (waitpid(pid, &status, 0) == -1) return -1; - return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status); + return statusCode(status); } static status_t wait_child(pid_t pid) { @@ -104,7 +110,7 @@ static status_t wait_child(pid_t pid) { nanosleep(&WAIT_INTERVAL_NS, NULL); } if (!died) return kill_child(pid); - return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status); + return statusCode(status); } // ================================================================================ static const Privacy* @@ -275,9 +281,9 @@ FileSection::Execute(ReportRequestSet* requests) const status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(), this->timeoutMs, mIsSysfs); if (readStatus != NO_ERROR || buffer.timedOut()) { - ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s, kill: %s", - this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false", - strerror(-kill_child(pid))); + ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s", + this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); + kill_child(pid); return readStatus; } @@ -543,10 +549,10 @@ CommandSection::Execute(ReportRequestSet* requests) const close(cmdPipe.writeFd()); status_t readStatus = buffer.read(ihPipe.readFd(), this->timeoutMs); if (readStatus != NO_ERROR || buffer.timedOut()) { - ALOGW("CommandSection '%s' failed to read data from incident helper: %s, " - "timedout: %s, kill command: %s, kill incident helper: %s", - this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false", - strerror(-kill_child(cmdPid)), strerror(-kill_child(ihPid))); + ALOGW("CommandSection '%s' failed to read data from incident helper: %s, timedout: %s", + this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); + kill_child(cmdPid); + kill_child(ihPid); return readStatus; } diff --git a/cmds/incidentd/src/io_util.cpp b/cmds/incidentd/src/io_util.cpp index af4a35cc0015bb04138c62370309e7396fea8c1a..90f543e30ff77c3f9addcf2af34fc95612bb988d 100644 --- a/cmds/incidentd/src/io_util.cpp +++ b/cmds/incidentd/src/io_util.cpp @@ -23,7 +23,7 @@ status_t write_all(int fd, uint8_t const* buf, size_t size) { while (size > 0) { - ssize_t amt = ::write(fd, buf, size); + ssize_t amt = TEMP_FAILURE_RETRY(::write(fd, buf, size)); if (amt < 0) { return -errno; } diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp index 65030b3a1799a5d6eea0144e35430b6b4369f705..20111d8ae89a67ea4e75734f921c63736c548e81 100644 --- a/cmds/incidentd/src/report_directory.cpp +++ b/cmds/incidentd/src/report_directory.cpp @@ -58,26 +58,9 @@ create_directory(const char* directory) goto done; } } else { - if (mkdir(dir, 0770)) { - ALOGE("No incident reports today. " - "Unable to create incident report dir %s: %s", dir, - strerror(errno)); - err = -errno; - goto done; - } - if (chmod(dir, 0770)) { - ALOGE("No incident reports today. " - "Unable to set permissions for incident report dir %s: %s", dir, - strerror(errno)); - err = -errno; - goto done; - } - if (chown(dir, AID_SYSTEM, AID_SYSTEM)) { - ALOGE("No incident reports today. Unable to change ownership of dir %s: %s\n", - dir, strerror(errno)); - err = -errno; - goto done; - } + ALOGE("No such directory %s, something wrong.", dir); + err = -1; + goto done; } if (!last) { *d++ = '/'; @@ -97,8 +80,7 @@ create_directory(const char* directory) err = BAD_VALUE; goto done; } - if ((st.st_uid != AID_SYSTEM && st.st_uid != AID_ROOT) || - (st.st_gid != AID_SYSTEM && st.st_gid != AID_ROOT)) { + if (st.st_uid != AID_INCIDENTD || st.st_gid != AID_INCIDENTD) { ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s", st.st_uid, st.st_gid, directory); err = BAD_VALUE; diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp index 531c9f29bf03971c42442c2f90184c7592634e48..c494bd646b0b282fcf4afa37c1cd5af3222b22c1 100644 --- a/cmds/incidentd/tests/Reporter_test.cpp +++ b/cmds/incidentd/tests/Reporter_test.cpp @@ -17,6 +17,7 @@ #include "Reporter.h" #include +#include #include #include @@ -29,6 +30,7 @@ using namespace android; using namespace android::base; using namespace android::binder; +using namespace android::os; using namespace std; using ::testing::StrEq; using ::testing::Test; @@ -141,7 +143,8 @@ TEST_F(ReporterTest, RunReportWithHeaders) { IncidentReportArgs args1, args2; args1.addSection(1); args2.addSection(2); - std::vector header {'a', 'b', 'c', 'd', 'e'}; + IncidentHeaderProto header; + header.set_alert_id(12); args2.addHeader(header); sp r1 = new ReportRequest(args1, l, tf.fd); sp r2 = new ReportRequest(args2, l, tf.fd); @@ -153,7 +156,7 @@ TEST_F(ReporterTest, RunReportWithHeaders) { string result; ReadFileToString(tf.path, &result); - EXPECT_THAT(result, StrEq("\n\x5" "abcde")); + EXPECT_THAT(result, StrEq("\n\x2" "\b\f")); EXPECT_EQ(l->startInvoked, 2); EXPECT_EQ(l->finishInvoked, 2); @@ -164,13 +167,16 @@ TEST_F(ReporterTest, RunReportWithHeaders) { TEST_F(ReporterTest, RunReportToGivenDirectory) { IncidentReportArgs args; - args.addHeader({'1', '2', '3'}); - args.addHeader({'a', 'b', 'c', 'd'}); + IncidentHeaderProto header1, header2; + header1.set_alert_id(12); + header2.set_reason("abcd"); + args.addHeader(header1); + args.addHeader(header2); sp r = new ReportRequest(args, l, -1); reporter->batch.add(r); ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport()); vector results = InspectFiles(); ASSERT_EQ((int)results.size(), 1); - EXPECT_EQ(results[0], "\n\x3" "123\n\x4" "abcd"); + EXPECT_EQ(results[0], "\n\x2" "\b\f\n\x6" "\x12\x4" "abcd"); } diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp index cbfb89685de0465c2f87656a0a92ab8e890e6fc3..2cfd7df6be846f1b05c468cb7a395bdb1b52372a 100644 --- a/cmds/incidentd/tests/Section_test.cpp +++ b/cmds/incidentd/tests/Section_test.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1 using namespace android::base; using namespace android::binder; +using namespace android::os; using namespace std; using ::testing::StrEq; using ::testing::internal::CaptureStdout; @@ -66,15 +68,9 @@ TEST(SectionTest, HeaderSection) { args1.addSection(2); args2.setAll(true); - vector head1; - head1.push_back('a'); - head1.push_back('x'); - head1.push_back('e'); - - vector head2; - head2.push_back('p'); - head2.push_back('u'); - head2.push_back('p'); + IncidentHeaderProto head1, head2; + head1.set_reason("axe"); + head2.set_reason("pup"); args1.addHeader(head1); args1.addHeader(head2); @@ -87,10 +83,10 @@ TEST(SectionTest, HeaderSection) { string content; CaptureStdout(); ASSERT_EQ(NO_ERROR, hs.Execute(&requests)); - EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x3" "axe\n\x03pup")); + EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "\x12\x3" "axe\n\x05\x12\x03pup")); EXPECT_TRUE(ReadFileToString(output2.path, &content)); - EXPECT_THAT(content, StrEq("\n\x03pup")); + EXPECT_THAT(content, StrEq("\n\x05\x12\x03pup")); } TEST(SectionTest, FileSection) { diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index ffe652f72256a93c8ee88c19e516e0614d61215f..eabbb96a392ef10e6c2ec2ba031c1a6ae7821648 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -34,9 +34,10 @@ statsd_common_src := \ src/config/ConfigKey.cpp \ src/config/ConfigListener.cpp \ src/config/ConfigManager.cpp \ + src/external/Perfetto.cpp \ src/external/StatsPuller.cpp \ src/external/StatsCompanionServicePuller.cpp \ - src/external/ResourcePowerManagerPuller.cpp \ + src/external/SubsystemSleepStatePuller.cpp \ src/external/CpuTimePerUidPuller.cpp \ src/external/CpuTimePerUidFreqPuller.cpp \ src/external/StatsPullerManagerImpl.cpp \ @@ -57,9 +58,11 @@ statsd_common_src := \ src/metrics/MetricsManager.cpp \ src/metrics/metrics_manager_util.cpp \ src/packages/UidMap.cpp \ + src/perfetto/perfetto_config.proto \ src/storage/StorageManager.cpp \ src/StatsLogProcessor.cpp \ src/StatsService.cpp \ + src/subscriber/SubscriberReporter.cpp \ src/HashableDimensionKey.cpp \ src/guardrail/MemoryLeakTrackUtil.cpp \ src/guardrail/StatsdStats.cpp @@ -186,7 +189,8 @@ LOCAL_SRC_FILES := \ tests/statsd_test_util.cpp \ tests/e2e/WakelockDuration_e2e_test.cpp \ tests/e2e/MetricConditionLink_e2e_test.cpp \ - tests/e2e/Attribution_e2e_test.cpp + tests/e2e/Attribution_e2e_test.cpp \ + tests/e2e/GaugeMetric_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ @@ -198,6 +202,63 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := lite include $(BUILD_NATIVE_TEST) +############################## +# stats proto static java lib +############################## + +include $(CLEAR_VARS) +LOCAL_MODULE := statsdprotolite + +LOCAL_SRC_FILES := \ + src/stats_log.proto \ + src/statsd_config.proto \ + src/perfetto/perfetto_config.proto \ + src/atoms.proto + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite + +LOCAL_STATIC_JAVA_LIBRARIES := \ + platformprotoslite + +include $(BUILD_STATIC_JAVA_LIBRARY) + +############################## +# statsd micro benchmark +############################## + +include $(CLEAR_VARS) +LOCAL_MODULE := statsd_benchmark + +LOCAL_SRC_FILES := $(statsd_common_src) \ + benchmark/main.cpp \ + benchmark/hello_world_benchmark.cpp \ + benchmark/log_event_benchmark.cpp + +LOCAL_C_INCLUDES := $(statsd_common_c_includes) + +LOCAL_CFLAGS := -Wall \ + -Werror \ + -Wno-unused-parameter \ + -Wno-unused-variable \ + -Wno-unused-function \ + +# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 +LOCAL_CFLAGS += -Wno-varargs + +LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes) + +LOCAL_STATIC_LIBRARIES := \ + $(statsd_common_static_libraries) + +LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ + libgtest_prod + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite + +LOCAL_MODULE_TAGS := eng tests + +include $(BUILD_NATIVE_BENCHMARK) + statsd_common_src:= statsd_common_aidl_includes:= @@ -206,6 +267,4 @@ statsd_common_static_libraries:= statsd_common_shared_libraries:= -############################## - -include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/cmds/statsd/benchmark/hello_world_benchmark.cpp b/cmds/statsd/benchmark/hello_world_benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c732d394bf646cdb23eaa62024e5fd560aac0d1d --- /dev/null +++ b/cmds/statsd/benchmark/hello_world_benchmark.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "benchmark/benchmark.h" + +static void BM_StringCreation(benchmark::State& state) { + while (state.KeepRunning()) std::string empty_string; +} +// Register the function as a benchmark +BENCHMARK(BM_StringCreation); + +// Define another benchmark +static void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + while (state.KeepRunning()) std::string copy(x); +} +BENCHMARK(BM_StringCopy); diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..43addc28f074b2cc39d96697502fd8d9d876635c --- /dev/null +++ b/cmds/statsd/benchmark/log_event_benchmark.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include "benchmark/benchmark.h" +#include "logd/LogEvent.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +/* Special markers for android_log_list_element type */ +static const char EVENT_TYPE_LIST_STOP = '\n'; /* declare end of list */ +static const char EVENT_TYPE_UNKNOWN = '?'; /* protocol error */ + +static const char EVENT_TYPE_INT = 0; +static const char EVENT_TYPE_LONG = 1; +static const char EVENT_TYPE_STRING = 2; +static const char EVENT_TYPE_LIST = 3; +static const char EVENT_TYPE_FLOAT = 4; + +static const int kLogMsgHeaderSize = 28; + +static void write4Bytes(int val, vector* buffer) { + buffer->push_back(static_cast(val)); + buffer->push_back(static_cast((val >> 8) & 0xFF)); + buffer->push_back(static_cast((val >> 16) & 0xFF)); + buffer->push_back(static_cast((val >> 24) & 0xFF)); +} + +static void getSimpleLogMsgData(log_msg* msg) { + vector buffer; + // stats_log tag id + write4Bytes(1937006964, &buffer); + buffer.push_back(EVENT_TYPE_LIST); + buffer.push_back(2); // field counts; + buffer.push_back(EVENT_TYPE_INT); + write4Bytes(10 /* atom id */, &buffer); + buffer.push_back(EVENT_TYPE_INT); + write4Bytes(99 /* a value to log*/, &buffer); + buffer.push_back(EVENT_TYPE_LIST_STOP); + + msg->entry_v1.len = buffer.size(); + msg->entry.hdr_size = kLogMsgHeaderSize; + msg->entry_v1.sec = time(nullptr); + std::copy(buffer.begin(), buffer.end(), msg->buf + kLogMsgHeaderSize); +} + +static void BM_LogEventCreation(benchmark::State& state) { + log_msg msg; + getSimpleLogMsgData(&msg); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(LogEvent(msg)); + } +} +BENCHMARK(BM_LogEventCreation); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/benchmark/main.cpp b/cmds/statsd/benchmark/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..08921f3c3f52d01dbd8c4890ea901ffcc1d4cdb9 --- /dev/null +++ b/cmds/statsd/benchmark/main.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +BENCHMARK_MAIN(); diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 9d6d8a1f2b732f2ea908c0c83cc37e84a6eb00e1..3e71b5368134a55235cb227f322319a3552ce336 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -92,10 +92,20 @@ void StatsLogProcessor::onAnomalyAlarmFired( void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { std::vector uidFields; - findFields( - event->getFieldValueMap(), - buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY), - &uidFields); + if (android::util::kAtomsWithAttributionChain.find(event->GetTagId()) != + android::util::kAtomsWithAttributionChain.end()) { + findFields( + event->getFieldValueMap(), + buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY), + &uidFields); + } else if (android::util::kAtomsWithUidField.find(event->GetTagId()) != + android::util::kAtomsWithUidField.end()) { + findFields( + event->getFieldValueMap(), + buildSimpleAtomFieldMatcher(event->GetTagId(), 1 /* uid is always the 1st field. */), + &uidFields); + } + for (size_t i = 0; i < uidFields.size(); ++i) { DimensionsValue* value = event->findFieldValueOrNull(uidFields[i]); if (value != nullptr && value->value_case() == DimensionsValue::ValueCase::kValueInt) { @@ -131,7 +141,13 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { // The field numbers need to be currently updated by hand with atoms.proto if (event->GetTagId() == android::util::ISOLATED_UID_CHANGED) { onIsolatedUidChangedEventLocked(*event); - } else { + } + + if (mMetricsManagers.empty()) { + return; + } + + if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) { // Map the isolated uid to host uid if necessary. mapIsolatedUidToHostUidIfNecessaryLocked(event); } @@ -180,6 +196,14 @@ size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const { return it->second->byteSize(); } +void StatsLogProcessor::dumpStates(FILE* out, bool verbose) { + std::lock_guard lock(mMetricsMutex); + fprintf(out, "MetricsManager count: %lu\n", (unsigned long)mMetricsManagers.size()); + for (auto metricsManager : mMetricsManagers) { + metricsManager.second->dumpStates(out, verbose); + } +} + void StatsLogProcessor::onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report) { std::lock_guard lock(mMetricsMutex); @@ -197,6 +221,10 @@ void StatsLogProcessor::onDumpReport(const ConfigKey& key, const uint64_t& dumpT void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector* outData) { std::lock_guard lock(mMetricsMutex); + onDumpReportLocked(key, outData); +} + +void StatsLogProcessor::onDumpReportLocked(const ConfigKey& key, vector* outData) { auto it = mMetricsManagers.find(key); if (it == mMetricsManagers.end()) { ALOGW("Config source %s does not exist", key.ToString().c_str()); @@ -236,7 +264,7 @@ void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector* outD // Then, check stats-data directory to see there's any file containing // ConfigMetricsReport from previous shutdowns to concatenate to reports. - StorageManager::appendConfigMetricsReport(STATS_DATA_DIR, proto); + StorageManager::appendConfigMetricsReport(proto); if (outData != nullptr) { outData->clear(); @@ -305,10 +333,10 @@ void StatsLogProcessor::WriteDataToDisk() { for (auto& pair : mMetricsManagers) { const ConfigKey& key = pair.first; vector data; - onDumpReport(key, &data); + onDumpReportLocked(key, &data); // TODO: Add a guardrail to prevent accumulation of file on disk. - string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(), - (long long)key.GetId(), time(nullptr)); + string file_name = StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, time(nullptr), + key.GetUid(), (long long)key.GetId()); StorageManager::writeFile(file_name.c_str(), &data[0], data.size()); } } diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 09e10a13615fa32c9cecd92df88502832053c534..c19ff63e28588b6140b0d6e8392616b62d92f4cd 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -61,6 +61,8 @@ public: return mUidMap; } + void dumpStates(FILE* out, bool verbose); + private: mutable mutex mMetricsMutex; @@ -75,6 +77,8 @@ private: sp mAnomalyMonitor; + void onDumpReportLocked(const ConfigKey& key, vector* outData); + /* Check if we should send a broadcast if approaching memory limits and if we're over, we * actually delete the data. */ void flushIfNecessaryLocked(uint64_t timestampNs, const ConfigKey& key, @@ -99,6 +103,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 0ed1c1f1183246512c1a3391ecd78bdf5f0d3e49..f545bb0738e9d95eba1614e7706b34098a45b3a2 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -24,6 +24,7 @@ #include "guardrail/MemoryLeakTrackUtil.h" #include "guardrail/StatsdStats.h" #include "storage/StorageManager.h" +#include "subscriber/SubscriberReporter.h" #include #include @@ -67,6 +68,7 @@ CompanionDeathRecipient::CompanionDeathRecipient(const sp& anoma void CompanionDeathRecipient::binderDied(const wp& who) { ALOGW("statscompanion service died"); mAnomalyMonitor->setStatsCompanionService(nullptr); + SubscriberReporter::getInstance().setStatsCompanionService(nullptr); } // ====================================================================== @@ -173,8 +175,13 @@ status_t StatsService::dump(int fd, const Vector& args) { return NO_MEMORY; // the fd is already open } + bool verbose = false; + if (args.size() > 0 && !args[0].compare(String16("-v"))) { + verbose = true; + } + // TODO: Proto format for incident reports - dump_impl(out); + dump_impl(out, verbose); fclose(out); return NO_ERROR; @@ -183,8 +190,9 @@ status_t StatsService::dump(int fd, const Vector& args) { /** * Write debugging data about statsd in text format. */ -void StatsService::dump_impl(FILE* out) { - mConfigManager->Dump(out); +void StatsService::dump_impl(FILE* out, bool verbose) { + StatsdStats::getInstance().dumpStats(out); + mProcessor->dumpStates(out, verbose); } /** @@ -296,9 +304,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& args) { @@ -380,6 +387,8 @@ status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector& 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 output; - statsdStats.dumpStats(&output, reset); + statsdStats.dumpStats(out); return NO_ERROR; } @@ -687,6 +690,7 @@ Status StatsService::statsCompanionReady() { VLOG("StatsService::statsCompanionReady linking to statsCompanion."); IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor)); mAnomalyMonitor->setStatsCompanionService(statsCompanion); + SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion); return Status::ok(); } @@ -731,7 +735,7 @@ Status StatsService::addConfiguration(int64_t key, if (checkCallingPermission(String16(kPermissionDump))) { ConfigKey configKey(ipc->getCallingUid(), key); StatsdConfig cfg; - if (!cfg.ParseFromArray(&config[0], config.size())) { + if (config.empty() || !cfg.ParseFromArray(&config[0], config.size())) { *success = false; return Status::ok(); } @@ -749,7 +753,9 @@ Status StatsService::addConfiguration(int64_t key, Status StatsService::removeConfiguration(int64_t key, bool* success) { IPCThreadState* ipc = IPCThreadState::self(); if (checkCallingPermission(String16(kPermissionDump))) { - mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key)); + ConfigKey configKey(ipc->getCallingUid(), key); + mConfigManager->RemoveConfig(configKey); + SubscriberReporter::getInstance().removeConfig(configKey); *success = true; return Status::ok(); } else { @@ -758,6 +764,42 @@ Status StatsService::removeConfiguration(int64_t key, bool* success) { } } +Status StatsService::setBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const sp& intentSender, + bool* success) { + VLOG("StatsService::setBroadcastSubscriber called."); + IPCThreadState* ipc = IPCThreadState::self(); + if (checkCallingPermission(String16(kPermissionDump))) { + ConfigKey configKey(ipc->getCallingUid(), configId); + SubscriberReporter::getInstance() + .setBroadcastSubscriber(configKey, subscriberId, intentSender); + *success = true; + return Status::ok(); + } else { + *success = false; + return Status::fromExceptionCode(binder::Status::EX_SECURITY); + } +} + +Status StatsService::unsetBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + bool* success) { + VLOG("StatsService::unsetBroadcastSubscriber called."); + IPCThreadState* ipc = IPCThreadState::self(); + if (checkCallingPermission(String16(kPermissionDump))) { + ConfigKey configKey(ipc->getCallingUid(), configId); + SubscriberReporter::getInstance() + .unsetBroadcastSubscriber(configKey, subscriberId); + *success = true; + return Status::ok(); + } else { + *success = false; + return Status::fromExceptionCode(binder::Status::EX_SECURITY); + } +} + + void StatsService::binderDied(const wp & who) { } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 8d299702c75d1aa40b701455a4b195b0b18f4ad3..be20893994f105f179c05281f70ed06f066f92ef 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -99,6 +99,21 @@ public: */ virtual Status removeConfiguration(int64_t key, bool* success) override; + /** + * Binder call to associate the given config's subscriberId with the given intentSender. + * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder). + */ + virtual Status setBroadcastSubscriber(int64_t configId, + int64_t subscriberId, + const sp& intentSender, + bool* success) override; + + /** + * Binder call to unassociate the given config's subscriberId with any intentSender. + */ + virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId, + bool* success) override; + // TODO: public for testing since statsd doesn't run when system starts. Change to private // later. /** Inform statsCompanion that statsd is ready. */ @@ -125,7 +140,7 @@ private: /** * Text output of dumpsys. */ - void dump_impl(FILE* out); + void dump_impl(FILE* out, bool verbose); /** * Print usage information for the commands diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index f10b2cf618cddc7ecabb94cf4dc554ff3ca4bf13..ded6c4c660be5940c57cc6d6a8dd5d59237f47ae 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -18,7 +18,10 @@ #include "Log.h" #include "AnomalyTracker.h" +#include "external/Perfetto.h" #include "guardrail/StatsdStats.h" +#include "frameworks/base/libs/incident/proto/android/os/header.pb.h" +#include "subscriber/SubscriberReporter.h" #include #include @@ -231,6 +234,7 @@ void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) { } std::set incidentdSections; + for (const Subscription& subscription : mSubscriptions) { switch (subscription.subscriber_information_case()) { case Subscription::SubscriberInformationCase::kIncidentdDetails: @@ -239,7 +243,11 @@ void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) { } break; case Subscription::SubscriberInformationCase::kPerfettoDetails: - ALOGW("Perfetto reports not implemented."); + CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details()); + break; + case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails: + SubscriberReporter::getInstance() + .alertBroadcastSubscriber(mConfigKey, subscription, key); break; default: break; @@ -253,10 +261,10 @@ void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) { for (const auto section : incidentdSections) { incidentReport.addSection(section); } - int64_t alertId = mAlert.id(); - std::vector header; - uint8_t* src = static_cast(static_cast(&alertId)); - header.insert(header.end(), src, src + sizeof(int64_t)); + android::os::IncidentHeaderProto header; + header.set_alert_id(mAlert.id()); + header.mutable_config_key()->set_uid(mConfigKey.GetUid()); + header.mutable_config_key()->set_id(mConfigKey.GetId()); incidentReport.addHeader(header); service->reportIncident(incidentReport); } else { diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h index de7093d3b1b64a98a12e3318352c130dc8636c41..33e55ab850c9069a4976610f254df891ffddb2f1 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -51,10 +51,8 @@ public: // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker // and removes it from firedAlarms. - // TODO: This will actually be called from a different thread, so make it thread-safe! - // This means that almost every function in DurationAnomalyTracker needs to be locked. - // But this should be done at the level of StatsLogProcessor, which needs to lock - // mMetricsMangers anyway. + // Note that this will generally be called from a different thread from the other functions; + // the caller is responsible for thread safety. void informAlarmsFired(const uint64_t& timestampNs, unordered_set, SpHash>& firedAlarms) override; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 4a7f0c4b65454ddfea7257ca73b71c26e63fac4d..b32af027f2fd8dcb695bfb23fd9883e8467a3f05 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -21,7 +21,11 @@ package android.os.statsd; option java_package = "com.android.os"; option java_outer_classname = "AtomsProto"; -import "frameworks/base/core/proto/android/app/activitymanager.proto"; +import "frameworks/base/core/proto/android/app/enums.proto"; +import "frameworks/base/core/proto/android/os/enums.proto"; +import "frameworks/base/core/proto/android/server/enums.proto"; +import "frameworks/base/core/proto/android/telephony/enums.proto"; +import "frameworks/base/core/proto/android/view/enums.proto"; /** * The master atom class. This message defines all of the available @@ -44,7 +48,7 @@ message Atom { BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3; BleScanResultReceived ble_scan_result_received = 4; SensorStateChanged sensor_state_changed = 5; - GpsScanStateChanged gps_scan_state_changed = 6; // TODO: untested + GpsScanStateChanged gps_scan_state_changed = 6; SyncStateChanged sync_state_changed = 7; ScheduledJobStateChanged scheduled_job_state_changed = 8; ScreenBrightnessChanged screen_brightness_changed = 9; @@ -84,24 +88,36 @@ message Atom { AppStartChanged app_start_changed = 48; AppStartCancelChanged app_start_cancel_changed = 49; AppStartFullyDrawnChanged app_start_fully_drawn_changed = 50; + LmkKillOccurred lmk_kill_occurred = 51; + PictureInPictureStateChanged picture_in_picture_state_changed = 52; + WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53; + LmkStateChanged lmk_state_changed = 54; + AppStartMemoryStateCaptured app_start_memory_state_captured = 55; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } // Pulled events will start at field 10000. + // Next: 10019 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; MobileBytesTransfer mobile_bytes_transfer = 10002; MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = 10003; + BluetoothBytesTransfer bluetooth_bytes_transfer = 10006; KernelWakelock kernel_wakelock = 10004; - PlatformSleepState platform_sleep_state = 10005; - SleepStateVoter sleep_state_voter = 10006; - SubsystemSleepState subsystem_sleep_state = 10007; + SubsystemSleepState subsystem_sleep_state = 10005; CpuTimePerFreq cpu_time_per_freq = 10008; CpuTimePerUid cpu_time_per_uid = 10009; CpuTimePerUidFreq cpu_time_per_uid_freq = 10010; WifiActivityEnergyInfo wifi_activity_energy_info = 10011; ModemActivityInfo modem_activity_info = 10012; + BluetoothActivityInfo bluetooth_activity_info = 10007; + ProcessMemoryState process_memory_state = 10013; + SystemElapsedRealtime system_elapsed_realtime = 10014; + SystemUptime system_uptime = 10015; + CpuActiveTime cpu_active_time = 10016; + CpuClusterTime cpu_cluster_time = 10017; + DiskSpace disk_space = 10018; } } @@ -153,18 +169,8 @@ message AttributionNode { * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java */ message ScreenStateChanged { - // TODO: Use the real screen state. - enum State { - STATE_UNKNOWN = 0; - STATE_OFF = 1; - STATE_ON = 2; - STATE_DOZE = 3; - STATE_DOZE_SUSPEND = 4; - STATE_VR = 5; - STATE_ON_SUSPEND = 6; - } - // New screen state. - optional State display_state = 1; + // New screen state, from frameworks/base/core/proto/android/view/enums.proto. + optional android.view.DisplayStateEnum state = 1; } /** @@ -176,9 +182,8 @@ message ScreenStateChanged { message UidProcessStateChanged { optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation - // The state. - // TODO: Use the real (mapped) process states. - optional android.app.ProcessState state = 2; + // The state, from frameworks/base/core/proto/android/app/enums.proto. + optional android.app.ProcessStateEnum state = 2; } /** @@ -188,7 +193,6 @@ message UidProcessStateChanged { * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java */ message ProcessLifeCycleStateChanged { - // TODO: Use the real (mapped) process states. optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation // TODO: What is this? @@ -322,8 +326,9 @@ message ScheduledJobStateChanged { optional string name = 2; enum State { - OFF = 0; - ON = 1; + FINISHED = 0; + STARTED = 1; + SCHEDULED = 2; } optional State state = 3; @@ -403,13 +408,9 @@ message CameraStateChanged { message WakelockStateChanged { repeated AttributionNode attribution_node = 1; - // Type of wakelock. - enum Type { - PARTIAL = 0; - FULL = 1; - WINDOW = 2; - } - optional Type type = 2; + // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock. + // From frameworks/base/core/proto/android/os/enums.proto. + optional android.os.WakeLockLevelEnum level = 2; // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). optional string tag = 3; @@ -466,13 +467,7 @@ message BatterySaverModeStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message DeviceIdleModeStateChanged { - // TODO: Use the enum matching BatteryStats.DEVICE_IDLE_MODE_. - enum State { - DEVICE_IDLE_MODE_OFF = 0; - DEVICE_IDLE_MODE_LIGHT = 1; - DEVICE_IDLE_MODE_DEEP = 2; - } - optional State state = 1; + optional android.server.DeviceIdleModeEnum state = 1; } @@ -483,13 +478,7 @@ message DeviceIdleModeStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message DeviceIdlingModeStateChanged { - // TODO: Use the enum matching BatteryStats.DEVICE_IDLE_MODE_. - enum State { - DEVICE_IDLE_MODE_OFF = 0; - DEVICE_IDLE_MODE_LIGHT = 1; - DEVICE_IDLE_MODE_DEEP = 2; - } - optional State state = 1; + optional android.server.DeviceIdleModeEnum state = 1; } /** @@ -521,15 +510,8 @@ message BatteryLevelChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message ChargingStateChanged { - // TODO: Link directly to BatteryManager.java's constants (via a proto). - enum State { - BATTERY_STATUS_UNKNOWN = 1; - BATTERY_STATUS_CHARGING = 2; - BATTERY_STATUS_DISCHARGING = 3; - BATTERY_STATUS_NOT_CHARGING = 4; - BATTERY_STATUS_FULL = 5; - } - optional State charging_state = 1; + // State of the battery, from frameworks/base/core/proto/android/os/enums.proto. + optional android.os.BatteryStatusEnum state = 1; } /** @@ -539,18 +521,8 @@ message ChargingStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message PluggedStateChanged { - // TODO: Link directly to BatteryManager.java's constants (via a proto). - enum State { - // Note that NONE is not in BatteryManager.java's constants. - BATTERY_PLUGGED_NONE = 0; - // Power source is an AC charger. - BATTERY_PLUGGED_AC = 1; - // Power source is a USB port. - BATTERY_PLUGGED_USB = 2; - // Power source is wireless. - BATTERY_PLUGGED_WIRELESS = 4; - } - optional State plugged_state = 1; + // Whether the device is plugged in, from frameworks/base/core/proto/android/os/enums.proto. + optional android.os.BatteryPluggedStateEnum state = 1; } /** @@ -604,13 +576,8 @@ message MobileRadioPowerStateChanged { // TODO: Add attribution instead of uid? optional int32 uid = 1; - // TODO: Reference telephony/java/android/telephony/DataConnectionRealTimeInfo.java states. - enum PowerState { - DC_POWER_STATE_LOW = 1; - DC_POWER_STATE_MEDIUM = 2; - DC_POWER_STATE_HIGH = 3; - } - optional PowerState power_state = 2; + // Power state, from frameworks/base/core/proto/android/telephony/enums.proto. + optional android.telephony.DataConnectionPowerStateEnum state = 2; } /** @@ -624,13 +591,8 @@ message WifiRadioPowerStateChanged { // TODO: Add attribution instead of uid? optional int32 uid = 1; - // TODO: Reference telephony/java/android/telephony/DataConnectionRealTimeInfo.java states. - enum PowerState { - DC_POWER_STATE_LOW = 1; - DC_POWER_STATE_MEDIUM = 2; - DC_POWER_STATE_HIGH = 3; - } - optional PowerState power_state = 2; + // Power state, from frameworks/base/core/proto/android/telephony/enums.proto. + optional android.telephony.DataConnectionPowerStateEnum state = 2; } /** @@ -670,15 +632,8 @@ message WifiLockStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message WifiSignalStrengthChanged { - // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states. - enum SignalStrength { - SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; - SIGNAL_STRENGTH_POOR = 1; - SIGNAL_STRENGTH_MODERATE = 2; - SIGNAL_STRENGTH_GOOD = 3; - SIGNAL_STRENGTH_GREAT = 4; - } - optional SignalStrength signal_strength = 1; + // Signal strength, from frameworks/base/core/proto/android/telephony/enums.proto. + optional android.telephony.SignalStrengthEnum signal_strength = 1; } /** @@ -697,6 +652,22 @@ message WifiScanStateChanged { optional State state = 2; } +/** + * Logs wifi multicast locks held by an app + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message WifiMulticastLockStateChanged { + repeated AttributionNode attribution_node = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + /** * Logs phone signal strength changes. * @@ -704,15 +675,8 @@ message WifiScanStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message PhoneSignalStrengthChanged { - // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states. - enum SignalStrength { - SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; - SIGNAL_STRENGTH_POOR = 1; - SIGNAL_STRENGTH_MODERATE = 2; - SIGNAL_STRENGTH_GOOD = 3; - SIGNAL_STRENGTH_GREAT = 4; - } - optional SignalStrength signal_strength = 1; + // Signal strength, from frameworks/base/core/proto/android/telephony/enums.proto. + optional android.telephony.SignalStrengthEnum signal_strength = 1; } /** @@ -865,15 +829,7 @@ message AppStartChanged { // Device uptime when activity started. optional int64 activity_start_msec = 7; - // TODO: Update android/app/ActivityManagerInternal.java constants to depend on our proto enum. - enum TransitionReason { - APP_START_TRANSITION_REASON_UNKNOWN = 0; - SPLASH_SCREEN = 1; - WINDOWS_DRAWN = 2; - TIMEOUT = 3; - SNAPSHOT = 4; - } - optional TransitionReason reason = 8; + optional android.app.AppTransitionReasonEnum reason = 8; optional int32 transition_delay_msec = 9; // -1 if not set. @@ -931,6 +887,31 @@ message AppStartFullyDrawnChanged { optional int64 app_startup_time_ms = 6; } +/** + * Logs a picture-in-picture action + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + * frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java + * frameworks/base/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java + */ +message PictureInPictureStateChanged { + optional int32 uid = 1; + + optional string package_name = 2; + + optional string class_name = 3; + + // Picture-in-Picture action occurred, similar to + // frameworks/base/proto/src/metrics_constants.proto + enum State { + ENTERED = 1; + EXPANDED_TO_FULL_SCREEN = 2; + MINIMIZED = 3; + DISMISSED = 4; + } + optional State state = 4; +} + /** * Pulls bytes transferred via wifi (Sum of foreground and background usage). * @@ -1009,6 +990,20 @@ message MobileBytesTransferByFgBg { optional int64 tx_packets = 6; } +/** + * Pulls bytes transferred via bluetooth. It is pulled from Bluetooth controller. + * + * Pulled from: + * StatsCompanionService + */ +message BluetoothBytesTransfer { + optional int32 uid = 1; + + optional int64 rx_bytes = 2; + + optional int64 tx_bytes = 3; +} + /** * Pulls the kernel wakelock durations. This atom is adapted from * android/internal/os/KernelWakelockStats.java @@ -1027,44 +1022,21 @@ message KernelWakelock { } /** - * Pulls PowerStatePlatformSleepState. - * - * Definition here: - * hardware/interfaces/power/1.0/types.hal - */ -message PlatformSleepState { - optional string name = 1; - optional uint64 residency_in_msec_since_boot = 2; - optional uint64 total_transitions = 3; - optional bool supported_only_in_suspend = 4; -} - -/** - * Pulls PowerStateVoter. - * - * Definition here: + * Pulls low power state information. This includes platform and subsystem sleep state information, + * PowerStatePlatformSleepState, PowerStateVoter or PowerStateSubsystemSleepState as defined in * hardware/interfaces/power/1.0/types.hal - */ -message SleepStateVoter { - optional string platform_sleep_state_name = 1; - optional string voter_name = 2; - optional uint64 total_time_in_msec_voted_for_since_boot = 3; - optional uint64 total_number_of_times_voted_since_boot = 4; -} - -/** - * Pulls PowerStateSubsystemSleepState. - * - * Definition here: * hardware/interfaces/power/1.1/types.hal */ message SubsystemSleepState { - optional string subsystem_name = 1; - optional string subsystem_sleep_state_name = 2; - optional uint64 residency_in_msec_since_boot = 3; - optional uint64 total_transitions = 4; - optional uint64 last_entry_timestamp_ms = 5; - optional bool supported_only_in_suspend = 6; + // Name should be in the format of XXX.YYY where XXX is subsystem name, + // YYY is corresponding voter name. + // If there are no voters, the format should just be XXX (with no dot). + // XXX and YYY should not contain a "." in it. + optional string name = 1; + // The number of times it entered, or voted for entering the sleep state + optional uint64 count = 2; + // The length of time spent in, or spent voting for, the sleep state + optional uint64 timeMs = 3; } /** @@ -1201,3 +1173,188 @@ message ModemActivityInfo { // product of current(mA), voltage(V) and time(ms) optional uint64 energy_used = 10; } + +/** + * Pulls Bluetooth Activity Energy Info + * Note: BluetoothBytesTransfer is pulled at the same time from the controller. + */ +message BluetoothActivityInfo { + // timestamp(wall clock) of record creation + optional uint64 timestamp_ms = 1; + // bluetooth stack state + optional int32 bluetooth_stack_state = 2; + // tx time in ms + optional uint64 controller_tx_time_ms = 3; + // rx time in ms + optional uint64 controller_rx_time_ms = 4; + // idle time in ms + optional uint64 controller_idle_time_ms = 5; + // product of current(mA), voltage(V) and time(ms) + optional uint64 energy_used = 6; +} + +/* + * Logs the memory stats for an app on startup. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message AppStartMemoryStateCaptured { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // The process name. + optional string process_name = 2; + + // The activity name. + optional string activity_name = 3; + + // # of page-faults + optional int64 pgfault = 4; + + // # of major page-faults + optional int64 pgmajfault = 5; + + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; +} + +/* + * Logs the memory stats for a process. + */ +message ProcessMemoryState { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // The process name. + optional string process_name = 2; + + // oom adj score. + optional int32 oom_score = 3; + + // # of page-faults + optional int64 pgfault = 4; + + // # of major page-faults + optional int64 pgmajfault = 5; + + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; +} + +/* + * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries + * for LMK event. + * Logged from: + * system/core/lmkd/lmkd.c + */ +message LmkStateChanged { + enum State { + UNKNOWN = 0; + START = 1; + STOP = 2; + } + optional State state = 1; +} + +/* + * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure. + * Logged from: + * system/core/lmkd/lmkd.c + */ +message LmkKillOccurred { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // The process name. + optional string process_name = 2; + + // oom adj score. + optional int32 oom_score = 3; + + // # of page-faults + optional int64 pgfault = 4; + + // # of major page-faults + optional int64 pgmajfault = 5; + + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; +} + +/* + * Elapsed real time from SystemClock. + */ +message SystemElapsedRealtime { + optional uint64 time_ms = 1; +} + +/* + * Up time from SystemClock. + */ +message SystemUptime { + // Milliseconds since the system was booted. + // This clock stops when the system enters deep sleep (CPU off, display dark, device waiting + // for external input). + // It is not affected by clock scaling, idle, or other power saving mechanisms. + optional uint64 uptime_ms = 1; +} + +/* + * Reads from /proc/uid_concurrent_active_time which has the format: + * active: X (X is # cores) + * [uid0]: [time-0] [time-1] [time-2] ... (# entries = # cores) + * [uid1]: [time-0] [time-1] [time-2] ... ... + * ... + * Time-N means the CPU time a UID spent running concurrently with N other processes. + * The file contains a monotonically increasing count of time for a single boot. + */ +message CpuActiveTime { + optional uint64 uid = 1; + optional uint64 idx = 2; + optional uint64 time_ms = 3; +} + +/** + * Reads from /proc/uid_concurrent_policy_time which has the format: + * policy0: X policy4: Y (there are X cores on policy0, Y cores on policy4) + * [uid0]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ... + * [uid1]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ... + * ... + * Time-X-Y means the time a UID spent on clusterX running concurrently with Y other processes. + * The file contains a monotonically increasing count of time for a single boot. + */ +message CpuClusterTime { + optional uint64 uid = 1; + optional uint64 idx = 2; + optional uint64 time_ms = 3; +} + +/* + * Pulls free disk space, for data, system partition and temporary directory. + */ +message DiskSpace { + // available bytes in data partition + optional uint64 data_available_bytes = 1; + // available bytes in system partition + optional uint64 system_available_bytes = 2; + // available bytes in download cache or temp directories + optional uint64 temp_available_bytes = 3; +} diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index afa26f6da08a96dffa422a8bfbf66f42f9878fd5..ea6586c25d80707bbeaa4b3fbabdf209a8ac1115 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -154,7 +154,7 @@ void CombinationConditionTracker::evaluateCondition( } } nonSlicedConditionCache[mIndex] = ConditionState::kUnknown; - ALOGD("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId, + VLOG("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId, conditionChangedCache[mIndex] == true); } } diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 25257213a5d0f73054548c0ba44c8c665711d0a3..7a1bb0c71373afd57d2dc57b346982afb1822a5c 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 @@ -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/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp index a5aee73ce84fbf1575dba8d1d0bfffe00e24ec00..ddfb8d12a33596d7b861d30455ba3852994190fe 100644 --- a/cmds/statsd/src/condition/condition_util.cpp +++ b/cmds/statsd/src/condition/condition_util.cpp @@ -158,15 +158,15 @@ void flattenValueLeaves(const DimensionsValue& value, std::vector getDimensionKeysForCondition( const LogEvent& event, const MetricConditionLink& link) { std::vector whatFields; - getFieldsFromFieldMatcher(link.dimensions_in_what(), &whatFields); + getFieldsFromFieldMatcher(link.fields_in_what(), &whatFields); std::vector conditionFields; - getFieldsFromFieldMatcher(link.dimensions_in_condition(), &conditionFields); + getFieldsFromFieldMatcher(link.fields_in_condition(), &conditionFields); std::vector hashableDimensionKeys; // TODO(yanglu): here we could simplify the logic to get the leaf value node in what and // directly construct the full condition value tree. - std::vector whatValues = getDimensionKeys(event, link.dimensions_in_what()); + std::vector whatValues = getDimensionKeys(event, link.fields_in_what()); for (size_t i = 0; i < whatValues.size(); ++i) { std::vector whatLeaves; @@ -185,7 +185,7 @@ std::vector getDimensionKeysForCondition( conditionValueMap.insert(std::make_pair(conditionFields[j], whatLeaves[j])); } std::vector conditionValues; - findDimensionsValues(conditionValueMap, link.dimensions_in_condition(), &conditionValues); + findDimensionsValues(conditionValueMap, link.fields_in_condition(), &conditionValues); if (conditionValues.size() != 1) { ALOGE("Not able to find unambiguous field value in condition atom."); continue; diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 554ff8a85094ad0bd864c8c363e883a6d8d74feb..61eeee3796df0fb3a42e3ff4a859bd54fe3124b4 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -101,8 +101,8 @@ void ConfigManager::RemoveConfig(const ConfigKey& key) { } void ConfigManager::remove_saved_configs(const ConfigKey& key) { - string prefix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId()); - StorageManager::deletePrefixedFiles(STATS_SERVICE_DIR, prefix.c_str()); + string suffix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId()); + StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str()); } void ConfigManager::RemoveConfigs(int uid) { @@ -111,6 +111,7 @@ void ConfigManager::RemoveConfigs(int uid) { for (auto it = mConfigs.begin(); it != mConfigs.end();) { // Remove from map if (it->GetUid() == uid) { + remove_saved_configs(*it); removed.push_back(*it); mConfigReceivers.erase(*it); it = mConfigs.erase(it); @@ -185,8 +186,8 @@ void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfi remove_saved_configs(key); // Then we save the latest config. - string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_SERVICE_DIR, key.GetUid(), - (long long)key.GetId(), time(nullptr)); + string file_name = StringPrintf("%s/%ld_%d_%lld", STATS_SERVICE_DIR, time(nullptr), + key.GetUid(), (long long)key.GetId()); const int numBytes = config.ByteSize(); vector buffer(numBytes); config.SerializeToArray(&buffer[0], numBytes); @@ -255,7 +256,7 @@ StatsdConfig build_fake_config() { metric->set_id(2); // "METRIC_2" metric->set_what(104); metric->set_bucket(ONE_MINUTE); - FieldMatcher* dimensions = metric->mutable_dimensions(); + FieldMatcher* dimensions = metric->mutable_dimensions_in_what(); dimensions->set_field(UID_PROCESS_STATE_TAG_ID); dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY); @@ -278,7 +279,7 @@ StatsdConfig build_fake_config() { metric->set_what(104); metric->set_bucket(ONE_MINUTE); - dimensions = metric->mutable_dimensions(); + dimensions = metric->mutable_dimensions_in_what(); dimensions->set_field(UID_PROCESS_STATE_TAG_ID); dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY); metric->set_condition(202); @@ -288,7 +289,7 @@ StatsdConfig build_fake_config() { metric->set_id(4); metric->set_what(107); metric->set_bucket(ONE_MINUTE); - dimensions = metric->mutable_dimensions(); + dimensions = metric->mutable_dimensions_in_what(); dimensions->set_field(WAKE_LOCK_TAG_ID); dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); @@ -296,44 +297,44 @@ StatsdConfig build_fake_config() { metric->set_condition(204); MetricConditionLink* link = metric->add_links(); link->set_condition(203); - link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); - link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); - link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); - link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); + link->mutable_fields_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_fields_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_fields_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_fields_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // Duration of an app holding any wl, while screen on and app in background, slice by uid DurationMetric* durationMetric = config.add_duration_metric(); durationMetric->set_id(5); durationMetric->set_bucket(ONE_MINUTE); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - dimensions = durationMetric->mutable_dimensions(); + dimensions = durationMetric->mutable_dimensions_in_what(); dimensions->set_field(WAKE_LOCK_TAG_ID); dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); durationMetric->set_what(205); durationMetric->set_condition(204); link = durationMetric->add_links(); link->set_condition(203); - link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); - link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); - link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); - link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); + link->mutable_fields_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_fields_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_fields_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_fields_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // max Duration of an app holding any wl, while screen on and app in background, slice by uid durationMetric = config.add_duration_metric(); durationMetric->set_id(6); durationMetric->set_bucket(ONE_MINUTE); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - dimensions = durationMetric->mutable_dimensions(); + dimensions = durationMetric->mutable_dimensions_in_what(); dimensions->set_field(WAKE_LOCK_TAG_ID); dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); durationMetric->set_what(205); durationMetric->set_condition(204); link = durationMetric->add_links(); link->set_condition(203); - link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); - link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); - link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); - link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); + link->mutable_fields_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_fields_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_fields_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_fields_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // Duration of an app holding any wl, while screen on and app in background durationMetric = config.add_duration_metric(); @@ -344,10 +345,10 @@ StatsdConfig build_fake_config() { durationMetric->set_condition(204); link = durationMetric->add_links(); link->set_condition(203); - link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); - link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); - link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); - link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); + link->mutable_fields_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_fields_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_fields_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_fields_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // Duration of screen on time. @@ -376,7 +377,7 @@ StatsdConfig build_fake_config() { valueMetric->mutable_value_field()->set_field(KERNEL_WAKELOCK_TAG_ID); valueMetric->mutable_value_field()->add_child()->set_field(KERNEL_WAKELOCK_COUNT_KEY); valueMetric->set_condition(201); - dimensions = valueMetric->mutable_dimensions(); + dimensions = valueMetric->mutable_dimensions_in_what(); dimensions->set_field(KERNEL_WAKELOCK_TAG_ID); dimensions->add_child()->set_field(KERNEL_WAKELOCK_NAME_KEY); // This is for testing easier. We should never set bucket size this small. diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp index 09499b6406e7721eb81980fce75bc1630bb68372..bb7a044fa8cef5c1c88ce4f640a2841302a08fce 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/dimension.h b/cmds/statsd/src/dimension.h index 845c13867e5a61ecc1e55e4e1f22dfb89f0f1737..d0f96a2abe6b4f593a7827f7321746399951b198 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -32,6 +32,10 @@ namespace statsd { const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); DimensionsValue getSingleLeafValue(const DimensionsValue& value); +// Appends the leaf node to the parent tree. +void appendLeafNodeToParent(const Field& field, const DimensionsValue& value, + DimensionsValue* parentValue); + // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto // represents a tree. When the input proto has repeated fields and the input "dimensions" wants // "ANY" locations, it will return multiple trees. diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp index a61afb429f54f3eaadc53b967daa6865d399a37f..a751273247456a4bf113b7de9b9cab7b980631fa 100644 --- a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp +++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp @@ -34,7 +34,7 @@ namespace android { namespace os { namespace statsd { -static const string sProcFile = "/proc/uid_cputime/show_uid_stat"; +static const string sProcFile = "/proc/uid_time_in_state"; static const int kLineBufferSize = 1024; /** diff --git a/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.cpp b/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7a2d1991a5389caa7d1c6aa7efef4ca090f8a310 --- /dev/null +++ b/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.cpp @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include + +#include "KernelUidCpuActiveTimeReader.h" +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" +#include "statslog.h" + +using std::make_shared; +using std::shared_ptr; +using std::ifstream; + +namespace android { +namespace os { +namespace statsd { + +static const string sProcFile = "/proc/uid_concurrent_active_time"; +static const int kLineBufferSize = 1024; + +/** + * Reads /proc/uid_concurrent_active_time which has the format: + * active: X (X is # cores) + * [uid0]: [time-0] [time-1] [time-2] ... (# entries = # cores) + * [uid1]: [time-0] [time-1] [time-2] ... ... + * ... + * Time-N means the CPU time a UID spent running concurrently with N other processes. + * The file contains a monotonically increasing count of time for a single boot. + */ +KernelUidCpuActiveTimeReader::KernelUidCpuActiveTimeReader() : StatsPuller(android::util::CPU_ACTIVE_TIME) { +} + +bool KernelUidCpuActiveTimeReader::PullInternal(vector>* data) { + data->clear(); + + ifstream fin; + fin.open(sProcFile); + if (!fin.good()) { + VLOG("Failed to read pseudo file %s", sProcFile.c_str()); + return false; + } + + uint64_t timestamp = time(nullptr) * NS_PER_SEC; + char buf[kLineBufferSize]; + char* pch; + while (!fin.eof()) { + fin.getline(buf, kLineBufferSize); + pch = strtok(buf, " :"); + if (pch == NULL) break; + uint64_t uid = std::stoull(pch); + pch = strtok(NULL, " "); + uint64_t timeMs; + int idx = 0; + do { + timeMs = std::stoull(pch); + auto ptr = make_shared(mTagId, timestamp); + ptr->write(uid); + ptr->write(idx); + ptr->write(timeMs); + ptr->init(); + data->push_back(ptr); + VLOG("uid %lld, freq idx %d, active time %lld", (long long)uid, idx, (long long)timeMs); + idx++; + pch = strtok(NULL, " "); + } while (pch != NULL); + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.h b/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.h new file mode 100644 index 0000000000000000000000000000000000000000..fcae35fa6eb5b35cb3d194f1addbc383fcf7d0e3 --- /dev/null +++ b/cmds/statsd/src/external/KernelUidCpuActiveTimeReader.h @@ -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. + */ + +#pragma once + +#include +#include "StatsPuller.h" + +namespace android { +namespace os { +namespace statsd { + +class KernelUidCpuActiveTimeReader : public StatsPuller { + public: + KernelUidCpuActiveTimeReader(); + bool PullInternal(vector>* data) override; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.cpp b/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7426e743c40f8af5aee59549bf9214bfe9827475 --- /dev/null +++ b/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.cpp @@ -0,0 +1,88 @@ +/* + * 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. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include +#include "KernelUidCpuClusterTimeReader.h" +#include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" +#include "statslog.h" + +using std::make_shared; +using std::shared_ptr; +using std::ifstream; + +namespace android { +namespace os { +namespace statsd { + +static const string sProcFile = "/proc/uid_concurrent_policy_time"; +static const int kLineBufferSize = 1024; + +/** + * Reads /proc/uid_concurrent_policy_time which has the format: + * policy0: X policy4: Y (there are X cores on policy0, Y cores on policy4) + * [uid0]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ... + * [uid1]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ... + * ... + * Time-X-Y means the time a UID spent on clusterX running concurrently with Y other processes. + * The file contains a monotonically increasing count of time for a single boot. + */ +KernelUidCpuClusterTimeReader::KernelUidCpuClusterTimeReader() : StatsPuller(android::util::CPU_CLUSTER_TIME) { +} + +bool KernelUidCpuClusterTimeReader::PullInternal(vector>* data) { + data->clear(); + + ifstream fin; + fin.open(sProcFile); + if (!fin.good()) { + VLOG("Failed to read pseudo file %s", sProcFile.c_str()); + return false; + } + + uint64_t timestamp = time(nullptr) * NS_PER_SEC; + char buf[kLineBufferSize]; + char* pch; + while (!fin.eof()) { + fin.getline(buf, kLineBufferSize); + pch = strtok(buf, " :"); + if (pch == NULL) break; + uint64_t uid = std::stoull(pch); + pch = strtok(NULL, " "); + uint64_t timeMs; + int idx = 0; + do { + timeMs = std::stoull(pch); + auto ptr = make_shared(mTagId, timestamp); + ptr->write(uid); + ptr->write(idx); + ptr->write(timeMs); + ptr->init(); + data->push_back(ptr); + VLOG("uid %lld, freq idx %d, cluster time %lld", (long long)uid, idx, (long long)timeMs); + idx++; + pch = strtok(NULL, " "); + } while (pch != NULL); + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.h b/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.h new file mode 100644 index 0000000000000000000000000000000000000000..90236ae0076232e154c67f6db658b40154fa238b --- /dev/null +++ b/cmds/statsd/src/external/KernelUidCpuClusterTimeReader.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "StatsPuller.h" + +namespace android { +namespace os { +namespace statsd { + +/** + * Reads /proc/uid_cputime/show_uid_stat which has the line format: + * + * uid: user_time_micro_seconds system_time_micro_seconds + * + * This provides the time a UID's processes spent executing in user-space and kernel-space. + * The file contains a monotonically increasing count of time for a single boot. + */ +class KernelUidCpuClusterTimeReader : public StatsPuller { + public: + KernelUidCpuClusterTimeReader(); + bool PullInternal(vector>* data) override; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/Perfetto.cpp b/cmds/statsd/src/external/Perfetto.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1d8a96850c4be8721705355c4468ff5f6f52e6e3 --- /dev/null +++ b/cmds/statsd/src/external/Perfetto.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Log.h" + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert + +#include +#include +#include +#include +#include +#include + +#include + +namespace { +const char kDropboxTag[] = "perfetto"; +} + +namespace android { +namespace os { +namespace statsd { + +bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config) { + ALOGD("Starting trace collection through perfetto"); + + if (!config.has_trace_config()) { + ALOGE("The perfetto trace config is empty, aborting"); + return false; + } + + android::base::unique_fd readPipe; + android::base::unique_fd writePipe; + if (!android::base::Pipe(&readPipe, &writePipe)) { + ALOGE("pipe() failed while calling the Perfetto client: %s", strerror(errno)); + return false; + } + + pid_t pid = fork(); + if (pid < 0) { + ALOGE("fork() failed while calling the Perfetto client: %s", strerror(errno)); + return false; + } + + if (pid == 0) { + // Child process. + + // No malloc calls or library calls after this point. Remember that even + // ALOGx (aka android_printLog()) can use dynamic memory for vsprintf(). + + writePipe.reset(); // Close the write end (owned by the main process). + + // Replace stdin with |readPipe| so the main process can write into it. + if (dup2(readPipe.get(), STDIN_FILENO) < 0) _exit(1); + readPipe.reset(); + + // Replace stdout/stderr with /dev/null and close any other file + // descriptor. This is to avoid SELinux complaining about perfetto + // trying to access files accidentally left open by statsd (i.e. files + // that have been opened without the O_CLOEXEC flag). + int devNullFd = open("/dev/null", O_RDWR | O_CLOEXEC); + if (dup2(devNullFd, STDOUT_FILENO) < 0) _exit(2); + if (dup2(devNullFd, STDERR_FILENO) < 0) _exit(3); + close(devNullFd); + for (int i = 0; i < 1024; i++) { + if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) close(i); + } + + execl("/system/bin/perfetto", "perfetto", "--background", "--config", "-", "--dropbox", + kDropboxTag, nullptr); + + // execl() doesn't return in case of success, if we get here something + // failed. + _exit(4); + } + + // Main process. + + readPipe.reset(); // Close the read end (owned by the child process). + + // Using fopen() because fwrite() has the right logic to chunking write() + // over a pipe (see __sfvwrite()). + FILE* writePipeStream = fdopen(writePipe.get(), "wb"); + if (!writePipeStream) { + ALOGE("fdopen() failed while calling the Perfetto client: %s", strerror(errno)); + return false; + } + + std::string cfgProto = config.trace_config().SerializeAsString(); + size_t bytesWritten = fwrite(cfgProto.data(), 1, cfgProto.size(), writePipeStream); + fclose(writePipeStream); + if (bytesWritten != cfgProto.size() || cfgProto.size() == 0) { + ALOGE("fwrite() failed (ret: %zd) while calling the Perfetto client: %s", bytesWritten, + strerror(errno)); + return false; + } + + // This does NOT wait for the full duration of the trace. It just waits until + // the process has read the config from stdin and detached. + int childStatus = 0; + waitpid(pid, &childStatus, 0); + if (!WIFEXITED(childStatus) || WEXITSTATUS(childStatus) != 0) { + ALOGE("Child process failed (0x%x) while calling the Perfetto client", childStatus); + return false; + } + + ALOGD("CollectPerfettoTraceAndUploadToDropbox() succeeded"); + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/Perfetto.h b/cmds/statsd/src/external/Perfetto.h new file mode 100644 index 0000000000000000000000000000000000000000..e2e02533f17fd5830791abfc4596e055bff1c114 --- /dev/null +++ b/cmds/statsd/src/external/Perfetto.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +using android::os::StatsLogEventWrapper; + +namespace android { +namespace os { +namespace statsd { + +class PerfettoDetails; // Declared in statsd_config.pb.h + +// Starts the collection of a Perfetto trace with the given |config|. +// The trace is uploaded to Dropbox by the perfetto cmdline util once done. +// This method returns immediately after passing the config and does NOT wait +// for the full duration of the trace. +bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp index 58c7b126a5a5bd9a64849015ebc3b89115ed6def..148c9ae9b249a847d9205331918902a4cc83470f 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp @@ -23,7 +23,7 @@ #include #include "CpuTimePerUidFreqPuller.h" #include "CpuTimePerUidPuller.h" -#include "ResourcePowerManagerPuller.h" +#include "SubsystemSleepStatePuller.h" #include "StatsCompanionServicePuller.h" #include "StatsPullerManagerImpl.h" #include "StatsService.h" @@ -58,16 +58,31 @@ StatsPullerManagerImpl::StatsPullerManagerImpl() mPullers.insert({android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG, make_shared( android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}); - mPullers.insert({android::util::PLATFORM_SLEEP_STATE, - make_shared(android::util::PLATFORM_SLEEP_STATE)}); - mPullers.insert({android::util::SLEEP_STATE_VOTER, - make_shared(android::util::SLEEP_STATE_VOTER)}); mPullers.insert( {android::util::SUBSYSTEM_SLEEP_STATE, - make_shared(android::util::SUBSYSTEM_SLEEP_STATE)}); + make_shared()}); mPullers.insert({android::util::CPU_TIME_PER_FREQ, make_shared(android::util::CPU_TIME_PER_FREQ)}); mPullers.insert({android::util::CPU_TIME_PER_UID, make_shared()}); mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, make_shared()}); + mPullers.insert( + {android::util::SYSTEM_ELAPSED_REALTIME, + make_shared(android::util::SYSTEM_ELAPSED_REALTIME)}); + mPullers.insert({android::util::SYSTEM_UPTIME, + make_shared(android::util::SYSTEM_UPTIME)}); + mPullers.insert({android::util::DISK_SPACE, + make_shared(android::util::DISK_SPACE)}); + mPullers.insert( + {android::util::BLUETOOTH_ACTIVITY_INFO, + make_shared(android::util::BLUETOOTH_ACTIVITY_INFO)}); + + mPullers.insert( + {android::util::BLUETOOTH_BYTES_TRANSFER, + make_shared(android::util::BLUETOOTH_BYTES_TRANSFER)}); + mPullers.insert( + {android::util::WIFI_ACTIVITY_ENERGY_INFO, + make_shared(android::util::WIFI_ACTIVITY_ENERGY_INFO)}); + mPullers.insert({android::util::MODEM_ACTIVITY_INFO, + make_shared(android::util::MODEM_ACTIVITY_INFO)}); mStatsCompanionService = StatsService::getStatsCompanionService(); } diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp similarity index 85% rename from cmds/statsd/src/external/ResourcePowerManagerPuller.cpp rename to cmds/statsd/src/external/SubsystemSleepStatePuller.cpp index 2e29fb04abfafd3d66ab02e64e2f467f5f909430..6d12e257df8ddef15a8c5b1fb6a3c7517789c838 100644 --- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp +++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp @@ -30,10 +30,10 @@ #include #include #include -#include "external/ResourcePowerManagerPuller.h" +#include "external/SubsystemSleepStatePuller.h" #include "external/StatsPuller.h" -#include "ResourcePowerManagerPuller.h" +#include "SubsystemSleepStatePuller.h" #include "logd/LogEvent.h" #include "statslog.h" @@ -73,10 +73,10 @@ bool getPowerHal() { return gPowerHalV1_0 != nullptr; } -ResourcePowerManagerPuller::ResourcePowerManagerPuller(int tagId) : StatsPuller(tagId) { +SubsystemSleepStatePuller::SubsystemSleepStatePuller() : StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) { } -bool ResourcePowerManagerPuller::PullInternal(vector>* data) { +bool SubsystemSleepStatePuller::PullInternal(vector>* data) { std::lock_guard lock(gPowerHalMutex); if (!getPowerHal()) { @@ -89,8 +89,6 @@ bool ResourcePowerManagerPuller::PullInternal(vector>* data data->clear(); Return ret; - if (mTagId == android::util::PLATFORM_SLEEP_STATE || - mTagId == android::util::SLEEP_STATE_VOTER) { ret = gPowerHalV1_0->getPlatformLowPowerStats( [&data, timestamp](hidl_vec states, Status status) { if (status != Status::SUCCESS) return; @@ -98,12 +96,11 @@ bool ResourcePowerManagerPuller::PullInternal(vector>* data for (size_t i = 0; i < states.size(); i++) { const PowerStatePlatformSleepState& state = states[i]; - auto statePtr = make_shared(android::util::PLATFORM_SLEEP_STATE, + auto statePtr = make_shared(android::util::SUBSYSTEM_SLEEP_STATE, timestamp); statePtr->write(state.name); - statePtr->write(state.residencyInMsecSinceBoot); statePtr->write(state.totalTransitions); - statePtr->write(state.supportedOnlyInSuspend); + statePtr->write(state.residencyInMsecSinceBoot); statePtr->init(); data->push_back(statePtr); VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(), @@ -111,12 +108,11 @@ bool ResourcePowerManagerPuller::PullInternal(vector>* data (long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0); for (auto voter : state.voters) { - auto voterPtr = make_shared(android::util::SLEEP_STATE_VOTER, + auto voterPtr = make_shared(android::util::SUBSYSTEM_SLEEP_STATE, timestamp); - voterPtr->write(state.name); - voterPtr->write(voter.name); - voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot); + voterPtr->write((std::string)state.name + "." + (std::string)voter.name); voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot); + voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot); voterPtr->init(); data->push_back(voterPtr); VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(), @@ -131,9 +127,7 @@ bool ResourcePowerManagerPuller::PullInternal(vector>* data gPowerHalV1_0 = nullptr; return false; } - } - if (mTagId == android::util::SUBSYSTEM_SLEEP_STATE) { // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1 sp gPowerHal_1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0); @@ -149,13 +143,10 @@ bool ResourcePowerManagerPuller::PullInternal(vector>* data const PowerStateSubsystemSleepState& state = subsystem.states[j]; auto subsystemStatePtr = make_shared( - android::util::SUBSYSTEM_SLEEP_STATE, timestamp); - subsystemStatePtr->write(subsystem.name); - subsystemStatePtr->write(state.name); - subsystemStatePtr->write(state.residencyInMsecSinceBoot); + android::util::SUBSYSTEM_SLEEP_STATE, timestamp); + subsystemStatePtr->write((std::string)subsystem.name + "." + (std::string)state.name); subsystemStatePtr->write(state.totalTransitions); - subsystemStatePtr->write(state.lastEntryTimestampMs); - subsystemStatePtr->write(state.supportedOnlyInSuspend); + subsystemStatePtr->write(state.residencyInMsecSinceBoot); subsystemStatePtr->init(); data->push_back(subsystemStatePtr); VLOG("subsystemstate: %s, %s, %lld, %lld, %lld", @@ -168,7 +159,6 @@ bool ResourcePowerManagerPuller::PullInternal(vector>* data } }); } - } return true; } diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.h b/cmds/statsd/src/external/SubsystemSleepStatePuller.h similarity index 90% rename from cmds/statsd/src/external/ResourcePowerManagerPuller.h rename to cmds/statsd/src/external/SubsystemSleepStatePuller.h index 339940839c4de06aa384a75d9382ac82b2b36ebc..17ce5b4cb918bd76616e54ca10f7db62d95e6a77 100644 --- a/cmds/statsd/src/external/ResourcePowerManagerPuller.h +++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.h @@ -26,9 +26,9 @@ namespace statsd { /** * Reads hal for sleep states */ -class ResourcePowerManagerPuller : public StatsPuller { +class SubsystemSleepStatePuller : public StatsPuller { public: - ResourcePowerManagerPuller(int tagId); + SubsystemSleepStatePuller(); bool PullInternal(vector>* data) override; }; diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 36dd6167663bb2cc47ab3ee976b6e450599180b4..77f54569cb4778cffad58646a502718cda0aaa00 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -45,6 +45,8 @@ const int FIELD_ID_CONFIG_STATS = 3; const int FIELD_ID_ATOM_STATS = 7; const int FIELD_ID_UIDMAP_STATS = 8; const int FIELD_ID_ANOMALY_ALARM_STATS = 9; +const int FIELD_ID_PULLED_ATOM_STATS = 10; +const int FIELD_ID_LOGGER_ERROR_STATS = 11; const int FIELD_ID_MATCHER_STATS_NAME = 1; const int FIELD_ID_MATCHER_STATS_COUNT = 2; @@ -60,14 +62,15 @@ const int FIELD_ID_ATOM_STATS_COUNT = 2; const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1; +const int FIELD_ID_LOGGER_STATS_TIME = 1; +const int FIELD_ID_LOGGER_STATS_ERROR_CODE = 2; + std::map StatsdStats::kPullerCooldownMap = { {android::util::KERNEL_WAKELOCK, 1}, {android::util::WIFI_BYTES_TRANSFER, 1}, {android::util::MOBILE_BYTES_TRANSFER, 1}, {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG, 1}, {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG, 1}, - {android::util::PLATFORM_SLEEP_STATE, 1}, - {android::util::SLEEP_STATE_VOTER, 1}, {android::util::SUBSYSTEM_SLEEP_STATE, 1}, {android::util::CPU_TIME_PER_FREQ, 1}, {android::util::CPU_TIME_PER_UID, 1}, @@ -284,6 +287,15 @@ void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) { mPushedAtomStats[atomId]++; } +void StatsdStats::noteLoggerError(int error) { + lock_guard lock(mLock); + // grows strictly one at a time. so it won't > kMaxLoggerErrors + if (mLoggerErrors.size() == kMaxLoggerErrors) { + mLoggerErrors.pop_front(); + } + mLoggerErrors.push_back(std::make_pair(time(nullptr), error)); +} + void StatsdStats::reset() { lock_guard lock(mLock); resetInternalLocked(); @@ -299,6 +311,7 @@ void StatsdStats::resetInternalLocked() { mAlertStats.clear(); mAnomalyAlarmRegisteredStats = 0; mMatcherStats.clear(); + mLoggerErrors.clear(); for (auto& config : mConfigStats) { config.second.clear_broadcast_sent_time_sec(); config.second.clear_data_drop_time_sec(); @@ -357,73 +370,143 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, } } -void StatsdStats::dumpStats(std::vector* output, bool reset) { +void StatsdStats::dumpStats(FILE* out) const { lock_guard 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); + } - 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); + 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]); + } + } + + 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()); + + for (const auto& error : mLoggerErrors) { + time_t error_time = error.first; + struct tm* error_tm = localtime(&error_time); + char buffer[80]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M%p\n", error_tm); + fprintf(out, "Logger error %d at %s\n", error.second, buffer); + } +} + +void StatsdStats::dumpStats(std::vector* output, bool reset) { + lock_guard 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 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(); @@ -439,7 +522,6 @@ void StatsdStats::dumpStats(std::vector* 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) { @@ -448,34 +530,32 @@ void StatsdStats::dumpStats(std::vector* 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 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()); + + for (const auto& error : mLoggerErrors) { + long long token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_LOGGER_ERROR_STATS | + FIELD_COUNT_REPEATED); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOGGER_STATS_TIME, error.first); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOGGER_STATS_ERROR_CODE, error.second); + proto.end(token); + } output->clear(); size_t bufferSize = proto.size(); @@ -495,7 +575,6 @@ void StatsdStats::dumpStats(std::vector* 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 52ab253b6721c617e617b5a12a2603643e4b4753..1f4bfa62c4533f07996f8ffd2dda98c61ad216a8 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -49,6 +49,8 @@ public: // The max number of old config stats we keep. const static int kMaxIceBoxSize = 20; + const static int kMaxLoggerErrors = 10; + const static int kMaxTimestampCount = 20; const static int kMaxLogSourceCount = 50; @@ -74,6 +76,15 @@ public: // Default cooldown time for a puller static const long kDefaultPullerCooldown = 1; + // Maximum age (30 days) that files on disk can exist in seconds. + static const int kMaxAgeSecond = 60 * 60 * 24 * 30; + + // Maximum number of files (1000) that can be in stats directory on disk. + static const int kMaxFileNumber = 1000; + + // Maximum size of all files that can be written to stats directory on disk. + static const int kMaxFileSize = 50 * 1024 * 1024; + /** * Report a new config has been received and report the static stats about the config. * @@ -175,6 +186,11 @@ public: // Notify pull request for an atom served from cached data void notePullFromCache(int pullAtomId); + /** + * Records statsd met an error while reading from logd. + */ + void noteLoggerError(int error); + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue @@ -189,6 +205,11 @@ public: */ void dumpStats(std::vector* buffer, bool reset); + /** + * Output statsd stats in human readable format to [out] file. + */ + void dumpStats(FILE* out) const; + typedef struct { long totalPull; long totalPullFromCache; @@ -232,6 +253,9 @@ private: // Maps PullAtomId to its stats. The size is capped by the puller atom counts. std::map mPulledAtomStats; + // Logd errors. Size capped by kMaxLoggerErrors. + std::list> mLoggerErrors; + // Stores the number of times statsd modified the anomaly alarm registered with // StatsCompanionService. int mAnomalyAlarmRegisteredStats = 0; diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 6782f3fd6fcff7031a1b9df45d1089aae4ad0411..1ca793c818783d638e5341078cd5a5d6fe9eb307 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -36,13 +36,14 @@ using std::string; using android::util::ProtoOutputStream; LogEvent::LogEvent(log_msg& msg) { - android_log_context context = + mContext = create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t)); mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec; mLogUid = msg.entry_v4.uid; - init(context); - if (context) { - android_log_destroy(&context); + init(mContext); + if (mContext) { + // android_log_destroy will set mContext to NULL + android_log_destroy(&mContext); } } @@ -64,12 +65,17 @@ void LogEvent::init() { mContext = create_android_log_parser(buffer, len); init(mContext); // destroy the context to save memory. - android_log_destroy(&mContext); + if (mContext) { + // android_log_destroy will set mContext to NULL + android_log_destroy(&mContext); + } } } LogEvent::~LogEvent() { if (mContext) { + // This is for the case when LogEvent is created using the test interface + // but init() isn't called. android_log_destroy(&mContext); } } diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index fdfa32eac8ae1cfcf522fd021409a933848a744c..5a4efd4b54a44d2325bde4224036d235967faa91 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -58,14 +58,14 @@ public: /** * Get the timestamp associated with this event. */ - uint64_t GetTimestampNs() const { return mTimestampNs; } + inline uint64_t GetTimestampNs() const { return mTimestampNs; } /** * Get the tag for this event. */ - int GetTagId() const { return mTagId; } + inline int GetTagId() const { return mTagId; } - uint32_t GetUid() const { + inline uint32_t GetUid() const { return mLogUid; } @@ -156,7 +156,7 @@ private: // This field is used when statsD wants to create log event object and write fields to it. After // calling init() function, this object would be destroyed to save memory usage. // When the log event is created from log msg, this field is never initiated. - android_log_context mContext; + android_log_context mContext = NULL; uint64_t mTimestampNs; diff --git a/cmds/statsd/src/logd/LogReader.cpp b/cmds/statsd/src/logd/LogReader.cpp index 7636268ccd3a217020ca880c287e63e8f1725235..0fe896bb10c342f749b34a2ca8a77ad4d9817906 100644 --- a/cmds/statsd/src/logd/LogReader.cpp +++ b/cmds/statsd/src/logd/LogReader.cpp @@ -16,10 +16,11 @@ #include "logd/LogReader.h" -#include +#include "guardrail/StatsdStats.h" #include #include +#include using namespace android; using namespace std; @@ -92,13 +93,15 @@ int LogReader::connect_and_read() { // Read forever if (eventLogger) { - + log_msg msg; while (true) { - log_msg msg; - // Read a message err = android_logger_list_read(loggers, &msg); - if (err < 0) { + // err = 0 - no content, unexpected connection drop or EOF. + // err = +ive number - size of retrieved data from logger + // err = -ive number, OS supplied error _except_ for -EAGAIN + if (err <= 0) { + StatsdStats::getInstance().noteLoggerError(err); fprintf(stderr, "logcat read failure: %s\n", strerror(err)); break; } diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 3e98098ad62a383cbd3fe46cc87363e5d2885d58..0455f6a210a9b4404ff6a9619f0f5caa75012a04 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -50,8 +50,9 @@ const int FIELD_ID_COUNT_METRICS = 5; // for CountMetricDataWrapper const int FIELD_ID_DATA = 1; // for CountMetricData -const int FIELD_ID_DIMENSION = 1; -const int FIELD_ID_BUCKET_INFO = 2; +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_DIMENSION_IN_CONDITION = 2; +const int FIELD_ID_BUCKET_INFO = 3; // for CountBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -70,7 +71,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric } // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions(); + mDimensions = metric.dimensions_in_what(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -98,7 +99,7 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog auto count_metrics = report->mutable_count_metrics(); for (const auto& counter : mPastBuckets) { CountMetricData* metricData = count_metrics->add_data(); - *metricData->mutable_dimension() = counter.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = counter.first.getDimensionsValue(); for (const auto& bucket : counter.second) { CountBucketInfo* bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -111,6 +112,9 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { flushIfNeededLocked(dumpTimeNs); + if (mPastBuckets.empty()) { + return; + } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); @@ -127,7 +131,7 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, // First fill dimension. long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index 16fc7ee3c6e9dd624912a719731a18d32c66e4c0..061b7a36817c249f30c8fef3633b72d2f3dfe7f3 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -68,6 +68,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& newEventTime); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index b5462978031813126017a76cd370629bf145b3bf..bfab9e6e490fadd13704e9b3016d500d104abb61 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -49,8 +49,9 @@ const int FIELD_ID_DURATION_METRICS = 6; // for DurationMetricDataWrapper const int FIELD_ID_DATA = 1; // for DurationMetricData -const int FIELD_ID_DIMENSION = 1; -const int FIELD_ID_BUCKET_INFO = 2; +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_DIMENSION_IN_CONDITION = 2; +const int FIELD_ID_BUCKET_INFO = 3; // for DurationBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -80,7 +81,7 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat } // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions(); + mDimensions = metric.dimensions_in_what(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -154,7 +155,7 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, Stats auto duration_metrics = report->mutable_duration_metrics(); for (const auto& pair : mPastBuckets) { DurationMetricData* metricData = duration_metrics->add_data(); - *metricData->mutable_dimension() = pair.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue(); for (const auto& bucket : pair.second) { auto bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -167,6 +168,9 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, Stats void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { flushIfNeededLocked(dumpTimeNs); + if (mPastBuckets.empty()) { + return; + } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); @@ -183,7 +187,7 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, // First fill dimension. long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); @@ -229,6 +233,21 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { mCurrentBucketNum += numBucketsForward; } +void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { + if (mCurrentSlicedDuration.size() == 0) { + return; + } + + fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId, + (unsigned long)mCurrentSlicedDuration.size()); + if (verbose) { + for (const auto& slice : mCurrentSlicedDuration) { + fprintf(out, "\t%s\n", slice.first.c_str()); + slice.second->dumpStates(out, verbose); + } + } +} + bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { // the key is not new, we are good. if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index e06b9a14563d2bedbe1dade0d5dec507a7742d33..d8cab92a2b84d11de74b3e74f76838b0ab3598ce 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -68,6 +68,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 821d8ea48aefce7f70615cdb8099bd32a259f685..25c86d0d46f19fad70c5833083202f5bc7d5e4ab 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -102,6 +102,9 @@ void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { + if (mProto->size() <= 0) { + return; + } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h index a57b07d6648ea80d4648ee73ee7a0303cb002eaf..9da0dd0569d6605b1ab57cf5a29a85ebbe551bb6 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -62,6 +62,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Maps to a EventMetricDataWrapper. Storing atom events in ProtoOutputStream // is more space efficient than storing LogEvent. std::unique_ptr mProto; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 1a4888c31ffff98cfde2ff46b41412a33d6ecdd9..24dc5b0fba5324e867a9e21d93bf55da7f6459d6 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -51,8 +51,9 @@ const int FIELD_ID_GAUGE_METRICS = 8; // for GaugeMetricDataWrapper const int FIELD_ID_DATA = 1; // for GaugeMetricData -const int FIELD_ID_DIMENSION = 1; -const int FIELD_ID_BUCKET_INFO = 2; +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_DIMENSION_IN_CONDITION = 2; +const int FIELD_ID_BUCKET_INFO = 3; // for GaugeBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -79,7 +80,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric mFieldFilter = metric.gauge_fields_filter(); // TODO: use UidMap if uid->pkg_name is required - mDimensions = metric.dimensions(); + mDimensions = metric.dimensions_in_what(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -114,6 +115,9 @@ GaugeMetricProducer::~GaugeMetricProducer() { void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { flushIfNeededLocked(dumpTimeNs); + ProtoOutputStream pbOutput; + onDumpReportLocked(dumpTimeNs, &pbOutput); + parseProtoOutputStream(pbOutput, report); } void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, @@ -121,6 +125,9 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, VLOG("gauge metric %lld report now...", (long long)mMetricId); flushIfNeededLocked(dumpTimeNs); + if (mPastBuckets.empty()) { + return; + } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); @@ -135,7 +142,7 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, // First fill dimension. long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index f267e987f46457f8516ac5468184bdd1c0a9097d..1895edf8f72918b99d47de2304eab1e025f0eff3 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -83,6 +83,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 3779c4487d233778fab3619a9db27493625d11ef..6f33073c633c945a2c9944922e4a0f51b143cb77 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -96,6 +96,11 @@ public: return onDumpReportLocked(dumpTimeNs, report); } + void dumpStates(FILE* out, bool verbose) const { + std::lock_guard lock(mMutex); + dumpStatesLocked(out, verbose); + } + // Returns the memory in bytes currently used to store this metric's data. Does not change // state. size_t byteSize() const { @@ -128,6 +133,7 @@ protected: android::util::ProtoOutputStream* protoOutput) = 0; virtual void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) = 0; virtual size_t byteSizeLocked() const = 0; + virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0; const int64_t mMetricId; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index f92951703030f5dd4da6291bbe0c665363739821..d0737de8acf3d83375e4af9f17c236d1588ef3ff 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -154,6 +154,20 @@ void MetricsManager::onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetrics } } +void MetricsManager::dumpStates(FILE* out, bool verbose) { + fprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str()); + { + std::lock_guard lock(mAllowedLogSourcesMutex); + for (const auto& source : mAllowedLogSources) { + fprintf(out, "%d ", source); + } + } + fprintf(out, "\n"); + for (const auto& producer : mAllMetricProducers) { + producer->dumpStates(out, verbose); + } +} + void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) { VLOG("=========================Metric Reports Start=========================="); uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 08b0981bb14fc3a38283a0651e185b5d32007920..9cdbafc75fb15ecf1ca37f85afd568251dbd76b5 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -61,6 +61,8 @@ public: return !mAllowedPkg.empty(); } + void dumpStates(FILE* out, bool verbose); + // Config source owner can call onDumpReport() to get all the metrics collected. virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput); virtual void onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report); @@ -68,7 +70,6 @@ public: // Computes the total byte size of all metrics managed by a single config source. // Does not change the state. virtual size_t byteSize(); - private: const ConfigKey mConfigKey; @@ -141,6 +142,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 74bd6f97d5356d17d175982081ea19ee25cf4e17..ae0c673e3490722f54d3a1829733208dd8d8d441 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -54,8 +54,9 @@ const int FIELD_ID_VALUE_METRICS = 7; // for ValueMetricDataWrapper const int FIELD_ID_DATA = 1; // for ValueMetricData -const int FIELD_ID_DIMENSION = 1; -const int FIELD_ID_BUCKET_INFO = 2; +const int FIELD_ID_DIMENSION_IN_WHAT = 1; +const int FIELD_ID_DIMENSION_IN_CONDITION = 2; +const int FIELD_ID_BUCKET_INFO = 3; // for ValueBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -80,7 +81,7 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric } mBucketSizeNs = bucketSizeMills * 1000000; - mDimensions = metric.dimensions(); + mDimensions = metric.dimensions_in_what(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -123,7 +124,7 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog auto value_metrics = report->mutable_value_metrics(); for (const auto& pair : mPastBuckets) { ValueMetricData* metricData = value_metrics->add_data(); - *metricData->mutable_dimension() = pair.first.getDimensionsValue(); + *metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue(); for (const auto& bucket : pair.second) { ValueBucketInfo* bucketInfo = metricData->add_bucket_info(); bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); @@ -137,6 +138,9 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { VLOG("metric %lld dump report now...", (long long)mMetricId); flushIfNeededLocked(dumpTimeNs); + if (mPastBuckets.empty()) { + return; + } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); @@ -149,7 +153,7 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, // First fill dimension. long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); protoOutput->end(dimensionToken); @@ -284,8 +288,15 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( interval.start = value; interval.startUpdated = true; } else { + // Generally we expect value to be monotonically increasing. + // If not, there was a reset event. We take the absolute value as + // diff in this case. if (interval.startUpdated) { - interval.sum += (value - interval.start); + if (value > interval.start) { + interval.sum += (value - interval.start); + } else { + interval.sum += value; + } interval.startUpdated = false; } else { VLOG("No start for matching end %ld", value); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 3e7032d8cf2df70febc1cca817772a23699137da..9f750cf419b5dff2314c4b978fe2a94289bbdb29 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -67,6 +67,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 371460e804bde34821e218a76b8699f893669407..c2d2cea2a1ff187f3a91ea086f378ca57f394b7e 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -97,6 +97,8 @@ public: // Predict the anomaly timestamp given the current status. virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const = 0; + // Dump internal states for debugging + virtual void dumpStates(FILE* out, bool verbose) const = 0; protected: // Starts the anomaly alarm. diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 0c99391d491f14a74cd581a033d3bd8ab111aaed..412a0c93576635fdf2f388cc9061ec2a8eb600fc 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -291,6 +291,11 @@ int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTrack return currentTimestamp; } +void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { + fprintf(out, "\t\t sub-durations %lu\n", (unsigned long)mInfos.size()); + fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 5d3c15804638ab8e0774b613955e8cc50369461f..e988ebc06307c0b0300feb542e91cfff78802bcc 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -48,6 +48,7 @@ public: int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; + void dumpStates(FILE* out, bool verbose) const override; private: std::map mInfos; diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 6bf42287e6dd9e0b3c9894d81b44f13056cae374..75d7c0898d78d817d0b5c4a7a65e00a0172fac7a 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -314,6 +314,12 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( return eventTimestampNs + thresholdNs; } +void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { + fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); + fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); + fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 638b7ad7af263a483da09295ad51602404c4050c..1b74ed1addca6b4110d0bf3e0bc88e22ac55a2c9 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -48,6 +48,7 @@ public: int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; + void dumpStates(FILE* out, bool verbose) const override; private: // We don't need to keep track of individual durations. The information that's needed is: diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index bc887ac0ba8fae711965a716b7bc961779a1a893..a0173ee9922f03d21b5074a9f19110d95620d311 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -227,7 +227,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti int metricIndex = allMetricProducers.size(); metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, + metric.has_dimensions_in_what(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -279,7 +280,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti int trackerIndices[3] = {-1, -1, -1}; if (!simplePredicate.has_start() || !handleMetricWithLogTrackers(simplePredicate.start(), metricIndex, - metric.has_dimensions(), allAtomMatchers, + metric.has_dimensions_in_what(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[0])) { ALOGE("Duration metrics must specify a valid the start event matcher"); return false; @@ -287,14 +288,14 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti if (simplePredicate.has_stop() && !handleMetricWithLogTrackers(simplePredicate.stop(), metricIndex, - metric.has_dimensions(), allAtomMatchers, + metric.has_dimensions_in_what(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[1])) { return false; } if (simplePredicate.has_stop_all() && !handleMetricWithLogTrackers(simplePredicate.stop_all(), metricIndex, - metric.has_dimensions(), allAtomMatchers, + metric.has_dimensions_in_what(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[2])) { return false; } @@ -371,7 +372,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti int metricIndex = allMetricProducers.size(); metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, + metric.has_dimensions_in_what(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -429,7 +431,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti int metricIndex = allMetricProducers.size(); metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, + metric.has_dimensions_in_what(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index b0c31975c91cf51fde32721c152dbf145236cca6..91279661b61f5d83dc09bacb6551e211a1b8994e 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& 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) { @@ -281,20 +281,10 @@ int UidMap::getHostUidOrSelf(int uid) const { void UidMap::clearOutput() { mOutput.Clear(); - // Re-initialize the initial state for the outputs. This results in extra data being uploaded - // but helps ensure we can re-construct the UID->app name, versionCode mapping in server. - auto snapshot = mOutput.add_snapshots(); - for (auto it : mMap) { - auto t = snapshot->add_package_info(); - t->set_name(it.second.packageName); - t->set_version(it.second.versionCode); - t->set_uid(it.first); - } - // Also update the guardrail trackers. StatsdStats::getInstance().setUidMapChanges(0); StatsdStats::getInstance().setUidMapSnapshots(1); - mBytesUsed = snapshot->ByteSize(); + mBytesUsed = 0; StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); } @@ -348,6 +338,19 @@ UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) { ++it_deltas; } } + + if (mOutput.snapshots_size() == 0) { + // Produce another snapshot. This results in extra data being uploaded but helps + // ensure we can re-construct the UID->app name, versionCode mapping in server. + auto snapshot = mOutput.add_snapshots(); + snapshot->set_timestamp_nanos(timestamp); + for (auto it : mMap) { + auto t = snapshot->add_package_info(); + t->set_name(it.second.packageName); + t->set_version(it.second.versionCode); + t->set_uid(it.first); + } + } } mBytesUsed = mOutput.ByteSize(); // Compute actual size after potential deletions. StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); @@ -471,10 +474,13 @@ const std::map UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_AUTOMOTIVE_EVS", 1062}, {"AID_LOWPAN", 1063}, {"AID_HSM", 1064}, + {"AID_RESERVED_DISK", 1065}, + {"AID_STATSD", 1066}, + {"AID_INCIDENTD", 1067}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, {"AID_DIAG", 2002}}; } // namespace statsd } // namespace os -} // namespace android \ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h index 4e37977dbf3bd3972705e3ba2fc44469e6762b9f..3304f6c976aa35d08797b1da0f7c8cf7be75f24a 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/perfetto/perfetto_config.proto b/cmds/statsd/src/perfetto/perfetto_config.proto new file mode 100644 index 0000000000000000000000000000000000000000..dc868f997a633169f5ae76ae3c2b4f9254d686d6 --- /dev/null +++ b/cmds/statsd/src/perfetto/perfetto_config.proto @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +option optimize_for = LITE_RUNTIME; + +package perfetto.protos; + +message DataSourceConfig { + message FtraceConfig { + repeated string event_names = 1; + } + + optional string name = 1; + + optional uint32 target_buffer = 2; + + optional FtraceConfig ftrace_config = 100; +} + +message TraceConfig { + message BufferConfig { + optional uint32 size_kb = 1; + + enum OptimizeFor { + DEFAULT = 0; + ONE_SHOT_READ = 1; + + } + optional OptimizeFor optimize_for = 3; + + enum FillPolicy { + UNSPECIFIED = 0; + RING_BUFFER = 1; + } + optional FillPolicy fill_policy = 4; + } + repeated BufferConfig buffers = 1; + + message DataSource { + optional protos.DataSourceConfig config = 1; + + repeated string producer_name_filter = 2; + } + repeated DataSource data_sources = 2; + + optional uint32 duration_ms = 3; +} diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 2596a5f4fdc46472bbaa99a664e3e29971b78e7c..a4ccbd42883dab768628b4979e937d0c723c6748 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -56,9 +56,11 @@ message CountBucketInfo { } message CountMetricData { - optional DimensionsValue dimension = 1; + optional DimensionsValue dimensions_in_what = 1; - repeated CountBucketInfo bucket_info = 2; + optional DimensionsValue dimensions_in_condition = 2; + + repeated CountBucketInfo bucket_info = 3; } message DurationBucketInfo { @@ -70,9 +72,11 @@ message DurationBucketInfo { } message DurationMetricData { - optional DimensionsValue dimension = 1; + optional DimensionsValue dimensions_in_what = 1; + + optional DimensionsValue dimensions_in_condition = 2; - repeated DurationBucketInfo bucket_info = 2; + repeated DurationBucketInfo bucket_info = 3; } message ValueBucketInfo { @@ -84,9 +88,11 @@ message ValueBucketInfo { } message ValueMetricData { - optional DimensionsValue dimension = 1; + optional DimensionsValue dimensions_in_what = 1; - repeated ValueBucketInfo bucket_info = 2; + optional DimensionsValue dimensions_in_condition = 2; + + repeated ValueBucketInfo bucket_info = 3; } message GaugeBucketInfo { @@ -98,9 +104,11 @@ message GaugeBucketInfo { } message GaugeMetricData { - optional DimensionsValue dimension = 1; + optional DimensionsValue dimensions_in_what = 1; + + optional DimensionsValue dimensions_in_condition = 2; - repeated GaugeBucketInfo bucket_info = 2; + repeated GaugeBucketInfo bucket_info = 3; } message UidMapping { @@ -180,78 +188,84 @@ message ConfigMetricsReportList { } message StatsdStatsReport { - optional int32 stats_begin_time_sec = 1; + optional int32 stats_begin_time_sec = 1; - optional int32 stats_end_time_sec = 2; + optional int32 stats_end_time_sec = 2; - message MatcherStats { - optional int64 id = 1; - optional int32 matched_times = 2; - } + message MatcherStats { + optional int64 id = 1; + optional int32 matched_times = 2; + } - message ConditionStats { - optional int64 id = 1; - optional int32 max_tuple_counts = 2; - } + message ConditionStats { + optional int64 id = 1; + optional int32 max_tuple_counts = 2; + } - message MetricStats { - optional int64 id = 1; - optional int32 max_tuple_counts = 2; - } + message MetricStats { + optional int64 id = 1; + optional int32 max_tuple_counts = 2; + } - message AlertStats { - optional int64 id = 1; - optional int32 alerted_times = 2; - } + message AlertStats { + optional int64 id = 1; + optional int32 alerted_times = 2; + } - message ConfigStats { - optional int32 uid = 1; - optional int64 id = 2; - optional int32 creation_time_sec = 3; - optional int32 deletion_time_sec = 4; - optional int32 metric_count = 5; - optional int32 condition_count = 6; - optional int32 matcher_count = 7; - optional int32 alert_count = 8; - optional bool is_valid = 9; - - repeated int32 broadcast_sent_time_sec = 10; - repeated int32 data_drop_time_sec = 11; - repeated int32 dump_report_time_sec = 12; - repeated MatcherStats matcher_stats = 13; - repeated ConditionStats condition_stats = 14; - repeated MetricStats metric_stats = 15; - repeated AlertStats alert_stats = 16; - } + message ConfigStats { + optional int32 uid = 1; + optional int64 id = 2; + optional int32 creation_time_sec = 3; + optional int32 deletion_time_sec = 4; + optional int32 metric_count = 5; + optional int32 condition_count = 6; + optional int32 matcher_count = 7; + optional int32 alert_count = 8; + optional bool is_valid = 9; + + repeated int32 broadcast_sent_time_sec = 10; + repeated int32 data_drop_time_sec = 11; + repeated int32 dump_report_time_sec = 12; + repeated MatcherStats matcher_stats = 13; + repeated ConditionStats condition_stats = 14; + repeated MetricStats metric_stats = 15; + repeated AlertStats alert_stats = 16; + } - repeated ConfigStats config_stats = 3; + repeated ConfigStats config_stats = 3; - message AtomStats { - optional int32 tag = 1; - optional int32 count = 2; - } + message AtomStats { + optional int32 tag = 1; + optional int32 count = 2; + } - repeated AtomStats atom_stats = 7; + repeated AtomStats atom_stats = 7; - message UidMapStats { - optional int32 snapshots = 1; - optional int32 changes = 2; - optional int32 bytes_used = 3; - optional int32 dropped_snapshots = 4; - optional int32 dropped_changes = 5; - } - optional UidMapStats uidmap_stats = 8; + message UidMapStats { + optional int32 snapshots = 1; + optional int32 changes = 2; + optional int32 bytes_used = 3; + optional int32 dropped_snapshots = 4; + optional int32 dropped_changes = 5; + } + optional UidMapStats uidmap_stats = 8; - message AnomalyAlarmStats { - optional int32 alarms_registered = 1; - } - optional AnomalyAlarmStats anomaly_alarm_stats = 9; + message AnomalyAlarmStats { + optional int32 alarms_registered = 1; + } + optional AnomalyAlarmStats anomaly_alarm_stats = 9; - message PulledAtomStats { - optional int32 atom_id = 1; - optional int64 total_pull = 2; - optional int64 total_pull_from_cache = 3; - optional int64 min_pull_interval_sec = 4; - } - repeated PulledAtomStats pulled_atom_stats = 10; + message PulledAtomStats { + optional int32 atom_id = 1; + optional int64 total_pull = 2; + optional int64 total_pull_from_cache = 3; + optional int64 min_pull_interval_sec = 4; + } + repeated PulledAtomStats pulled_atom_stats = 10; + + message LoggerErrorStats { + optional int32 logger_disconnection_sec = 1; + optional int32 error_code = 2; + } + repeated LoggerErrorStats logger_error_stats = 11; } \ No newline at end of file diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 09a43f5c881f5e53ae21354003f54073d5576695..cee920038eefc69ac7b19c91fceadae50b040655 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -43,6 +43,19 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit); // Helper function to write PulledAtomStats to ProtoOutputStream void writePullerStatsToStream(const std::pair& pair, util::ProtoOutputStream* protoOutput); + +template +bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { + std::string pbBytes; + auto iter = protoOutput.data(); + while (iter.readBuffer() != NULL) { + size_t toRead = iter.currentToRead(); + pbBytes.append(reinterpret_cast(iter.readBuffer()), toRead); + iter.rp()->move(toRead); + } + return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); +} + } // namespace statsd } // namespace os } // namespace android \ No newline at end of file diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index d45a6b038ccb1ddb29ab1ad2a8fdef18ce6c6699..07bbcb2190e8d193af9f2517c7061e7ef7213a39 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -22,293 +22,306 @@ package android.os.statsd; option java_package = "com.android.internal.os"; option java_outer_classname = "StatsdConfigProto"; +import "frameworks/base/cmds/statsd/src/perfetto/perfetto_config.proto"; + enum Position { - POSITION_UNKNOWN = 0; - FIRST = 1; - LAST = 2; - ANY = 3; + POSITION_UNKNOWN = 0; + + FIRST = 1; + + LAST = 2; + + ANY = 3; } enum TimeUnit { - TIME_UNIT_UNSPECIFIED = 0; - ONE_MINUTE = 1; - FIVE_MINUTES = 2; - TEN_MINUTES = 3; - THIRTY_MINUTES = 4; - ONE_HOUR = 5; - THREE_HOURS = 6; - SIX_HOURS = 7; - TWELVE_HOURS = 8; - ONE_DAY = 9; - CTS = 1000; + TIME_UNIT_UNSPECIFIED = 0; + ONE_MINUTE = 1; + FIVE_MINUTES = 2; + TEN_MINUTES = 3; + THIRTY_MINUTES = 4; + ONE_HOUR = 5; + THREE_HOURS = 6; + SIX_HOURS = 7; + TWELVE_HOURS = 8; + ONE_DAY = 9; + CTS = 1000; } message FieldMatcher { - optional int32 field = 1; + optional int32 field = 1; - optional Position position = 2; + optional Position position = 2; - repeated FieldMatcher child = 3; + repeated FieldMatcher child = 3; } message FieldValueMatcher { - // Field id, as specified in the atom proto message. - optional int32 field = 1; - - // For repeated fields, specifies the position in the array. - // FIRST and LAST mean that if the values are found at the first - // or last position, it's a match. ANY means that if the values are found - // anywhere in the array, then it's a match. - optional Position position = 2; - - oneof value_matcher { - bool eq_bool = 3; - string eq_string = 4; - int32 eq_int = 5; - - int64 lt_int = 6; - int64 gt_int = 7; - float lt_float = 8; - float gt_float = 9; - - int64 lte_int = 10; - int64 gte_int = 11; - - MessageMatcher matches_tuple = 12; - } + optional int32 field = 1; + + optional Position position = 2; + + oneof value_matcher { + bool eq_bool = 3; + string eq_string = 4; + int32 eq_int = 5; + + int64 lt_int = 6; + int64 gt_int = 7; + float lt_float = 8; + float gt_float = 9; + + int64 lte_int = 10; + int64 gte_int = 11; + + MessageMatcher matches_tuple = 12; + } } message MessageMatcher { - repeated FieldValueMatcher field_value_matcher = 1; + repeated FieldValueMatcher field_value_matcher = 1; } enum LogicalOperation { - LOGICAL_OPERATION_UNSPECIFIED = 0; - AND = 1; - OR = 2; - NOT = 3; - NAND = 4; - NOR = 5; + LOGICAL_OPERATION_UNSPECIFIED = 0; + AND = 1; + OR = 2; + NOT = 3; + NAND = 4; + NOR = 5; } message SimpleAtomMatcher { - optional int32 atom_id = 1; + optional int32 atom_id = 1; - repeated FieldValueMatcher field_value_matcher = 2; + repeated FieldValueMatcher field_value_matcher = 2; } message AtomMatcher { - optional int64 id = 1; + optional int64 id = 1; - message Combination { - optional LogicalOperation operation = 1; + message Combination { + optional LogicalOperation operation = 1; - repeated int64 matcher = 2; - } - oneof contents { - SimpleAtomMatcher simple_atom_matcher = 2; - Combination combination = 3; - } + repeated int64 matcher = 2; + } + oneof contents { + SimpleAtomMatcher simple_atom_matcher = 2; + Combination combination = 3; + } } message SimplePredicate { - optional int64 start = 1; + optional int64 start = 1; - optional int64 stop = 2; + optional int64 stop = 2; - optional bool count_nesting = 3 [default = true]; + optional bool count_nesting = 3 [default = true]; - optional int64 stop_all = 4; + optional int64 stop_all = 4; - enum InitialValue { - UNKNOWN = 0; - FALSE = 1; - } - optional InitialValue initial_value = 5 [default = FALSE]; + enum InitialValue { + UNKNOWN = 0; + FALSE = 1; + } + optional InitialValue initial_value = 5 [default = FALSE]; - optional FieldMatcher dimensions = 6; + optional FieldMatcher dimensions = 6; } message Predicate { - optional int64 id = 1; + optional int64 id = 1; - message Combination { - optional LogicalOperation operation = 1; + message Combination { + optional LogicalOperation operation = 1; - repeated int64 predicate = 2; - } - - oneof contents { - SimplePredicate simple_predicate = 2; - Combination combination = 3; - } -} + repeated int64 predicate = 2; + } -message Bucket { - optional int64 bucket_size_millis = 1; + oneof contents { + SimplePredicate simple_predicate = 2; + Combination combination = 3; + } } message MetricConditionLink { - optional int64 condition = 1; + optional int64 condition = 1; - optional FieldMatcher dimensions_in_what = 2; + optional FieldMatcher fields_in_what = 2; - optional FieldMatcher dimensions_in_condition = 3; + optional FieldMatcher fields_in_condition = 3; } message FieldFilter { - optional bool include_all = 1 [default = false]; - optional FieldMatcher fields = 2; + optional bool include_all = 1 [default = false]; + optional FieldMatcher fields = 2; } message EventMetric { - optional int64 id = 1; + optional int64 id = 1; - optional int64 what = 2; + optional int64 what = 2; - optional int64 condition = 3; + optional int64 condition = 3; - repeated MetricConditionLink links = 4; + repeated MetricConditionLink links = 4; } message CountMetric { - optional int64 id = 1; + optional int64 id = 1; - optional int64 what = 2; + optional int64 what = 2; - optional int64 condition = 3; + optional int64 condition = 3; - optional FieldMatcher dimensions = 4; + optional FieldMatcher dimensions_in_what = 4; - optional TimeUnit bucket = 5; + optional FieldMatcher dimensions_in_condition = 7; - repeated MetricConditionLink links = 6; + optional TimeUnit bucket = 5; + + repeated MetricConditionLink links = 6; } message DurationMetric { - optional int64 id = 1; + optional int64 id = 1; + + optional int64 what = 2; - optional int64 what = 2; + optional int64 condition = 3; - optional int64 condition = 3; + repeated MetricConditionLink links = 4; - repeated MetricConditionLink links = 4; + enum AggregationType { + SUM = 1; - enum AggregationType { - SUM = 1; + MAX_SPARSE = 2; + } + optional AggregationType aggregation_type = 5 [default = SUM]; - MAX_SPARSE = 2; - } - optional AggregationType aggregation_type = 5 [default = SUM]; + optional FieldMatcher dimensions_in_what = 6; - optional FieldMatcher dimensions = 6; + optional FieldMatcher dimensions_in_condition = 8; - optional TimeUnit bucket = 7; + optional TimeUnit bucket = 7; } message GaugeMetric { - optional int64 id = 1; + optional int64 id = 1; + + optional int64 what = 2; - optional int64 what = 2; + optional FieldFilter gauge_fields_filter = 3; - optional FieldFilter gauge_fields_filter = 3; + optional int64 condition = 4; - optional int64 condition = 4; + optional FieldMatcher dimensions_in_what = 5; - optional FieldMatcher dimensions = 5; + optional FieldMatcher dimensions_in_condition = 8; - optional TimeUnit bucket = 6; + optional TimeUnit bucket = 6; - repeated MetricConditionLink links = 7; + repeated MetricConditionLink links = 7; } message ValueMetric { - optional int64 id = 1; + optional int64 id = 1; - optional int64 what = 2; + optional int64 what = 2; - optional FieldMatcher value_field = 3; + optional FieldMatcher value_field = 3; - optional int64 condition = 4; + optional int64 condition = 4; - optional FieldMatcher dimensions = 5; + optional FieldMatcher dimensions_in_what = 5; - optional TimeUnit bucket = 6; + optional FieldMatcher dimensions_in_condition = 9; - repeated MetricConditionLink links = 7; + optional TimeUnit bucket = 6; - enum AggregationType { SUM = 1; } - optional AggregationType aggregation_type = 8 [default = SUM]; + repeated MetricConditionLink links = 7; + + enum AggregationType { + SUM = 1; + } + optional AggregationType aggregation_type = 8 [default = SUM]; } message Alert { - optional int64 id = 1; + optional int64 id = 1; - optional int64 metric_id = 2; + optional int64 metric_id = 2; - optional int32 num_buckets = 3; + optional int32 num_buckets = 3; - optional int32 refractory_period_secs = 4; + optional int32 refractory_period_secs = 4; - optional double trigger_if_sum_gt = 5; + optional double trigger_if_sum_gt = 5; } message Alarm { - optional int64 id = 1; - optional int64 offset_millis = 2; - optional int64 period_millis = 3; + optional int64 id = 1; + + optional int64 offset_millis = 2; + + optional int64 period_millis = 3; } message IncidentdDetails { - repeated int32 section = 1; + repeated int32 section = 1; } message PerfettoDetails { - optional int32 perfetto_stuff = 1; + optional perfetto.protos.TraceConfig trace_config = 1; } -message Subscription { - optional int64 id = 1; - - enum RuleType { - RULE_TYPE_UNSPECIFIED = 0; - ALARM = 1; - ALERT = 2; - } - optional RuleType rule_type = 2; - - optional int64 rule_id = 3; +message BroadcastSubscriberDetails { + optional int64 subscriber_id = 1; +} - oneof subscriber_information { - IncidentdDetails incidentd_details = 4; - PerfettoDetails perfetto_details = 5; - } +message Subscription { + optional int64 id = 1; + + enum RuleType { + RULE_TYPE_UNSPECIFIED = 0; + ALARM = 1; + ALERT = 2; + } + optional RuleType rule_type = 2; + + optional int64 rule_id = 3; + + oneof subscriber_information { + IncidentdDetails incidentd_details = 4; + PerfettoDetails perfetto_details = 5; + BroadcastSubscriberDetails broadcast_subscriber_details = 6; + } } message StatsdConfig { - optional int64 id = 1; + optional int64 id = 1; - repeated EventMetric event_metric = 2; + repeated EventMetric event_metric = 2; - repeated CountMetric count_metric = 3; + repeated CountMetric count_metric = 3; - repeated ValueMetric value_metric = 4; + repeated ValueMetric value_metric = 4; - repeated GaugeMetric gauge_metric = 5; + repeated GaugeMetric gauge_metric = 5; - repeated DurationMetric duration_metric = 6; + repeated DurationMetric duration_metric = 6; - repeated AtomMatcher atom_matcher = 7; + repeated AtomMatcher atom_matcher = 7; - repeated Predicate predicate = 8; + repeated Predicate predicate = 8; - repeated Alert alert = 9; + repeated Alert alert = 9; - repeated Alarm alarm = 10; + repeated Alarm alarm = 10; - repeated Subscription subscription = 11; + repeated Subscription subscription = 11; - repeated string allowed_log_source = 12; + repeated string allowed_log_source = 12; - repeated int64 no_report_metric = 13; + repeated int64 no_report_metric = 13; } diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index c542db2312ea5058c5655baa32c88a8baa0ba5bf..00d86582951f02bc7bbc137303115b6bf8e398a3 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -17,11 +17,15 @@ #define DEBUG true // STOPSHIP if true #include "Log.h" -#include "storage/StorageManager.h" #include "android-base/stringprintf.h" +#include "guardrail/StatsdStats.h" +#include "storage/StorageManager.h" #include #include +#include +#include +#include namespace android { namespace os { @@ -31,6 +35,7 @@ using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_MESSAGE; using std::map; +#define STATS_DATA_DIR "/data/misc/stats-data" #define STATS_SERVICE_DIR "/data/misc/stats-service" // for ConfigMetricsReportList @@ -39,12 +44,37 @@ const int FIELD_ID_REPORTS = 2; using android::base::StringPrintf; using std::unique_ptr; +// Returns array of int64_t which contains timestamp in seconds, uid, and +// configID. +static void parseFileName(char* name, int64_t* result) { + int index = 0; + char* substr = strtok(name, "_"); + while (substr != nullptr && index < 3) { + result[index] = StrToInt64(substr); + index++; + substr = strtok(nullptr, "_"); + } + // When index ends before hitting 3, file name is corrupted. We + // intentionally put -1 at index 0 to indicate the error to caller. + // TODO: consider removing files with unexpected name format. + if (index < 3) { + result[0] = -1; + } +} + +static string getFilePath(const char* path, int64_t timestamp, int64_t uid, int64_t configID) { + return StringPrintf("%s/%lld_%d_%lld", path, (long long)timestamp, (int)uid, + (long long)configID); +} + void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) { int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { VLOG("Attempt to access %s but failed", file); return; } + trimToFit(STATS_SERVICE_DIR); + trimToFit(STATS_DATA_DIR); int result = write(fd, buffer, numBytes); if (result == numBytes) { @@ -52,6 +82,12 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte } else { VLOG("Failed to write %s", file); } + + result = fchown(fd, AID_STATSD, AID_STATSD); + if (result) { + VLOG("Failed to chown %s to statsd", file); + } + close(fd); } @@ -78,7 +114,7 @@ void StorageManager::deleteAllFiles(const char* path) { } } -void StorageManager::deletePrefixedFiles(const char* path, const char* prefix) { +void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) { unique_ptr dir(opendir(path), closedir); if (dir == NULL) { VLOG("Directory does not exist: %s", path); @@ -88,10 +124,14 @@ void StorageManager::deletePrefixedFiles(const char* path, const char* prefix) { dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; - if (name[0] == '.' || strncmp(name, prefix, strlen(prefix)) != 0) { + if (name[0] == '.') { continue; } - deleteFile(StringPrintf("%s/%s", path, name).c_str()); + size_t nameLen = strlen(name); + size_t suffixLen = strlen(suffix); + if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) { + deleteFile(StringPrintf("%s/%s", path, name).c_str()); + } } } @@ -109,29 +149,20 @@ void StorageManager::sendBroadcast(const char* path, if (name[0] == '.') continue; VLOG("file %s", name); - int index = 0; - int uid = 0; - string configName; - 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) { - uid = atoi(substr); - } else { - configName = substr; - } - index++; - } - if (index < 2) continue; + int64_t result[3]; + parseFileName(name, result); + if (result[0] == -1) continue; + int64_t uid = result[1]; + int64_t configID = result[2]; - sendBroadcast(ConfigKey(uid, StrToInt64(configName))); + sendBroadcast(ConfigKey((int)uid, configID)); } } -void StorageManager::appendConfigMetricsReport(const char* path, ProtoOutputStream& proto) { - unique_ptr dir(opendir(path), closedir); +void StorageManager::appendConfigMetricsReport(ProtoOutputStream& proto) { + unique_ptr dir(opendir(STATS_DATA_DIR), closedir); if (dir == NULL) { - VLOG("Path %s does not exist", path); + VLOG("Path %s does not exist", STATS_DATA_DIR); return; } @@ -141,21 +172,13 @@ void StorageManager::appendConfigMetricsReport(const char* path, ProtoOutputStre if (name[0] == '.') continue; VLOG("file %s", name); - int index = 0; - int uid = 0; - string configName; - 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) { - uid = atoi(substr); - } else { - configName = substr; - } - index++; - } - if (index < 2) continue; - string file_name = StringPrintf("%s/%s", path, name); + int64_t result[3]; + parseFileName(name, result); + if (result[0] == -1) continue; + int64_t timestamp = result[0]; + int64_t uid = result[1]; + int64_t configID = result[2]; + string file_name = getFilePath(STATS_DATA_DIR, timestamp, uid, configID); int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); if (fd != -1) { string content; @@ -177,6 +200,7 @@ void StorageManager::readConfigFromDisk(map& configsMap VLOG("no default config on disk"); return; } + trimToFit(STATS_SERVICE_DIR); dirent* de; while ((de = readdir(dir.get()))) { @@ -184,31 +208,21 @@ void StorageManager::readConfigFromDisk(map& configsMap if (name[0] == '.') continue; VLOG("file %s", name); - int index = 0; - int uid = 0; - string configName; - 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) { - uid = atoi(substr); - } else { - configName = substr; - } - index++; - } - if (index < 2) continue; - - string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name); - VLOG("full file %s", file_name.c_str()); + int64_t result[3]; + parseFileName(name, result); + if (result[0] == -1) continue; + int64_t timestamp = result[0]; + int64_t uid = result[1]; + int64_t configID = result[2]; + string file_name = getFilePath(STATS_SERVICE_DIR, timestamp, uid, configID); 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=%lld|configID=%lld", (long long)uid, (long long)configID); } } close(fd); @@ -216,6 +230,67 @@ void StorageManager::readConfigFromDisk(map& configsMap } } +void StorageManager::trimToFit(const char* path) { + unique_ptr dir(opendir(path), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", path); + return; + } + dirent* de; + int totalFileSize = 0; + vector fileNames; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') continue; + + int64_t result[3]; + parseFileName(name, result); + if (result[0] == -1) continue; + int64_t timestamp = result[0]; + int64_t uid = result[1]; + int64_t configID = result[2]; + string file_name = getFilePath(path, timestamp, uid, configID); + + // Check for timestamp and delete if it's too old. + long fileAge = time(nullptr) - timestamp; + if (fileAge > StatsdStats::kMaxAgeSecond) { + deleteFile(file_name.c_str()); + } + + fileNames.push_back(file_name); + ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); + if (file.is_open()) { + file.seekg(0, ios::end); + int fileSize = file.tellg(); + file.close(); + totalFileSize += fileSize; + } + } + + if (fileNames.size() > StatsdStats::kMaxFileNumber || + totalFileSize > StatsdStats::kMaxFileSize) { + // Reverse sort to effectively remove from the back (oldest entries). + // This will sort files in reverse-chronological order. + sort(fileNames.begin(), fileNames.end(), std::greater()); + } + + // Start removing files from oldest to be under the limit. + while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber || + totalFileSize > StatsdStats::kMaxFileSize)) { + string file_name = fileNames.at(fileNames.size() - 1); + ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); + if (file.is_open()) { + file.seekg(0, ios::end); + int fileSize = file.tellg(); + file.close(); + totalFileSize -= fileSize; + } + + deleteFile(file_name.c_str()); + fileNames.pop_back(); + } +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index caf5b8b42887d89de63823d398098dd81d4a6be1..d319674b898760eee5a536d89bdc794a70da2168 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -47,9 +47,9 @@ public: static void deleteAllFiles(const char* path); /** - * Deletes all files whose name matches with a provided prefix. + * Deletes all files whose name matches with a provided suffix. */ - static void deletePrefixedFiles(const char* path, const char* prefix); + static void deleteSuffixedFiles(const char* path, const char* suffix); /** * Send broadcasts to relevant receiver for each data stored on disk. @@ -61,12 +61,18 @@ public: * Appends ConfigMetricsReport found on disk to the specific proto and * delete it. */ - static void appendConfigMetricsReport(const char* path, ProtoOutputStream& proto); + static void appendConfigMetricsReport(ProtoOutputStream& proto); /** * Call to load the saved configs from disk. */ static void readConfigFromDisk(std::map& configsMap); + + /** + * Trims files in the provided directory to limit the total size, number of + * files, accumulation of outdated files. + */ + static void trimToFit(const char* dir); }; } // namespace statsd diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f912e4b2cd2432fd8ae83a525b85cb9d6b416f38 --- /dev/null +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -0,0 +1,147 @@ +/* + * 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "SubscriberReporter.h" + +using android::IBinder; +using std::lock_guard; +using std::unordered_map; + +namespace android { +namespace os { +namespace statsd { + +void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId, + const sp& intentSender) { + VLOG("SubscriberReporter::setBroadcastSubscriber called."); + lock_guard lock(mLock); + mIntentMap[configKey][subscriberId] = intentSender; +} + +void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId) { + VLOG("SubscriberReporter::unsetBroadcastSubscriber called."); + lock_guard lock(mLock); + auto subscriberMapIt = mIntentMap.find(configKey); + if (subscriberMapIt != mIntentMap.end()) { + subscriberMapIt->second.erase(subscriberId); + if (subscriberMapIt->second.empty()) { + mIntentMap.erase(configKey); + } + } +} + +void SubscriberReporter::removeConfig(const ConfigKey& configKey) { + VLOG("SubscriberReporter::removeConfig called."); + lock_guard lock(mLock); + mIntentMap.erase(configKey); +} + +void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const { + // Reminder about ids: + // subscription id - name of the Subscription (that ties the Alert to the broadcast) + // subscription rule_id - the name of the Alert (that triggers the broadcast) + // subscriber_id - name of the PendingIntent to use to send the broadcast + // config uid - the uid that uploaded the config (and therefore gave the PendingIntent, + // although the intent may be to broadcast to a different uid) + // config id - the name of this config (for this particular uid) + + VLOG("SubscriberReporter::alertBroadcastSubscriber called."); + lock_guard lock(mLock); + + if (!subscription.has_broadcast_subscriber_details() + || !subscription.broadcast_subscriber_details().has_subscriber_id()) { + ALOGE("Broadcast subscriber does not have an id."); + return; + } + int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id(); + + auto it1 = mIntentMap.find(configKey); + if (it1 == mIntentMap.end()) { + ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str()); + return; + } + auto it2 = it1->second.find(subscriberId); + if (it2 == it1->second.end()) { + ALOGW("Cannot inform subscriber of config %s for missing subscriberId %lld ", + configKey.ToString().c_str(), (long long)subscriberId); + return; + } + sendBroadcastLocked(it2->second, configKey, subscription, dimKey); +} + +void SubscriberReporter::sendBroadcastLocked(const sp& intentSender, + const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const { + VLOG("SubscriberReporter::sendBroadcastLocked called."); + if (mStatsCompanionService == nullptr) { + ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService."); + return; + } + mStatsCompanionService->sendSubscriberBroadcast(intentSender, + configKey.GetUid(), + configKey.GetId(), + subscription.id(), + subscription.rule_id(), + protoToStatsDimensionsValue(dimKey)); +} + +StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( + const HashableDimensionKey& dimKey) { + return protoToStatsDimensionsValue(dimKey.getDimensionsValue()); +} + +StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue( + const DimensionsValue& protoDimsVal) { + int32_t field = protoDimsVal.field(); + + switch (protoDimsVal.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return StatsDimensionsValue(field, String16(protoDimsVal.value_str().c_str())); + case DimensionsValue::ValueCase::kValueInt: + return StatsDimensionsValue(field, static_cast(protoDimsVal.value_int())); + case DimensionsValue::ValueCase::kValueLong: + return StatsDimensionsValue(field, static_cast(protoDimsVal.value_long())); + case DimensionsValue::ValueCase::kValueBool: + return StatsDimensionsValue(field, static_cast(protoDimsVal.value_bool())); + case DimensionsValue::ValueCase::kValueFloat: + return StatsDimensionsValue(field, static_cast(protoDimsVal.value_float())); + case DimensionsValue::ValueCase::kValueTuple: + { + int sz = protoDimsVal.value_tuple().dimensions_value_size(); + std::vector sdvVec(sz); + for (int i = 0; i < sz; i++) { + sdvVec[i] = protoToStatsDimensionsValue( + protoDimsVal.value_tuple().dimensions_value(i)); + } + return StatsDimensionsValue(field, sdvVec); + } + default: + ALOGW("protoToStatsDimensionsValue failed: illegal type."); + return StatsDimensionsValue(); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h new file mode 100644 index 0000000000000000000000000000000000000000..5bb458a8b1d8e53ad5e7d8ea8e91630d12012a48 --- /dev/null +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -0,0 +1,117 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include "config/ConfigKey.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" // DimensionsValue +#include "android/os/StatsDimensionsValue.h" +#include "HashableDimensionKey.h" + +#include +#include + +namespace android { +namespace os { +namespace statsd { + +// Reports information to subscribers. +// Single instance shared across the process. All methods are thread safe. +class SubscriberReporter { +public: + /** Get (singleton) instance of SubscriberReporter. */ + static SubscriberReporter& getInstance() { + static SubscriberReporter subscriberReporter; + return subscriberReporter; + } + + ~SubscriberReporter(){}; + SubscriberReporter(SubscriberReporter const&) = delete; + void operator=(SubscriberReporter const&) = delete; + + /** + * Tells SubscriberReporter what IStatsCompanionService to use. + * May be nullptr, but SubscriberReporter will not send broadcasts for any calls + * to alertBroadcastSubscriber that occur while nullptr. + */ + void setStatsCompanionService(sp statsCompanionService) { + std::lock_guard lock(mLock); + sp tmpForLock = mStatsCompanionService; + mStatsCompanionService = statsCompanionService; + } + + /** + * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair. + * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder). + */ + void setBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId, + const sp& intentSender); + + /** + * Erases any intentSender information from the given (configKey, subscriberId) pair. + */ + void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); + + /** Remove all information stored by SubscriberReporter about the given config. */ + void removeConfig(const ConfigKey& configKey); + + /** + * Sends a broadcast via the intentSender previously stored for the + * given (configKey, subscriberId) pair by setBroadcastSubscriber. + * Information about the subscriber, as well as information extracted from the dimKey, is sent. + */ + void alertBroadcastSubscriber(const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const; + +private: + SubscriberReporter() {}; + + mutable std::mutex mLock; + + /** Binder interface for communicating with StatsCompanionService. */ + sp mStatsCompanionService = nullptr; + + /** Maps -> IBinder (which represents an IIntentSender). */ + std::unordered_map>> mIntentMap; + + /** + * Sends a broadcast via the given intentSender (using mStatsCompanionService), along + * with the information in the other parameters. + */ + void sendBroadcastLocked(const sp& intentSender, + const ConfigKey& configKey, + const Subscription& subscription, + const HashableDimensionKey& dimKey) const; + + /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */ + static StatsDimensionsValue protoToStatsDimensionsValue( + const DimensionsValue& protoDimsVal); + + /** Converts a HashableDimensionKey to a StatsDimensionsValue. */ + static StatsDimensionsValue protoToStatsDimensionsValue( + const HashableDimensionKey& dimKey); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc index c260ae1aa6c8e60a08b9ff3f9aab8418fafee36e..920273b5dfaa72379a33e3ad2582c8fceae9768c 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 cc02f34519cd259c48f6c0520cc7655269642bf8..62bdba406de2e400db8e3f6dd3cb366ae0c355db 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/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp index fe0f59d30c8192a0f78beda3334d4231bf29ba71..f90ca409e84cffd7f88f8985ed51ad881d95f625 100644 --- a/cmds/statsd/tests/MetricsManager_test.cpp +++ b/cmds/statsd/tests/MetricsManager_test.cpp @@ -81,8 +81,8 @@ StatsdConfig buildGoodConfig() { metric->set_id(3); metric->set_what(StringToId("SCREEN_IS_ON")); metric->set_bucket(ONE_MINUTE); - metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/); - metric->mutable_dimensions()->add_child()->set_field(1); + metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions_in_what()->add_child()->set_field(1); config.add_no_report_metric(3); @@ -132,8 +132,8 @@ StatsdConfig buildAlertWithUnknownMetric() { metric->set_id(3); metric->set_what(StringToId("SCREEN_IS_ON")); metric->set_bucket(ONE_MINUTE); - metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/); - metric->mutable_dimensions()->add_child()->set_field(1); + metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions_in_what()->add_child()->set_field(1); auto alert = config.add_alert(); alert->set_id(3); @@ -217,7 +217,7 @@ StatsdConfig buildDimensionMetricsWithMultiTags() { metric->set_what(StringToId("BATTERY_LOW")); metric->set_bucket(ONE_MINUTE); // This case is interesting. We want to dimension across two atoms. - metric->mutable_dimensions()->add_child()->set_field(1); + metric->mutable_dimensions_in_what()->add_child()->set_field(1); auto alert = config.add_alert(); alert->set_id(103); diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index 5292f24f0bee687b39ed903cfd78e4f42dcae13d..f26c10d33e6731dde07686312eb83d45fc2b2f0b 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -178,16 +178,16 @@ TEST(UidMapTest, TestClearingOutput) { EXPECT_EQ(1, results.snapshots_size()); // It should be cleared now - EXPECT_EQ(0, m.mOutput.snapshots_size()); + EXPECT_EQ(1, m.mOutput.snapshots_size()); results = m.getOutput(3, config1); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); // Now add another configuration. m.OnConfigUpdated(config2); m.updateApp(5, String16(kApp1.c_str()), 1000, 40); EXPECT_EQ(1, m.mOutput.changes_size()); results = m.getOutput(6, config1); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); EXPECT_EQ(1, results.changes_size()); EXPECT_EQ(1, m.mOutput.changes_size()); @@ -197,15 +197,15 @@ TEST(UidMapTest, TestClearingOutput) { // We still can't remove anything. results = m.getOutput(8, config1); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); EXPECT_EQ(2, results.changes_size()); EXPECT_EQ(2, m.mOutput.changes_size()); results = m.getOutput(9, config2); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); EXPECT_EQ(2, results.changes_size()); // At this point both should be cleared. - EXPECT_EQ(0, m.mOutput.snapshots_size()); + EXPECT_EQ(1, m.mOutput.snapshots_size()); EXPECT_EQ(0, m.mOutput.changes_size()); } @@ -228,10 +228,8 @@ TEST(UidMapTest, TestMemoryComputed) { m.updateApp(3, String16(kApp1.c_str()), 1000, 40); EXPECT_TRUE(m.mBytesUsed > snapshot_bytes); - size_t bytesWithSnapshotChange = m.mBytesUsed; m.getOutput(2, config1); - EXPECT_TRUE(m.mBytesUsed < bytesWithSnapshotChange); size_t prevBytes = m.mBytesUsed; m.getOutput(4, config1); diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp index 39c95496055a125b4300c3ebe1c3019db12692f0..a56db28e64b39af3b12968a07766957765330053 100644 --- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp @@ -44,7 +44,7 @@ StatsdConfig CreateStatsdConfig() { auto countMetric = config.add_count_metric(); countMetric->set_id(123456); countMetric->set_what(wakelockAcquireMatcher.id()); - *countMetric->mutable_dimensions() = + *countMetric->mutable_dimensions_in_what() = CreateAttributionUidAndTagDimensions( android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); countMetric->set_bucket(ONE_MINUTE); @@ -155,7 +155,8 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSlice) { auto data = countMetrics.data(0); ValidateAttributionUidAndTagDimension( - data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111, "App1"); + data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111, + "App1"); EXPECT_EQ(data.bucket_info_size(), 2); EXPECT_EQ(data.bucket_info(0).count(), 2); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); @@ -166,7 +167,8 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSlice) { data = countMetrics.data(1); ValidateAttributionUidAndTagDimension( - data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1"); + data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); EXPECT_EQ(data.bucket_info_size(), 2); EXPECT_EQ(data.bucket_info(0).count(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); @@ -177,7 +179,8 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSlice) { data = countMetrics.data(2); ValidateAttributionUidAndTagDimension( - data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule3"); + data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule3"); EXPECT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); @@ -185,7 +188,8 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSlice) { data = countMetrics.data(3); ValidateAttributionUidAndTagDimension( - data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2"); + data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 444, + "GMSCoreModule2"); EXPECT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e56a6c57848f2aa0beed0be965b02b85dd264855 --- /dev/null +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp @@ -0,0 +1,194 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfigForPushedEvent() { + StatsdConfig config; + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED); + *config.add_atom_matcher() = atomMatcher; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + *config.add_predicate() = isInBackgroundPredicate; + + auto gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(123456); + gaugeMetric->set_what(atomMatcher.id()); + gaugeMetric->set_condition(isInBackgroundPredicate.id()); + gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); + auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + fieldMatcher->set_field(android::util::APP_START_CHANGED); + fieldMatcher->add_child()->set_field(3); // type (enum) + fieldMatcher->add_child()->set_field(4); // activity_name(str) + fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) + *gaugeMetric->mutable_dimensions_in_what() = + CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ }); + gaugeMetric->set_bucket(ONE_MINUTE); + + auto links = gaugeMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + auto dimensionWhat = links->mutable_fields_in_what(); + dimensionWhat->set_field(android::util::APP_START_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_fields_in_condition(); + dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} + +std::unique_ptr CreateAppStartChangedEvent( + const int uid, const string& pkg_name, AppStartChanged::TransitionType type, + const string& activity_name, const string& calling_pkg_name, const bool is_instant_app, + int64_t activity_start_msec, uint64_t timestampNs) { + auto logEvent = std::make_unique( + android::util::APP_START_CHANGED, timestampNs); + logEvent->write(uid); + logEvent->write(pkg_name); + logEvent->write(type); + logEvent->write(activity_name); + logEvent->write(calling_pkg_name); + logEvent->write(is_instant_app); + logEvent->write(activity_start_msec); + logEvent->init(); + return logEvent; +} + +} // namespace + +TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { + auto config = CreateStatsdConfigForPushedEvent(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid1 = 123; + int appUid2 = 456; + std::vector> events; + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15)); + events.push_back(CreateMoveToForegroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 250)); + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 350)); + events.push_back(CreateMoveToForegroundEvent( + appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100)); + + + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1", + true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name2", "calling_pkg_name2", + true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name3", "calling_pkg_name3", + true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name4", "calling_pkg_name4", + true /*is_instant_app*/, 104 /*activity_start_msec*/, + bucketStartTimeNs + bucketSizeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name5", "calling_pkg_name5", + true /*is_instant_app*/, 105 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name6", "calling_pkg_name6", + false /*is_instant_app*/, 106 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + events.push_back(CreateMoveToBackgroundEvent(appUid2, bucketStartTimeNs + bucketSizeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid2, "app2", AppStartChanged::COLD, "activity_name7", "calling_pkg_name7", + true /*is_instant_app*/, 201 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + EXPECT_EQ(gaugeMetrics.data_size(), 2); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid1); + EXPECT_EQ(data.bucket_info_size(), 3); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L); + + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4"); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L); + + EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5"); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L); + + data = gaugeMetrics.data(1); + + EXPECT_EQ(data.dimensions_in_what().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid2); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android \ No newline at end of file diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp index cdc44675a54df46bbae04a7cb1d5358d81286e97..4504a95c8ef0137c8b55de6477202c7d9afd3b9c 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() = @@ -68,27 +68,27 @@ StatsdConfig CreateStatsdConfig() { countMetric->set_what(appCrashMatcher.id()); countMetric->set_condition(combinationPredicate->id()); // The metric is dimensioning by uid only. - *countMetric->mutable_dimensions() = + *countMetric->mutable_dimensions_in_what() = CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); countMetric->set_bucket(ONE_MINUTE); // Links between crash atom and condition of app is in syncing. auto links = countMetric->add_links(); links->set_condition(isSyncingPredicate.id()); - auto dimensionWhat = links->mutable_dimensions_in_what(); + auto dimensionWhat = links->mutable_fields_in_what(); dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. - auto dimensionCondition = links->mutable_dimensions_in_condition(); + auto dimensionCondition = links->mutable_fields_in_condition(); dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED); dimensionCondition->add_child()->set_field(1); // uid field. // Links between crash atom and condition of app is in background. links = countMetric->add_links(); links->set_condition(isInBackgroundPredicate.id()); - dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat = links->mutable_fields_in_what(); dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. - dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition = links->mutable_fields_in_condition(); dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); dimensionCondition->add_child()->set_field(1); // uid field. return config; @@ -123,11 +123,13 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) { auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2); auto screenTurnedOnEvent = - CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 2); + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + bucketStartTimeNs + 2); auto screenTurnedOffEvent = - CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200); + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + bucketStartTimeNs + 200); auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 2 * bucketSizeNs - 100); auto syncOnEvent1 = @@ -199,12 +201,11 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) { EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); auto data = reports.reports(0).metrics(0).count_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); // Uid field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid); reports.Clear(); processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); @@ -216,12 +217,12 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) { EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3); data = reports.reports(0).metrics(0).count_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), + EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); // Uid field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid); } #else diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index 278335674130a53437b9900fa6c2a587a2209b63..1186a166dfab8a3e52da24da884bbf2d6a245573 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -26,6 +26,8 @@ namespace statsd { #ifdef __ANDROID__ +namespace { + StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { StatsdConfig config; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); @@ -49,13 +51,15 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) durationMetric->set_condition(screenIsOffPredicate.id()); durationMetric->set_aggregation_type(aggregationType); // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions() = + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); durationMetric->set_bucket(ONE_MINUTE); return config; } +} // namespace + TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { ConfigKey cfgKey; for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) { @@ -69,11 +73,13 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); auto screenTurnedOnEvent = - CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 1); + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + bucketStartTimeNs + 1); auto screenTurnedOffEvent = - CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200); + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + bucketStartTimeNs + 200); auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, + CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + bucketSizeNs + 500); std::vector attributions1 = @@ -94,6 +100,7 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto releaseEvent2 = CreateReleaseWakelockEvent( attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15); + std::vector> events; events.push_back(std::move(screenTurnedOnEvent)); @@ -119,18 +126,9 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimensions_in_what(), + android::util::WAKELOCK_STATE_CHANGED, 111); // Validate bucket info. EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); data = reports.reports(0).metrics(0).duration_metrics().data(0); @@ -147,18 +145,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111); // Two output buckets. // The wakelock holding interval in the 1st bucket starts from the screen off event and to // the end of the 1st bucket. @@ -167,6 +155,33 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and // ends at the second screen on event. EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); + + events.clear(); + events.push_back(CreateScreenStateChangedEvent( + android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + bucketStartTimeNs + 2 * bucketSizeNs + 90)); + events.push_back(CreateAcquireWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100)); + events.push_back(CreateReleaseWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100)); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension( + data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); + EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); } } diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index c39151313c55616471f4a891cf7a234eafeaa01b..897328d4635ef0908514034c68f11611bcaa26bf 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -149,8 +149,8 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); - *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); + *link->mutable_fields_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); + *link->mutable_fields_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); LogEvent event1(tagId, bucketStartTimeNs + 1); event1.write("111"); // uid diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index 7171de939c62a24662dd47aa9244120422e7e314..34cde607988e473e721cf2c9fe96435459ce006e 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -98,8 +98,8 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); - *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); + *link->mutable_fields_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); + *link->mutable_fields_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); LogEvent event1(tagId, bucketStartTimeNs + 1); EXPECT_TRUE(event1.write("111")); diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index e7882353bec0520adba173c191b6469568343d6a..9f4582dc6994ea42580449d2dbe7b7491e12b6bd 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -19,6 +19,14 @@ namespace android { namespace os { namespace statsd { +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(atomId); + return atom_matcher; +} + AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, WakelockStateChanged::State state) { AtomMatcher atom_matcher; @@ -40,7 +48,7 @@ AtomMatcher CreateReleaseWakelockAtomMatcher() { } AtomMatcher CreateScreenStateChangedAtomMatcher( - const string& name, ScreenStateChanged::State state) { + const string& name, android::view::DisplayStateEnum state) { AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); @@ -52,11 +60,13 @@ AtomMatcher CreateScreenStateChangedAtomMatcher( } AtomMatcher CreateScreenTurnedOnAtomMatcher() { - return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", ScreenStateChanged::STATE_ON); + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", + android::view::DisplayStateEnum::DISPLAY_STATE_ON); } AtomMatcher CreateScreenTurnedOffAtomMatcher() { - return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", ScreenStateChanged::STATE_OFF); + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", + ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF); } AtomMatcher CreateSyncStateChangedAtomMatcher( @@ -201,7 +211,7 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector& fields) } std::unique_ptr CreateScreenStateChangedEvent( - const ScreenStateChanged::State state, uint64_t timestampNs) { + const android::view::DisplayStateEnum state, uint64_t timestampNs) { auto event = std::make_unique(android::util::SCREEN_STATE_CHANGED, timestampNs); EXPECT_TRUE(event->write(state)); event->init(); @@ -213,7 +223,7 @@ std::unique_ptr CreateWakelockStateChangedEvent( const WakelockStateChanged::State state, uint64_t timestampNs) { auto event = std::make_unique(android::util::WAKELOCK_STATE_CHANGED, timestampNs); event->write(attributions); - event->write(WakelockStateChanged::PARTIAL); + event->write(android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); event->write(wakelockName); event->write(state); event->init(); diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 1bbbd9a30540965e3a678c6b463329620cbf4d11..ff8fef0c46b6115a9579b49e29cc5ba999ca6105 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -23,6 +23,9 @@ namespace android { namespace os { namespace statsd { +// Create AtomMatcher proto to simply match a specific atom type. +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); + // Create AtomMatcher proto for acquiring wakelock. AtomMatcher CreateAcquireWakelockAtomMatcher(); @@ -81,7 +84,7 @@ FieldMatcher CreateAttributionUidDimensions(const int atomId, // Create log event for screen state changed. std::unique_ptr CreateScreenStateChangedEvent( - const ScreenStateChanged::State state, uint64_t timestampNs); + const android::view::DisplayStateEnum state, uint64_t timestampNs); // Create log event for app moving to background. std::unique_ptr CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); @@ -135,7 +138,7 @@ template void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) { std::map dimensionIndexMap; for (int i = 0; i < metricData.data_size(); ++i) { - dimensionIndexMap.insert(std::make_pair(metricData.data(i).dimension(), i)); + dimensionIndexMap.insert(std::make_pair(metricData.data(i).dimensions_in_what(), i)); } for (const auto& itr : dimensionIndexMap) { *sortedMetricData->add_data() = metricData.data(itr.second); diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk index a4c08006328459b4fbcb845c09352c8cfc165432..32a85b1360235d79fc37178f3bfd8a673a2507ac 100644 --- a/cmds/statsd/tools/dogfood/Android.mk +++ b/cmds/statsd/tools/dogfood/Android.mk @@ -19,13 +19,10 @@ include $(CLEAR_VARS) LOCAL_PACKAGE_NAME := StatsdDogfood LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_SRC_FILES += ../../src/stats_log.proto \ - ../../src/atoms.proto \ - ../../src/statsd_config.proto -LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite +LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \ + statsdprotolite LOCAL_PROTOC_OPTIMIZE_TYPE := lite LOCAL_PRIVILEGED_MODULE := true diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java index 93dba71716a5780e00c1ee85aa4b1c4f049df619..33c8abf0346239650c9b56fae47b157bc78e96c7 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java @@ -97,9 +97,14 @@ public class DisplayProtoUtils { = log.getDurationMetrics(); sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { - sb.append("dimension: "); - displayDimension(sb, duration.getDimension()); + sb.append("dimension_in_what: "); + displayDimension(sb, duration.getDimensionsInWhat()); sb.append("\n"); + if (duration.hasDimensionsInCondition()) { + sb.append("dimension_in_condition: "); + displayDimension(sb, duration.getDimensionsInCondition()); + sb.append("\n"); + } for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") @@ -124,9 +129,14 @@ public class DisplayProtoUtils { = log.getCountMetrics(); sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { - sb.append("dimension: "); - displayDimension(sb, count.getDimension()); + sb.append("dimension_in_what: "); + displayDimension(sb, count.getDimensionsInWhat()); sb.append("\n"); + if (count.hasDimensionsInCondition()) { + sb.append("dimension_in_condition: "); + displayDimension(sb, count.getDimensionsInCondition()); + sb.append("\n"); + } for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java index 4f9032ff01b49824a90aec8bb325fa6c64e4a5cd..d39aa1d314e6b833efc395d1659a88e6f3a84680 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java @@ -16,12 +16,12 @@ package com.android.statsd.dogfood; import android.app.Activity; +import android.app.StatsManager; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; import android.util.StatsLog; -import android.util.StatsManager; import android.view.View; import android.widget.TextView; import android.widget.Toast; diff --git a/cmds/statsd/tools/loadtest/Android.mk b/cmds/statsd/tools/loadtest/Android.mk index 0a0fd6647fbbd1b21f975311818e7eaf6217be05..f5722c2c3d19a12e34dc709dc861ed6b6358088f 100644 --- a/cmds/statsd/tools/loadtest/Android.mk +++ b/cmds/statsd/tools/loadtest/Android.mk @@ -21,6 +21,7 @@ LOCAL_PACKAGE_NAME := StatsdLoadtest LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_SRC_FILES += ../../src/stats_log.proto \ ../../src/atoms.proto \ + ../../src/perfetto/perfetto_config.proto \ ../../src/statsd_config.proto LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java index 843b1e5f90ac1b1de218c89c1668b9099892520f..2e0161be809693b5d2b60e0ba968be82269c7af7 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java @@ -19,7 +19,6 @@ import android.content.Context; import android.content.res.Resources; import android.util.Log; -import com.android.internal.os.StatsdConfigProto.Bucket; import com.android.internal.os.StatsdConfigProto.Predicate; import com.android.internal.os.StatsdConfigProto.CountMetric; import com.android.internal.os.StatsdConfigProto.DurationMetric; diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java index 862ebe143e867675b25e4f9073e3756036ef39ed..75489ade6432fcfa9f8b53ec2449af1bfc268bd0 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java @@ -108,9 +108,14 @@ public class DisplayProtoUtils { = log.getDurationMetrics(); sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { - sb.append("dimension: "); - displayDimension(sb, duration.getDimension()); + sb.append("dimension_in_what: "); + displayDimension(sb, duration.getDimensionsInWhat()); sb.append("\n"); + if (duration.hasDimensionsInCondition()) { + sb.append("dimension_in_condition: "); + displayDimension(sb, duration.getDimensionsInCondition()); + sb.append("\n"); + } for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") @@ -135,9 +140,14 @@ public class DisplayProtoUtils { = log.getCountMetrics(); sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { - sb.append("dimension: "); - displayDimension(sb, count.getDimension()); + sb.append("dimension_in_what: "); + displayDimension(sb, count.getDimensionsInWhat()); sb.append("\n"); + if (count.hasDimensionsInCondition()) { + sb.append("dimension_in_condition: "); + displayDimension(sb, count.getDimensionsInCondition()); + sb.append("\n"); + } for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java index 26c1c72be4c9629da7211995d3be2fdd033958d4..652f6b2d08a95a02a6020bb9f3719ce248d0f5d4 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java @@ -19,6 +19,7 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; +import android.app.StatsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -34,7 +35,6 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.util.StatsLog; -import android.util.StatsManager; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.view.MotionEvent; diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java index d4b2aa4ff2410aa6498448c6bef2fca6f77c7a9e..5dcce9acb4015d0621410391469e7333c6a6b597 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java @@ -90,7 +90,7 @@ public class SequencePusher { case 2: case 10: StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, - StatsLog.CHARGING_STATE_CHANGED__CHARGING_STATE__BATTERY_STATUS_CHARGING + StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_CHARGING /* charging_state */); break; case 3: @@ -103,7 +103,7 @@ public class SequencePusher { case 4: case 12: StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, - StatsLog.CHARGING_STATE_CHANGED__CHARGING_STATE__BATTERY_STATUS_NOT_CHARGING + StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_NOT_CHARGING /* charging_state */); break; case 5: @@ -115,7 +115,7 @@ public class SequencePusher { break; case 6: StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__DISPLAY_STATE__STATE_ON /* display_state */); + StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_ON /* display_state */); break; case 7: for (int i = 0; i < mBurst; i++) { @@ -125,7 +125,7 @@ public class SequencePusher { break; case 14: StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__DISPLAY_STATE__STATE_OFF /* display_state */); + StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF /* display_state */); break; case 15: for (int i = 0; i < mBurst; i++) { @@ -147,14 +147,14 @@ public class SequencePusher { public void finish() { // Screen goes back to off. This will ensure that conditions get back to false. StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__DISPLAY_STATE__STATE_OFF /* display_state */); + StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF /* display_state */); for (int i = 0; i < mBurst; i++) { StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, i /* uid */, StatsLog.AUDIO_STATE_CHANGED__STATE__OFF /* state */); } // Stop charging, to ensure the corresponding durations are closed. StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, - StatsLog.CHARGING_STATE_CHANGED__CHARGING_STATE__BATTERY_STATUS_NOT_CHARGING + StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_NOT_CHARGING /* charging_state */); // Stop scanning GPS, to ensure the corresponding conditions get back to false. for (int i = 0; i < mBurst; i++) { diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk index ed99f3e149224799df5fe2e915c05193337727b4..e887539b718ab10ffec09b940cd68554619d39b5 100644 --- a/cmds/uiautomator/instrumentation/Android.mk +++ b/cmds/uiautomator/instrumentation/Android.mk @@ -21,7 +21,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \ $(call all-java-files-under, ../library/core-src) -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs +LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs LOCAL_STATIC_JAVA_LIBRARIES := junit LOCAL_MODULE := uiautomator-instrumentation # TODO: change this to 18 when it's available diff --git a/config/hiddenapi-blacklist.txt b/config/hiddenapi-blacklist.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/config/hiddenapi-dark-greylist.txt b/config/hiddenapi-dark-greylist.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/core/java/Android.bp b/core/java/Android.bp index afa08e65cbaa1dc82fc0850cd34e70a10eb0eeb9..f7c5c57a07e4dc3c6e538831a0ffd17c4c7c0581 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -11,7 +11,8 @@ filegroup { // only used by key_store_service cc_library_shared { name: "libkeystore_aidl", - srcs: ["android/security/IKeystoreService.aidl"], + srcs: ["android/security/IKeystoreService.aidl", + "android/security/IConfirmationPromptCallback.aidl"], aidl: { export_aidl_headers: true, include_dirs: [ diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 97dcb90bbb99c0a9c29b231f404d38dd6bd9c142..0a4541ba47778d6ca0633d92fa07426fc5e9de6a 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -363,6 +363,11 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; + /** + * Action to take a screenshot + */ + public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; + private static final String LOG_TAG = "AccessibilityService"; /** diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index aa099eb19d37a46e89c0d26b61b5785372275e9d..e7d0c8be47619d4fc2bc33e97b06aeb9f2990366 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -16,6 +16,7 @@ package android.app; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static java.lang.Character.MIN_VALUE; import android.annotation.CallSuper; @@ -98,6 +99,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.RemoteAnimationDefinition; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; @@ -126,6 +128,8 @@ import com.android.internal.app.WindowDecorActionBar; import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneWindow; +import dalvik.system.VMRuntime; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -857,6 +861,7 @@ public class Activity extends ContextThemeWrapper private boolean mHasCurrentPermissionsRequest; private boolean mAutoFillResetNeeded; + private boolean mAutoFillIgnoreFirstResumePause; /** The last autofill id that was returned from {@link #getNextAutofillId()} */ private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID; @@ -1253,10 +1258,7 @@ public class Activity extends ContextThemeWrapper getApplication().dispatchActivityStarted(this); if (mAutoFillResetNeeded) { - AutofillManager afm = getAutofillManager(); - if (afm != null) { - afm.onVisibleForAutofill(); - } + getAutofillManager().onVisibleForAutofill(); } } @@ -1320,6 +1322,20 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); getApplication().dispatchActivityResumed(this); mActivityTransitionState.onResume(this, isTopOfTask()); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest# + // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial + // window visibility after recreation is INVISIBLE in onResume() and next frame + // ViewRootImpl.performTraversals() changes window visibility to VISIBLE. + // So we cannot call View.notifyEnterOrExited() which will do nothing + // when View.isVisibleToUser() is false. + getAutofillManager().notifyViewEntered(focus); + } + } + } mCalled = true; } @@ -1681,6 +1697,19 @@ public class Activity extends ContextThemeWrapper protected void onPause() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this); getApplication().dispatchActivityPaused(this); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill notifyViewExited " + this); + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + getAutofillManager().notifyViewExited(focus); + } + } else { + // reset after first pause() + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill got first pause " + this); + mAutoFillIgnoreFirstResumePause = false; + } + } mCalled = true; } @@ -1871,6 +1900,10 @@ public class Activity extends ContextThemeWrapper mTranslucentCallback = null; mCalled = true; + if (mAutoFillResetNeeded) { + getAutofillManager().onInvisibleForAutofill(); + } + if (isFinishing()) { if (mAutoFillResetNeeded) { getAutofillManager().onActivityFinished(); @@ -2104,11 +2137,15 @@ public class Activity extends ContextThemeWrapper * @param params non-null parameters to be combined with previously set parameters when entering * picture-in-picture. * - * @return true if the system puts this activity into picture-in-picture mode or was already - * in picture-in-picture mode (@see {@link #isInPictureInPictureMode()) + * @return true if the system successfully put this activity into picture-in-picture mode or was + * already in picture-in-picture mode (@see {@link #isInPictureInPictureMode()). If the device + * does not support picture-in-picture, return false. */ public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) { try { + if (!deviceSupportsPictureInPictureMode()) { + return false; + } if (params == null) { throw new IllegalArgumentException("Expected non-null picture-in-picture params"); } @@ -2136,6 +2173,9 @@ public class Activity extends ContextThemeWrapper */ public void setPictureInPictureParams(@NonNull PictureInPictureParams params) { try { + if (!deviceSupportsPictureInPictureMode()) { + return; + } if (params == null) { throw new IllegalArgumentException("Expected non-null picture-in-picture params"); } @@ -2158,6 +2198,13 @@ public class Activity extends ContextThemeWrapper } } + /** + * @return Whether this device supports picture-in-picture. + */ + private boolean deviceSupportsPictureInPictureMode() { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + } + void dispatchMovedToDisplay(int displayId, Configuration config) { updateDisplay(displayId); onMovedToDisplay(displayId, config); @@ -2587,12 +2634,37 @@ public class Activity extends ContextThemeWrapper * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise * @see View#findViewById(int) + * @see Activity#requireViewById(int) */ @Nullable public T findViewById(@IdRes int id) { return getWindow().findViewById(id); } + /** + * Finds a view that was identified by the {@code android:id} XML attribute that was processed + * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, or there is + * no matching view in the hierarchy. + *

+ * Note: In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#requireViewById(int) + * @see Activity#findViewById(int) + */ + @NonNull + public final T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Activity"); + } + return view; + } + /** * Retrieve a reference to this activity's ActionBar. * @@ -6266,7 +6338,7 @@ public class Activity extends ContextThemeWrapper mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); - final AutofillManager afm = getAutofillManager(); + final AutofillManager afm = mAutofillManager; if (afm != null) { afm.dump(prefix, writer); } else { @@ -6616,7 +6688,6 @@ public class Activity extends ContextThemeWrapper * to run as a {@link android.service.vr.VrListenerService} is not installed, or has * not been enabled in user settings. * - * @see android.content.pm.PackageManager#FEATURE_VR_MODE * @see android.content.pm.PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE * @see android.service.vr.VrListenerService * @see android.provider.Settings#ACTION_VR_LISTENER_SETTINGS @@ -7042,11 +7113,12 @@ public class Activity extends ContextThemeWrapper mFragments.dispatchStart(); mFragments.reportLoaderStart(); - // This property is set for all builds except final release - boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1; boolean isAppDebuggable = (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + // This property is set for all non-user builds except final release + boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1; + if (isAppDebuggable || isDlwarningEnabled) { String dlwarning = getDlWarning(); if (dlwarning != null) { @@ -7067,6 +7139,28 @@ public class Activity extends ContextThemeWrapper } } + // This property is set for all non-user builds except final release + boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1; + + if (isAppDebuggable || isApiWarningEnabled) { + if (VMRuntime.getRuntime().hasUsedHiddenApi()) { + String appName = getApplicationInfo().loadLabel(getPackageManager()) + .toString(); + String warning = "Detected problems with API compatiblity\n" + + "(please consult log for detail)"; + if (isAppDebuggable) { + new AlertDialog.Builder(this) + .setTitle(appName) + .setMessage(warning) + .setPositiveButton(android.R.string.ok, null) + .setCancelable(false) + .show(); + } else { + Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show(); + } + } + } + mActivityTransitionState.enterReady(this); } @@ -7120,13 +7214,23 @@ public class Activity extends ContextThemeWrapper } } - final void performResume() { + final void performResume(boolean followedByPause) { performRestart(true /* start */); mFragments.execPendingActions(); mLastNonConfigurationInstances = null; + if (mAutoFillResetNeeded) { + // When Activity is destroyed in paused state, and relaunch activity, there will be + // extra onResume and onPause event, ignore the first onResume and onPause. + // see ActivityThread.handleRelaunchActivity() + mAutoFillIgnoreFirstResumePause = followedByPause; + if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) { + Slog.v(TAG, "autofill will ignore first pause when relaunching " + this); + } + } + mCalled = false; // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); @@ -7311,7 +7415,7 @@ public class Activity extends ContextThemeWrapper } } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) { Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null; - getAutofillManager().onAuthenticationResult(requestCode, resultData); + getAutofillManager().onAuthenticationResult(requestCode, resultData, getCurrentFocus()); } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { @@ -7585,6 +7689,12 @@ public class Activity extends ContextThemeWrapper return !mStopped; } + /** @hide */ + @Override + public boolean isDisablingEnterExitEventForAutofill() { + return mAutoFillIgnoreFirstResumePause || !mResumed; + } + /** * If set to true, this indicates to the system that it should never take a * screenshot of the activity to be used as a representation while it is not in a started state. @@ -7659,6 +7769,22 @@ public class Activity extends ContextThemeWrapper } } + /** + * Registers remote animations per transition type for this activity. + * + * @param definition The remote animation definition that defines which transition whould run + * which remote animation. + * @hide + */ + @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public void registerRemoteAnimations(RemoteAnimationDefinition definition) { + try { + ActivityManager.getService().registerRemoteAnimations(mToken, definition); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call registerRemoteAnimations", e); + } + } + class HostCallbacks extends FragmentHostCallback { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 455458436c2f2cdd69d1032b99b062188180bc06..e2ce8b177d2ee08e8e2d89f12dda1d5eac0486f7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -28,7 +28,6 @@ import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.UriPermission; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; @@ -181,7 +180,8 @@ public class ActivityManager { BUGREPORT_OPTION_INTERACTIVE, BUGREPORT_OPTION_REMOTE, BUGREPORT_OPTION_WEAR, - BUGREPORT_OPTION_TELEPHONY + BUGREPORT_OPTION_TELEPHONY, + BUGREPORT_OPTION_WIFI }) public @interface BugreportMode {} /** @@ -215,6 +215,12 @@ public class ActivityManager { */ public static final int BUGREPORT_OPTION_TELEPHONY = 4; + /** + * Takes a lightweight bugreport that only includes a few sections related to Wifi. + * @hide + */ + public static final int BUGREPORT_OPTION_WIFI = 5; + /** * {@code * } name for a 'home' Activity that declares a package that is to be @@ -544,18 +550,68 @@ public class ActivityManager { /** @hide Process does not exist. */ public static final int PROCESS_STATE_NONEXISTENT = 19; - // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added - // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must + // NOTE: If PROCESS_STATEs are added, then new fields must be added + // to frameworks/base/core/proto/android/app/enums.proto and the following method must // be updated to correctly map between them. + // However, if the current ActivityManager values are merely modified, no update should be made + // to enums.proto, to which values can only be added but never modified. Note that the proto + // versions do NOT have the ordering restrictions of the ActivityManager process state. /** - * Maps ActivityManager.PROCESS_STATE_ values to ProcessState enum. + * Maps ActivityManager.PROCESS_STATE_ values to enums.proto ProcessStateEnum value. * * @param amInt a process state of the form ActivityManager.PROCESS_STATE_ - * @return the value of the corresponding ActivityManager's ProcessState enum. + * @return the value of the corresponding enums.proto ProcessStateEnum value. * @hide */ public static final int processStateAmToProto(int amInt) { - return amInt * 100; + switch (amInt) { + case PROCESS_STATE_UNKNOWN: + return AppProtoEnums.PROCESS_STATE_UNKNOWN; + case PROCESS_STATE_PERSISTENT: + return AppProtoEnums.PROCESS_STATE_PERSISTENT; + case PROCESS_STATE_PERSISTENT_UI: + return AppProtoEnums.PROCESS_STATE_PERSISTENT_UI; + case PROCESS_STATE_TOP: + return AppProtoEnums.PROCESS_STATE_TOP; + case PROCESS_STATE_FOREGROUND_SERVICE: + return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE; + case PROCESS_STATE_BOUND_FOREGROUND_SERVICE: + return AppProtoEnums.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + case PROCESS_STATE_IMPORTANT_FOREGROUND: + return AppProtoEnums.PROCESS_STATE_IMPORTANT_FOREGROUND; + case PROCESS_STATE_IMPORTANT_BACKGROUND: + return AppProtoEnums.PROCESS_STATE_IMPORTANT_BACKGROUND; + case PROCESS_STATE_TRANSIENT_BACKGROUND: + return AppProtoEnums.PROCESS_STATE_TRANSIENT_BACKGROUND; + case PROCESS_STATE_BACKUP: + return AppProtoEnums.PROCESS_STATE_BACKUP; + case PROCESS_STATE_SERVICE: + return AppProtoEnums.PROCESS_STATE_SERVICE; + case PROCESS_STATE_RECEIVER: + return AppProtoEnums.PROCESS_STATE_RECEIVER; + case PROCESS_STATE_TOP_SLEEPING: + return AppProtoEnums.PROCESS_STATE_TOP_SLEEPING; + case PROCESS_STATE_HEAVY_WEIGHT: + return AppProtoEnums.PROCESS_STATE_HEAVY_WEIGHT; + case PROCESS_STATE_HOME: + return AppProtoEnums.PROCESS_STATE_HOME; + case PROCESS_STATE_LAST_ACTIVITY: + return AppProtoEnums.PROCESS_STATE_LAST_ACTIVITY; + case PROCESS_STATE_CACHED_ACTIVITY: + return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY; + case PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; + case PROCESS_STATE_CACHED_RECENT: + return AppProtoEnums.PROCESS_STATE_CACHED_RECENT; + case PROCESS_STATE_CACHED_EMPTY: + return AppProtoEnums.PROCESS_STATE_CACHED_EMPTY; + case PROCESS_STATE_NONEXISTENT: + return AppProtoEnums.PROCESS_STATE_NONEXISTENT; + default: + // ActivityManager process state (amInt) + // could not be mapped to an AppProtoEnums ProcessState state. + return AppProtoEnums.PROCESS_STATE_UNKNOWN_TO_PROTO; + } } /** @hide The lowest process state number */ @@ -2654,17 +2710,22 @@ public class ActivityManager { /** * Permits an application to get the persistent URI permissions granted to another. * - *

Typically called by Settings. + *

Typically called by Settings or DocumentsUI, requires + * {@code GET_APP_GRANTED_URI_PERMISSIONS}. * - * @param packageName application to look for the granted permissions + * @param packageName application to look for the granted permissions, or {@code null} to get + * granted permissions for all applications * @return list of granted URI permissions * * @hide */ - public ParceledListSlice getGrantedUriPermissions(String packageName) { + public ParceledListSlice getGrantedUriPermissions( + @Nullable String packageName) { try { - return getService().getGrantedUriPermissions(packageName, - UserHandle.myUserId()); + @SuppressWarnings("unchecked") + final ParceledListSlice castedList = getService() + .getGrantedUriPermissions(packageName, UserHandle.myUserId()); + return castedList; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2673,7 +2734,7 @@ public class ActivityManager { /** * Permits an application to clear the persistent URI permissions granted to another. * - *

Typically called by Settings. + *

Typically called by Settings, requires {@code CLEAR_APP_GRANTED_URI_PERMISSIONS}. * * @param packageName application to clear its granted permissions * diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 60a5a110193a61b8139199843edd26ef863bc392..5ee7edee9db7d1a4e195aebe0b6c15d46988c0fe 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -43,25 +43,29 @@ public abstract class ActivityManagerInternal { * Type for {@link #notifyAppTransitionStarting}: The transition was started because we drew * the splash screen. */ - public static final int APP_TRANSITION_SPLASH_SCREEN = 1; + public static final int APP_TRANSITION_SPLASH_SCREEN = + AppProtoEnums.APP_TRANSITION_SPLASH_SCREEN; // 1 /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because we all * app windows were drawn */ - public static final int APP_TRANSITION_WINDOWS_DRAWN = 2; + public static final int APP_TRANSITION_WINDOWS_DRAWN = + AppProtoEnums.APP_TRANSITION_WINDOWS_DRAWN; // 2 /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because of a * timeout. */ - public static final int APP_TRANSITION_TIMEOUT = 3; + public static final int APP_TRANSITION_TIMEOUT = + AppProtoEnums.APP_TRANSITION_TIMEOUT; // 3 /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because of a * we drew a task snapshot. */ - public static final int APP_TRANSITION_SNAPSHOT = 4; + public static final int APP_TRANSITION_SNAPSHOT = + AppProtoEnums.APP_TRANSITION_SNAPSHOT; // 4 /** * The bundle key to extract the assist data. @@ -319,4 +323,29 @@ public abstract class ActivityManagerInternal { } public abstract void registerScreenObserver(ScreenObserver observer); + + /** + * Returns if more users can be started without stopping currently running users. + */ + public abstract boolean canStartMoreUsers(); + + /** + * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}. + */ + public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage); + + /** + * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}. + */ + public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage); + + /** + * Returns maximum number of users that can run simultaneously. + */ + public abstract int getMaxRunningUsers(); + + /** + * Returns is the caller has the same uid as the Recents component + */ + public abstract boolean isCallerRecents(int callingUid); } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index e61c5b7c78a18264c73758e7a080896b7599af73..fee58274a5fc6c1b34f52e3a29c349236b742dcb 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -16,12 +16,14 @@ package android.app; +import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; @@ -44,6 +46,7 @@ import android.util.Pair; import android.util.Slog; import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; +import android.view.RemoteAnimationAdapter; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -203,6 +206,12 @@ public class ActivityOptions { private static final String KEY_TASK_OVERLAY_CAN_RESUME = "android.activity.taskOverlayCanResume"; + /** + * See {@link #setAvoidMoveToFront()}. + * @hide + */ + private static final String KEY_AVOID_MOVE_TO_FRONT = "android.activity.avoidMoveToFront"; + /** * Where the split-screen-primary stack should be positioned. * @hide @@ -241,6 +250,8 @@ public class ActivityOptions { private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE = "android:instantapps.installerbundle"; private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture"; + private static final String KEY_REMOTE_ANIMATION_ADAPTER + = "android:activity.remoteAnimationAdapter"; /** @hide */ public static final int ANIM_NONE = 0; @@ -268,6 +279,8 @@ public class ActivityOptions { public static final int ANIM_CLIP_REVEAL = 11; /** @hide */ public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12; + /** @hide */ + public static final int ANIM_REMOTE_ANIMATION = 13; private String mPackageName; private Rect mLaunchBounds; @@ -300,10 +313,12 @@ public class ActivityOptions { private boolean mDisallowEnterPictureInPictureWhileLaunching; private boolean mTaskOverlay; private boolean mTaskOverlayCanResume; + private boolean mAvoidMoveToFront; private AppTransitionAnimationSpec mAnimSpecs[]; private int mRotationAnimationHint = -1; private Bundle mAppVerificationBundle; private IAppTransitionAnimationSpecsFuture mSpecsFuture; + private RemoteAnimationAdapter mRemoteAnimationAdapter; /** * Create an ActivityOptions specifying a custom animation to run when @@ -826,6 +841,20 @@ public class ActivityOptions { return opts; } + /** + * Create an {@link ActivityOptions} instance that lets the application control the entire + * animation using a {@link RemoteAnimationAdapter}. + * @hide + */ + @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public static ActivityOptions makeRemoteAnimation( + RemoteAnimationAdapter remoteAnimationAdapter) { + final ActivityOptions opts = new ActivityOptions(); + opts.mRemoteAnimationAdapter = remoteAnimationAdapter; + opts.mAnimationType = ANIM_REMOTE_ANIMATION; + return opts; + } + /** @hide */ public boolean getLaunchTaskBehind() { return mAnimationType == ANIM_LAUNCH_TASK_BEHIND; @@ -901,6 +930,7 @@ public class ActivityOptions { mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false); mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false); + mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false); mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean( @@ -922,6 +952,7 @@ public class ActivityOptions { mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder( KEY_SPECS_FUTURE)); } + mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER); } /** @@ -1069,6 +1100,11 @@ public class ActivityOptions { return mSpecsFuture; } + /** @hide */ + public RemoteAnimationAdapter getRemoteAnimationAdapter() { + return mRemoteAnimationAdapter; + } + /** @hide */ public static ActivityOptions fromBundle(Bundle bOptions) { return bOptions != null ? new ActivityOptions(bOptions) : null; @@ -1211,6 +1247,25 @@ public class ActivityOptions { return mTaskOverlayCanResume; } + /** + * Sets whether the activity launched should not cause the activity stack it is contained in to + * be moved to the front as a part of launching. + * + * @hide + */ + public void setAvoidMoveToFront() { + mAvoidMoveToFront = true; + } + + /** + * @return whether the activity launch should prevent moving the associated activity stack to + * the front. + * @hide + */ + public boolean getAvoidMoveToFront() { + return mAvoidMoveToFront; + } + /** @hide */ public int getSplitScreenCreateMode() { return mSplitScreenCreateMode; @@ -1309,6 +1364,7 @@ public class ActivityOptions { mAnimSpecs = otherOptions.mAnimSpecs; mAnimationFinishedListener = otherOptions.mAnimationFinishedListener; mSpecsFuture = otherOptions.mSpecsFuture; + mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter; } /** @@ -1387,6 +1443,7 @@ public class ActivityOptions { b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId); b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay); b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume); + b.putBoolean(KEY_AVOID_MOVE_TO_FRONT, mAvoidMoveToFront); b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode); b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, mDisallowEnterPictureInPictureWhileLaunching); @@ -1403,7 +1460,9 @@ public class ActivityOptions { if (mAppVerificationBundle != null) { b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle); } - + if (mRemoteAnimationAdapter != null) { + b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter); + } return b; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e610ac4a318b869c32e1bbc46f4ebf83dc7688a8..6c3dbf4979c45998e0b6c831ebb62259d570a732 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -166,6 +166,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetAddress; import java.text.DateFormat; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -220,6 +221,9 @@ public final class ActivityThread extends ClientTransactionHandler { // Whether to invoke an activity callback after delivering new configuration. private static final boolean REPORT_TO_ACTIVITY = true; + // Maximum number of recent tokens to maintain for debugging purposes + private static final int MAX_RECENT_TOKENS = 10; + /** * Denotes an invalid sequence number corresponding to a process state change. */ @@ -252,6 +256,8 @@ public final class ActivityThread extends ClientTransactionHandler { final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); final ArrayMap mActivities = new ArrayMap<>(); + final ArrayDeque mRecentTokens = new ArrayDeque<>(); + // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. ActivityClientRecord mNewActivities = null; @@ -383,7 +389,7 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityInfo activityInfo; CompatibilityInfo compatInfo; - public LoadedApk loadedApk; + public LoadedApk packageInfo; List pendingResults; List pendingIntents; @@ -426,7 +432,7 @@ public final class ActivityThread extends ClientTransactionHandler { this.isForward = isForward; this.profilerInfo = profilerInfo; this.overrideConfig = overrideConfig; - this.loadedApk = client.getLoadedApkNoCheck(activityInfo.applicationInfo, + this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo, compatInfo); init(); } @@ -608,7 +614,7 @@ public final class ActivityThread extends ClientTransactionHandler { } static final class AppBindData { - LoadedApk loadedApk; + LoadedApk info; String processName; ApplicationInfo appInfo; List providers; @@ -1752,9 +1758,11 @@ public final class ActivityThread extends ClientTransactionHandler { handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1, (IVoiceInteractor) ((SomeArgs) msg.obj).arg2); break; - case ATTACH_AGENT: - handleAttachAgent((String) msg.obj); + case ATTACH_AGENT: { + Application app = getApplication(); + handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null); break; + } case APPLICATION_INFO_CHANGED: mUpdatingSystemConfig = true; try { @@ -1905,13 +1913,13 @@ public final class ActivityThread extends ClientTransactionHandler { return mH; } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags) { - return getLoadedApkForPackageName(packageName, compatInfo, flags, UserHandle.myUserId()); + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags) { + return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId()); } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags, int userId) { + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags, int userId) { final boolean differentUser = (UserHandle.myUserId() != userId); synchronized (mResourcesManager) { WeakReference ref; @@ -1924,13 +1932,13 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - //Slog.i(TAG, "getLoadedApkForPackageName " + packageName + ": " + loadedApk); - //if (loadedApk != null) Slog.i(TAG, "isUptoDate " + loadedApk.mResDir - // + ": " + loadedApk.mResources.getAssets().isUpToDate()); - if (loadedApk != null && (loadedApk.mResources == null - || loadedApk.mResources.getAssets().isUpToDate())) { - if (loadedApk.isSecurityViolation() + LoadedApk packageInfo = ref != null ? ref.get() : null; + //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); + //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir + // + ": " + packageInfo.mResources.getAssets().isUpToDate()); + if (packageInfo != null && (packageInfo.mResources == null + || packageInfo.mResources.getAssets().isUpToDate())) { + if (packageInfo.isSecurityViolation() && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { throw new SecurityException( "Requesting code from " + packageName @@ -1938,7 +1946,7 @@ public final class ActivityThread extends ClientTransactionHandler { + mBoundApplication.processName + "/" + mBoundApplication.appInfo.uid); } - return loadedApk; + return packageInfo; } } @@ -1953,13 +1961,13 @@ public final class ActivityThread extends ClientTransactionHandler { } if (ai != null) { - return getLoadedApk(ai, compatInfo, flags); + return getPackageInfo(ai, compatInfo, flags); } return null; } - public final LoadedApk getLoadedApk(ApplicationInfo ai, CompatibilityInfo compatInfo, + public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo, int flags) { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 @@ -1981,17 +1989,17 @@ public final class ActivityThread extends ClientTransactionHandler { throw new SecurityException(msg); } } - return getLoadedApk(ai, compatInfo, null, securityViolation, includeCode, + return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode, registerPackage); } @Override - public final LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { - return getLoadedApk(ai, compatInfo, null, false, true, false); + return getPackageInfo(ai, compatInfo, null, false, true, false); } - public final LoadedApk peekLoadedApk(String packageName, boolean includeCode) { + public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { synchronized (mResourcesManager) { WeakReference ref; if (includeCode) { @@ -2003,7 +2011,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - private LoadedApk getLoadedApk(ApplicationInfo aInfo, CompatibilityInfo compatInfo, + private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); @@ -2018,35 +2026,35 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(aInfo.packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk == null || (loadedApk.mResources != null - && !loadedApk.mResources.getAssets().isUpToDate())) { + LoadedApk packageInfo = ref != null ? ref.get() : null; + if (packageInfo == null || (packageInfo.mResources != null + && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); - loadedApk = + packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); if (mSystemThread && "android".equals(aInfo.packageName)) { - loadedApk.installSystemApplicationInfo(aInfo, - getSystemContext().mLoadedApk.getClassLoader()); + packageInfo.installSystemApplicationInfo(aInfo, + getSystemContext().mPackageInfo.getClassLoader()); } if (differentUser) { // Caching not supported across users } else if (includeCode) { mPackages.put(aInfo.packageName, - new WeakReference(loadedApk)); + new WeakReference(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, - new WeakReference(loadedApk)); + new WeakReference(packageInfo)); } } - return loadedApk; + return packageInfo; } } @@ -2166,6 +2174,18 @@ public final class ActivityThread extends ClientTransactionHandler { pw.println(String.format(format, objs)); } + @Override + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mActivities:"); + + for (ArrayMap.Entry entry : mActivities.entrySet()) { + pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:" + + entry.getValue().toString() + "]"); + } + + pw.println(prefix + "mRecentTokens:" + mRecentTokens); + } + public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, int pid, String processName, @@ -2758,8 +2778,8 @@ public final class ActivityThread extends ClientTransactionHandler { /** Core implementation of activity launch. */ private Activity performLaunchActivity(ActivityClientRecord r) { ActivityInfo aInfo = r.activityInfo; - if (r.loadedApk == null) { - r.loadedApk = getLoadedApk(aInfo.applicationInfo, r.compatInfo, + if (r.packageInfo == null) { + r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } @@ -2796,15 +2816,15 @@ public final class ActivityThread extends ClientTransactionHandler { } try { - Application app = r.loadedApk.makeApplication(false, mInstrumentation); + Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + r.loadedApk.getPackageName() + + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() - + ", dir=" + r.loadedApk.getAppDir()); + + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); @@ -2850,6 +2870,11 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_CREATE); mActivities.put(r.token, r); + mRecentTokens.push(r.token.hashCode()); + + if (mRecentTokens.size() > MAX_RECENT_TOKENS) { + mRecentTokens.removeLast(); + } } catch (SuperNotCalledException e) { throw e; @@ -2944,7 +2969,7 @@ public final class ActivityThread extends ClientTransactionHandler { } ContextImpl appContext = ContextImpl.createActivityContext( - this, r.loadedApk, r.activityInfo, r.token, displayId, r.overrideConfig); + this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); // For debugging purposes, if the activity's package name contains the value of @@ -2952,7 +2977,7 @@ public final class ActivityThread extends ClientTransactionHandler { // its content on a secondary display if there is one. String pkgName = SystemProperties.get("debug.second-display.pkg"); if (pkgName != null && !pkgName.isEmpty() - && r.loadedApk.mPackageName.contains(pkgName)) { + && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { if (id != Display.DEFAULT_DISPLAY) { Display display = @@ -3071,7 +3096,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverNewIntents(r, intents); if (resumed) { - r.activity.performResume(); + r.activity.performResume(false); r.activity.mTemporaryPause = false; } @@ -3246,11 +3271,23 @@ public final class ActivityThread extends ClientTransactionHandler { } } - static final void handleAttachAgent(String agent) { + private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) { try { - VMDebug.attachAgent(agent); + VMDebug.attachAgent(agent, classLoader); + return true; } catch (IOException e) { - Slog.e(TAG, "Attaching agent failed: " + agent); + Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent); + return false; + } + } + + static void handleAttachAgent(String agent, LoadedApk loadedApk) { + ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null; + if (attemptAttachAgent(agent, classLoader)) { + return; + } + if (classLoader != null) { + attemptAttachAgent(agent, null); } } @@ -3272,7 +3309,7 @@ public final class ActivityThread extends ClientTransactionHandler { String component = data.intent.getComponent().getClassName(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); IActivityManager mgr = ActivityManager.getService(); @@ -3281,7 +3318,7 @@ public final class ActivityThread extends ClientTransactionHandler { BroadcastReceiver receiver; ContextImpl context; try { - app = loadedApk.makeApplication(false, mInstrumentation); + app = packageInfo.makeApplication(false, mInstrumentation); context = (ContextImpl) app.getBaseContext(); if (data.info.splitName != null) { context = (ContextImpl) context.createContextForSplit(data.info.splitName); @@ -3290,7 +3327,7 @@ public final class ActivityThread extends ClientTransactionHandler { data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); - receiver = loadedApk.getAppFactory() + receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { if (DEBUG_BROADCAST) Slog.i(TAG, @@ -3306,9 +3343,9 @@ public final class ActivityThread extends ClientTransactionHandler { TAG, "Performing receive of " + data.intent + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + loadedApk.getPackageName() + + ", pkg=" + packageInfo.getPackageName() + ", comp=" + data.intent.getComponent().toShortString() - + ", dir=" + loadedApk.getAppDir()); + + ", dir=" + packageInfo.getAppDir()); sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); @@ -3353,8 +3390,8 @@ public final class ActivityThread extends ClientTransactionHandler { unscheduleGcIdler(); // instantiate the BackupAgent class named in the manifest - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; if (packageName == null) { Slog.d(TAG, "Asked to create backup agent for nonexistent package"); return; @@ -3380,11 +3417,11 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); - java.lang.ClassLoader cl = loadedApk.getClassLoader(); + java.lang.ClassLoader cl = packageInfo.getClassLoader(); agent = (BackupAgent) cl.loadClass(classname).newInstance(); // set up the agent's context - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(agent); agent.attach(context); @@ -3420,8 +3457,8 @@ public final class ActivityThread extends ClientTransactionHandler { private void handleDestroyBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; BackupAgent agent = mBackupAgents.get(packageName); if (agent != null) { try { @@ -3441,12 +3478,12 @@ public final class ActivityThread extends ClientTransactionHandler { // we are back active so skip it. unscheduleGcIdler(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { - java.lang.ClassLoader cl = loadedApk.getClassLoader(); - service = loadedApk.getAppFactory() + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = packageInfo.getAppFactory() .instantiateService(cl, data.info.name, data.intent); } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { @@ -3459,10 +3496,10 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); - Application app = loadedApk.makeApplication(false, mInstrumentation); + Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); @@ -3681,7 +3718,7 @@ public final class ActivityThread extends ClientTransactionHandler { deliverResults(r, r.pendingResults); r.pendingResults = null; } - r.activity.performResume(); + r.activity.performResume(r.startsNotResumed); synchronized (mResourcesManager) { // If there is a pending local relaunch that was requested when the activity was @@ -4326,11 +4363,11 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { - LoadedApk apk = peekLoadedApk(data.pkg, false); + LoadedApk apk = peekPackageInfo(data.pkg, false); if (apk != null) { apk.setCompatibilityInfo(data.info); } - apk = peekLoadedApk(data.pkg, true); + apk = peekPackageInfo(data.pkg, true); if (apk != null) { apk.setCompatibilityInfo(data.info); } @@ -4400,7 +4437,7 @@ public final class ActivityThread extends ClientTransactionHandler { checkAndBlockForNetworkAccess(); deliverResults(r, results); if (resumed) { - r.activity.performResume(); + r.activity.performResume(false); r.activity.mTemporaryPause = false; } } @@ -4824,7 +4861,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (a != null) { Configuration thisConfig = applyConfigCompatMainThread( mCurDefaultDisplayDpi, newConfig, - ar.loadedApk.getCompatibilityInfo()); + ar.packageInfo.getCompatibilityInfo()); if (!ar.activity.mFinished && (allActivities || !ar.paused)) { // If the activity is currently resumed, its configuration // needs to change right now. @@ -5077,6 +5114,8 @@ public final class ActivityThread extends ClientTransactionHandler { // as that method uses the same check on the activity config override as well. final boolean equivalent = config != null && mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); + final Theme systemTheme = getSystemContext().getTheme(); + final Theme systemUiTheme = getSystemUiContext().getTheme(); synchronized (mResourcesManager) { if (mPendingConfiguration != null) { @@ -5109,12 +5148,10 @@ public final class ActivityThread extends ClientTransactionHandler { configDiff = mConfiguration.updateFrom(config); config = applyCompatConfiguration(mCurDefaultDisplayDpi); - final Theme systemTheme = getSystemContext().getTheme(); if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { systemTheme.rebase(); } - final Theme systemUiTheme = getSystemUiContext().getTheme(); if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { systemUiTheme.rebase(); } @@ -5310,7 +5347,7 @@ public final class ActivityThread extends ClientTransactionHandler { } final void handleDispatchPackageBroadcast(int cmd, String[] packages) { - boolean hasLoadedApk = false; + boolean hasPkgInfo = false; switch (cmd) { case ApplicationThreadConstants.PACKAGE_REMOVED: case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL: @@ -5321,14 +5358,14 @@ public final class ActivityThread extends ClientTransactionHandler { } synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { - if (!hasLoadedApk) { + if (!hasPkgInfo) { WeakReference ref = mPackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } } } @@ -5348,21 +5385,21 @@ public final class ActivityThread extends ClientTransactionHandler { synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { WeakReference ref = mPackages.get(packages[i]); - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + LoadedApk pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); - loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } } // If the package is being replaced, yet it still has a valid // LoadedApk object, the package was updated with _DONT_KILL. // Adjust it's internal references to the application info and // resources. - if (loadedApk != null) { + if (pkgInfo != null) { try { final String packageName = packages[i]; final ApplicationInfo aInfo = @@ -5376,13 +5413,13 @@ public final class ActivityThread extends ClientTransactionHandler { if (ar.activityInfo.applicationInfo.packageName .equals(packageName)) { ar.activityInfo.applicationInfo = aInfo; - ar.loadedApk = loadedApk; + ar.packageInfo = pkgInfo; } } } final List oldPaths = sPackageManager.getPreviousCodePaths(packageName); - loadedApk.updateApplicationInfo(aInfo, oldPaths); + pkgInfo.updateApplicationInfo(aInfo, oldPaths); } catch (RemoteException e) { } } @@ -5391,7 +5428,7 @@ public final class ActivityThread extends ClientTransactionHandler { break; } } - ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasLoadedApk); + ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); } final void handleLowMemory() { @@ -5542,12 +5579,16 @@ public final class ActivityThread extends ClientTransactionHandler { mCompatConfiguration = new Configuration(data.config); mProfiler = new Profiler(); + String agent = null; if (data.initProfilerInfo != null) { mProfiler.profileFile = data.initProfilerInfo.profileFile; mProfiler.profileFd = data.initProfilerInfo.profileFd; mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval; mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler; mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput; + if (data.initProfilerInfo.attachAgentDuringBind) { + agent = data.initProfilerInfo.agent; + } } // send up app name; do this *before* waiting for debugger @@ -5595,7 +5636,11 @@ public final class ActivityThread extends ClientTransactionHandler { applyCompatConfiguration(mCurDefaultDisplayDpi); } - data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + + if (agent != null) { + handleAttachAgent(agent, data.info); + } /** * Switch this process to density compatibility mode if needed. @@ -5639,7 +5684,7 @@ public final class ActivityThread extends ClientTransactionHandler { // XXX should have option to change the port. Debug.changeDebugPort(8100); if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " is waiting for the debugger on port 8100..."); IActivityManager mgr = ActivityManager.getService(); @@ -5658,7 +5703,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " can be debugged on port 8100..."); } } @@ -5706,14 +5751,14 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentationAppDir = ii.sourceDir; mInstrumentationSplitAppDirs = ii.splitSourceDirs; mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii); - mInstrumentedAppDir = data.loadedApk.getAppDir(); - mInstrumentedSplitAppDirs = data.loadedApk.getSplitAppDirs(); - mInstrumentedLibDir = data.loadedApk.getLibDir(); + mInstrumentedAppDir = data.info.getAppDir(); + mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); + mInstrumentedLibDir = data.info.getLibDir(); } else { ii = null; } - final ContextImpl appContext = ContextImpl.createAppContext(this, data.loadedApk); + final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); updateLocaleListFromAppContext(appContext, mResourcesManager.getConfiguration().getLocales()); @@ -5757,9 +5802,9 @@ public final class ActivityThread extends ClientTransactionHandler { } ii.copyTo(instrApp); instrApp.initForUser(UserHandle.myUserId()); - final LoadedApk loadedApk = getLoadedApk(instrApp, data.compatInfo, + final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true, false); - final ContextImpl instrContext = ContextImpl.createAppContext(this, loadedApk); + final ContextImpl instrContext = ContextImpl.createAppContext(this, pi); try { final ClassLoader cl = instrContext.getClassLoader(); @@ -5804,7 +5849,7 @@ public final class ActivityThread extends ClientTransactionHandler { try { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. - app = data.loadedApk.makeApplication(data.restrictedBackupMode, null); + app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; // don't bring up providers in restricted mode; they may depend on the @@ -5858,7 +5903,7 @@ public final class ActivityThread extends ClientTransactionHandler { final int preloadedFontsResource = info.metaData.getInt( ApplicationInfo.METADATA_PRELOADED_FONTS, 0); if (preloadedFontsResource != 0) { - data.loadedApk.getResources().preloadFonts(preloadedFontsResource); + data.info.getResources().preloadFonts(preloadedFontsResource); } } } catch (RemoteException e) { @@ -6316,12 +6361,12 @@ public final class ActivityThread extends ClientTransactionHandler { try { final java.lang.ClassLoader cl = c.getClassLoader(); - LoadedApk loadedApk = peekLoadedApk(ai.packageName, true); - if (loadedApk == null) { + LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); + if (packageInfo == null) { // System startup case. - loadedApk = getSystemContext().mLoadedApk; + packageInfo = getSystemContext().mPackageInfo; } - localProvider = loadedApk.getAppFactory() + localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name); provider = localProvider.getIContentProvider(); if (provider == null) { @@ -6470,8 +6515,8 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentation = new Instrumentation(); mInstrumentation.basicInit(this); ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mLoadedApk); - mInitialApplication = context.mLoadedApk.makeApplication(true, null); + this, getSystemContext().mPackageInfo); + mInitialApplication = context.mPackageInfo.makeApplication(true, null); mInitialApplication.onCreate(); } catch (Exception e) { throw new RuntimeException( diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 9f1e98399dce3353dcd16c7517af9c5b72858917..5d0143a505f321328cce47dbc38f498bca55da94 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.NonNull; +import android.app.ActivityManager.StackInfo; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; @@ -34,9 +35,12 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import dalvik.system.CloseGuard; +import java.util.List; + /** * Activity container that allows launching activities into itself and does input forwarding. *

Creation of this view is only allowed to callers who have @@ -57,7 +61,12 @@ public class ActivityView extends ViewGroup { private final SurfaceCallback mSurfaceCallback; private StateCallback mActivityViewCallback; + private IActivityManager mActivityManager; private IInputForwarder mInputForwarder; + // Temp container to store view coordinates on screen. + private final int[] mLocationOnScreen = new int[2]; + + private TaskStackListener mTaskStackListener; private final CloseGuard mGuard = CloseGuard.get(); private boolean mOpened; // Protected by mGuard. @@ -73,6 +82,7 @@ public class ActivityView extends ViewGroup { public ActivityView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mActivityManager = ActivityManager.getService(); mSurfaceView = new SurfaceView(context); mSurfaceCallback = new SurfaceCallback(); mSurfaceView.getHolder().addCallback(mSurfaceCallback); @@ -198,11 +208,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 +270,7 @@ public class ActivityView extends ViewGroup { } else { mVirtualDisplay.setSurface(surfaceHolder.getSurface()); } + updateLocation(); } @Override @@ -248,6 +278,7 @@ public class ActivityView extends ViewGroup { if (mVirtualDisplay != null) { mVirtualDisplay.resize(width, height, getBaseDisplayDensity()); } + updateLocation(); } @Override @@ -257,6 +288,7 @@ public class ActivityView extends ViewGroup { if (mVirtualDisplay != null) { mVirtualDisplay.setSurface(null); } + cleanTapExcludeRegion(); } } @@ -278,6 +310,12 @@ public class ActivityView extends ViewGroup { mInputForwarder = InputManager.getInstance().createInputForwarder( mVirtualDisplay.getDisplay().getDisplayId()); + mTaskStackListener = new TaskBackgroundChangeListener(); + try { + mActivityManager.registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register task stack listener", e); + } } private void performRelease() { @@ -290,6 +328,16 @@ public class ActivityView extends ViewGroup { if (mInputForwarder != null) { mInputForwarder = null; } + cleanTapExcludeRegion(); + + if (mTaskStackListener != null) { + try { + mActivityManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister task stack listener", e); + } + mTaskStackListener = null; + } final boolean displayReleased; if (mVirtualDisplay != null) { @@ -313,6 +361,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); @@ -332,4 +391,42 @@ public class ActivityView extends ViewGroup { super.finalize(); } } + + /** + * A task change listener that detects background color change of the topmost stack on our + * virtual display and updates the background of the surface view. This background will be shown + * when surface view is resized, but the app hasn't drawn its content in new size yet. + */ + private class TaskBackgroundChangeListener extends TaskStackListener { + + @Override + public void onTaskDescriptionChanged(int taskId, ActivityManager.TaskDescription td) + throws RemoteException { + if (mVirtualDisplay == null) { + return; + } + + // Find the topmost task on our virtual display - it will define the background + // color of the surface view during resizing. + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + final List stackInfoList = mActivityManager.getAllStackInfos(); + + // Iterate through stacks from top to bottom. + final int stackCount = stackInfoList.size(); + for (int i = 0; i < stackCount; i++) { + final StackInfo stackInfo = stackInfoList.get(i); + // Only look for stacks on our virtual display. + if (stackInfo.displayId != displayId) { + continue; + } + // Found the topmost stack on target display. Now check if the topmost task's + // description changed. + if (taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mSurfaceView.setResizeBackgroundColor(td.getBackgroundColor()); + } + break; + } + } + } + } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 57f9f67b4f9c3b579a26628e5a0d14c4ed23b869..e923fb217beaec2b860d4aa9b2177ab9eba9a52d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -263,8 +263,10 @@ public class AppOpsManager { public static final int OP_REQUEST_DELETE_PACKAGES = 72; /** @hide Bind an accessibility service. */ public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73; + /** @hide Continue handover of a call from another app */ + public static final int OP_ACCEPT_HANDOVER = 74; /** @hide */ - public static final int _NUM_OP = 74; + public static final int _NUM_OP = 75; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -378,7 +380,13 @@ public class AppOpsManager { /** Answer incoming phone calls */ public static final String OPSTR_ANSWER_PHONE_CALLS = "android:answer_phone_calls"; - + /** + * Accept call handover + * @hide + */ + @SystemApi @TestApi + public static final String OPSTR_ACCEPT_HANDOVER + = "android:accept_handover"; /** @hide */ @SystemApi @TestApi public static final String OPSTR_GPS = "android:gps"; @@ -528,6 +536,7 @@ public class AppOpsManager { OP_USE_SIP, OP_PROCESS_OUTGOING_CALLS, OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER, // Microphone OP_RECORD_AUDIO, // Camera @@ -626,6 +635,7 @@ public class AppOpsManager { OP_CHANGE_WIFI_STATE, OP_REQUEST_DELETE_PACKAGES, OP_BIND_ACCESSIBILITY_SERVICE, + OP_ACCEPT_HANDOVER, }; /** @@ -706,6 +716,7 @@ public class AppOpsManager { OPSTR_CHANGE_WIFI_STATE, OPSTR_REQUEST_DELETE_PACKAGES, OPSTR_BIND_ACCESSIBILITY_SERVICE, + OPSTR_ACCEPT_HANDOVER, }; /** @@ -787,6 +798,7 @@ public class AppOpsManager { "CHANGE_WIFI_STATE", "REQUEST_DELETE_PACKAGES", "BIND_ACCESSIBILITY_SERVICE", + "ACCEPT_HANDOVER", }; /** @@ -868,6 +880,7 @@ public class AppOpsManager { Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.BIND_ACCESSIBILITY_SERVICE, + Manifest.permission.ACCEPT_HANDOVER, }; /** @@ -950,6 +963,7 @@ public class AppOpsManager { null, // OP_CHANGE_WIFI_STATE null, // REQUEST_DELETE_PACKAGES null, // OP_BIND_ACCESSIBILITY_SERVICE + null, // ACCEPT_HANDOVER }; /** @@ -1031,6 +1045,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** @@ -1111,6 +1126,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES AppOpsManager.MODE_ALLOWED, // OP_BIND_ACCESSIBILITY_SERVICE + AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER }; /** @@ -1195,6 +1211,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** @@ -1325,6 +1342,25 @@ public class AppOpsManager { return sOpDefaultMode[op]; } + /** + * Retrieve the human readable mode. + * @hide + */ + public static String modeToString(int mode) { + switch (mode) { + case MODE_ALLOWED: + return "allow"; + case MODE_IGNORED: + return "ignore"; + case MODE_ERRORED: + return "deny"; + case MODE_DEFAULT: + return "default"; + default: + return "mode=" + mode; + } + } + /** * Retrieve whether the op allows itself to be reset. * @hide diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 5822f5c8f6c1e1bccc79150f1bcfafb448a196a9..a13ac49bb55370b9263fa2bb67bb837ff4939553 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -86,11 +86,21 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { /** * Called when the application is starting, before any activity, service, * or receiver objects (excluding content providers) have been created. - * Implementations should be as quick as possible (for example using + * + *

Implementations should be as quick as possible (for example using * lazy initialization of state) since the time spent in this function * directly impacts the performance of starting the first activity, - * service, or receiver in a process. - * If you override this method, be sure to call super.onCreate(). + * service, or receiver in a process.

+ * + *

If you override this method, be sure to call {@code super.onCreate()}.

+ * + *

Be aware that direct boot may also affect callback order on + * Android {@link android.os.Build.VERSION_CODES#N} and later devices. + * Until the user unlocks the device, only direct boot aware components are + * allowed to run. You should consider that all direct boot unaware + * components, including such {@link android.content.ContentProvider}, are + * disabled until user unlock happens, especially when component callback + * order matters.

*/ @CallSuper public void onCreate() { @@ -187,7 +197,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { */ /* package */ final void attach(Context context) { attachBaseContext(context); - mLoadedApk = ContextImpl.getImpl(context).mLoadedApk; + mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; } /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 4048e6596cfcd8d0c4f90a46a08c2e4be1043cae..6b0a2f90c38dca04a81b1571f265db20011885a1 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -222,9 +222,18 @@ public class ApplicationPackageManager extends PackageManager { @Override public Intent getLeanbackLaunchIntentForPackage(String packageName) { - // Try to find a main leanback_launcher activity. + return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_LEANBACK_LAUNCHER); + } + + @Override + public Intent getCarLaunchIntentForPackage(String packageName) { + return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_CAR_LAUNCHER); + } + + private Intent getLaunchIntentForPackageAndCategory(String packageName, String category) { + // Try to find a main launcher activity for the given categories. Intent intentToResolve = new Intent(Intent.ACTION_MAIN); - intentToResolve.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER); + intentToResolve.addCategory(category); intentToResolve.setPackage(packageName); List ris = queryIntentActivities(intentToResolve, 0); @@ -689,6 +698,26 @@ public class ApplicationPackageManager extends PackageManager { } } + @Override + public boolean hasSigningCertificate( + String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) { + try { + return mPM.hasSigningCertificate(packageName, certificate, type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean hasSigningCertificate( + int uid, byte[] certificate, @PackageManager.CertificateInputType int type) { + try { + return mPM.hasUidSigningCertificate(uid, certificate, type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override public String[] getPackagesForUid(int uid) { try { @@ -1380,7 +1409,7 @@ public class ApplicationPackageManager extends PackageManager { sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - mContext.mLoadedApk); + mContext.mPackageInfo); if (r != null) { return r; } diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 45c0e0cdfb257bf4468aea5b1e8e7bfd369cab8a..5b61fdf8677fbad24e7981b52c28360db25c6212 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -24,6 +24,7 @@ import android.os.IBinder; import com.android.internal.content.ReferrerIntent; +import java.io.PrintWriter; import java.util.List; /** @@ -110,7 +111,7 @@ public abstract class ClientTransactionHandler { PendingTransactionActions pendingActions); /** Get package info. */ - public abstract LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo); /** Deliver app configuration change notification. */ @@ -121,4 +122,11 @@ public abstract class ClientTransactionHandler { * provided token. */ public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token); + + /** + * Debugging output. + * @param pw {@link PrintWriter} to write logs to. + * @param prefix Prefix to prepend to output. + */ + public abstract void dump(PrintWriter pw, String prefix); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 165343058a2aa6adca91207a5417a09ff12c68da..6496110ac9c6aef46f2c86c186c16943861f2224 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -159,7 +159,7 @@ class ContextImpl extends Context { private ArrayMap mSharedPrefsPaths; final @NonNull ActivityThread mMainThread; - final @NonNull LoadedApk mLoadedApk; + final @NonNull LoadedApk mPackageInfo; private @Nullable ClassLoader mClassLoader; private final @Nullable IBinder mActivityToken; @@ -257,8 +257,8 @@ class ContextImpl extends Context { @Override public Context getApplicationContext() { - return (mLoadedApk != null) ? - mLoadedApk.getApplication() : mMainThread.getApplication(); + return (mPackageInfo != null) ? + mPackageInfo.getApplication() : mMainThread.getApplication(); } @Override @@ -302,15 +302,15 @@ class ContextImpl extends Context { @Override public ClassLoader getClassLoader() { - return mClassLoader != null ? mClassLoader : (mLoadedApk != null ? mLoadedApk.getClassLoader() : ClassLoader.getSystemClassLoader()); + return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader()); } @Override public String getPackageName() { - if (mLoadedApk != null) { - return mLoadedApk.getPackageName(); + if (mPackageInfo != null) { + return mPackageInfo.getPackageName(); } - // No mLoadedApk means this is a Context for the system itself, + // No mPackageInfo means this is a Context for the system itself, // and this here is its name. return "android"; } @@ -329,24 +329,24 @@ class ContextImpl extends Context { @Override public ApplicationInfo getApplicationInfo() { - if (mLoadedApk != null) { - return mLoadedApk.getApplicationInfo(); + if (mPackageInfo != null) { + return mPackageInfo.getApplicationInfo(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageResourcePath() { - if (mLoadedApk != null) { - return mLoadedApk.getResDir(); + if (mPackageInfo != null) { + return mPackageInfo.getResDir(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageCodePath() { - if (mLoadedApk != null) { - return mLoadedApk.getAppDir(); + if (mPackageInfo != null) { + return mPackageInfo.getAppDir(); } throw new RuntimeException("Not supported in system context"); } @@ -356,7 +356,7 @@ class ContextImpl extends Context { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. - if (mLoadedApk.getApplicationInfo().targetSdkVersion < + if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; @@ -872,13 +872,19 @@ class ContextImpl extends Context { // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is // generally not allowed, except if the caller specifies the task id the activity should - // be launched in. - if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0 - && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) { + // be launched in. A bug was existed between N and O-MR1 which allowed this to work. We + // maintain this for backwards compatibility. + final int targetSdkVersion = getApplicationInfo().targetSdkVersion; + + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 + && (targetSdkVersion < Build.VERSION_CODES.N + || targetSdkVersion >= Build.VERSION_CODES.P) + && (options == null + || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " - + " context requires the FLAG_ACTIVITY_NEW_TASK flag." - + " Is this really what you want?"); + + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, @@ -907,14 +913,14 @@ class ContextImpl extends Context { /** @hide */ @Override - public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivities() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent." + " Is this really what you want?"); } - mMainThread.getInstrumentation().execStartActivitiesAsUser( + return mMainThread.getInstrumentation().execStartActivitiesAsUser( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intents, options, userHandle.getIdentifier()); } @@ -1098,11 +1104,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1202,11 +1208,11 @@ class ContextImpl extends Context { Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1256,11 +1262,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1338,11 +1344,11 @@ class ContextImpl extends Context { Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1419,11 +1425,11 @@ class ContextImpl extends Context { Handler scheduler, Context context, int flags) { IIntentReceiver rd = null; if (receiver != null) { - if (mLoadedApk != null && context != null) { + if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { @@ -1450,8 +1456,8 @@ class ContextImpl extends Context { @Override public void unregisterReceiver(BroadcastReceiver receiver) { - if (mLoadedApk != null) { - IIntentReceiver rd = mLoadedApk.forgetReceiverDispatcher( + if (mPackageInfo != null) { + IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher( getOuterContext(), receiver); try { ActivityManager.getService().unregisterReceiver(rd); @@ -1584,7 +1590,7 @@ class ContextImpl extends Context { @Override public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, int flags) { - return mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + return mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } /** @hide */ @@ -1606,16 +1612,16 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - sd = mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + if (mPackageInfo != null) { + sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } else { throw new RuntimeException("Not supported in system context"); } validateServiceIntent(service); try { IBinder token = getActivityToken(); - if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mLoadedApk != null - && mLoadedApk.getApplicationInfo().targetSdkVersion + if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null + && mPackageInfo.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } @@ -1639,8 +1645,8 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - IServiceConnection sd = mLoadedApk.forgetServiceDispatcher( + if (mPackageInfo != null) { + IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( getOuterContext(), conn); try { ActivityManager.getService().unbindService(sd); @@ -1985,20 +1991,40 @@ class ContextImpl extends Context { } } + private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { + final String[] splitResDirs; + final ClassLoader classLoader; + try { + splitResDirs = pi.getSplitPaths(splitName); + classLoader = pi.getSplitClassLoader(splitName); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + return ResourcesManager.getInstance().getResources(activityToken, + pi.getResDir(), + splitResDirs, + pi.getOverlayDirs(), + pi.getApplicationInfo().sharedLibraryFiles, + displayId, + overrideConfig, + compatInfo, + classLoader); + } + @Override public Context createApplicationContext(ApplicationInfo application, int flags) throws NameNotFoundException { - LoadedApk loadedApk = mMainThread.getLoadedApk(application, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2022,21 +2048,20 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. - return new ContextImpl(this, mMainThread, mLoadedApk, null, mActivityToken, user, + return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user, flags, null); } - LoadedApk loadedApk = mMainThread.getLoadedApkForPackageName(packageName, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, user, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user, flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2050,21 +2075,30 @@ class ContextImpl extends Context { @Override public Context createContextForSplit(String splitName) throws NameNotFoundException { - if (!mLoadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { // All Splits are always loaded. return this; } - final ClassLoader classLoader = mLoadedApk.getSplitClassLoader(splitName); + final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName); + final String[] paths = mPackageInfo.getSplitPaths(splitName); - final ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, splitName, + final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName, mActivityToken, mUser, mFlags, classLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.getOrCreateResourcesForSplit(splitName, - mActivityToken, displayId)); + context.setResources(ResourcesManager.getInstance().getResources( + mActivityToken, + mPackageInfo.getResDir(), + paths, + mPackageInfo.getOverlayDirs(), + mPackageInfo.getApplicationInfo().sharedLibraryFiles, + displayId, + null, + mPackageInfo.getCompatibilityInfo(), + classLoader)); return context; } @@ -2074,11 +2108,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("overrideConfiguration must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2089,11 +2123,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("display must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = display.getDisplayId(); - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; @@ -2103,7 +2137,7 @@ class ContextImpl extends Context { public Context createDeviceProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2111,7 +2145,7 @@ class ContextImpl extends Context { public Context createCredentialProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2160,14 +2194,14 @@ class ContextImpl extends Context { @Override public File getDataDir() { - if (mLoadedApk != null) { + if (mPackageInfo != null) { File res = null; if (isCredentialProtectedStorage()) { - res = mLoadedApk.getCredentialProtectedDataDirFile(); + res = mPackageInfo.getCredentialProtectedDataDirFile(); } else if (isDeviceProtectedStorage()) { - res = mLoadedApk.getDeviceProtectedDataDirFile(); + res = mPackageInfo.getDeviceProtectedDataDirFile(); } else { - res = mLoadedApk.getDataDirFile(); + res = mPackageInfo.getDataDirFile(); } if (res != null) { @@ -2218,10 +2252,10 @@ class ContextImpl extends Context { } static ContextImpl createSystemContext(ActivityThread mainThread) { - LoadedApk loadedApk = new LoadedApk(mainThread); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + LoadedApk packageInfo = new LoadedApk(mainThread); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); return context; @@ -2232,35 +2266,35 @@ class ContextImpl extends Context { * Make sure that the created system UI context shares the same LoadedApk as the system context. */ static ContextImpl createSystemUiContext(ContextImpl systemContext) { - final LoadedApk loadedApk = systemContext.mLoadedApk; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, loadedApk, null, + final LoadedApk packageInfo = systemContext.mPackageInfo; + ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.createResources(null, null, Display.DEFAULT_DISPLAY, null, - loadedApk.getCompatibilityInfo())); + context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null, + packageInfo.getCompatibilityInfo())); return context; } - static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk loadedApk) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); return context; } static ContextImpl createActivityContext(ActivityThread mainThread, - LoadedApk loadedApk, ActivityInfo activityInfo, IBinder activityToken, int displayId, + LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); - String[] splitDirs = loadedApk.getSplitResDirs(); - ClassLoader classLoader = loadedApk.getClassLoader(); + String[] splitDirs = packageInfo.getSplitResDirs(); + ClassLoader classLoader = packageInfo.getClassLoader(); - if (loadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies"); try { - classLoader = loadedApk.getSplitClassLoader(activityInfo.splitName); - splitDirs = loadedApk.getSplitPaths(activityInfo.splitName); + classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName); + splitDirs = packageInfo.getSplitPaths(activityInfo.splitName); } catch (NameNotFoundException e) { // Nothing above us can handle a NameNotFoundException, better crash. throw new RuntimeException(e); @@ -2269,14 +2303,14 @@ class ContextImpl extends Context { } } - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName, + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader); // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) - ? loadedApk.getCompatibilityInfo() + ? packageInfo.getCompatibilityInfo() : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; final ResourcesManager resourcesManager = ResourcesManager.getInstance(); @@ -2284,10 +2318,10 @@ class ContextImpl extends Context { // Create the base resources for which all configuration contexts for this Activity // will be rebased upon. context.setResources(resourcesManager.createBaseActivityResources(activityToken, - loadedApk.getResDir(), + packageInfo.getResDir(), splitDirs, - loadedApk.getOverlayDirs(), - loadedApk.getApplicationInfo().sharedLibraryFiles, + packageInfo.getOverlayDirs(), + packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, @@ -2298,7 +2332,7 @@ class ContextImpl extends Context { } private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, - @NonNull LoadedApk loadedApk, @Nullable String splitName, + @NonNull LoadedApk packageInfo, @Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader) { mOuterContext = this; @@ -2307,10 +2341,10 @@ class ContextImpl extends Context { // location for application. if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE | Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) { - final File dataDir = loadedApk.getDataDirFile(); - if (Objects.equals(dataDir, loadedApk.getCredentialProtectedDataDirFile())) { + final File dataDir = packageInfo.getDataDirFile(); + if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) { flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - } else if (Objects.equals(dataDir, loadedApk.getDeviceProtectedDataDirFile())) { + } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) { flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE; } } @@ -2324,7 +2358,7 @@ class ContextImpl extends Context { } mUser = user; - mLoadedApk = loadedApk; + mPackageInfo = packageInfo; mSplitName = splitName; mClassLoader = classLoader; mResourcesManager = ResourcesManager.getInstance(); @@ -2335,8 +2369,8 @@ class ContextImpl extends Context { setResources(container.mResources); mDisplay = container.mDisplay; } else { - mBasePackageName = loadedApk.mPackageName; - ApplicationInfo ainfo = loadedApk.getApplicationInfo(); + mBasePackageName = packageInfo.mPackageName; + ApplicationInfo ainfo = packageInfo.getApplicationInfo(); if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) { // Special case: system components allow themselves to be loaded in to other // processes. For purposes of app ops, we must then consider the context as @@ -2359,7 +2393,7 @@ class ContextImpl extends Context { } void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { - mLoadedApk.installSystemApplicationInfo(info, classLoader); + mPackageInfo.installSystemApplicationInfo(info, classLoader); } final void scheduleFinalCleanup(String who, String what) { @@ -2368,7 +2402,7 @@ class ContextImpl extends Context { final void performFinalCleanup(String who, String what) { //Log.i(TAG, "Cleanup up context: " + this); - mLoadedApk.removeContextRegistrations(getOuterContext(), who, what); + mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); } final Context getReceiverRestrictedContext() { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index b162cb165fbaf3738ae3b297dbeaba2661e1fa90..2b648ea6937d0bf7824e3f795db9805c1c2cbdd8 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -16,10 +16,6 @@ package android.app; -import com.android.internal.R; -import com.android.internal.app.WindowDecorActionBar; -import com.android.internal.policy.PhoneWindow; - import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.IdRes; @@ -32,8 +28,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.DialogInterface; -import android.content.res.Configuration; import android.content.pm.ApplicationInfo; +import android.content.res.Configuration; import android.content.res.ResourceId; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -62,6 +58,10 @@ import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; +import com.android.internal.R; +import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.policy.PhoneWindow; + import java.lang.ref.WeakReference; /** @@ -512,12 +512,37 @@ public class Dialog implements DialogInterface, Window.Callback, * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise * @see View#findViewById(int) + * @see Dialog#requireViewById(int) */ @Nullable public T findViewById(@IdRes int id) { return mWindow.findViewById(id); } + /** + * Finds the first descendant view with the given ID or throws an IllegalArgumentException if + * the ID is invalid (< 0), there is no matching view in the hierarchy, or the dialog has not + * yet been fully created (for example, via {@link #show()} or {@link #create()}). + *

+ * Note: In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#requireViewById(int) + * @see Dialog#findViewById(int) + */ + @NonNull + public final T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Dialog"); + } + return view; + } + /** * Set the screen content from a layout resource. The resource will be * inflated, adding all top-level views to the screen. diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java index 427a0386e87c4f767295e4d0cc7a56c01a1a9965..d1c244136ec9858a23920e66301857ae4b164160 100644 --- a/core/java/android/app/EphemeralResolverService.java +++ b/core/java/android/app/EphemeralResolverService.java @@ -17,20 +17,10 @@ package android.app; import android.annotation.SystemApi; -import android.app.Service; -import android.app.InstantAppResolverService.InstantAppResolutionCallback; -import android.content.Context; -import android.content.Intent; import android.content.pm.EphemeralResolveInfo; import android.content.pm.InstantAppResolveInfo; import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.IRemoteCallback; import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; @@ -85,7 +75,6 @@ public abstract class EphemeralResolverService extends InstantAppResolverService return super.getLooper(); } - @Override void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { @@ -101,7 +90,6 @@ public abstract class EphemeralResolverService extends InstantAppResolverService callback.onInstantAppResolveInfo(resultList); } - @Override void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, String hostName, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { diff --git a/core/java/android/app/GrantedUriPermission.aidl b/core/java/android/app/GrantedUriPermission.aidl new file mode 100644 index 0000000000000000000000000000000000000000..2734af00cdaada2818d5c87d95f9a4dec70bdf04 --- /dev/null +++ b/core/java/android/app/GrantedUriPermission.aidl @@ -0,0 +1,20 @@ +/* + * 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.app; + +/** @hide */ +parcelable GrantedUriPermission; \ No newline at end of file diff --git a/core/java/android/app/GrantedUriPermission.java b/core/java/android/app/GrantedUriPermission.java new file mode 100644 index 0000000000000000000000000000000000000000..9e84fe13472f3ced3a3839afa697552ddd69a47c --- /dev/null +++ b/core/java/android/app/GrantedUriPermission.java @@ -0,0 +1,74 @@ +/* + * 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.UriPermission; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents an {@link UriPermission} granted to a package. + * + * {@hide} + */ +public class GrantedUriPermission implements Parcelable { + + public final Uri uri; + public final String packageName; + + public GrantedUriPermission(@NonNull Uri uri, @Nullable String packageName) { + this.uri = uri; + this.packageName = packageName; + } + + @Override + public String toString() { + return packageName + ":" + uri; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(uri, flags); + out.writeString(packageName); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public GrantedUriPermission createFromParcel(Parcel in) { + return new GrantedUriPermission(in); + } + + @Override + public GrantedUriPermission[] newArray(int size) { + return new GrantedUriPermission[size]; + } + }; + + private GrantedUriPermission(Parcel in) { + uri = in.readParcelable(null); + packageName = in.readString(); + } +} diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 696899f73b960b90366bf732a79841e874050d8d..02be00268a4565add36532715df516e584937a91 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -19,6 +19,7 @@ package android.app; import android.app.ActivityManager; import android.app.ApplicationErrorReport; import android.app.ContentProviderHolder; +import android.app.GrantedUriPermission; import android.app.IApplicationThread; import android.app.IActivityController; import android.app.IAppTask; @@ -65,6 +66,8 @@ import android.os.PersistableBundle; import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationDefinition; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; @@ -144,6 +147,7 @@ interface IActivityManager { void publishService(in IBinder token, in Intent intent, in IBinder service); void activityResumed(in IBinder token); void setDebugApp(in String packageName, boolean waitForDebugger, boolean persistent); + void setAgentApp(in String packageName, @nullable String agent); void setAlwaysFinish(boolean enabled); boolean startInstrumentation(in ComponentName className, in String profileFile, int flags, in Bundle arguments, in IInstrumentationWatcher watcher, @@ -356,6 +360,20 @@ interface IActivityManager { */ void requestTelephonyBugReport(in String shareTitle, in String shareDescription); + /** + * Deprecated - This method is only used by Wifi, and it will soon be replaced by a proper + * bug report API. + * + * Takes a minimal bugreport of Wifi-related state. + * + * @param shareTitle should be a valid legible string less than 50 chars long + * @param shareDescription should be less than 91 bytes when encoded into UTF-8 format + * + * @throws IllegalArgumentException if shareTitle or shareDescription is too big or if the + * parameters cannot be encoding to an UTF-8 charset. + */ + void requestWifiBugReport(in String shareTitle, in String shareDescription); + long inputDispatchingTimedOut(int pid, boolean aboveSystem, in String reason); void clearPendingBackup(); Intent getIntentForIntentSender(in IIntentSender sender); @@ -426,8 +444,9 @@ interface IActivityManager { in Bundle options, int userId); int startAssistantActivity(in String callingPackage, int callingPid, int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId); - int startRecentsActivity(in IAssistDataReceiver assistDataReceiver, in Bundle options, - in Bundle activityOptions, int userId); + void startRecentsActivity(in Intent intent, in IAssistDataReceiver assistDataReceiver, + in IRecentsAnimationRunner recentsAnimationRunner); + void cancelRecentsAnimation(); int startActivityFromRecents(int taskId, in Bundle options); Bundle getActivityOptions(in IBinder token); List getAppTasks(in String callingPackage); @@ -553,7 +572,7 @@ interface IActivityManager { in Rect tempDockedTaskInsetBounds, in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds); int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName); - // Gets the URI permissions granted to an arbitrary package. + // Gets the URI permissions granted to an arbitrary package (or all packages if null) // NOTE: this is different from getPersistedUriPermissions(), which returns the URIs the package // granted to another packages (instead of those granted to it). ParceledListSlice getGrantedUriPermissions(in String packageName, int userId); @@ -672,4 +691,9 @@ interface IActivityManager { * user unlock progress. */ boolean startUserInBackgroundWithListener(int userid, IProgressListener unlockProgressListener); + + /** + * Registers remote animations for a specific activity. + */ + void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition); } diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 4a85efd9e4c2a8678df0cb7a6785f0a16ce8ef59..3aeef148b4e7f9ed81f3c7d8098d478b6fc6ae2d 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -49,11 +49,13 @@ oneway interface IBackupAgent { * * @param callbackBinder Binder on which to indicate operation completion, * passed here as a convenience to the agent. + * + * @param transportFlags Flags with additional information about the transport. */ void doBackup(in ParcelFileDescriptor oldState, in ParcelFileDescriptor data, in ParcelFileDescriptor newState, - long quotaBytes, int token, IBackupManager callbackBinder); + long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags); /** * Restore an entire data snapshot to the application. @@ -100,13 +102,17 @@ oneway interface IBackupAgent { * * @param callbackBinder Binder on which to indicate operation completion, * passed here as a convenience to the agent. + * + * @param transportFlags Flags with additional information about transport. */ - void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token, IBackupManager callbackBinder); + void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token, + IBackupManager callbackBinder, int transportFlags); /** * Estimate how much data a full backup will deliver */ - void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder); + void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder, + int transportFlags); /** * Tells the application agent that the backup data size exceeded current transport quota. diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl index 805d8c057d27de960881930986f8771c5e657034..ae200578d829b17b12da01044e8f36f49a150c0c 100644 --- a/core/java/android/app/IInstantAppResolver.aidl +++ b/core/java/android/app/IInstantAppResolver.aidl @@ -16,13 +16,15 @@ package android.app; +import android.content.Intent; import android.os.IRemoteCallback; /** @hide */ oneway interface IInstantAppResolver { - void getInstantAppResolveInfoList(in int[] digestPrefix, + void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix, String token, int sequence, IRemoteCallback callback); - void getInstantAppIntentFilterList(in int[] digestPrefix, - String token, String hostName, IRemoteCallback callback); + void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix, + String token, IRemoteCallback callback); + } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index d1aacad30a64fc70fef5f3f0d59d5862fe6a81ca..d378f227d921da7b96fc42531d78fe7d3f545d0a 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -75,6 +75,7 @@ interface INotificationManager NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId); ParceledListSlice getNotificationChannelGroups(String pkg); boolean onlyHasDefaultChannel(String pkg, int uid); + ParceledListSlice getRecentNotifyingAppsForUser(int userId); // TODO: Remove this when callers have been migrated to the equivalent // INotificationListener method. diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index c5dc86c79ef954262eb6438c93267357a8d4fa8d..89aff36f883a103469fb91515f8ff504ca85e805 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -17,7 +17,6 @@ package android.app; import android.annotation.SystemApi; -import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.InstantAppResolveInfo; @@ -35,6 +34,7 @@ import android.util.Slog; import com.android.internal.os.SomeArgs; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -53,23 +53,65 @@ public abstract class InstantAppResolverService extends Service { Handler mHandler; /** - * Called to retrieve resolve info for instant applications. + * Called to retrieve resolve info for instant applications immediately. * * @param digestPrefix The hash prefix of the instant app's domain. + * @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String, + * InstantAppResolutionCallback)} */ + @Deprecated public void onGetInstantAppResolveInfo( int digestPrefix[], String token, InstantAppResolutionCallback callback) { throw new IllegalStateException("Must define"); } /** - * Called to retrieve intent filters for instant applications. + * Called to retrieve intent filters for instant applications from potentially expensive + * sources. * * @param digestPrefix The hash prefix of the instant app's domain. + * @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String, + * InstantAppResolutionCallback)} */ + @Deprecated public void onGetInstantAppIntentFilter( int digestPrefix[], String token, InstantAppResolutionCallback callback) { - throw new IllegalStateException("Must define"); + throw new IllegalStateException("Must define onGetInstantAppIntentFilter"); + } + + /** + * Called to retrieve resolve info for instant applications immediately. + * + * @param sanitizedIntent The sanitized {@link Intent} used for resolution. + * @param hostDigestPrefix The hash prefix of the instant app's domain. + */ + public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix, + String token, InstantAppResolutionCallback callback) { + // if not overridden, forward to old methods and filter out non-web intents + if (sanitizedIntent.isBrowsableWebIntent()) { + onGetInstantAppResolveInfo(hostDigestPrefix, token, callback); + } else { + callback.onInstantAppResolveInfo(Collections.emptyList()); + } + } + + /** + * Called to retrieve intent filters for instant applications from potentially expensive + * sources. + * + * @param sanitizedIntent The sanitized {@link Intent} used for resolution. + * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is + * defined. + */ + public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix, + String token, InstantAppResolutionCallback callback) { + Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden"); + // if not overridden, forward to old methods and filter out non-web intents + if (sanitizedIntent.isBrowsableWebIntent()) { + onGetInstantAppIntentFilter(hostDigestPrefix, token, callback); + } else { + callback.onInstantAppResolveInfo(Collections.emptyList()); + } } /** @@ -89,8 +131,8 @@ public abstract class InstantAppResolverService extends Service { public final IBinder onBind(Intent intent) { return new IInstantAppResolver.Stub() { @Override - public void getInstantAppResolveInfoList( - int digestPrefix[], String token, int sequence, IRemoteCallback callback) { + public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, + String token, int sequence, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } @@ -98,14 +140,14 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - mHandler.obtainMessage( - ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args) - .sendToTarget(); + args.arg4 = sanitizedIntent; + mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, + sequence, 0, args).sendToTarget(); } @Override - public void getInstantAppIntentFilterList( - int digestPrefix[], String token, String hostName, IRemoteCallback callback) { + public void getInstantAppIntentFilterList(Intent sanitizedIntent, + int[] digestPrefix, String token, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } @@ -113,9 +155,9 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - args.arg4 = hostName; - mHandler.obtainMessage( - ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget(); + args.arg4 = sanitizedIntent; + mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, + callback).sendToTarget(); } }; } @@ -142,29 +184,9 @@ public abstract class InstantAppResolverService extends Service { } } - @Deprecated - void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase1 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppResolveInfo(digestPrefix, token, callback); - } - @Deprecated - void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase2 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppIntentFilter(digestPrefix, token, callback); - } - private final class ServiceHandler extends Handler { public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1; public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2; - public ServiceHandler(Looper looper) { super(looper, null /*callback*/, true /*async*/); } @@ -179,9 +201,13 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; + final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - _onGetInstantAppResolveInfo( - digestPrefix, token, + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase1 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppResolveInfo(intent, digestPrefix, token, new InstantAppResolutionCallback(sequence, callback)); } break; @@ -190,9 +216,12 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; - final String hostName = (String) args.arg4; - _onGetInstantAppIntentFilter( - digestPrefix, token, hostName, + final Intent intent = (Intent) args.arg4; + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase2 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppIntentFilter(intent, digestPrefix, token, new InstantAppResolutionCallback(-1 /*sequence*/, callback)); } break; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index b469de56d857c9ed99e8a1fa91da5bbd5a579e0e..67f5a2e641ae2aed69659e44b1f17ef7ce217833 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -458,7 +459,8 @@ public class Instrumentation { * * @see Context#startActivity(Intent, Bundle) */ - public Activity startActivitySync(Intent intent, @Nullable Bundle options) { + @NonNull + public Activity startActivitySync(@NonNull Intent intent, @Nullable Bundle options) { validateNotAppThread(); synchronized (mSync) { @@ -1214,10 +1216,10 @@ public class Instrumentation { + " disabling AppComponentFactory", new Throwable()); return AppComponentFactory.DEFAULT; } - LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true); + LoadedApk apk = mThread.peekPackageInfo(pkg, true); // This is in the case of starting up "android". - if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk; - return loadedApk.getAppFactory(); + if (apk == null) apk = mThread.getSystemContext().mPackageInfo; + return apk.getAppFactory(); } private void prePerformCreate(Activity activity) { @@ -1686,9 +1688,13 @@ public class Instrumentation { * {@link ActivityMonitor} objects only match against the first activity in * the array. * + * @return The corresponding flag {@link ActivityManager#START_CANCELED}, + * {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was + * successful. + * * {@hide} */ - public void execStartActivitiesAsUser(Context who, IBinder contextThread, + public int execStartActivitiesAsUser(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; @@ -1703,11 +1709,11 @@ public class Instrumentation { } if (result != null) { am.mHits++; - return; + return ActivityManager.START_CANCELED; } else if (am.match(who, null, intents[0])) { am.mHits++; if (am.isBlocking()) { - return; + return ActivityManager.START_CANCELED; } break; } @@ -1725,6 +1731,7 @@ public class Instrumentation { .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes, token, options, userId); checkStartActivityResult(result, intents[0]); + return result; } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 024dbcbe416989d935ca46ba27f9e66b0e3ec1e0..553099f25516fe215043ff95915db2435f8054f1 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -167,10 +167,11 @@ public class KeyguardManager { * clicking this button, the activity returns * {@link #RESULT_ALTERNATE} * - * @return the intent for launching the activity or null if the credential of the previous - * owner can not be verified (e.g. because there was none, or the device does not support - * verifying credentials after a factory reset, or device setup has already been completed). - * + * @return the intent for launching the activity or null if the previous owner of the device + * did not set a credential. + * @throws UnsupportedOperationException if the device does not support factory reset + * credentials + * @throws IllegalStateException if the device has already been provisioned * @hide */ @SystemApi @@ -178,14 +179,14 @@ public class KeyguardManager { CharSequence title, CharSequence description, CharSequence alternateButtonLabel) { if (!LockPatternUtils.frpCredentialEnabled(mContext)) { Log.w(TAG, "Factory reset credentials not supported."); - return null; + throw new UnsupportedOperationException("not supported on this device"); } // Cannot verify credential if the device is provisioned if (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0) { Log.e(TAG, "Factory reset credential cannot be verified after provisioning."); - return null; + throw new IllegalStateException("must not be provisioned yet"); } // Make sure we have a credential @@ -194,8 +195,10 @@ public class KeyguardManager { ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE)); if (pdb == null) { Log.e(TAG, "No persistent data block service"); - return null; + throw new UnsupportedOperationException("not supported on this device"); } + // The following will throw an UnsupportedOperationException if the device does not + // support factory reset credentials (or something went wrong retrieving it). if (!pdb.hasFrpCredentialHandle()) { Log.i(TAG, "The persistent data block does not have a factory reset credential."); return null; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 26f498087786319109ca135caa52899cf72a272f..ea5932cd4ae4151a324ed2768eaadb467ebbee94 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -28,14 +28,13 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.dex.ArtManager; import android.content.pm.split.SplitDependencyLoader; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; -import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -49,13 +48,15 @@ import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.Log; -import android.util.LogPrinter; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; + import com.android.internal.util.ArrayUtils; + import dalvik.system.VMRuntime; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -749,13 +750,6 @@ public final class LoadedApk { } } - // Keep in sync with installd (frameworks/native/cmds/installd/commands.cpp). - private static File getPrimaryProfileFile(String packageName) { - File profileDir = Environment.getDataProfilesDePackageDirectory( - UserHandle.myUserId(), packageName); - return new File(profileDir, "primary.prof"); - } - private void setupJitProfileSupport() { if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { return; @@ -783,10 +777,12 @@ public final class LoadedApk { return; } - final File profileFile = getPrimaryProfileFile(mPackageName); - - VMRuntime.registerAppInfo(profileFile.getPath(), - codePaths.toArray(new String[codePaths.size()])); + for (int i = codePaths.size() - 1; i >= 0; i--) { + String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1]; + String profileFile = ArtManager.getCurrentProfilePath( + mPackageName, UserHandle.myUserId(), splitName); + VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)}); + } // Register the app data directory with the reporter. It will // help deciding whether or not a dex file is the primary apk or a @@ -967,78 +963,14 @@ public final class LoadedApk { throw new AssertionError("null split not found"); } - mResources = ResourcesManager.getInstance().getResources( - null, - mResDir, - splitPaths, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), + mResources = ResourcesManager.getInstance().getResources(null, mResDir, + splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), getClassLoader()); } return mResources; } - public Resources getOrCreateResourcesForSplit(@NonNull String splitName, - @Nullable IBinder activityToken, int displayId) throws NameNotFoundException { - return ResourcesManager.getInstance().getResources( - activityToken, - mResDir, - getSplitPaths(splitName), - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - null, - getCompatibilityInfo(), - getSplitClassLoader(splitName)); - } - - /** - * Creates the top level resources for the given package. Will return an existing - * Resources if one has already been created. - */ - public Resources getOrCreateTopLevelResources(@NonNull ApplicationInfo appInfo) { - // Request for this app, short circuit - if (appInfo.uid == Process.myUid()) { - return getResources(); - } - - // Get resources for a different package - return ResourcesManager.getInstance().getResources( - null, - appInfo.publicSourceDir, - appInfo.splitPublicSourceDirs, - appInfo.resourceDirs, - appInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), - getClassLoader()); - } - - public Resources createResources(IBinder activityToken, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { - final String[] splitResDirs; - final ClassLoader classLoader; - try { - splitResDirs = getSplitPaths(splitName); - classLoader = getSplitClassLoader(splitName); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - return ResourcesManager.getInstance().getResources(activityToken, - mResDir, - splitResDirs, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - overrideConfig, - compatInfo, - classLoader); - } - public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java index 998ac5f2a4872ef31cfac8f7cbacca3c68f7b65c..c34f4d9f0fe873cc530069429fdb42a8a8ae4b91 100644 --- a/core/java/android/app/LocalActivityManager.java +++ b/core/java/android/app/LocalActivityManager.java @@ -16,6 +16,8 @@ package android.app; +import android.app.ActivityThread.ActivityClientRecord; +import android.app.servertransaction.PendingTransactionActions; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Binder; @@ -141,6 +143,21 @@ public class LocalActivityManager { } r.window = r.activity.getWindow(); r.instanceState = null; + + final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); + final PendingTransactionActions pendingActions; + + if (!r.activity.mFinished) { + // This matches pending actions set in ActivityThread#handleLaunchActivity + pendingActions = new PendingTransactionActions(); + pendingActions.setOldState(clientRecord.state); + pendingActions.setRestoreInstanceState(true); + pendingActions.setCallOnPostCreate(true); + } else { + pendingActions = null; + } + + mActivityThread.handleStartActivity(clientRecord, pendingActions); r.curState = STARTED; if (desiredState == RESUMED) { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 75dc57110c9387655fee255b734f695693624076..6e4098646b2746d2130079f4131bed1b5737409d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -200,6 +200,16 @@ public class Notification implements Parcelable */ private static final int MAX_REPLY_HISTORY = 5; + + /** + * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, + * we're adding the draft as a String extra to the {@link #contentIntent} using this key. + * + *

Apps may use this extra to prepopulate text fields in the app, where the user usually + * sends messages.

+ */ + public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; + /** * A timestamp related to this notification, in milliseconds since the epoch. * @@ -483,7 +493,7 @@ public class Notification implements Parcelable * *

* Since hardware varies, you are not guaranteed that any of the values - * you pass are honored exactly. Use the system defaults (TODO) if possible + * you pass are honored exactly. Use the system defaults if possible * because they will be set to values that work on any given hardware. *

* The alpha channel must be set for forward compatibility. @@ -4130,7 +4140,7 @@ public class Notification implements Parcelable final Bundle ex = mN.extras; updateBackgroundColor(contentView); bindNotificationHeader(contentView, p.ambient, p.headerTextSecondary); - bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply); + bindLargeIcon(contentView, p.hideLargeIcon || p.ambient, p.alwaysShowReply); boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex); if (p.title != null) { contentView.setViewVisibility(R.id.title, View.VISIBLE); @@ -6469,8 +6479,11 @@ public class Notification implements Parcelable ? super.mBigContentTitle : mConversationTitle; boolean isOneToOne = TextUtils.isEmpty(conversationTitle); + CharSequence nameReplacement = null; if (hasOnlyWhiteSpaceSenders()) { isOneToOne = true; + nameReplacement = conversationTitle; + conversationTitle = null; } RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), @@ -6489,6 +6502,8 @@ public class Notification implements Parcelable mBuilder.resolveContrastColor()); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); + contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", + nameReplacement); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", isOneToOne); contentView.setBundle(R.id.status_bar_latest_event_content, "setData", diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index c06ad3f32cf4eb9f83e89e97f5eea1e64c8502e9..30f2697c1b1f073993ad71f5ac89aef734e0098c 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -32,8 +32,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; -import com.android.internal.util.Preconditions; - import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; @@ -936,7 +934,9 @@ public final class NotificationChannel implements Parcelable { } /** @hide */ - public void toProto(ProtoOutputStream proto) { + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(NotificationChannelProto.ID, mId); proto.write(NotificationChannelProto.NAME, mName); proto.write(NotificationChannelProto.DESCRIPTION, mDesc); @@ -959,10 +959,10 @@ public final class NotificationChannel implements Parcelable { proto.write(NotificationChannelProto.IS_DELETED, mDeleted); proto.write(NotificationChannelProto.GROUP, mGroup); if (mAudioAttributes != null) { - long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES); - mAudioAttributes.toProto(proto); - proto.end(aToken); + mAudioAttributes.writeToProto(proto, NotificationChannelProto.AUDIO_ATTRIBUTES); } proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem); + + proto.end(token); } } diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 5cb7fb7a670757b06933d7b5e60be6acdc1e8387..16166f7cf1cf72c092a57e25e211f2069296a931 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -298,13 +298,17 @@ public final class NotificationChannelGroup implements Parcelable { } /** @hide */ - public void toProto(ProtoOutputStream proto) { + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(NotificationChannelGroupProto.ID, mId); proto.write(NotificationChannelGroupProto.NAME, mName.toString()); proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription); proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked); for (NotificationChannel channel : mChannels) { - channel.toProto(proto); + channel.writeToProto(proto, NotificationChannelGroupProto.CHANNELS); } + + proto.end(token); } } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 659cf169e99463702dcec6d5cae1b6dbee8d9396..49c03ab9cfac8aeecee04f5dddd840b0c2bfd81d 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -92,6 +92,18 @@ public class NotificationManager { private static String TAG = "NotificationManager"; private static boolean localLOGV = false; + /** + * Intent that is broadcast when an application is blocked or unblocked. + * + * This broadcast is only sent to the app whose block state has changed. + * + * Input: nothing + * Output: nothing + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_APP_BLOCK_STATE_CHANGED = + "android.app.action.APP_BLOCK_STATE_CHANGED"; + /** * Intent that is broadcast when a {@link NotificationChannel} is blocked * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked @@ -1133,7 +1145,7 @@ public class NotificationManager { } /** @hide */ - public void toProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId) { final long pToken = proto.start(fieldId); bitwiseToProtoEnum(proto, PolicyProto.PRIORITY_CATEGORIES, priorityCategories); diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 8b76cc7c966b97a397102b0769ef137f7d4087cd..d6429ae9e5407ef5c32d4c5b06a85414c7f01176 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -867,19 +867,30 @@ public final class PendingIntent implements Parcelable { @Nullable OnFinished onFinished, @Nullable Handler handler, @Nullable String requiredPermission, @Nullable Bundle options) throws CanceledException { + if (sendAndReturnResult(context, code, intent, onFinished, handler, requiredPermission, + options) < 0) { + throw new CanceledException(); + } + } + + /** + * Like {@link #send}, but returns the result + * @hide + */ + public int sendAndReturnResult(Context context, int code, @Nullable Intent intent, + @Nullable OnFinished onFinished, @Nullable Handler handler, + @Nullable String requiredPermission, @Nullable Bundle options) + throws CanceledException { try { String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) : null; - int res = ActivityManager.getService().sendIntentSender( + return ActivityManager.getService().sendIntentSender( mTarget, mWhitelistToken, code, intent, resolvedType, onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null, requiredPermission, options); - if (res < 0) { - throw new CanceledException(); - } } catch (RemoteException e) { throw new CanceledException(e); } diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java index d5234278da7dcd700a87ffa21d5330692f9a6c41..6fbe9c6efb40a105e342563289678f692cccf5ee 100644 --- a/core/java/android/app/ProfilerInfo.java +++ b/core/java/android/app/ProfilerInfo.java @@ -20,6 +20,7 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import java.io.IOException; import java.util.Objects; @@ -55,14 +56,24 @@ public class ProfilerInfo implements Parcelable { */ public final String agent; + /** + * Whether the {@link agent} should be attached early (before bind-application) or during + * bind-application. Agents attached prior to binding cannot be loaded from the app's APK + * directly and must be given as an absolute path (or available in the default LD_LIBRARY_PATH). + * Agents attached during bind-application will miss early setup (e.g., resource initialization + * and classloader generation), but are searched in the app's library search path. + */ + public final boolean attachAgentDuringBind; + public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop, - boolean streaming, String agent) { + boolean streaming, String agent, boolean attachAgentDuringBind) { profileFile = filename; profileFd = fd; samplingInterval = interval; autoStopProfiler = autoStop; streamingOutput = streaming; this.agent = agent; + this.attachAgentDuringBind = attachAgentDuringBind; } public ProfilerInfo(ProfilerInfo in) { @@ -72,6 +83,16 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.autoStopProfiler; streamingOutput = in.streamingOutput; agent = in.agent; + attachAgentDuringBind = in.attachAgentDuringBind; + } + + /** + * Return a new ProfilerInfo instance, with fields populated from this object, + * and {@link agent} and {@link attachAgentDuringBind} as given. + */ + public ProfilerInfo setAgent(String agent, boolean attachAgentDuringBind) { + return new ProfilerInfo(this.profileFile, this.profileFd, this.samplingInterval, + this.autoStopProfiler, this.streamingOutput, agent, attachAgentDuringBind); } /** @@ -110,6 +131,21 @@ public class ProfilerInfo implements Parcelable { out.writeInt(autoStopProfiler ? 1 : 0); out.writeInt(streamingOutput ? 1 : 0); out.writeString(agent); + out.writeBoolean(attachAgentDuringBind); + } + + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(ProfilerInfoProto.PROFILE_FILE, profileFile); + if (profileFd != null) { + proto.write(ProfilerInfoProto.PROFILE_FD, profileFd.getFd()); + } + proto.write(ProfilerInfoProto.SAMPLING_INTERVAL, samplingInterval); + proto.write(ProfilerInfoProto.AUTO_STOP_PROFILER, autoStopProfiler); + proto.write(ProfilerInfoProto.STREAMING_OUTPUT, streamingOutput); + proto.write(ProfilerInfoProto.AGENT, agent); + proto.end(token); } public static final Parcelable.Creator CREATOR = @@ -132,6 +168,7 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.readInt() != 0; streamingOutput = in.readInt() != 0; agent = in.readString(); + attachAgentDuringBind = in.readBoolean(); } @Override diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 02a01242c3ba7576d93c4db896be247dd8d57a58..b7100e6f4d8713b078bb87d0baadf21520e30c28 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; + import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -73,6 +74,15 @@ public final class RemoteInput implements Parcelable { private static final String EXTRA_DATA_TYPE_RESULTS_DATA = "android.remoteinput.dataTypeResultsData"; + /** Extra added to a clip data intent object identifying the source of the results. */ + private static final String EXTRA_RESULTS_SOURCE = "android.remoteinput.resultsSource"; + + /** The user manually entered the data. */ + public static final int SOURCE_FREE_FORM_INPUT = 0; + + /** The user selected one of the choices from {@link #getChoices}. */ + public static final int SOURCE_CHOICE = 1; + // Flags bitwise-ored to mFlags private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; @@ -416,6 +426,48 @@ public final class RemoteInput implements Parcelable { intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); } + /** + * Set the source of the RemoteInput results. This method should only be called by remote + * input collection services (e.g. + * {@link android.service.notification.NotificationListenerService}) + * when sending results to a pending intent. + * + * @see #SOURCE_FREE_FORM_INPUT + * @see #SOURCE_CHOICE + * + * @param intent The intent to add remote input source to. The {@link ClipData} + * field of the intent will be modified to contain the source. + * field of the intent will be modified to contain the source. + * @param source The source of the results. + */ + public static void setResultsSource(Intent intent, int source) { + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { + clipDataIntent = new Intent(); // First time we've added a result. + } + clipDataIntent.putExtra(EXTRA_RESULTS_SOURCE, source); + intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); + } + + /** + * Get the source of the RemoteInput results. + * + * @see #SOURCE_FREE_FORM_INPUT + * @see #SOURCE_CHOICE + * + * @param intent The intent object that fired in response to an action or content intent + * which also had one or more remote input requested. + * @return The source of the results. If no source was set, {@link #SOURCE_FREE_FORM_INPUT} will + * be returned. + */ + public static int getResultsSource(Intent intent) { + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { + return SOURCE_FREE_FORM_INPUT; + } + return clipDataIntent.getExtras().getInt(EXTRA_RESULTS_SOURCE, SOURCE_FREE_FORM_INPUT); + } + private static String getExtraResultsKeyForData(String mimeType) { return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType; } diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java new file mode 100644 index 0000000000000000000000000000000000000000..c525c89c88e78d00e20b46ff277ee88440330f6e --- /dev/null +++ b/core/java/android/app/StatsManager.java @@ -0,0 +1,244 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.IStatsManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +/** + * API for statsd clients to send configurations and retrieve data. + * + * @hide + */ +@SystemApi +public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends. + IStatsManager mService; + private static final String TAG = "StatsManager"; + + /** Long extra of uid that added the relevant stats config. */ + public static final String EXTRA_STATS_CONFIG_UID = + "android.app.extra.STATS_CONFIG_UID"; + /** Long extra of the relevant stats config's configKey. */ + public static final String EXTRA_STATS_CONFIG_KEY = + "android.app.extra.STATS_CONFIG_KEY"; + /** Long extra of the relevant statsd_config.proto's Subscription.id. */ + public static final String EXTRA_STATS_SUBSCRIPTION_ID = + "android.app.extra.STATS_SUBSCRIPTION_ID"; + /** Long extra of the relevant statsd_config.proto's Subscription.rule_id. */ + public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = + "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; + /** + * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value + * information. + */ + public static final String EXTRA_STATS_DIMENSIONS_VALUE = + "android.app.extra.STATS_DIMENSIONS_VALUE"; + + /** + * Broadcast Action: Statsd has started. + * Configurations and PendingIntents can now be sent to it. + */ + public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; + + /** + * Constructor for StatsManagerClient. + * + * @hide + */ + public StatsManager() { + } + + /** + * Clients can send a configuration and simultaneously registers the name of a broadcast + * receiver that listens for when it should request data. + * + * @param configKey An arbitrary integer that allows clients to track the configuration. + * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all + * dependencies eg, conditions and matchers). + * @param pkg The package name to receive the broadcast. + * @param cls The name of the class that receives the broadcast. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when adding configuration"); + return false; + } + return service.addConfiguration(configKey, config, pkg, cls); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connect to statsd when adding configuration"); + return false; + } + } + } + + /** + * Remove a configuration from logging. + * + * @param configKey Configuration key to remove. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(long configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when removing configuration"); + return false; + } + return service.removeConfiguration(configKey); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connect to statsd when removing configuration"); + return false; + } + } + } + + /** + * Set the PendingIntent to be used when broadcasting subscriber information to the given + * subscriberId within the given config. + * + *

+ * Suppose that the calling uid has added a config with key configKey, and that in this config + * it is specified that when a particular anomaly is detected, a broadcast should be sent to + * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with + * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast + * when the anomaly is detected. + * + *

+ * When statsd sends the broadcast, the PendingIntent will used to send an intent with + * information of + * {@link #EXTRA_STATS_CONFIG_UID}, + * {@link #EXTRA_STATS_CONFIG_KEY}, + * {@link #EXTRA_STATS_SUBSCRIPTION_ID}, + * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and + * {@link #EXTRA_STATS_DIMENSIONS_VALUE}. + * + *

+ * This function can only be called by the owner (uid) of the config. It must be called each + * time statsd starts. The config must have been added first (via addConfiguration()). + * + * @param configKey The integer naming the config to which this subscriber is attached. + * @param subscriberId ID of the subscriber, as used in the config. + * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber + * associated with the given subscriberId. May be null, in which case + * it undoes any previous setting of this subscriberId. + * @return true if successful + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean setBroadcastSubscriber(long configKey, + long subscriberId, + PendingIntent pendingIntent) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber"); + return false; + } + if (pendingIntent != null) { + // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. + IBinder intentSender = pendingIntent.getTarget().asBinder(); + return service.setBroadcastSubscriber(configKey, subscriberId, intentSender); + } else { + return service.unsetBroadcastSubscriber(configKey, subscriberId); + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e); + return false; + } + } + } + + /** + * Clients can request data with a binder call. This getter is destructive and also clears + * the retrieved metrics from statsd memory. + * + * @param configKey Configuration key to retrieve data from. + * @return Serialized ConfigMetricsReportList proto. Returns null on failure. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getData(long configKey) { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when getting data"); + return null; + } + return service.getData(configKey); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connecto statsd when getting data"); + return null; + } + } + } + + /** + * Clients can request metadata for statsd. Will contain stats across all configurations but not + * the actual metrics themselves (metrics must be collected via {@link #getData(String)}. + * This getter is not destructive and will not reset any metrics/counters. + * + * @return Serialized StatsdStatsReport proto. Returns null on failure. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getMetadata() { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + Slog.d(TAG, "Failed to find statsd when getting metadata"); + return null; + } + return service.getMetadata(); + } catch (RemoteException e) { + Slog.d(TAG, "Failed to connecto statsd when getting metadata"); + return null; + } + } + } + + private class StatsdDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (this) { + mService = null; + } + } + } + + private IStatsManager getIStatsManagerLocked() throws RemoteException { + if (mService != null) { + return mService; + } + mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); + if (mService != null) { + mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); + } + return mService; + } +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 6eafcc43744717aaf3210d832209e3ab7bb04f9a..4310434c70be4f2ff7a8d7a7c3a90abf2a46a33e 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; @@ -112,6 +112,7 @@ import android.os.IBinder; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; +import android.os.ISystemUpdateManager; import android.os.IUserManager; import android.os.IncidentManager; import android.os.PowerManager; @@ -119,6 +120,7 @@ import android.os.Process; import android.os.RecoverySystem; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.os.SystemUpdateManager; import android.os.SystemVibrator; import android.os.UserHandle; import android.os.UserManager; @@ -139,7 +141,6 @@ import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccCardManager; import android.telephony.euicc.EuiccManager; import android.util.Log; -import android.util.StatsManager; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.WindowManager; @@ -485,6 +486,17 @@ final class SystemServiceRegistry { return new StorageStatsManager(ctx, service); }}); + registerService(Context.SYSTEM_UPDATE_SERVICE, SystemUpdateManager.class, + new CachedServiceFetcher() { + @Override + public SystemUpdateManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.SYSTEM_UPDATE_SERVICE); + ISystemUpdateManager service = ISystemUpdateManager.Stub.asInterface(b); + return new SystemUpdateManager(service); + }}); + registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class, new CachedServiceFetcher() { @Override diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java index bab993f855e9b272e5d251bd15ba8d51f3f5389c..ab5974777c9eafd714000fe142faeb7b1aba5e74 100644 --- a/core/java/android/app/TaskStackBuilder.java +++ b/core/java/android/app/TaskStackBuilder.java @@ -213,13 +213,13 @@ public class TaskStackBuilder { * Start the task stack constructed by this builder. * @hide */ - public void startActivities(Bundle options, UserHandle userHandle) { + public int startActivities(Bundle options, UserHandle userHandle) { if (mIntents.isEmpty()) { throw new IllegalStateException( "No intents added to TaskStackBuilder; cannot startActivities"); } - mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle); + return mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle); } /** diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index ba39740be12e11dc214a01b87cd99199a8862209..8f0168530273b544517a835edfbca18a5bbc21db 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -24,6 +24,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.TestApi; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -46,14 +47,10 @@ import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; - -import com.android.internal.util.CollectionUtils; - import libcore.io.IoUtils; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; @@ -583,8 +580,6 @@ public final class UiAutomation { // Execute the command *without* the lock being held. command.run(); - List eventsReceived = Collections.emptyList(); - // Acquire the lock and wait for the event. try { // Wait for the event. @@ -605,14 +600,14 @@ public final class UiAutomation { if (filter.accept(event)) { return event; } - eventsReceived = CollectionUtils.add(eventsReceived, event); + event.recycle(); } // Check if timed out and if not wait. final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; if (remainingTimeMillis <= 0) { throw new TimeoutException("Expected event not received within: " - + timeoutMillis + " ms, among " + eventsReceived); + + timeoutMillis + " ms."); } synchronized (mLock) { if (mEventQueue.isEmpty()) { @@ -625,10 +620,6 @@ public final class UiAutomation { } } } finally { - for (int i = 0; i < CollectionUtils.size(eventsReceived); i++) { - AccessibilityEvent event = eventsReceived.get(i); - event.recycle(); - } synchronized (mLock) { mWaitingForEventDelivery = false; mEventQueue.clear(); diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java index f06a9257b7f84514f91c8fef618a2fd35e80ae2c..d511c57b1f514f1c889c6306eb7e1c2c09ee0d73 100644 --- a/core/java/android/app/admin/ConnectEvent.java +++ b/core/java/android/app/admin/ConnectEvent.java @@ -68,7 +68,7 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp, + return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 1de1d2fbb8226840e8f5486180bc33d985c4ccff..ed2aaf915ef25984991995378d281525741be638 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -75,6 +75,10 @@ public final class DeviceAdminInfo implements Parcelable { * *

To control this policy, the device admin must have a "limit-password" * tag in the "uses-policies" section of its meta-data. + * + *

This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to enforce constraints on user + * passwords. */ public static final int USES_POLICY_LIMIT_PASSWORD = 0; @@ -136,6 +140,9 @@ public final class DeviceAdminInfo implements Parcelable { * *

To control this policy, the device admin must have an "expire-password" * tag in the "uses-policies" section of its meta-data. + * + *

This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to enforce password expiry. */ public static final int USES_POLICY_EXPIRE_PASSWORD = 6; @@ -152,6 +159,9 @@ public final class DeviceAdminInfo implements Parcelable { * *

To control this policy, the device admin must have a "disable-camera" * tag in the "uses-policies" section of its meta-data. + * + *

This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to disable use of the camera. */ public static final int USES_POLICY_DISABLE_CAMERA = 8; @@ -160,6 +170,10 @@ public final class DeviceAdminInfo implements Parcelable { * *

To control this policy, the device admin must have a "disable-keyguard-features" * tag in the "uses-policies" section of its meta-data. + * + *

This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to disable use of keyguard + * features. */ public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9; diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index aa05b7630c9d038f8fd6982a167ec51916532bb2..28e845a04e44ebb6bf52d3b33c3ae2a83586dbb9 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -339,13 +339,43 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Broadcast action: notify the device owner that a user or profile has been removed. * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of - * the new user. + * the user. * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @BroadcastBehavior(explicitOnly = true) public static final String ACTION_USER_REMOVED = "android.app.action.USER_REMOVED"; + /** + * Broadcast action: notify the device owner that a user or profile has been started. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_STARTED = "android.app.action.USER_STARTED"; + + /** + * Broadcast action: notify the device owner that a user or profile has been stopped. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_STOPPED = "android.app.action.USER_STOPPED"; + + /** + * Broadcast action: notify the device owner that a user or profile has been switched to. + * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of + * the user. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_SWITCHED = "android.app.action.USER_SWITCHED"; + /** * A string containing the SHA-256 hash of the bugreport file. * @@ -452,20 +482,54 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String ACTION_TRANSFER_OWNERSHIP_COMPLETE = "android.app.action.TRANSFER_OWNERSHIP_COMPLETE"; + /** + * Broadcast action: notify the device owner that the ownership of one of its affiliated + * profiles is transferred. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE = + "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE"; + /** * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that * allows a mobile device management application to pass data to the management application * instance after owner transfer. * - *

- * If the transfer is successful, the new device owner receives the data in + *

If the transfer is successful, the new owner receives the data in * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}. * The bundle is not changed during the ownership transfer. * * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) */ - public static final String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = - "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE"; + public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = + "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE"; + + /** + * Name under which a device administration component indicates whether it supports transfer of + * ownership. This meta-data is of type boolean. A value of true + * allows this administrator to be used as a target administrator for a transfer. If the value + * is false, ownership cannot be transferred to this administrator. The default + * value is false. + *

This metadata is used to avoid ownership transfer migration to an administrator with a + * version which does not yet support it. + *

Usage: + *

+     * <receiver name="..." android:permission="android.permission.BIND_DEVICE_ADMIN">
+     *     <meta-data
+     *         android:name="android.app.device_admin"
+     *         android:resource="@xml/..." />
+     *     <meta-data
+     *         android:name="android.app.support_transfer_ownership"
+     *         android:value="true" />
+     * </receiver>
+     * 
+ * + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + public static final String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = + "android.app.support_transfer_ownership"; private DevicePolicyManager mManager; private ComponentName mWho; @@ -888,6 +952,42 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public void onUserRemoved(Context context, Intent intent, UserHandle removedUser) { } + /** + * Called when a user or profile is started. + * + *

This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param startedUser The {@link UserHandle} of the user that has just been started. + */ + public void onUserStarted(Context context, Intent intent, UserHandle startedUser) { + } + + /** + * Called when a user or profile is stopped. + * + *

This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param stoppedUser The {@link UserHandle} of the user that has just been stopped. + */ + public void onUserStopped(Context context, Intent intent, UserHandle stoppedUser) { + } + + /** + * Called when a user or profile is switched to. + * + *

This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param switchedUser The {@link UserHandle} of the user that has just been switched to. + */ + public void onUserSwitched(Context context, Intent intent, UserHandle switchedUser) { + } + /** * Called on the newly assigned owner (either device owner or profile owner) when the ownership * transfer has completed successfully. @@ -902,6 +1002,26 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @Nullable PersistableBundle bundle) { } + /** + * Called on the device owner when the ownership of one of its affiliated profiles is + * transferred. + * + *

This can be used when transferring both device and profile ownership when using + * work profile on a fully managed device. The process would look like this: + *

    + *
  1. Transfer profile ownership
  2. + *
  3. The device owner gets notified with this callback
  4. + *
  5. Transfer device ownership
  6. + *
  7. Both profile and device ownerships have been transferred
  8. + *
+ * + * @param context the running context as per {@link #onReceive} + * @param user the {@link UserHandle} of the affiliated user + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + public void onTransferAffiliatedProfileOwnershipComplete(Context context, UserHandle user) { + } + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the @@ -964,10 +1084,19 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onUserAdded(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_USER_REMOVED.equals(action)) { onUserRemoved(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_USER_STARTED.equals(action)) { + onUserStarted(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_USER_STOPPED.equals(action)) { + onUserStopped(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); + } else if (ACTION_USER_SWITCHED.equals(action)) { + onUserSwitched(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { PersistableBundle bundle = - intent.getParcelableExtra(EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE); + intent.getParcelableExtra(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE); onTransferOwnershipComplete(context, bundle); + } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { + onTransferAffiliatedProfileOwnershipComplete(context, + intent.getParcelableExtra(Intent.EXTRA_USER)); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 10539d19d563930284e57062f452744314fb2afe..131abb5e46648e29b8c6a305a885082c5e8ba220 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -18,7 +18,6 @@ package android.app.admin; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; -import android.annotation.Condemned; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -50,8 +49,6 @@ import android.graphics.Bitmap; import android.net.ProxyInfo; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerExecutor; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Process; @@ -71,6 +68,7 @@ import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.service.restrictions.RestrictionsReceiver; import android.telephony.TelephonyManager; +import android.telephony.data.ApnSetting; import android.util.ArraySet; import android.util.Log; @@ -1159,10 +1157,18 @@ public class DevicePolicyManager { */ public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture"; + /** + * Constant to indicate the feature of mandatory backups. Used as argument to + * {@link #createAdminSupportIntent(String)}. + * @see #setMandatoryBackupTransport(ComponentName, ComponentName) + */ + public static final String POLICY_MANDATORY_BACKUPS = "policy_mandatory_backups"; + /** * A String indicating a specific restricted feature. Can be a user restriction from the * {@link UserManager}, e.g. {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the values - * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}. + * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or + * {@link #POLICY_MANDATORY_BACKUPS}. * @see #createAdminSupportIntent(String) * @hide */ @@ -6505,12 +6511,6 @@ public class DevicePolicyManager { */ public static final int MAKE_USER_DEMO = 0x0004; - /** - * Flag used by {@link #createAndManageUser} to specify that the newly created user should be - * started in the background as part of the user creation. - */ - public static final int START_USER_IN_BACKGROUND = 0x0008; - /** * Flag used by {@link #createAndManageUser} to specify that the newly created user should skip * the disabling of system apps during provisioning. @@ -6524,7 +6524,6 @@ public class DevicePolicyManager { SKIP_SETUP_WIZARD, MAKE_USER_EPHEMERAL, MAKE_USER_DEMO, - START_USER_IN_BACKGROUND, LEAVE_ALL_SYSTEM_APPS_ENABLED }) @Retention(RetentionPolicy.SOURCE) @@ -6553,7 +6552,8 @@ public class DevicePolicyManager { * IllegalArgumentException is thrown. * @param adminExtras Extras that will be passed to onEnable of the admin receiver on the new * user. - * @param flags {@link #SKIP_SETUP_WIZARD} is supported. + * @param flags {@link #SKIP_SETUP_WIZARD}, {@link #MAKE_USER_EPHEMERAL} and + * {@link #LEAVE_ALL_SYSTEM_APPS_ENABLED} are supported. * @see UserHandle * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the * user could not be created. @@ -6572,8 +6572,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner to remove a user and all associated data. The primary user can not - * be removed. + * Called by a device owner to remove a user/profile and all associated data. The primary user + * can not be removed. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to remove. @@ -6590,14 +6590,14 @@ public class DevicePolicyManager { } /** - * Called by a device owner to switch the specified user to the foreground. - *

This cannot be used to switch to a managed profile. + * Called by a device owner to switch the specified secondary user to the foreground. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to switch to; null will switch to primary. * @return {@code true} if the switch was successful, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. * @see Intent#ACTION_USER_FOREGROUND + * @see #getSecondaryUsers(ComponentName) */ public boolean switchUser(@NonNull ComponentName admin, @Nullable UserHandle userHandle) { throwIfParentInstance("switchUser"); @@ -6608,14 +6608,33 @@ public class DevicePolicyManager { } } + /** + * Called by a device owner to start the specified secondary user in background. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param userHandle the user to be stopped. + * @return {@code true} if the user can be started, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * @see #getSecondaryUsers(ComponentName) + */ + public boolean startUserInBackground( + @NonNull ComponentName admin, @NonNull UserHandle userHandle) { + throwIfParentInstance("startUserInBackground"); + try { + return mService.startUserInBackground(admin, userHandle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Called by a device owner to stop the specified secondary user. - *

This cannot be used to stop the primary user or a managed profile. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to be stopped. * @return {@code true} if the user can be stopped, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. + * @see #getSecondaryUsers(ComponentName) */ public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("stopUser"); @@ -6627,14 +6646,13 @@ public class DevicePolicyManager { } /** - * Called by a profile owner that is affiliated with the device to stop the calling user - * and switch back to primary. - *

This has no effect when called on a managed profile. + * Called by a profile owner of secondary user that is affiliated with the device to stop the + * calling user and switch back to primary. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return {@code true} if the exit was successful, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device. - * @see #isAffiliatedUser + * @see #getSecondaryUsers(ComponentName) */ public boolean logoutUser(@NonNull ComponentName admin) { throwIfParentInstance("logoutUser"); @@ -6646,17 +6664,18 @@ public class DevicePolicyManager { } /** - * Called by a device owner to list all secondary users on the device, excluding managed - * profiles. + * Called by a device owner to list all secondary users on the device. Managed profiles are not + * considered as secondary users. *

Used for various user management APIs, including {@link #switchUser}, {@link #removeUser} * and {@link #stopUser}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return list of other {@link UserHandle}s on the device. * @throws SecurityException if {@code admin} is not a device owner. - * @see #switchUser - * @see #removeUser - * @see #stopUser + * @see #removeUser(ComponentName, UserHandle) + * @see #switchUser(ComponentName, UserHandle) + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) */ public List getSecondaryUsers(@NonNull ComponentName admin) { throwIfParentInstance("getSecondaryUsers"); @@ -6796,7 +6815,8 @@ public class DevicePolicyManager { * @param restriction Indicates for which feature the dialog should be displayed. Can be a * user restriction from {@link UserManager}, e.g. * {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the constants - * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}. + * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or + * {@link #POLICY_MANDATORY_BACKUPS}. * @return Intent An intent to be used to start the dialog-activity if the restriction is * set by an admin, or null if the restriction does not exist or no admin set it. */ @@ -7017,14 +7037,14 @@ public class DevicePolicyManager { * task. From {@link android.os.Build.VERSION_CODES#M} removing packages from the lock task * package list results in locked tasks belonging to those packages to be finished. *

- * This function can only be called by the device owner or by a profile owner of a user/profile - * that is affiliated with the device. See {@link #isAffiliatedUser}. Any packages - * set via this method will be cleared if the user becomes unaffiliated. + * This function can only be called by the device owner, a profile owner of an affiliated user + * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}. + * Any package set via this method will be cleared if the user becomes unaffiliated. * * @param packages The list of packages allowed to enter lock task mode * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an + * affiliated user or profile, or the profile owner when no device owner is set. * @see #isAffiliatedUser * @see Activity#startLockTask() * @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String) @@ -7046,8 +7066,8 @@ public class DevicePolicyManager { /** * Returns the list of packages allowed to start the lock task mode. * - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an + * affiliated user or profile, or the profile owner when no device owner is set. * @see #isAffiliatedUser * @see #setLockTaskPackages */ @@ -7087,9 +7107,9 @@ public class DevicePolicyManager { * is in LockTask mode. If this method is not called, none of the features listed here will be * enabled. *

- * This function can only be called by the device owner or by a profile owner of a user/profile - * that is affiliated with the device. See {@link #isAffiliatedUser}. Any features - * set via this method will be cleared if the user becomes unaffiliated. + * This function can only be called by the device owner, a profile owner of an affiliated user + * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}. + * Any features set via this method will be cleared if the user becomes unaffiliated. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param flags Bitfield of feature flags: @@ -7100,9 +7120,10 @@ public class DevicePolicyManager { * {@link #LOCK_TASK_FEATURE_RECENTS}, * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}, * {@link #LOCK_TASK_FEATURE_KEYGUARD} - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an + * affiliated user or profile, or the profile owner when no device owner is set. * @see #isAffiliatedUser + * @throws SecurityException if {@code admin} is not the device owner or the profile owner. */ public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) { throwIfParentInstance("setLockTaskFeatures"); @@ -7120,8 +7141,8 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list. - * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of - * an affiliated user or profile. + * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an + * affiliated user or profile, or the profile owner when no device owner is set. * @see #isAffiliatedUser * @see #setLockTaskFeatures */ @@ -7519,13 +7540,28 @@ public class DevicePolicyManager { /** * Called by device owners to set a local system update policy. When a new policy is set, * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcasted. + *

+ * If the supplied system update policy has freeze periods set but the freeze periods do not + * meet 90-day maximum length or 60-day minimum separation requirement set out in + * {@link SystemUpdatePolicy#setFreezePeriods}, + * {@link SystemUpdatePolicy.ValidationFailedException} will the thrown. Note that the system + * keeps a record of freeze periods the device experienced previously, and combines them with + * the new freeze periods to be set when checking the maximum freeze length and minimum freeze + * separation constraints. As a result, freeze periods that passed validation during + * {@link SystemUpdatePolicy#setFreezePeriods} might fail the additional checks here due to + * the freeze period history. If this is causing issues during development, + * {@code adb shell dpm clear-freeze-period-record} can be used to clear the record. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. All * components in the device owner package can set system update policies and the most * recent policy takes effect. * @param policy the new policy, or {@code null} to clear the current policy. * @throws SecurityException if {@code admin} is not a device owner. + * @throws IllegalArgumentException if the policy type or maintenance window is not valid. + * @throws SystemUpdatePolicy.ValidationFailedException if the policy's freeze period does not + * meet the requirement. * @see SystemUpdatePolicy + * @see SystemUpdatePolicy#setFreezePeriods(List) */ public void setSystemUpdatePolicy(@NonNull ComponentName admin, SystemUpdatePolicy policy) { throwIfParentInstance("setSystemUpdatePolicy"); @@ -7555,6 +7591,22 @@ public class DevicePolicyManager { return null; } + /** + * Reset record of previous system update freeze period the device went through. + * Only callable by ADB. + * @hide + */ + public void clearSystemUpdatePolicyFreezePeriodRecord() { + throwIfParentInstance("clearSystemUpdatePolicyFreezePeriodRecord"); + if (mService == null) { + return; + } + try { + mService.clearSystemUpdatePolicyFreezePeriodRecord(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } /** * Called by a device owner or profile owner of secondary users that is affiliated with the * device to disable the keyguard altogether. @@ -8208,6 +8260,71 @@ public class DevicePolicyManager { return new DevicePolicyManager(mContext, mService, true); } + /** + * Called by a device or profile owner to restrict packages from using metered data. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param packageNames the list of package names to be restricted. + * @return a list of package names which could not be restricted. + * @throws SecurityException if {@code admin} is not a device or profile owner. + */ + public @NonNull List setMeteredDataDisabled(@NonNull ComponentName admin, + @NonNull List packageNames) { + throwIfParentInstance("setMeteredDataDisabled"); + if (mService != null) { + try { + return mService.setMeteredDataDisabled(admin, packageNames); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return packageNames; + } + + /** + * Called by a device or profile owner to retrieve the list of packages which are restricted + * by the admin from using metered data. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @return the list of restricted package names. + * @throws SecurityException if {@code admin} is not a device or profile owner. + */ + public @NonNull List getMeteredDataDisabled(@NonNull ComponentName admin) { + throwIfParentInstance("getMeteredDataDisabled"); + if (mService != null) { + try { + return mService.getMeteredDataDisabled(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return new ArrayList<>(); + } + + /** + * Called by the system to check if a package is restricted from using metered data + * by {@param admin}. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName the package whose restricted status is needed. + * @param userId the user to which {@param packageName} belongs. + * @return {@code true} if the package is restricted by admin, otherwise {@code false} + * @throws SecurityException if the caller doesn't run with {@link Process#SYSTEM_UID} + * @hide + */ + public boolean isMeteredDataDisabledForUser(@NonNull ComponentName admin, String packageName, + @UserIdInt int userId) { + throwIfParentInstance("getMeteredDataDisabledForUser"); + if (mService != null) { + try { + return mService.isMeteredDataDisabledForUser(admin, packageName, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return false; + } + /** * Called by device owners to retrieve device logs from before the device's last reboot. *

@@ -8620,6 +8737,13 @@ public class DevicePolicyManager { * *

Backup service is off by default when device owner is present. * + *

If backups are made mandatory by specifying a non-null mandatory backup transport using + * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is + * automatically enabled. + * + *

If the backup service is disabled using this method after the mandatory backup transport + * has been set, the mandatory backup transport is cleared. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled {@code true} to enable the backup service, {@code false} to disable it. * @throws SecurityException if {@code admin} is not a device owner. @@ -8650,6 +8774,45 @@ public class DevicePolicyManager { } } + /** + * Makes backups mandatory and enforces the usage of the specified backup transport. + * + *

When a {@code null} backup transport is specified, backups are made optional again. + *

Only device owner can call this method. + *

If backups were disabled and a non-null backup transport {@link ComponentName} is + * specified, backups will be enabled. + * + * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param backupTransportComponent The backup transport layer to be used for mandatory backups. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setMandatoryBackupTransport( + @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) { + throwIfParentInstance("setMandatoryBackupTransport"); + try { + mService.setMandatoryBackupTransport(admin, backupTransportComponent); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the backup transport which has to be used for backups if backups are mandatory or + * {@code null} if backups are not mandatory. + * + * @return a {@link ComponentName} of the backup transport layer to be used if backups are + * mandatory or {@code null} if backups are not mandatory. + */ + public ComponentName getMandatoryBackupTransport() { + throwIfParentInstance("getMandatoryBackupTransport"); + try { + return mService.getMandatoryBackupTransport(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Called by a device owner to control the network logging feature. * @@ -8926,15 +9089,6 @@ public class DevicePolicyManager { } } - /** {@hide} */ - @Condemned - @Deprecated - public boolean clearApplicationUserData(@NonNull ComponentName admin, - @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull Handler handler) { - return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler)); - } - /** * Called by the device owner or profile owner to clear application user data of a given * package. The behaviour of this is equivalent to the target application calling @@ -8945,18 +9099,18 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageName The name of the package which will have its user data wiped. - * @param listener A callback object that will inform the caller when the clearing is done. * @param executor The executor through which the listener should be invoked. + * @param listener A callback object that will inform the caller when the clearing is done. * @throws SecurityException if the caller is not the device owner/profile owner. - * @return whether the clearing succeeded. */ - public boolean clearApplicationUserData(@NonNull ComponentName admin, - @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull @CallbackExecutor Executor executor) { + public void clearApplicationUserData(@NonNull ComponentName admin, + @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, + @NonNull OnClearApplicationUserDataListener listener) { throwIfParentInstance("clearAppData"); Preconditions.checkNotNull(executor); + Preconditions.checkNotNull(listener); try { - return mService.clearApplicationUserData(admin, packageName, + mService.clearApplicationUserData(admin, packageName, new IPackageDataObserver.Stub() { public void onRemoveCompleted(String pkg, boolean succeeded) { executor.execute(() -> @@ -9047,19 +9201,28 @@ public class DevicePolicyManager { *

  • A profile owner can only be transferred to a new profile owner
  • * * - *

    Use the {@code bundle} parameter to pass data to the new administrator. The parameters + *

    Use the {@code bundle} parameter to pass data to the new administrator. The data * will be received in the - * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} callback. + * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} + * callback of the new administrator. + * + *

    The transfer has failed if the original administrator is still the corresponding owner + * after calling this method. + * + *

    The incoming target administrator must have the + * {@link DeviceAdminReceiver#SUPPORT_TRANSFER_OWNERSHIP_META_DATA} meta-data tag + * included in its corresponding receiver component with a value of {@code true}. + * Otherwise an {@link IllegalArgumentException} will be thrown. * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param target which {@link DeviceAdminReceiver} we want the new administrator to be * @param bundle data to be sent to the new administrator * @throws SecurityException if {@code admin} is not a device owner nor a profile owner - * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, - * both are components in the same package or {@code target} is not an active admin + * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, they + * are components in the same package or {@code target} is not an active admin */ public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target, - PersistableBundle bundle) { + @Nullable PersistableBundle bundle) { throwIfParentInstance("transferOwnership"); try { mService.transferOwnership(admin, target, bundle); @@ -9067,4 +9230,273 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Called by a device owner to specify the user session start message. This may be displayed + * during a user switch. + *

    + * The message should be limited to a short statement or it may be truncated. + *

    + * If the message needs to be localized, it is the responsibility of the + * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast + * and set a new version of this message accordingly. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param startUserSessionMessage message for starting user session, or {@code null} to use + * system default message. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setStartUserSessionMessage( + @NonNull ComponentName admin, @Nullable CharSequence startUserSessionMessage) { + throwIfParentInstance("setStartUserSessionMessage"); + try { + mService.setStartUserSessionMessage(admin, startUserSessionMessage); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Called by a device owner to specify the user session end message. This may be displayed + * during a user switch. + *

    + * The message should be limited to a short statement or it may be truncated. + *

    + * If the message needs to be localized, it is the responsibility of the + * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast + * and set a new version of this message accordingly. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param endUserSessionMessage message for ending user session, or {@code null} to use system + * default message. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setEndUserSessionMessage( + @NonNull ComponentName admin, @Nullable CharSequence endUserSessionMessage) { + throwIfParentInstance("setEndUserSessionMessage"); + try { + mService.setEndUserSessionMessage(admin, endUserSessionMessage); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the user session start message. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public CharSequence getStartUserSessionMessage(@NonNull ComponentName admin) { + throwIfParentInstance("getStartUserSessionMessage"); + try { + return mService.getStartUserSessionMessage(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the user session end message. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public CharSequence getEndUserSessionMessage(@NonNull ComponentName admin) { + throwIfParentInstance("getEndUserSessionMessage"); + try { + return mService.getEndUserSessionMessage(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Allows/disallows printing. + * + * Called by a device owner or a profile owner. + * Device owner changes policy for all users. Profile owner can override it if present. + * Printing is enabled by default. If {@code FEATURE_PRINTING} is absent, the call is ignored. + * + * @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. + */ + 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 this user. + * + * Always {@code false} if {@code FEATURE_PRINTING} is absent. + * Otherwise, {@code true} by default. + * + * @return {@code true} iff printing is enabled. + */ + public boolean isPrintingEnabled() { + try { + return mService.isPrintingEnabled(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Called by device owner to add an override APN. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param apnSetting the override APN to insert + * @return The {@code id} of inserted override APN. Or {@code -1} when failed to insert into + * the database. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public int addOverrideApn(@NonNull ComponentName admin, @NonNull ApnSetting apnSetting) { + throwIfParentInstance("addOverrideApn"); + if (mService != null) { + try { + return mService.addOverrideApn(admin, apnSetting); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return -1; + } + + /** + * Called by device owner to update an override APN. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param apnId the {@code id} of the override APN to update + * @param apnSetting the override APN to update + * @return {@code true} if the required override APN is successfully updated, + * {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public boolean updateOverrideApn(@NonNull ComponentName admin, int apnId, + @NonNull ApnSetting apnSetting) { + throwIfParentInstance("updateOverrideApn"); + if (mService != null) { + try { + return mService.updateOverrideApn(admin, apnId, apnSetting); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device owner to remove an override APN. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param apnId the {@code id} of the override APN to remove + * @return {@code true} if the required override APN is successfully removed, {@code false} + * otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public boolean removeOverrideApn(@NonNull ComponentName admin, int apnId) { + throwIfParentInstance("removeOverrideApn"); + if (mService != null) { + try { + return mService.removeOverrideApn(admin, apnId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device owner to get all override APNs inserted by device owner. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @return A list of override APNs inserted by device owner. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public List getOverrideApns(@NonNull ComponentName admin) { + throwIfParentInstance("getOverrideApns"); + if (mService != null) { + try { + return mService.getOverrideApns(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return Collections.emptyList(); + } + + /** + * Called by device owner to set if override APNs should be enabled. + *

    Override APNs are separated from other APNs on the device, and can only be inserted or + * modified by the device owner. When enabled, only override APNs are in use, any other APNs + * are ignored. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param enabled {@code true} if override APNs should be enabled, {@code false} otherwise + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setOverrideApnsEnabled(@NonNull ComponentName admin, boolean enabled) { + throwIfParentInstance("setOverrideApnEnabled"); + if (mService != null) { + try { + mService.setOverrideApnsEnabled(admin, enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Called by device owner to check if override APNs are currently enabled. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @return {@code true} if override APNs are currently enabled, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * + * @see #setOverrideApnsEnabled(ComponentName, boolean) + */ + public boolean isOverrideApnEnabled(@NonNull ComponentName admin) { + throwIfParentInstance("isOverrideApnEnabled"); + if (mService != null) { + try { + return mService.isOverrideApnEnabled(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Returns the data passed from the current administrator to the new administrator during an + * ownership transfer. This is the same {@code bundle} passed in + * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}. + * + *

    Returns null if no ownership transfer was started for the calling user. + * + * @see #transferOwnership + * @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle) + */ + @Nullable + public PersistableBundle getTransferOwnershipBundle() { + throwIfParentInstance("getTransferOwnershipBundle"); + try { + return mService.getTransferOwnershipBundle(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index b692ffd95e72d5ef30c0b258883ea3363d645346..ebaf4648d80af16ff654fe794347bc7d9b840181 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -123,4 +123,22 @@ public abstract class DevicePolicyManagerInternal { * @param userId User ID of the profile. */ public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId); + + /** + * Check whether the user could have their password reset in an untrusted manor due to there + * being an admin which can call {@link #resetPassword} to reset the password without knowledge + * of the previous password. + * + * @param userId The user in question + */ + public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId); + + /** + * Return text of error message if printing is disabled. + * Called by Print Service when printing is disabled by PO or DO when printing is attempted. + * + * @param userId The user in question + * @return localized error message + */ + public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId); } diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java index 4ddf13e073441e4bb100014938d25d75dd8abfb6..a2d704b8664971c854c91b1e03836337b60f6d2f 100644 --- a/core/java/android/app/admin/DnsEvent.java +++ b/core/java/android/app/admin/DnsEvent.java @@ -96,7 +96,7 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname, + return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname, (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses), mIpAddressesCount, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/FreezeInterval.java b/core/java/android/app/admin/FreezeInterval.java new file mode 100644 index 0000000000000000000000000000000000000000..7acdfc8fe100ee05cc8a26d6b034d86721de9cf4 --- /dev/null +++ b/core/java/android/app/admin/FreezeInterval.java @@ -0,0 +1,299 @@ +/* + * 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.app.admin; + +import android.app.admin.SystemUpdatePolicy.ValidationFailedException; +import android.util.Log; +import android.util.Pair; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * An interval representing one freeze period which repeats annually. We use the number of days + * since the start of (non-leap) year to define the start and end dates of an interval, both + * inclusive. If the end date is smaller than the start date, the interval is considered wrapped + * around the year-end. As far as an interval is concerned, February 29th should be treated as + * if it were February 28th: so an interval starting or ending on February 28th are not + * distinguishable from an interval on February 29th. When calulating interval length or + * distance between two dates, February 29th is also disregarded. + * + * @see SystemUpdatePolicy#setFreezePeriods + * @hide + */ +public class FreezeInterval { + private static final String TAG = "FreezeInterval"; + + private static final int DUMMY_YEAR = 2001; + static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year + + final int mStartDay; // [1,365] + final int mEndDay; // [1,365] + + FreezeInterval(int startDay, int endDay) { + if (startDay < 1 || startDay > 365 || endDay < 1 || endDay > 365) { + throw new RuntimeException("Bad dates for Interval: " + startDay + "," + endDay); + } + mStartDay = startDay; + mEndDay = endDay; + } + + int getLength() { + return getEffectiveEndDay() - mStartDay + 1; + } + + boolean isWrapped() { + return mEndDay < mStartDay; + } + + /** + * Returns the effective end day, taking wrapping around year-end into consideration + */ + int getEffectiveEndDay() { + if (!isWrapped()) { + return mEndDay; + } else { + return mEndDay + DAYS_IN_YEAR; + } + } + + boolean contains(LocalDate localDate) { + final int daysOfYear = dayOfYearDisregardLeapYear(localDate); + if (!isWrapped()) { + // ---[start---now---end]--- + return (mStartDay <= daysOfYear) && (daysOfYear <= mEndDay); + } else { + // ---end]---[start---now--- + // or ---now---end]---[start--- + return (mStartDay <= daysOfYear) || (daysOfYear <= mEndDay); + } + } + + /** + * Instantiate the current interval to real calendar dates, given a calendar date + * {@code now}. If the interval contains now, the returned calendar dates should be the + * current interval (in real calendar dates) that includes now. If the interval does not + * include now, the returned dates represents the next future interval. + * The result will always have the same month and dayOfMonth value as the non-instantiated + * interval itself. + */ + Pair toCurrentOrFutureRealDates(LocalDate now) { + final int nowDays = dayOfYearDisregardLeapYear(now); + final int startYearAdjustment, endYearAdjustment; + if (contains(now)) { + // current interval + if (mStartDay <= nowDays) { + // ----------[start---now---end]--- + // or ---end]---[start---now---------- + startYearAdjustment = 0; + endYearAdjustment = isWrapped() ? 1 : 0; + } else /* nowDays <= mEndDay */ { + // or ---now---end]---[start---------- + startYearAdjustment = -1; + endYearAdjustment = 0; + } + } else { + // next interval + if (mStartDay > nowDays) { + // ----------now---[start---end]--- + // or ---end]---now---[start---------- + startYearAdjustment = 0; + endYearAdjustment = isWrapped() ? 1 : 0; + } else /* mStartDay <= nowDays */ { + // or ---[start---end]---now---------- + startYearAdjustment = 1; + endYearAdjustment = 1; + } + } + final LocalDate startDate = LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).withYear( + now.getYear() + startYearAdjustment); + final LocalDate endDate = LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).withYear( + now.getYear() + endYearAdjustment); + return new Pair<>(startDate, endDate); + } + + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd"); + return LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).format(formatter) + " - " + + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter); + } + + // Treat the supplied date as in a non-leap year and return its day of year. + static int dayOfYearDisregardLeapYear(LocalDate date) { + return date.withYear(DUMMY_YEAR).getDayOfYear(); + } + + /** + * Compute the number of days between first (inclusive) and second (exclusive), + * treating all years in between as non-leap. + */ + public static int distanceWithoutLeapYear(LocalDate first, LocalDate second) { + return dayOfYearDisregardLeapYear(first) - dayOfYearDisregardLeapYear(second) + + DAYS_IN_YEAR * (first.getYear() - second.getYear()); + } + + /** + * Sort, de-duplicate and merge an interval list + * + * Instead of using any fancy logic for merging intervals which has loads of corner cases, + * simply flatten the interval onto a list of 365 calendar days and recreate the interval list + * from that. + * + * This method should return a list of intervals with the following post-conditions: + * 1. Interval.startDay in strictly ascending order + * 2. No two intervals should overlap or touch + * 3. At most one wrapped Interval remains, and it will be at the end of the list + * @hide + */ + private static List canonicalizeIntervals(List intervals) { + boolean[] taken = new boolean[DAYS_IN_YEAR]; + // First convert the intervals into flat array + for (FreezeInterval interval : intervals) { + for (int i = interval.mStartDay; i <= interval.getEffectiveEndDay(); i++) { + taken[(i - 1) % DAYS_IN_YEAR] = true; + } + } + // Then reconstruct intervals from the array + List result = new ArrayList<>(); + int i = 0; + while (i < DAYS_IN_YEAR) { + if (!taken[i]) { + i++; + continue; + } + final int intervalStart = i + 1; + while (i < DAYS_IN_YEAR && taken[i]) i++; + result.add(new FreezeInterval(intervalStart, i)); + } + // Check if the last entry can be merged to the first entry to become one single + // wrapped interval + final int lastIndex = result.size() - 1; + if (lastIndex > 0 && result.get(lastIndex).mEndDay == DAYS_IN_YEAR + && result.get(0).mStartDay == 1) { + FreezeInterval wrappedInterval = new FreezeInterval(result.get(lastIndex).mStartDay, + result.get(0).mEndDay); + result.set(lastIndex, wrappedInterval); + result.remove(0); + } + return result; + } + + /** + * Verifies if the supplied freeze periods satisfies the constraints set out in + * {@link SystemUpdatePolicy#setFreezePeriods(List)}, and in particular, any single freeze + * period cannot exceed {@link SystemUpdatePolicy#FREEZE_PERIOD_MAX_LENGTH} days, and two freeze + * periods need to be at least {@link SystemUpdatePolicy#FREEZE_PERIOD_MIN_SEPARATION} days + * apart. + * + * @hide + */ + protected static void validatePeriods(List periods) { + List allPeriods = FreezeInterval.canonicalizeIntervals(periods); + if (allPeriods.size() != periods.size()) { + throw SystemUpdatePolicy.ValidationFailedException.duplicateOrOverlapPeriods(); + } + for (int i = 0; i < allPeriods.size(); i++) { + FreezeInterval current = allPeriods.get(i); + if (current.getLength() > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { + throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooLong("Freeze " + + "period " + current + " is too long: " + current.getLength() + " days"); + } + FreezeInterval previous = i > 0 ? allPeriods.get(i - 1) + : allPeriods.get(allPeriods.size() - 1); + if (previous != current) { + final int separation; + if (i == 0 && !previous.isWrapped()) { + // -->[current]---[-previous-]<--- + separation = current.mStartDay + + (DAYS_IN_YEAR - previous.mEndDay) - 1; + } else { + // --[previous]<--->[current]--------- + // OR ----prev---]<--->[current]---[prev- + separation = current.mStartDay - previous.mEndDay - 1; + } + if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { + throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooClose("Freeze" + + " periods " + previous + " and " + current + " are too close " + + "together: " + separation + " days apart"); + } + } + } + } + + /** + * Verifies that the current freeze periods are still legal, considering the previous freeze + * periods the device went through. In particular, when combined with the previous freeze + * period, the maximum freeze length or the minimum freeze separation should not be violated. + * + * @hide + */ + protected static void validateAgainstPreviousFreezePeriod(List periods, + LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) { + if (periods.size() == 0 || prevPeriodStart == null || prevPeriodEnd == null) { + return; + } + if (prevPeriodStart.isAfter(now) || prevPeriodEnd.isAfter(now)) { + Log.w(TAG, "Previous period (" + prevPeriodStart + "," + prevPeriodEnd + ") is after" + + " current date " + now); + // Clock was adjusted backwards. We can continue execution though, the separation + // and length validation below still works under this condition. + } + List allPeriods = FreezeInterval.canonicalizeIntervals(periods); + // Given current time now, find the freeze period that's either current, or the one + // that's immediately afterwards. For the later case, it might be after the year-end, + // but this can only happen if there is only one freeze period. + FreezeInterval curOrNextFreezePeriod = allPeriods.get(0); + for (FreezeInterval interval : allPeriods) { + if (interval.contains(now) + || interval.mStartDay > FreezeInterval.dayOfYearDisregardLeapYear(now)) { + curOrNextFreezePeriod = interval; + break; + } + } + Pair curOrNextFreezeDates = curOrNextFreezePeriod + .toCurrentOrFutureRealDates(now); + if (now.isAfter(curOrNextFreezeDates.first)) { + curOrNextFreezeDates = new Pair<>(now, curOrNextFreezeDates.second); + } + if (curOrNextFreezeDates.first.isAfter(curOrNextFreezeDates.second)) { + throw new IllegalStateException("Current freeze dates inverted: " + + curOrNextFreezeDates.first + "-" + curOrNextFreezeDates.second); + } + // Now validate [prevPeriodStart, prevPeriodEnd] against curOrNextFreezeDates + final String periodsDescription = "Prev: " + prevPeriodStart + "," + prevPeriodEnd + + "; cur: " + curOrNextFreezeDates.first + "," + curOrNextFreezeDates.second; + long separation = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.first, + prevPeriodEnd) - 1; + if (separation > 0) { + // Two intervals do not overlap, check separation + if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { + throw ValidationFailedException.combinedPeriodTooClose("Previous freeze period " + + "too close to new period: " + separation + ", " + periodsDescription); + } + } else { + // Two intervals overlap, check combined length + long length = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.second, + prevPeriodStart) + 1; + if (length > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { + throw ValidationFailedException.combinedPeriodTooLong("Combined freeze period " + + "exceeds maximum days: " + length + ", " + periodsDescription); + } + } + } +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 1d8ddeebcc26c25119d1010d40149e3692694d12..cba9311ff68b5b6ce92103597d6f03b340be4a5f 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -38,6 +38,7 @@ import android.os.RemoteCallback; import android.os.UserHandle; import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.ParcelableKeyGenParameterSpec; +import android.telephony.data.ApnSetting; import java.util.List; @@ -226,6 +227,7 @@ interface IDevicePolicyManager { UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags); boolean removeUser(in ComponentName who, in UserHandle userHandle); boolean switchUser(in ComponentName who, in UserHandle userHandle); + boolean startUserInBackground(in ComponentName who, in UserHandle userHandle); boolean stopUser(in ComponentName who, in UserHandle userHandle); boolean logoutUser(in ComponentName who); List getSecondaryUsers(in ComponentName who); @@ -293,6 +295,7 @@ interface IDevicePolicyManager { void setSystemUpdatePolicy(in ComponentName who, in SystemUpdatePolicy policy); SystemUpdatePolicy getSystemUpdatePolicy(); + void clearSystemUpdatePolicyFreezePeriodRecord(); boolean setKeyguardDisabled(in ComponentName admin, boolean disabled); boolean setStatusBarDisabled(in ComponentName who, boolean disabled); @@ -358,6 +361,8 @@ interface IDevicePolicyManager { void setBackupServiceEnabled(in ComponentName admin, boolean enabled); boolean isBackupServiceEnabled(in ComponentName admin); + void setMandatoryBackupTransport(in ComponentName admin, in ComponentName backupTransportComponent); + ComponentName getMandatoryBackupTransport(); void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); boolean isNetworkLoggingEnabled(in ComponentName admin); @@ -381,11 +386,33 @@ interface IDevicePolicyManager { boolean isCurrentInputMethodSetByOwner(); StringParceledListSlice getOwnerInstalledCaCerts(in UserHandle user); - boolean clearApplicationUserData(in ComponentName admin, in String packageName, in IPackageDataObserver callback); + void clearApplicationUserData(in ComponentName admin, in String packageName, in IPackageDataObserver callback); void setLogoutEnabled(in ComponentName admin, boolean enabled); boolean isLogoutEnabled(); List getDisallowedSystemApps(in ComponentName admin, int userId, String provisioningAction); + void transferOwnership(in ComponentName admin, in ComponentName target, in PersistableBundle bundle); + PersistableBundle getTransferOwnershipBundle(); + + void setStartUserSessionMessage(in ComponentName admin, in CharSequence startUserSessionMessage); + 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(); + + List setMeteredDataDisabled(in ComponentName admin, in List packageNames); + List getMeteredDataDisabled(in ComponentName admin); + + int addOverrideApn(in ComponentName admin, in ApnSetting apnSetting); + boolean updateOverrideApn(in ComponentName admin, int apnId, in ApnSetting apnSetting); + boolean removeOverrideApn(in ComponentName admin, int apnId); + List getOverrideApns(in ComponentName admin); + void setOverrideApnsEnabled(in ComponentName admin, boolean enabled); + boolean isOverrideApnEnabled(in ComponentName admin); + + boolean isMeteredDataDisabledForUser(in ComponentName admin, String packageName, int userId); } diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java index d3b66d0d144a06d6059f30d9215c5bd1399b2697..08effd9c148aa05e4695eaca913622ebf2d612c2 100644 --- a/core/java/android/app/admin/SecurityLog.java +++ b/core/java/android/app/admin/SecurityLog.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.IntDef; import android.annotation.TestApi; +import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemProperties; @@ -53,63 +54,366 @@ public class SecurityLog { TAG_APP_PROCESS_START, TAG_KEYGUARD_DISMISSED, TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, - TAG_KEYGUARD_SECURED + TAG_KEYGUARD_SECURED, + TAG_OS_STARTUP, + TAG_OS_SHUTDOWN, + TAG_LOGGING_STARTED, + TAG_LOGGING_STOPPED, + TAG_MEDIA_MOUNT, + TAG_MEDIA_UNMOUNT, + TAG_LOG_BUFFER_SIZE_CRITICAL, + TAG_PASSWORD_EXPIRATION_SET, + TAG_PASSWORD_COMPLEXITY_SET, + TAG_PASSWORD_HISTORY_LENGTH_SET, + TAG_MAX_SCREEN_LOCK_TIMEOUT_SET, + TAG_MAX_PASSWORD_ATTEMPTS_SET, + TAG_KEYGUARD_DISABLED_FEATURES_SET, + TAG_REMOTE_LOCK, + TAG_USER_RESTRICTION_ADDED, + TAG_USER_RESTRICTION_REMOVED, + TAG_WIPE_FAILURE, + TAG_KEY_GENERATED, + TAG_KEY_IMPORT, + TAG_KEY_DESTRUCTION, + TAG_CERT_AUTHORITY_INSTALLED, + TAG_CERT_AUTHORITY_REMOVED, }) public @interface SecurityLogTag {} + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "LEVEL_" }, value = { + LEVEL_INFO, + LEVEL_WARNING, + LEVEL_ERROR + }) + public @interface SecurityLogLevel {} + /** - * Indicate that an ADB interactive shell was opened via "adb shell". + * Indicates that an ADB interactive shell was opened via "adb shell". * There is no extra payload in the log event. */ public static final int TAG_ADB_SHELL_INTERACTIVE = SecurityLogTags.SECURITY_ADB_SHELL_INTERACTIVE; + /** - * Indicate that an shell command was issued over ADB via "adb shell command" - * The log entry contains a string data of the shell command, accessible via - * {@link SecurityEvent#getData()} + * Indicates that a shell command was issued over ADB via {@code adb shell } + * The log entry contains a {@code String} payload containing the shell command, accessible + * via {@link SecurityEvent#getData()}. */ public static final int TAG_ADB_SHELL_CMD = SecurityLogTags.SECURITY_ADB_SHELL_COMMAND; + /** - * Indicate that a file was pulled from the device via the adb daemon, for example via - * "adb pull". The log entry contains a string data of the path of the pulled file, - * accessible via {@link SecurityEvent#getData()} + * Indicates that a file was pulled from the device via the adb daemon, for example via + * {@code adb pull}. The log entry contains a {@code String} payload containing the path of the + * pulled file on the device, accessible via {@link SecurityEvent#getData()}. */ public static final int TAG_SYNC_RECV_FILE = SecurityLogTags.SECURITY_ADB_SYNC_RECV; + /** - * Indicate that a file was pushed to the device via the adb daemon, for example via - * "adb push". The log entry contains a string data of the destination path of the - * pushed file, accessible via {@link SecurityEvent#getData()} + * Indicates that a file was pushed to the device via the adb daemon, for example via + * {@code adb push}. The log entry contains a {@code String} payload containing the destination + * path of the pushed file, accessible via {@link SecurityEvent#getData()}. */ public static final int TAG_SYNC_SEND_FILE = SecurityLogTags.SECURITY_ADB_SYNC_SEND; + /** - * Indicate that an app process was started. The log entry contains the following + * Indicates that an app process was started. The log entry contains the following * information about the process encapsulated in an {@link Object} array, accessible via * {@link SecurityEvent#getData()}: - * process name (String), exact start time (long), app Uid (integer), app Pid (integer), - * seinfo tag (String), SHA-256 hash of the base APK in hexadecimal (String) + *

  • [0] process name ({@code String}) + *
  • [1] exact start time in milliseconds according to {@code System.currentTimeMillis()} + * ({@code Long}) + *
  • [2] app uid ({@code Integer}) + *
  • [3] app pid ({@code Integer}) + *
  • [4] seinfo tag ({@code String}) + *
  • [5] SHA-256 hash of the base APK in hexadecimal ({@code String}) */ public static final int TAG_APP_PROCESS_START = SecurityLogTags.SECURITY_APP_PROCESS_START; + /** - * Indicate that keyguard is being dismissed. + * Indicates that keyguard has been dismissed. * There is no extra payload in the log event. */ - public static final int TAG_KEYGUARD_DISMISSED = - SecurityLogTags.SECURITY_KEYGUARD_DISMISSED; + public static final int TAG_KEYGUARD_DISMISSED = SecurityLogTags.SECURITY_KEYGUARD_DISMISSED; + /** - * Indicate that there has been an authentication attempt to dismiss the keyguard. The log entry - * contains the following information about the attempt encapsulated in an {@link Object} array, - * accessible via {@link SecurityEvent#getData()}: - * attempt result (integer, 1 for successful, 0 for unsuccessful), strength of auth method - * (integer, 1 if strong auth method was used, 0 otherwise) + * Indicates that there has been an authentication attempt to dismiss the keyguard. The log + * entry contains the following information about the attempt encapsulated in an {@link Object} + * array, accessible via {@link SecurityEvent#getData()}: + *
  • [0] attempt result ({@code Integer}, 1 for successful, 0 for unsuccessful) + *
  • [1] strength of authentication method ({@code Integer}, 1 if strong authentication + * method was used, 0 otherwise) */ public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = SecurityLogTags.SECURITY_KEYGUARD_DISMISS_AUTH_ATTEMPT; + /** - * Indicate that the device has been locked, either by user or by timeout. - * There is no extra payload in the log event. + * Indicates that the device has been locked, either by the user or by a timeout. There is no + * extra payload in the log event. */ public static final int TAG_KEYGUARD_SECURED = SecurityLogTags.SECURITY_KEYGUARD_SECURED; + /** + * Indicates that the Android OS has started. The log entry contains the following information + * about the startup time software integrity check encapsulated in an {@link Object} array, + * accessible via {@link SecurityEvent#getData()}: + *
  • [0] Verified Boot state ({@code String}) + *
  • [1] dm-verity mode ({@code String}). + *

    Verified Boot state can be one of the following: + *

  • {@code green} indicates that there is a full chain of trust extending from the + * bootloader to verified partitions including the bootloader, boot partition, and all verified + * partitions. + *
  • {@code yellow} indicates that the boot partition has been verified using the embedded + * certificate and the signature is valid. + *
  • {@code orange} indicates that the device may be freely modified. Device integrity is + * left to the user to verify out-of-band. + *

    dm-verity mode can be one of the following: + *

  • {@code enforcing} indicates that the device will be restarted when corruption is + * detected. + *
  • {@code eio} indicates that an I/O error will be returned for an attempt to read + * corrupted data blocks. + * For details see Verified Boot documentation. + */ + public static final int TAG_OS_STARTUP = SecurityLogTags.SECURITY_OS_STARTUP; + + /** + * Indicates that the Android OS has shutdown. There is no extra payload in the log event. + */ + public static final int TAG_OS_SHUTDOWN = SecurityLogTags.SECURITY_OS_SHUTDOWN; + + /** + * Indicates start-up of audit logging. There is no extra payload in the log event. + */ + public static final int TAG_LOGGING_STARTED = SecurityLogTags.SECURITY_LOGGING_STARTED; + + /** + * Indicates shutdown of audit logging. There is no extra payload in the log event. + */ + public static final int TAG_LOGGING_STOPPED = SecurityLogTags.SECURITY_LOGGING_STOPPED; + + /** + * Indicates that removable media has been mounted on the device. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + *
  • [0] mount point ({@code String}) + *
  • [1] volume label ({@code String}). + */ + public static final int TAG_MEDIA_MOUNT = SecurityLogTags.SECURITY_MEDIA_MOUNTED; + + /** + * Indicates that removable media was unmounted from the device. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + *
  • [0] mount point ({@code String}) + *
  • [1] volume label ({@code String}). + */ + public static final int TAG_MEDIA_UNMOUNT = SecurityLogTags.SECURITY_MEDIA_UNMOUNTED; + + /** + * Indicates that the audit log buffer has reached 90% of its capacity. There is no extra + * payload in the log event. + */ + public static final int TAG_LOG_BUFFER_SIZE_CRITICAL = + SecurityLogTags.SECURITY_LOG_BUFFER_SIZE_CRITICAL; + + /** + * Indicates that an admin has set a password expiration timeout. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and accessible + * via {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] target user ID ({@code Integer}) + *
  • [3] new password expiration timeout in milliseconds ({@code Long}). + * @see DevicePolicyManager#setPasswordExpirationTimeout(ComponentName, long) + */ + public static final int TAG_PASSWORD_EXPIRATION_SET = + SecurityLogTags.SECURITY_PASSWORD_EXPIRATION_SET; + + /** + * Indicates that an admin has set a requirement for password complexity. The log entry contains + * the following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] target user ID ({@code Integer}) + *
  • [3] minimum password length ({@code Integer}) + *
  • [4] password quality constraint ({@code Integer}) + *
  • [5] minimum number of letters ({@code Integer}) + *
  • [6] minimum number of non-letters ({@code Integer}) + *
  • [7] minimum number of digits ({@code Integer}) + *
  • [8] minimum number of uppercase letters ({@code Integer}) + *
  • [9] minimum number of lowercase letters ({@code Integer}) + *
  • [10] minimum number of symbols ({@code Integer}) + * + * @see DevicePolicyManager#setPasswordMinimumLength(ComponentName, int) + * @see DevicePolicyManager#setPasswordQuality(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumLetters(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumNonLetter(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumLowerCase(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumUpperCase(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumNumeric(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumSymbols(ComponentName, int) + */ + public static final int TAG_PASSWORD_COMPLEXITY_SET = + SecurityLogTags.SECURITY_PASSWORD_COMPLEXITY_SET; + + /** + * Indicates that an admin has set a password history length. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible + * via {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] target user ID ({@code Integer}) + *
  • [3] new password history length value ({@code Integer}) + * @see DevicePolicyManager#setPasswordHistoryLength(ComponentName, int) + */ + public static final int TAG_PASSWORD_HISTORY_LENGTH_SET = + SecurityLogTags.SECURITY_PASSWORD_HISTORY_LENGTH_SET; + + /** + * Indicates that an admin has set a maximum screen lock timeout. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible + * via {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] target user ID ({@code Integer}) + *
  • [3] new screen lock timeout in milliseconds ({@code Long}) + * @see DevicePolicyManager#setMaximumTimeToLock(ComponentName, long) + */ + public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = + SecurityLogTags.SECURITY_MAX_SCREEN_LOCK_TIMEOUT_SET; + + /** + * Indicates that an admin has set a maximum number of failed password attempts before wiping + * data. The log entry contains the following information about the event encapsulated in an + * {@link Object} array, accessible via {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] target user ID ({@code Integer}) + *
  • [3] new maximum number of failed password attempts ({@code Integer}) + * @see DevicePolicyManager#setMaximumTimeToLock(ComponentName, long) + */ + public static final int TAG_MAX_PASSWORD_ATTEMPTS_SET = + SecurityLogTags.SECURITY_MAX_PASSWORD_ATTEMPTS_SET; + + /** + * Indicates that an admin has set disabled keyguard features. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible via + * {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] target user ID ({@code Integer}) + *
  • [3] disabled keyguard feature mask ({@code Integer}). + * @see DevicePolicyManager#setKeyguardDisabledFeatures(ComponentName, int) + */ + public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET = + SecurityLogTags.SECURITY_KEYGUARD_DISABLED_FEATURES_SET; + + /** + * Indicates that an admin remotely locked the device or profile. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible via + * {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}), + *
  • [1] admin user ID ({@code Integer}). + */ + public static final int TAG_REMOTE_LOCK = SecurityLogTags.SECURITY_REMOTE_LOCK; + + /** + * Indicates a failure to wipe device or user data. There is no extra payload in the log event. + */ + public static final int TAG_WIPE_FAILURE = SecurityLogTags.SECURITY_WIPE_FAILED; + + /** + * Indicates that an authentication key was generated. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + *
  • [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + *
  • [1] alias of the key ({@code String}) + *
  • [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_GENERATED = + SecurityLogTags.SECURITY_KEY_GENERATED; + + /** + * Indicates that a cryptographic key was imported. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + *
  • [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + *
  • [1] alias of the key ({@code String}) + *
  • [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_IMPORT = SecurityLogTags.SECURITY_KEY_IMPORTED; + + /** + * Indicates that a cryptographic key was destroyed. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + *
  • [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + *
  • [1] alias of the key ({@code String}) + *
  • [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_DESTRUCTION = SecurityLogTags.SECURITY_KEY_DESTROYED; + + /** + * Indicates that a new root certificate has been installed into system's trusted credential + * storage. The log entry contains the following information about the event, encapsulated in an + * {@link Object} array and accessible via {@link SecurityEvent#getData()}: + *
  • [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + *
  • [1] subject of the certificate ({@code String}). + */ + public static final int TAG_CERT_AUTHORITY_INSTALLED = + SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED; + + /** + * Indicates that a new oot certificate has been removed from system's trusted credential + * storage. The log entry contains the following information about the event, encapsulated in an + * {@link Object} array and accessible via {@link SecurityEvent#getData()}: + *
  • [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + *
  • [1] subject of the certificate ({@code String}). + */ + public static final int TAG_CERT_AUTHORITY_REMOVED = + SecurityLogTags.SECURITY_CERT_AUTHORITY_REMOVED; + + /** + * Indicates that an admin has set a user restriction. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] user restriction ({@code String}) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + */ + public static final int TAG_USER_RESTRICTION_ADDED = + SecurityLogTags.SECURITY_USER_RESTRICTION_ADDED; + + /** + * Indicates that an admin has removed a user restriction. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + *
  • [0] admin package name ({@code String}) + *
  • [1] admin user ID ({@code Integer}) + *
  • [2] user restriction ({@code String}) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + */ + public static final int TAG_USER_RESTRICTION_REMOVED = + SecurityLogTags.SECURITY_USER_RESTRICTION_REMOVED; + + /** + * Event severity level indicating that the event corresponds to normal workflow. + */ + public static final int LEVEL_INFO = 1; + + /** + * Event severity level indicating that the event may require admin attention. + */ + public static final int LEVEL_WARNING = 2; + + /** + * Event severity level indicating that the event requires urgent admin action. + */ + public static final int LEVEL_ERROR = 3; + /** * Returns if security logging is enabled. Log producers should only write new logs if this is * true. Under the hood this is the logical AND of whether device owner exists and whether @@ -198,6 +502,60 @@ public class SecurityLog { return mId; } + /** + * Returns severity level for the event. + */ + public @SecurityLogLevel int getLogLevel() { + switch (mEvent.getTag()) { + case TAG_ADB_SHELL_INTERACTIVE: + case TAG_ADB_SHELL_CMD: + case TAG_SYNC_RECV_FILE: + case TAG_SYNC_SEND_FILE: + case TAG_APP_PROCESS_START: + case TAG_KEYGUARD_DISMISSED: + case TAG_KEYGUARD_SECURED: + case TAG_OS_STARTUP: + case TAG_OS_SHUTDOWN: + case TAG_LOGGING_STARTED: + case TAG_LOGGING_STOPPED: + case TAG_MEDIA_MOUNT: + case TAG_MEDIA_UNMOUNT: + case TAG_PASSWORD_EXPIRATION_SET: + case TAG_PASSWORD_COMPLEXITY_SET: + case TAG_PASSWORD_HISTORY_LENGTH_SET: + case TAG_MAX_SCREEN_LOCK_TIMEOUT_SET: + case TAG_MAX_PASSWORD_ATTEMPTS_SET: + case TAG_USER_RESTRICTION_ADDED: + case TAG_USER_RESTRICTION_REMOVED: + return LEVEL_INFO; + case TAG_CERT_AUTHORITY_REMOVED: + return getSuccess() ? LEVEL_INFO : LEVEL_ERROR; + case TAG_CERT_AUTHORITY_INSTALLED: + case TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT: + case TAG_KEY_IMPORT: + case TAG_KEY_DESTRUCTION: + case TAG_KEY_GENERATED: + return getSuccess() ? LEVEL_INFO : LEVEL_WARNING; + case TAG_LOG_BUFFER_SIZE_CRITICAL: + case TAG_WIPE_FAILURE: + return LEVEL_ERROR; + default: + return LEVEL_INFO; + } + } + + // Success/failure if present is encoded as an integer in the first (0th) element of data. + private boolean getSuccess() { + final Object data = getData(); + if (data == null || !(data instanceof Object[])) { + return false; + } + + final Object[] array = (Object[]) data; + return array.length >= 1 && array[0] instanceof Integer && (Integer) array[0] != 0; + } + + @Override public int describeContents() { return 0; @@ -263,8 +621,8 @@ public class SecurityLog { throws IOException; /** - * Retrieve all security logs whose timestamp (in nanosceonds) is equal to or greater than the - * given timestamp. This method will block until either the last log earlier than the given + * Retrieve all security logs whose timestamp is equal to or greater than the given timestamp in + * nanoseconds. This method will block until either the last log earlier than the given * timestamp is about to be pruned, or after a 2-hour timeout has passed. * @hide */ diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags index 39371c7923e0cc1affe9e369d0345d96ad3690c6..be626786c3c6339a85b02a85dede3585999d3481 100644 --- a/core/java/android/app/admin/SecurityLogTags.logtags +++ b/core/java/android/app/admin/SecurityLogTags.logtags @@ -10,3 +10,28 @@ option java_package android.app.admin 210006 security_keyguard_dismissed 210007 security_keyguard_dismiss_auth_attempt (success|1),(method_strength|1) 210008 security_keyguard_secured + +# Additional event types for NIAP MDFPP 3.1 compliant audit logging. + +210009 security_os_startup (boot_state|3),(verity_mode|3) +210010 security_os_shutdown +210011 security_logging_started +210012 security_logging_stopped +210013 security_media_mounted (path|3),(label|3) +210014 security_media_unmounted (path|3),(label|3) +210015 security_log_buffer_size_critical +210016 security_password_expiration_set (package|3),(admin_user|1),(target_user|1),(timeout|2|3) +210017 security_password_complexity_set (package|3),(admin_user|1),(target_user|1),(length|1),(quality|1),(num_letters|1),(num_non_letters|1),(num_numeric|1),(num_uppercase|1),(num_lowercase|1),(num_symbols|1) +210018 security_password_history_length_set (package|3),(admin_user|1),(target_user|1),(length|1) +210019 security_max_screen_lock_timeout_set (package|3),(admin_user|1),(target_user|1),(timeout|2|3) +210020 security_max_password_attempts_set (package|3),(admin_user|1),(target_user|1),(num_failures|1) +210021 security_keyguard_disabled_features_set (package|3),(admin_user|1),(target_user|1),(features|1) +210022 security_remote_lock (package|3),(admin_user|1),(target_user|1) +210023 security_wipe_failed (package|3),(admin_user|1) +210024 security_key_generated (success|1),(key_id|3),(uid|1) +210025 security_key_imported (success|1),(key_id|3),(uid|1) +210026 security_key_destroyed (success|1),(key_id|3),(uid|1) +210027 security_user_restriction_added (package|3),(admin_user|1),(restriction|3) +210028 security_user_restriction_removed (package|3),(admin_user|1),(restriction|3) +210029 security_cert_authority_installed (success|1),(subject|3) +210030 security_cert_authority_removed (success|1),(subject|3) \ No newline at end of file diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java index 232a688762dedcb75c43750e80f7a324bb5db809..05d3fd9c632cc1f3c4a727619b7b311ccad00dba 100644 --- a/core/java/android/app/admin/SystemUpdatePolicy.java +++ b/core/java/android/app/admin/SystemUpdatePolicy.java @@ -16,16 +16,27 @@ package android.app.admin; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.TEXT; + import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; +import android.util.Pair; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; /** * A class that represents a local system update policy set by the device owner. @@ -34,6 +45,7 @@ import java.lang.annotation.RetentionPolicy; * @see DevicePolicyManager#getSystemUpdatePolicy */ public class SystemUpdatePolicy implements Parcelable { + private static final String TAG = "SystemUpdatePolicy"; /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { @@ -94,20 +106,157 @@ public class SystemUpdatePolicy implements Parcelable { private static final String KEY_POLICY_TYPE = "policy_type"; private static final String KEY_INSTALL_WINDOW_START = "install_window_start"; private static final String KEY_INSTALL_WINDOW_END = "install_window_end"; + private static final String KEY_FREEZE_TAG = "freeze"; + private static final String KEY_FREEZE_START = "start"; + private static final String KEY_FREEZE_END = "end"; + /** * The upper boundary of the daily maintenance window: 24 * 60 minutes. */ private static final int WINDOW_BOUNDARY = 24 * 60; + /** + * The maximum length of a single freeze period: 90 days. + */ + static final int FREEZE_PERIOD_MAX_LENGTH = 90; + + /** + * The minimum allowed time between two adjacent freeze period (from the end of the first + * freeze period to the start of the second freeze period, both exclusive): 60 days. + */ + static final int FREEZE_PERIOD_MIN_SEPARATION = 60; + + + /** + * An exception class that represents various validation errors thrown from + * {@link SystemUpdatePolicy#setFreezePeriods} and + * {@link DevicePolicyManager#setSystemUpdatePolicy} + */ + public static final class ValidationFailedException extends IllegalArgumentException + implements Parcelable { + + /** @hide */ + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_NONE, + ERROR_DUPLICATE_OR_OVERLAP, + ERROR_NEW_FREEZE_PERIOD_TOO_LONG, + ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, + ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, + ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ValidationFailureType {} + + /** @hide */ + public static final int ERROR_NONE = 0; + + /** + * The freeze periods contains duplicates, periods that overlap with each + * other or periods whose start and end joins. + */ + public static final int ERROR_DUPLICATE_OR_OVERLAP = 1; + + /** + * There exists at least one freeze period whose length exceeds 90 days. + */ + public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2; + + /** + * There exists some freeze period which starts within 60 days of the preceding period's + * end time. + */ + public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3; + + /** + * The device has been in a freeze period and when combining with the new freeze period + * to be set, it will result in the total freeze period being longer than 90 days. + */ + public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4; + + /** + * The device has been in a freeze period and some new freeze period to be set is less + * than 60 days from the end of the last freeze period the device went through. + */ + public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5; + + @ValidationFailureType + private final int mErrorCode; + + private ValidationFailedException(int errorCode, String message) { + super(message); + mErrorCode = errorCode; + } + + /** + * Returns the type of validation error associated with this exception. + */ + public @ValidationFailureType int getErrorCode() { + return mErrorCode; + } + + /** @hide */ + public static ValidationFailedException duplicateOrOverlapPeriods() { + return new ValidationFailedException(ERROR_DUPLICATE_OR_OVERLAP, + "Found duplicate or overlapping periods"); + } + + /** @hide */ + public static ValidationFailedException freezePeriodTooLong(String message) { + return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_LONG, message); + } + + /** @hide */ + public static ValidationFailedException freezePeriodTooClose(String message) { + return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, message); + } + + /** @hide */ + public static ValidationFailedException combinedPeriodTooLong(String message) { + return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, message); + } + + /** @hide */ + public static ValidationFailedException combinedPeriodTooClose(String message) { + return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, message); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mErrorCode); + dest.writeString(getMessage()); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ValidationFailedException createFromParcel(Parcel source) { + return new ValidationFailedException(source.readInt(), source.readString()); + } + + @Override + public ValidationFailedException[] newArray(int size) { + return new ValidationFailedException[size]; + } + + }; + } + @SystemUpdatePolicyType private int mPolicyType; private int mMaintenanceWindowStart; private int mMaintenanceWindowEnd; + private final ArrayList mFreezePeriods; private SystemUpdatePolicy() { mPolicyType = TYPE_UNKNOWN; + mFreezePeriods = new ArrayList<>(); } /** @@ -206,24 +355,129 @@ public class SystemUpdatePolicy implements Parcelable { } /** - * Return if this object represents a valid policy. + * Return if this object represents a valid policy with: + * 1. Correct type + * 2. Valid maintenance window if applicable + * 3. Valid freeze periods * @hide */ public boolean isValid() { - if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) { + try { + validateType(); + validateFreezePeriods(); return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Validate the type and maintenance window (if applicable) of this policy object, + * throws {@link IllegalArgumentException} if it's invalid. + * @hide + */ + public void validateType() { + if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) { + return; } else if (mPolicyType == TYPE_INSTALL_WINDOWED) { - return mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY - && mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY; + if (!(mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY + && mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY)) { + throw new IllegalArgumentException("Invalid maintenance window"); + } } else { - return false; + throw new IllegalArgumentException("Invalid system update policy type."); + } + } + + /** + * Configure a list of freeze periods on top of the current policy. When the device's clock is + * within any of the freeze periods, all incoming system updates including security patches will + * be blocked and cannot be installed. When the device is outside the freeze periods, the normal + * policy behavior will apply. + *

    + * Each freeze period is defined by a starting and finishing date (both inclusive). Since the + * freeze period repeats annually, both of these dates are simply represented by integers + * counting the number of days since year start, similar to {@link LocalDate#getDayOfYear()}. We + * do not consider leap year when handling freeze period so the valid range of the integer is + * always [1,365] (see last section for more details on leap year). If the finishing date is + * smaller than the starting date, the freeze period is considered to be spanning across + * year-end. + *

    + * Each individual freeze period is allowed to be at most 90 days long, and adjacent freeze + * periods need to be at least 60 days apart. Also, the list of freeze periods should not + * contain duplicates or overlap with each other. If any of these conditions is not met, a + * {@link ValidationFailedException} will be thrown. + *

    + * Handling of leap year: we do not consider leap year when handling freeze period, in + * particular, + *

      + *
    • When a freeze period is defined by the day of year, February 29th does not count as one + * day, so day 59 is February 28th while day 60 is March 1st.
    • + *
    • When applying freeze period behavior to the device, a system clock of February 29th is + * treated as if it were February 28th
    • + *
    • When calculating the number of days of a freeze period or separation between two freeze + * periods, February 29th is also ignored and not counted as one day.
    • + *
    + * + * @param freezePeriods the list of freeze periods + * @throws ValidationFailedException if the supplied freeze periods do not meet the + * requirement set above + * @return this instance + */ + public SystemUpdatePolicy setFreezePeriods(List> freezePeriods) { + List newPeriods = freezePeriods.stream().map( + p -> new FreezeInterval(p.first, p.second)).collect(Collectors.toList()); + FreezeInterval.validatePeriods(newPeriods); + mFreezePeriods.clear(); + mFreezePeriods.addAll(newPeriods); + return this; + } + + /** + * Returns the list of freeze periods previously set on this system update policy object. + * + * @return the list of freeze periods, or an empty list if none was set. + */ + public List> getFreezePeriods() { + List> result = new ArrayList<>(mFreezePeriods.size()); + for (FreezeInterval interval : mFreezePeriods) { + result.add(new Pair<>(interval.mStartDay, interval.mEndDay)); } + return result; + } + + /** + * Returns the real calendar dates of the current freeze period, or null if the device + * is not in a freeze period at the moment. + * @hide + */ + public Pair getCurrentFreezePeriod(LocalDate now) { + for (FreezeInterval interval : mFreezePeriods) { + if (interval.contains(now)) { + return interval.toCurrentOrFutureRealDates(now); + } + } + return null; + } + + /** @hide */ + public void validateFreezePeriods() { + FreezeInterval.validatePeriods(mFreezePeriods); + } + + /** @hide */ + public void validateAgainstPreviousFreezePeriod(LocalDate prevPeriodStart, + LocalDate prevPeriodEnd, LocalDate now) { + FreezeInterval.validateAgainstPreviousFreezePeriod(mFreezePeriods, prevPeriodStart, + prevPeriodEnd, now); } @Override public String toString() { - return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d)", - mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd); + return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d, " + + "freezes: [%s])", + mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd, + mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(","))); } @Override @@ -236,6 +490,13 @@ public class SystemUpdatePolicy implements Parcelable { dest.writeInt(mPolicyType); dest.writeInt(mMaintenanceWindowStart); dest.writeInt(mMaintenanceWindowEnd); + int freezeCount = mFreezePeriods.size(); + dest.writeInt(freezeCount); + for (int i = 0; i < freezeCount; i++) { + FreezeInterval interval = mFreezePeriods.get(i); + dest.writeInt(interval.mStartDay); + dest.writeInt(interval.mEndDay); + } } public static final Parcelable.Creator CREATOR = @@ -247,6 +508,12 @@ public class SystemUpdatePolicy implements Parcelable { policy.mPolicyType = source.readInt(); policy.mMaintenanceWindowStart = source.readInt(); policy.mMaintenanceWindowEnd = source.readInt(); + int freezeCount = source.readInt(); + policy.mFreezePeriods.ensureCapacity(freezeCount); + for (int i = 0; i < freezeCount; i++) { + policy.mFreezePeriods.add( + new FreezeInterval(source.readInt(), source.readInt())); + } return policy; } @@ -256,8 +523,10 @@ public class SystemUpdatePolicy implements Parcelable { } }; - /** + * Restore a previously saved SystemUpdatePolicy from XML. No need to validate + * the reconstructed policy since the XML is supposed to be created by the + * system server from a validated policy object previously. * @hide */ public static SystemUpdatePolicy restoreFromXml(XmlPullParser parser) { @@ -275,10 +544,26 @@ public class SystemUpdatePolicy implements Parcelable { if (value != null) { policy.mMaintenanceWindowEnd = Integer.parseInt(value); } + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != END_DOCUMENT + && (type != END_TAG || parser.getDepth() > outerDepth)) { + if (type == END_TAG || type == TEXT) { + continue; + } + if (!parser.getName().equals(KEY_FREEZE_TAG)) { + continue; + } + policy.mFreezePeriods.add(new FreezeInterval( + Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_START)), + Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_END)))); + } return policy; } - } catch (NumberFormatException e) { + } catch (NumberFormatException | XmlPullParserException | IOException e) { // Fail through + Log.w(TAG, "Load xml failed", e); } return null; } @@ -290,6 +575,13 @@ public class SystemUpdatePolicy implements Parcelable { out.attribute(null, KEY_POLICY_TYPE, Integer.toString(mPolicyType)); out.attribute(null, KEY_INSTALL_WINDOW_START, Integer.toString(mMaintenanceWindowStart)); out.attribute(null, KEY_INSTALL_WINDOW_END, Integer.toString(mMaintenanceWindowEnd)); + for (int i = 0; i < mFreezePeriods.size(); i++) { + FreezeInterval interval = mFreezePeriods.get(i); + out.startTag(null, KEY_FREEZE_TAG); + out.attribute(null, KEY_FREEZE_START, Integer.toString(interval.mStartDay)); + out.attribute(null, KEY_FREEZE_END, Integer.toString(interval.mEndDay)); + out.endTag(null, KEY_FREEZE_TAG); + } } } diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 861cb9a8d035d65e6922df8e77f87629f1cfd575..72eb4948f0f9333a3acb44eab7bc802491162528 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -143,6 +143,26 @@ public abstract class BackupAgent extends ContextWrapper { /** @hide */ public static final int TYPE_SYMLINK = 3; + /** + * Flag for {@link BackupDataOutput#getTransportFlags()} and + * {@link FullBackupDataOutput#getTransportFlags()} only. + * + *

    The transport has client-side encryption enabled. i.e., the user's backup has been + * encrypted with a key known only to the device, and not to the remote storage solution. Even + * if an attacker had root access to the remote storage provider they should not be able to + * decrypt the user's backup data. + */ + public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; + + /** + * Flag for {@link BackupDataOutput#getTransportFlags()} and + * {@link FullBackupDataOutput#getTransportFlags()} only. + * + *

    The transport is for a device-to-device transfer. There is no third party or intermediate + * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi. + */ + public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2; + Handler mHandler = null; Handler getHandler() { @@ -920,12 +940,14 @@ public abstract class BackupAgent extends ContextWrapper { public void doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, - long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException { + long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) + throws RemoteException { // Ensure that we're running with the app's normal permission level long ident = Binder.clearCallingIdentity(); if (DEBUG) Log.v(TAG, "doBackup() invoked"); - BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes); + BackupDataOutput output = new BackupDataOutput( + data.getFileDescriptor(), quotaBytes, transportFlags); try { BackupAgent.this.onBackup(oldState, output, newState); @@ -999,7 +1021,7 @@ public abstract class BackupAgent extends ContextWrapper { @Override public void doFullBackup(ParcelFileDescriptor data, - long quotaBytes, int token, IBackupManager callbackBinder) { + long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) { // Ensure that we're running with the app's normal permission level long ident = Binder.clearCallingIdentity(); @@ -1010,7 +1032,8 @@ public abstract class BackupAgent extends ContextWrapper { waitForSharedPrefs(); try { - BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes)); + BackupAgent.this.onFullBackup(new FullBackupDataOutput( + data, quotaBytes, transportFlags)); } catch (IOException ex) { Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw new RuntimeException(ex); @@ -1044,10 +1067,12 @@ public abstract class BackupAgent extends ContextWrapper { } } - public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) { + public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder, + int transportFlags) { // Ensure that we're running with the app's normal permission level final long ident = Binder.clearCallingIdentity(); - FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes); + FullBackupDataOutput measureOutput = + new FullBackupDataOutput(quotaBytes, transportFlags); waitForSharedPrefs(); try { diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java index c7586a299dd8d2491b3893ee1161688b93a678d7..5a66f340741764022ec56b6c3e0653d4511fdf89 100644 --- a/core/java/android/app/backup/BackupDataOutput.java +++ b/core/java/android/app/backup/BackupDataOutput.java @@ -18,6 +18,7 @@ package android.app.backup; import android.annotation.SystemApi; import android.os.ParcelFileDescriptor; + import java.io.FileDescriptor; import java.io.IOException; @@ -62,7 +63,10 @@ import java.io.IOException; * @see BackupAgent */ public class BackupDataOutput { - final long mQuota; + + private final long mQuota; + private final int mTransportFlags; + long mBackupWriter; /** @@ -71,14 +75,20 @@ public class BackupDataOutput { * @hide */ @SystemApi public BackupDataOutput(FileDescriptor fd) { - this(fd, -1); + this(fd, /*quota=*/ -1, /*transportFlags=*/ 0); } /** @hide */ @SystemApi public BackupDataOutput(FileDescriptor fd, long quota) { + this(fd, quota, /*transportFlags=*/ 0); + } + + /** @hide */ + public BackupDataOutput(FileDescriptor fd, long quota, int transportFlags) { if (fd == null) throw new NullPointerException(); mQuota = quota; + mTransportFlags = transportFlags; mBackupWriter = ctor(fd); if (mBackupWriter == 0) { throw new RuntimeException("Native initialization failed with fd=" + fd); @@ -95,6 +105,16 @@ public class BackupDataOutput { return mQuota; } + /** + * Returns flags with additional information about the backup transport. For supported flags see + * {@link android.app.backup.BackupAgent} + * + * @see FullBackupDataOutput#getTransportFlags() + */ + public int getTransportFlags() { + return mTransportFlags; + } + /** * Mark the beginning of one record in the backup data stream. This must be called before * {@link #writeEntityData}. diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 3a6a5b20edf7c75bc4cebbead2dce92c125702c5..6ec0969771c6729a721569927e458c2f372e30fc 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.util.Pair; @@ -251,6 +252,8 @@ public class BackupManager { } /** + * @deprecated Since Android P app can no longer request restoring of its backup. + * * Restore the calling application from backup. The data will be restored from the * current backup dataset if the application has stored data there, or from * the dataset used during the last full device setup operation if the current @@ -268,6 +271,7 @@ public class BackupManager { * * @return Zero on success; nonzero on error. */ + @Deprecated public int requestRestore(RestoreObserver observer) { return requestRestore(observer, null); } @@ -275,6 +279,8 @@ public class BackupManager { // system APIs start here /** + * @deprecated Since Android P app can no longer request restoring of its backup. + * * Restore the calling application from backup. The data will be restored from the * current backup dataset if the application has stored data there, or from * the dataset used during the last full device setup operation if the current @@ -297,28 +303,12 @@ public class BackupManager { * * @hide */ + @Deprecated @SystemApi public int requestRestore(RestoreObserver observer, BackupManagerMonitor monitor) { - int result = -1; - checkServiceBinder(); - if (sService != null) { - RestoreSession session = null; - try { - IRestoreSession binder = sService.beginRestoreSession(mContext.getPackageName(), - null); - if (binder != null) { - session = new RestoreSession(mContext, binder); - result = session.restorePackage(mContext.getPackageName(), observer, monitor); - } - } catch (RemoteException e) { - Log.e(TAG, "restoreSelf() unable to contact service"); - } finally { - if (session != null) { - session.endRestoreSession(); - } - } - } - return result; + Log.w(TAG, "requestRestore(): Since Android P app can no longer request restoring" + + " of its backup."); + return -1; } /** @@ -386,6 +376,29 @@ public class BackupManager { return false; } + /** + * Report whether the backup mechanism is currently active. + * When it is inactive, the device will not perform any backup operations, nor will it + * deliver data for restore, although clients can still safely call BackupManager methods. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public boolean isBackupServiceActive(UserHandle user) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + "isBackupServiceActive"); + checkServiceBinder(); + if (sService != null) { + try { + return sService.isBackupServiceActive(user.getIdentifier()); + } catch (RemoteException e) { + Log.e(TAG, "isBackupEnabled() couldn't connect"); + } + } + return false; + } + /** * Enable/disable data restore at application install time. When enabled, app * installation will include an attempt to fetch the app's historical data from diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java index ae4a98a4e06e7fea7b0a08e52e9c853c0d0d947a..07e7688a48aefef4eba463686f309c71a003f031 100644 --- a/core/java/android/app/backup/BackupManagerMonitor.java +++ b/core/java/android/app/backup/BackupManagerMonitor.java @@ -172,6 +172,11 @@ public class BackupManagerMonitor { public static final int LOG_EVENT_ID_NO_PACKAGES = 49; public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50; + /** + * The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}. + */ + public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51; + diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index da81d19ca3cb2369f064ba4525ae893a02d66049..f456395aa2b53fc7cc3ffdb3d07b20a6ad0f3005 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -51,10 +51,38 @@ public class BackupTransport { public static final int AGENT_UNKNOWN = -1004; public static final int TRANSPORT_QUOTA_EXCEEDED = -1005; + /** + * Indicates that the transport cannot accept a diff backup for this package. + * + *

    Backup manager should clear its state for this package and immediately retry a + * non-incremental backup. This might be used if the transport no longer has data for this + * package in its backing store. + * + *

    This is only valid when backup manager called {@link + * #performBackup(PackageInfo, ParcelFileDescriptor, int)} with {@link #FLAG_INCREMENTAL}. + */ + public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006; + // Indicates that operation was initiated by user, not a scheduled one. // Transport should ignore its own moratoriums for call with this flag set. public static final int FLAG_USER_INITIATED = 1; + /** + * For key value backup, indicates that the backup data is a diff from a previous backup. The + * transport must apply this diff to an existing backup to build the new backup set. + * + * @see #performBackup(PackageInfo, ParcelFileDescriptor, int) + */ + public static final int FLAG_INCREMENTAL = 1 << 1; + + /** + * For key value backup, indicates that the backup data is a complete set, not a diff from a + * previous backup. The transport should clear any previous backup when storing this backup. + * + * @see #performBackup(PackageInfo, ParcelFileDescriptor, int) + */ + public static final int FLAG_NON_INCREMENTAL = 1 << 2; + IBackupTransport mBinderImpl = new TransportImpl(); public IBinder getBinder() { @@ -231,18 +259,33 @@ public class BackupTransport { * {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data * is sent and recorded successfully. * + * If the backup data is a diff against the previous backup then the flag {@link + * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup + * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will + * be set regardless of whether the backup is incremental or not. + * + *

    If {@link BackupTransport#FLAG_INCREMENTAL} is set and the transport does not have data + * for this package in its storage backend then it cannot apply the incremental diff. Thus it + * should return {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} to indicate + * that backup manager should delete its state and retry the package as a non-incremental + * backup. Before P, or if this is a non-incremental backup, then this return code is equivalent + * to {@link BackupTransport#TRANSPORT_ERROR}. + * * @param packageInfo The identity of the application whose data is being backed up. * This specifically includes the signature list for the package. * @param inFd Descriptor of file with data that resulted from invoking the application's * BackupService.doBackup() method. This may be a pipe rather than a file on * persistent media, so it may not be seekable. - * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0. + * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link + * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0. * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this * specific package, but allow others to proceed), - * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or - * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has - * become lost due to inactivity purge or some other reason and needs re-initializing) + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), {@link + * BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} (if the transport cannot accept + * an incremental backup for this package), or {@link + * BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has become lost due to + * inactivity purge or some other reason and needs re-initializing) */ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) { return performBackup(packageInfo, inFd); @@ -563,6 +606,15 @@ public class BackupTransport { return BackupTransport.TRANSPORT_OK; } + /** + * Returns flags with additional information about the transport, which is accessible to the + * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to do based on + * properties of the transport. + */ + public int getTransportFlags() { + return 0; + } + /** * Bridge between the actual IBackupTransport implementation and the stable API. If the * binder interface needs to change, we use this layer to translate so that we can @@ -694,6 +746,11 @@ public class BackupTransport { return BackupTransport.this.getBackupQuota(packageName, isFullBackup); } + @Override + public int getTransportFlags() { + return BackupTransport.this.getTransportFlags(); + } + @Override public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { return BackupTransport.this.getNextFullRestoreDataChunk(socket); diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java index 5deedd035d0b4b53915c29a388ad76465985bd36..18f4283399415b867c2abbc9f915613d78547d74 100644 --- a/core/java/android/app/backup/FullBackupDataOutput.java +++ b/core/java/android/app/backup/FullBackupDataOutput.java @@ -11,6 +11,7 @@ public class FullBackupDataOutput { // Currently a name-scoping shim around BackupDataOutput private final BackupDataOutput mData; private final long mQuota; + private final int mTransportFlags; private long mSize; /** @@ -23,22 +24,49 @@ public class FullBackupDataOutput { return mQuota; } + /** + * Returns flags with additional information about the backup transport. For supported flags see + * {@link android.app.backup.BackupAgent} + * + * @see BackupDataOutput#getTransportFlags() + */ + public int getTransportFlags() { + return mTransportFlags; + } + /** @hide - used only in measure operation */ public FullBackupDataOutput(long quota) { mData = null; mQuota = quota; mSize = 0; + mTransportFlags = 0; + } + + /** @hide - used only in measure operation */ + public FullBackupDataOutput(long quota, int transportFlags) { + mData = null; + mQuota = quota; + mSize = 0; + mTransportFlags = transportFlags; } /** @hide */ public FullBackupDataOutput(ParcelFileDescriptor fd, long quota) { - mData = new BackupDataOutput(fd.getFileDescriptor(), quota); + mData = new BackupDataOutput(fd.getFileDescriptor(), quota, 0); + mQuota = quota; + mTransportFlags = 0; + } + + /** @hide */ + public FullBackupDataOutput(ParcelFileDescriptor fd, long quota, int transportFlags) { + mData = new BackupDataOutput(fd.getFileDescriptor(), quota, transportFlags); mQuota = quota; + mTransportFlags = transportFlags; } /** @hide - used only internally to the backup manager service's stream construction */ public FullBackupDataOutput(ParcelFileDescriptor fd) { - this(fd, -1); + this(fd, /*quota=*/ -1, /*transportFlags=*/ 0); } /** @hide */ diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 792cb5f29f9c49446b0c306d7c2def3b50e2d75b..f3ca74656e8ca1cbb17cbfbcc21cc05a234c3b58 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -294,7 +294,8 @@ interface IBackupManager { * * @param transport ComponentName of the service hosting the transport. This is different from * the transport's name that is returned by {@link BackupTransport#name()}. - * @param listener A listener object to get a callback on the transport being selected. + * @param listener A listener object to get a callback on the transport being selected. It may + * be {@code null}. * * @hide */ diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 7c40b4eaf3757e7baf6d188dc7673298aa00c64b..a1ad825c3a2917d8672f8d05c7bea8391d24576f 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -250,6 +250,19 @@ public class JobInfo implements Parcelable { */ public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1; + /** + * @hide + */ + public static final int FLAG_IS_PREFETCH = 1 << 2; + + /** + * This job needs to be exempted from the app standby throttling. Only the system (UID 1000) + * can set it. Jobs with a time constrant must not have it. + * + * @hide + */ + public static final int FLAG_EXEMPT_FROM_APP_STANDBY = 1 << 3; + /** * @hide */ @@ -350,6 +363,13 @@ public class JobInfo implements Parcelable { return flags; } + /** @hide */ + public boolean isExemptedFromAppStandby() { + return ((flags & FLAG_EXEMPT_FROM_APP_STANDBY) != 0) + && !hasEarlyConstraint() + && !hasLateConstraint(); + } + /** * Whether this job requires that the device be charging (or be a non-battery-powered * device connected to permanent power, such as Android TV devices). @@ -1363,6 +1383,28 @@ public class JobInfo implements Parcelable { return this; } + /** + * 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. + *

    + * 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. * diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java index 5053dc6fdf05257ff622ea335982b6127d594b9a..c71bf2e65731fc1ea811f6caee6c17970561d799 100644 --- a/core/java/android/app/job/JobParameters.java +++ b/core/java/android/app/job/JobParameters.java @@ -70,6 +70,7 @@ public class JobParameters implements Parcelable { private final Network network; private int stopReason; // Default value of stopReason is REASON_CANCELED + private String debugStopReason; // Human readable stop reason for debugging. /** @hide */ public JobParameters(IBinder callback, int jobId, PersistableBundle extras, @@ -103,6 +104,14 @@ public class JobParameters implements Parcelable { return stopReason; } + /** + * Reason onStopJob() was called on this job. + * @hide + */ + public String getDebugStopReason() { + return debugStopReason; + } + /** * @return The extras you passed in when constructing this job with * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will @@ -288,11 +297,13 @@ public class JobParameters implements Parcelable { network = null; } stopReason = in.readInt(); + debugStopReason = in.readString(); } /** @hide */ - public void setStopReason(int reason) { + public void setStopReason(int reason, String debugStopReason) { stopReason = reason; + this.debugStopReason = debugStopReason; } @Override @@ -323,6 +334,7 @@ public class JobParameters implements Parcelable { dest.writeInt(0); } dest.writeInt(stopReason); + dest.writeString(debugStopReason); } public static final Creator CREATOR = new Creator() { diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java index 0fdc7c56fd0123a23e3a848ad4682cf1e9cfaf7c..9a50a009ce34cdd72fc83c5618839d36f8be0c52 100644 --- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java +++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java @@ -17,7 +17,9 @@ package android.app.servertransaction; import android.annotation.IntDef; +import android.os.Parcel; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +28,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public abstract class ActivityLifecycleItem extends ClientTransactionItem { + private String mDescription; @IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = { UNDEFINED, @@ -53,4 +56,39 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem { /** A final lifecycle state that an activity should reach. */ @LifecycleState public abstract int getTargetState(); + + + protected ActivityLifecycleItem() { + } + + protected ActivityLifecycleItem(Parcel in) { + mDescription = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mDescription); + } + + /** + * Sets a description that can be retrieved later for debugging purposes. + * @param description Description to set. + * @return The {@link ActivityLifecycleItem}. + */ + public ActivityLifecycleItem setDescription(String description) { + mDescription = description; + return this; + } + + /** + * Retrieves description if set through {@link #setDescription(String)}. + */ + public String getDescription() { + return mDescription; + } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "target state:" + getTargetState()); + pw.println(prefix + "description: " + mDescription); + } } diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 08ad2f055774639e8f1047752a632262c07397fe..fc078798f6b9719c68e1c8a09df4f778082f5495 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -26,6 +26,7 @@ import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -237,4 +238,12 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { result = 31 * result + Objects.hashCode(mLifecycleStateRequest); return result; } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mActivityToken:" + mActivityToken.hashCode()); + pw.println(prefix + "mLifecycleStateRequest:"); + if (mLifecycleStateRequest != null) { + mLifecycleStateRequest.dump(pw, prefix + " "); + } + } } diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index 83da5f33c62a9ba5f6c06dc6ad17c6f04d611da2..cbcf6c750fed5cdde5cf37ed9bd137936b168f35 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -76,12 +76,14 @@ public class DestroyActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private DestroyActivityItem(Parcel in) { + super(in); mFinished = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 880fef73c6f25941b472f20d44c9ef5a78d89063..70a4755f99afccf98743150b5fa09a0c81e32300 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -114,6 +114,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeBoolean(mUserLeaving); dest.writeInt(mConfigChanges); @@ -122,6 +123,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private PauseActivityItem(Parcel in) { + super(in); mFinished = in.readBoolean(); mUserLeaving = in.readBoolean(); mConfigChanges = in.readInt(); diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java index 8304c1c5a2d8329f972ea14b006295f9e9d87623..073d28cfa27f53ef9d470c39c0afc514de39b9cd 100644 --- a/core/java/android/app/servertransaction/PendingTransactionActions.java +++ b/core/java/android/app/servertransaction/PendingTransactionActions.java @@ -134,7 +134,7 @@ public class PendingTransactionActions { Bundle.dumpStats(pw, mPersistentState); if (ex instanceof TransactionTooLargeException - && mActivity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) { + && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); return; } diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index 9249c6e8ed54369100f1adf94127424106ffe152..ed90f2cb1013786bf4716d2b36155ef4be32745a 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -113,6 +113,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeInt(mProcState); dest.writeBoolean(mUpdateProcState); dest.writeBoolean(mIsForward); @@ -120,6 +121,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private ResumeActivityItem(Parcel in) { + super(in); mProcState = in.readInt(); mUpdateProcState = in.readBoolean(); mIsForward = in.readBoolean(); diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index 5c5c3041344f4fd9b65585fe5df7e10e51843ca5..b814d1ae13923c497ea6e405d8550052f8aebf4a 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -83,12 +83,14 @@ public class StopActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); dest.writeBoolean(mShowWindow); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private StopActivityItem(Parcel in) { + super(in); mShowWindow = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 5b0ea6b1f9d40ea5bb6b5c2a7a14e0a765b153ba..78b393a831f9d3a9d39277945c4ba17eba605fa9 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -33,6 +33,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.List; /** @@ -122,6 +124,21 @@ public class TransactionExecutor { final IBinder token = transaction.getActivityToken(); final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + // TODO(b/71506345): Remove once root cause is found. + if (r == null) { + final StringWriter stringWriter = new StringWriter(); + final PrintWriter pw = new PrintWriter(stringWriter); + final String prefix = " "; + + pw.println("Lifecycle transaction does not have valid ActivityClientRecord."); + pw.println("Transaction:"); + transaction.dump(pw, prefix); + pw.println("Executor:"); + dump(pw, prefix); + + Slog.wtf(TAG, stringWriter.toString()); + } + // Cycle to the state right before the final requested state. cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */); @@ -245,4 +262,9 @@ public class TransactionExecutor { private static void log(String message) { if (DEBUG_RESOLVER) Slog.d(TAG, message); } + + private void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "mTransactionHandler:"); + mTransactionHandler.dump(pw, prefix + " "); + } } diff --git a/core/java/android/app/slice/ISliceManager.aidl b/core/java/android/app/slice/ISliceManager.aidl index 5f0e542f4b12352bd95c25f276baf311c141d3ec..4461b16fe15c764e717fdbcb84f1e34a69c7a35b 100644 --- a/core/java/android/app/slice/ISliceManager.aidl +++ b/core/java/android/app/slice/ISliceManager.aidl @@ -29,4 +29,6 @@ interface ISliceManager { void unpinSlice(String pkg, in Uri uri); boolean hasSliceAccess(String pkg); SliceSpec[] getPinnedSpecs(in Uri uri, String pkg); + int checkSlicePermission(in Uri uri, String pkg, int pid, int uid); + void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices); } diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index b8fb2e34d083b1f62304d088b1dd3585c6929019..4c24a2dd881201e0f6aae6b6337f02e39871009f 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -21,12 +21,10 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; -import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.IContentProvider; import android.content.Intent; -import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; @@ -67,6 +65,7 @@ public final class Slice implements Parcelable { HINT_TOGGLE, HINT_HORIZONTAL, HINT_PARTIAL, + HINT_SEE_MORE }) @Retention(RetentionPolicy.SOURCE) public @interface SliceHint {} @@ -147,15 +146,26 @@ public final class Slice implements Parcelable { */ public static final String HINT_PARTIAL = "partial"; /** - * A hint representing that this item is the max value possible for the slice containing this. - * Used to indicate the maximum integer value for a {@link #SUBTYPE_SLIDER}. + * A hint representing that this item should be used to indicate that there's more + * content associated with this slice. */ - public static final String HINT_MAX = "max"; - + public static final String HINT_SEE_MORE = "see_more"; + /** + * A hint used when implementing app-specific slice permissions. + * Tells the system that for this slice the return value of + * {@link SliceProvider#onBindSlice(Uri, List)} may be different depending on + * {@link SliceProvider#getBindingPackage} and should not be cached for multiple + * apps. + */ + public static final String HINT_CALLER_NEEDED = "caller_needed"; /** * Key to retrieve an extra added to an intent when a control is changed. */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; + /** + * Key to retrieve an extra added to an intent when the value of a slider is changed. + */ + public static final String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; /** * Subtype to indicate that this is a message as part of a communication * sequence in this slice. @@ -170,9 +180,17 @@ public final class Slice implements Parcelable { */ public static final String SUBTYPE_COLOR = "color"; /** - * Subtype to tag an item represents a slider. + * Subtype to tag an item as representing a slider. */ public static final String SUBTYPE_SLIDER = "slider"; + /** + * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_SLIDER}. + */ + public static final String SUBTYPE_MAX = "max"; + /** + * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_SLIDER}. + */ + public static final String SUBTYPE_VALUE = "value"; /** * Subtype to indicate that this content has a toggle action associated with it. To indicate * that the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the @@ -415,28 +433,6 @@ public final class Slice implements Parcelable { return this; } - /** - * Add a color to the slice being constructed - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} - * @deprecated will be removed once supportlib updates - */ - public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) { - mItems.add(new SliceItem(color, SliceItem.FORMAT_INT, subType, hints)); - return this; - } - - /** - * Add a color to the slice being constructed - * @param subType Optional template-specific type information - * @see {@link SliceItem#getSubType()} - * @deprecated will be removed once supportlib updates - */ - public Builder addColor(int color, @Nullable String subType, - @SliceHint List hints) { - return addColor(color, subType, hints.toArray(new String[hints.size()])); - } - /** * Add a color to the slice being constructed * @param subType Optional template-specific type information @@ -553,16 +549,11 @@ public final class Slice implements Parcelable { } /** - * Turns a slice Uri into slice content. - * - * @param resolver ContentResolver to be used. - * @param uri The URI to a slice provider - * @param supportedSpecs List of supported specs. - * @return The Slice provided by the app or null if none is given. - * @see Slice - */ - public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri, - List supportedSpecs) { + * @deprecated TO BE REMOVED. + */ + @Deprecated + public static @Nullable Slice bindSlice(ContentResolver resolver, + @NonNull Uri uri, @NonNull List supportedSpecs) { Preconditions.checkNotNull(uri, "uri"); IContentProvider provider = resolver.acquireProvider(uri); if (provider == null) { @@ -590,60 +581,11 @@ public final class Slice implements Parcelable { } /** - * Turns a slice intent into slice content. Expects an explicit intent. If there is no - * {@link ContentProvider} associated with the given intent this will throw - * {@link IllegalArgumentException}. - * - * @param context The context to use. - * @param intent The intent associated with a slice. - * @param supportedSpecs List of supported specs. - * @return The Slice provided by the app or null if none is given. - * @see Slice - * @see SliceProvider#onMapIntentToUri(Intent) - * @see Intent + * @deprecated TO BE REMOVED. */ + @Deprecated public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent, - List supportedSpecs) { - Preconditions.checkNotNull(intent, "intent"); - Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, - "Slice intent must be explicit " + intent); - ContentResolver resolver = context.getContentResolver(); - - // Check if the intent has data for the slice uri on it and use that - final Uri intentData = intent.getData(); - if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { - return bindSlice(resolver, intentData, supportedSpecs); - } - // Otherwise ask the app - List providers = - context.getPackageManager().queryIntentContentProviders(intent, 0); - if (providers == null) { - throw new IllegalArgumentException("Unable to resolve intent " + intent); - } - String authority = providers.get(0).providerInfo.authority; - Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(authority).build(); - IContentProvider provider = resolver.acquireProvider(uri); - if (provider == null) { - throw new IllegalArgumentException("Unknown URI " + uri); - } - try { - Bundle extras = new Bundle(); - extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); - extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, - new ArrayList<>(supportedSpecs)); - final Bundle res = provider.call(resolver.getPackageName(), - SliceProvider.METHOD_MAP_INTENT, null, extras); - if (res == null) { - return null; - } - return res.getParcelable(SliceProvider.EXTRA_SLICE); - } catch (RemoteException e) { - // Arbitrary and not worth documenting, as Activity - // Manager will kill this process shortly anyway. - return null; - } finally { - resolver.releaseProvider(provider); - } + @NonNull List supportedSpecs) { + return context.getSystemService(SliceManager.class).bindSlice(intent, supportedSpecs); } } diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java index bcfd413fb82325130a17892c27c3421c236fa0a7..9eb2bb896356a4a452b9c9a9083eb712b2365ba0 100644 --- a/core/java/android/app/slice/SliceItem.java +++ b/core/java/android/app/slice/SliceItem.java @@ -97,11 +97,6 @@ public final class SliceItem implements Parcelable { * A {@link SliceItem} that contains an int. */ public static final String FORMAT_INT = "int"; - /** - * A {@link SliceItem} that contains an int. - * @deprecated to be removed - */ - public static final String FORMAT_COLOR = "color"; /** * A {@link SliceItem} that contains a timestamp. */ @@ -230,13 +225,6 @@ public final class SliceItem implements Parcelable { return (Integer) mObj; } - /** - * @deprecated to be removed. - */ - public int getColor() { - return (Integer) mObj; - } - /** * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem */ diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java index 0c5f225d515eba8bf6ecb31d906c538c6ee81bff..2fa9d8e0882f6c2e8f3377c716243b563e600e45 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -16,18 +16,31 @@ package android.app.slice; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; +import android.content.ContentResolver; import android.content.Context; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.ArrayMap; +import android.util.Log; import android.util.Pair; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; @@ -39,11 +52,35 @@ import java.util.concurrent.Executor; @SystemService(Context.SLICE_SERVICE) public class SliceManager { + private static final String TAG = "SliceManager"; + + /** + * @hide + */ + public static final String ACTION_REQUEST_SLICE_PERMISSION = + "android.intent.action.REQUEST_SLICE_PERMISSION"; + private final ISliceManager mService; private final Context mContext; private final ArrayMap, ISliceListener> mListenerLookup = new ArrayMap<>(); + /** + * Permission denied. + * @hide + */ + public static final int PERMISSION_DENIED = -1; + /** + * Permission granted. + * @hide + */ + public static final int PERMISSION_GRANTED = 0; + /** + * Permission just granted by the user, and should be granted uri permission as well. + * @hide + */ + public static final int PERMISSION_USER_GRANTED = 1; + /** * @hide */ @@ -54,21 +91,21 @@ public class SliceManager { } /** - * Adds a callback to a specific slice uri. - *

    - * This is a convenience that performs a few slice actions at once. It will put - * the slice in a pinned state since there is a callback attached. It will also - * listen for content changes, when a content change observes, the android system - * will bind the new slice and provide it to all registered {@link SliceCallback}s. - * - * @param uri The uri of the slice being listened to. - * @param callback The listener that should receive the callbacks. - * @param specs The list of supported {@link SliceSpec}s of the callback. - * @see SliceProvider#onSlicePinned(Uri) + * @deprecated TO BE REMOVED. */ + @Deprecated public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, @NonNull List specs) { - registerSliceCallback(uri, callback, specs, Handler.getMain()); + registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback); + } + + /** + * @deprecated TO BE REMOVED. + */ + @Deprecated + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List specs, Executor executor) { + registerSliceCallback(uri, specs, executor, callback); } /** @@ -84,19 +121,9 @@ public class SliceManager { * @param specs The list of supported {@link SliceSpec}s of the callback. * @see SliceProvider#onSlicePinned(Uri) */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List specs, Handler handler) { - try { - mService.addSliceListener(uri, mContext.getPackageName(), - getListener(uri, callback, new ISliceListener.Stub() { - @Override - public void onSliceUpdated(Slice s) throws RemoteException { - handler.post(() -> callback.onSliceUpdated(s)); - } - }), specs.toArray(new SliceSpec[specs.size()])); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + public void registerSliceCallback(@NonNull Uri uri, @NonNull List specs, + @NonNull SliceCallback callback) { + registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback); } /** @@ -112,8 +139,8 @@ public class SliceManager { * @param specs The list of supported {@link SliceSpec}s of the callback. * @see SliceProvider#onSlicePinned(Uri) */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List specs, Executor executor) { + public void registerSliceCallback(@NonNull Uri uri, @NonNull List specs, + @NonNull @CallbackExecutor Executor executor, @NonNull SliceCallback callback) { try { mService.addSliceListener(uri, mContext.getPackageName(), getListener(uri, callback, new ISliceListener.Stub() { @@ -223,6 +250,165 @@ public class SliceManager { } } + /** + * Obtains a list of slices that are descendants of the specified Uri. + *

    + * Not all slice providers will implement this functionality, in which case, + * an empty collection will be returned. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see SliceProvider#onGetSliceDescendants(Uri) + */ + public @NonNull Collection getSliceDescendants(@NonNull Uri uri) { + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + final Bundle res = provider.call(resolver.getPackageName(), + SliceProvider.METHOD_GET_DESCENDANTS, null, extras); + return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get slice descendants", e); + } finally { + resolver.releaseProvider(provider); + } + return Collections.emptyList(); + } + + /** + * Turns a slice Uri into slice content. + * + * @param uri The URI to a slice provider + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + */ + public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List supportedSpecs) { + Preconditions.checkNotNull(uri, "uri"); + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); + final Bundle res = provider.call(mContext.getPackageName(), SliceProvider.METHOD_SLICE, + null, extras); + Bundle.setDefusable(res, true); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + resolver.releaseProvider(provider); + } + } + + /** + * Turns a slice intent into slice content. Expects an explicit intent. If there is no + * {@link android.content.ContentProvider} associated with the given intent this will throw + * {@link IllegalArgumentException}. + * + * @param intent The intent associated with a slice. + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @see SliceProvider#onMapIntentToUri(Intent) + * @see Intent + */ + public @Nullable Slice bindSlice(@NonNull Intent intent, + @NonNull List supportedSpecs) { + Preconditions.checkNotNull(intent, "intent"); + Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, + "Slice intent must be explicit " + intent); + ContentResolver resolver = mContext.getContentResolver(); + + // Check if the intent has data for the slice uri on it and use that + final Uri intentData = intent.getData(); + if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { + return bindSlice(intentData, supportedSpecs); + } + // Otherwise ask the app + List providers = + mContext.getPackageManager().queryIntentContentProviders(intent, 0); + if (providers == null) { + throw new IllegalArgumentException("Unable to resolve intent " + intent); + } + String authority = providers.get(0).providerInfo.authority; + Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).build(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); + final Bundle res = provider.call(mContext.getPackageName(), + SliceProvider.METHOD_MAP_INTENT, null, extras); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + resolver.releaseProvider(provider); + } + } + + /** + * Does the permission check to see if a caller has access to a specific slice. + * @hide + */ + public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) { + try { + if (pkg == null) { + throw new SecurityException("No pkg specified"); + } + int result = mService.checkSlicePermission(uri, pkg, pid, uid); + if (result == PERMISSION_DENIED) { + throw new SecurityException("User " + uid + " does not have slice permission for " + + uri + "."); + } + if (result == PERMISSION_USER_GRANTED) { + // We just had a user grant of this permission and need to grant this to the app + // permanently. + mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(), + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Called by SystemUI to grant a slice permission after a dialog is shown. + * @hide + */ + public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) { + try { + mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Class that listens to changes in {@link Slice}s. */ diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 8483931ceaec415950552d450d586819e4620bc3..00e8ccad0f5f340296106618ca498746f63d319f 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -15,13 +15,19 @@ */ package android.app.slice; -import android.Manifest.permission; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ProviderInfo; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; @@ -36,6 +42,9 @@ import android.os.StrictMode.ThreadPolicy; import android.os.UserHandle; import android.util.Log; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -80,7 +89,7 @@ import java.util.concurrent.CountDownLatch; */ public abstract class SliceProvider extends ContentProvider { /** - * This is the Android platform's MIME type for a slice: URI + * This is the Android platform's MIME type for a URI * containing a slice implemented through {@link SliceProvider}. */ public static final String SLICE_TYPE = "vnd.android.slice"; @@ -110,6 +119,10 @@ public abstract class SliceProvider extends ContentProvider { * @hide */ public static final String METHOD_UNPIN = "unpin"; + /** + * @hide + */ + public static final String METHOD_GET_DESCENDANTS = "get_descendants"; /** * @hide */ @@ -118,9 +131,44 @@ public abstract class SliceProvider extends ContentProvider { * @hide */ public static final String EXTRA_SLICE = "slice"; + /** + * @hide + */ + public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants"; + /** + * @hide + */ + public static final String EXTRA_PKG = "pkg"; + /** + * @hide + */ + public static final String EXTRA_PROVIDER_PKG = "provider_pkg"; + /** + * @hide + */ + public static final String EXTRA_OVERRIDE_PKG = "override_pkg"; private static final boolean DEBUG = false; + private String mBindingPkg; + private SliceManager mSliceManager; + + /** + * Return the package name of the caller that initiated the binding request + * currently happening. The returned package will have been + * verified to belong to the calling UID. Returns {@code null} if not + * currently performing an {@link #onBindSlice(Uri, List)}. + */ + public final @Nullable String getBindingPackage() { + return mBindingPkg; + } + + @Override + public void attachInfo(Context context, ProviderInfo info) { + super.attachInfo(context, info); + mSliceManager = context.getSystemService(SliceManager.class); + } + /** * Implemented to create a slice. Will be called on the main thread. *

    @@ -139,14 +187,6 @@ public abstract class SliceProvider extends ContentProvider { * @see {@link Slice#HINT_PARTIAL} */ public Slice onBindSlice(Uri sliceUri, List supportedSpecs) { - return onBindSlice(sliceUri); - } - - /** - * @deprecated migrating to {@link #onBindSlice(Uri, List)} - */ - @Deprecated - public Slice onBindSlice(Uri sliceUri) { return null; } @@ -182,6 +222,20 @@ public abstract class SliceProvider extends ContentProvider { public void onSliceUnpinned(Uri sliceUri) { } + /** + * Obtains a list of slices that are descendants of the specified Uri. + *

    + * Implementing this is optional for a SliceProvider, but does provide a good + * discovery mechanism for finding slice Uris. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see SliceManager#getSliceDescendants(Uri) + */ + public @NonNull Collection onGetSliceDescendants(@NonNull Uri uri) { + return Collections.emptyList(); + } + /** * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. * In that case, this method can be called and is expected to return a non-null Uri representing @@ -244,56 +298,74 @@ public abstract class SliceProvider extends ContentProvider { @Override public Bundle call(String method, String arg, Bundle extras) { if (method.equals(METHOD_SLICE)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { - getContext().enforceUriPermission(uri, permission.BIND_SLICE, - permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires the permission BIND_SLICE"); - } + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); List supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); - Slice s = handleBindSlice(uri, supportedSpecs); + String callingPackage = getCallingPackage(); + if (extras.containsKey(EXTRA_OVERRIDE_PKG)) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can override calling pkg"); + } + callingPackage = extras.getString(EXTRA_OVERRIDE_PKG); + } + Slice s = handleBindSlice(uri, supportedSpecs, callingPackage); Bundle b = new Bundle(); b.putParcelable(EXTRA_SLICE, s); return b; } else if (method.equals(METHOD_MAP_INTENT)) { - getContext().enforceCallingPermission(permission.BIND_SLICE, - "Slice binding requires the permission BIND_SLICE"); Intent intent = extras.getParcelable(EXTRA_INTENT); if (intent == null) return null; Uri uri = onMapIntentToUri(intent); List supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); Bundle b = new Bundle(); if (uri != null) { - Slice s = handleBindSlice(uri, supportedSpecs); + Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage()); b.putParcelable(EXTRA_SLICE, s); } else { b.putParcelable(EXTRA_SLICE, null); } return b; } else if (method.equals(METHOD_PIN)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { - getContext().enforceUriPermission(uri, permission.BIND_SLICE, - permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires the permission BIND_SLICE"); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can pin/unpin slices"); } handlePinSlice(uri); } else if (method.equals(METHOD_UNPIN)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { - getContext().enforceUriPermission(uri, permission.BIND_SLICE, - permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires the permission BIND_SLICE"); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can pin/unpin slices"); } handleUnpinSlice(uri); + } else if (method.equals(METHOD_GET_DESCENDANTS)) { + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); + Bundle b = new Bundle(); + b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS, + new ArrayList<>(handleGetDescendants(uri))); + return b; } return super.call(method, arg, extras); } + private Collection handleGetDescendants(Uri uri) { + if (Looper.myLooper() == Looper.getMainLooper()) { + return onGetSliceDescendants(uri); + } else { + CountDownLatch latch = new CountDownLatch(1); + Collection[] output = new Collection[1]; + Handler.getMain().post(() -> { + output[0] = onGetSliceDescendants(uri); + latch.countDown(); + }); + try { + latch.await(); + return output[0]; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + private void handlePinSlice(Uri sliceUri) { if (Looper.myLooper() == Looper.getMainLooper()) { onSlicePinned(sliceUri); @@ -328,14 +400,27 @@ public abstract class SliceProvider extends ContentProvider { } } - private Slice handleBindSlice(Uri sliceUri, List supportedSpecs) { + private Slice handleBindSlice(Uri sliceUri, List supportedSpecs, + String callingPkg) { + // This can be removed once Slice#bindSlice is removed and everyone is using + // SliceManager#bindSlice. + String pkg = callingPkg != null ? callingPkg + : getContext().getPackageManager().getNameForUid(Binder.getCallingUid()); + if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { + try { + mSliceManager.enforceSlicePermission(sliceUri, pkg, + Binder.getCallingPid(), Binder.getCallingUid()); + } catch (SecurityException e) { + return createPermissionSlice(getContext(), sliceUri, pkg); + } + } if (Looper.myLooper() == Looper.getMainLooper()) { - return onBindSliceStrict(sliceUri, supportedSpecs); + return onBindSliceStrict(sliceUri, supportedSpecs, pkg); } else { CountDownLatch latch = new CountDownLatch(1); Slice[] output = new Slice[1]; Handler.getMain().post(() -> { - output[0] = onBindSliceStrict(sliceUri, supportedSpecs); + output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg); latch.countDown(); }); try { @@ -347,15 +432,66 @@ public abstract class SliceProvider extends ContentProvider { } } - private Slice onBindSliceStrict(Uri sliceUri, List supportedSpecs) { + /** + * @hide + */ + public static Slice createPermissionSlice(Context context, Uri sliceUri, + String callingPackage) { + return new Slice.Builder(sliceUri) + .addAction(createPermissionIntent(context, sliceUri, callingPackage), + new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build()) + .addText(getPermissionString(context, callingPackage), null) + .build()) + .addHints(Slice.HINT_LIST_ITEM) + .build(); + } + + /** + * @hide + */ + public static PendingIntent createPermissionIntent(Context context, Uri sliceUri, + String callingPackage) { + Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION); + intent.setComponent(new ComponentName("com.android.systemui", + "com.android.systemui.SlicePermissionActivity")); + intent.putExtra(EXTRA_BIND_URI, sliceUri); + intent.putExtra(EXTRA_PKG, callingPackage); + intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName()); + // Unique pending intent. + intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage) + .build()); + + return PendingIntent.getActivity(context, 0, intent, 0); + } + + /** + * @hide + */ + public static CharSequence getPermissionString(Context context, String callingPackage) { + PackageManager pm = context.getPackageManager(); + try { + return context.getString( + com.android.internal.R.string.slices_permission_request, + pm.getApplicationInfo(callingPackage, 0).loadLabel(pm), + context.getApplicationInfo().loadLabel(pm)); + } catch (NameNotFoundException e) { + // This shouldn't be possible since the caller is verified. + throw new RuntimeException("Unknown calling app", e); + } + } + + private Slice onBindSliceStrict(Uri sliceUri, List supportedSpecs, + String callingPackage) { ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); + mBindingPkg = callingPackage; return onBindSlice(sliceUri, supportedSpecs); } finally { + mBindingPkg = null; StrictMode.setThreadPolicy(oldPolicy); } } diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java index 16309fab0924b3600157baa6c8c7c27e7767cc7c..e86d348a85fa1b858456eb36c1d6571245233d5a 100644 --- a/core/java/android/app/timezone/RulesState.java +++ b/core/java/android/app/timezone/RulesState.java @@ -126,9 +126,6 @@ public final class RulesState implements Parcelable { mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */, "stagedDistroRulesVersion", stagedDistroRulesVersion); - if (operationInProgress && distroStatus != DISTRO_STATUS_UNKNOWN) { - throw new IllegalArgumentException("distroInstalled != DISTRO_STATUS_UNKNOWN"); - } this.mDistroStatus = validateDistroStatus(distroStatus); this.mInstalledDistroRulesVersion = validateConditionalNull( mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */, diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java index 2e44a630b0567094bd262368b4f4e369e583c9c3..da36157d85f8c5b924df2e358888df38d2099ccb 100644 --- a/core/java/android/app/usage/NetworkStats.java +++ b/core/java/android/app/usage/NetworkStats.java @@ -227,6 +227,30 @@ public final class NetworkStats implements AutoCloseable { */ public static final int ROAMING_YES = 0x2; + /** @hide */ + @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = { + DEFAULT_NETWORK_ALL, + DEFAULT_NETWORK_NO, + DEFAULT_NETWORK_YES + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DefaultNetwork {} + + /** + * Combined usage for this network regardless of whether it was the active default network. + */ + public static final int DEFAULT_NETWORK_ALL = -1; + + /** + * Usage that occurs while this network is not the active default network. + */ + public static final int DEFAULT_NETWORK_NO = 0x1; + + /** + * Usage that occurs while this network is the active default network. + */ + public static final int DEFAULT_NETWORK_YES = 0x2; + /** * Special TAG value for total data across all tags */ @@ -235,6 +259,7 @@ public final class NetworkStats implements AutoCloseable { private int mUid; private int mTag; private int mState; + private int mDefaultNetwork; private int mMetered; private int mRoaming; private long mBeginTimeStamp; @@ -286,6 +311,15 @@ public final class NetworkStats implements AutoCloseable { return 0; } + private static @DefaultNetwork int convertDefaultNetwork(int defaultNetwork) { + switch (defaultNetwork) { + case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL; + case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO; + case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES; + } + return 0; + } + public Bucket() { } @@ -350,6 +384,21 @@ public final class NetworkStats implements AutoCloseable { return mRoaming; } + /** + * Default network state. One of the following values:

    + *

      + *
    • {@link #DEFAULT_NETWORK_ALL}
    • + *
    • {@link #DEFAULT_NETWORK_NO}
    • + *
    • {@link #DEFAULT_NETWORK_YES}
    • + *
    + *

    Indicates whether the network usage occurred on the system default network for this + * type of traffic, or whether the application chose to send this traffic on a network that + * was not the one selected by the system. + */ + public @DefaultNetwork int getDefaultNetwork() { + return mDefaultNetwork; + } + /** * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. @@ -551,6 +600,8 @@ public final class NetworkStats implements AutoCloseable { bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid); bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag); bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set); + bucketOut.mDefaultNetwork = Bucket.convertDefaultNetwork( + mRecycledSummaryEntry.defaultNetwork); bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered); bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming); bucketOut.mBeginTimeStamp = mStartTimeStamp; @@ -600,6 +651,7 @@ public final class NetworkStats implements AutoCloseable { bucketOut.mUid = Bucket.convertUid(getUid()); bucketOut.mTag = Bucket.convertTag(mTag); bucketOut.mState = Bucket.STATE_ALL; + bucketOut.mDefaultNetwork = Bucket.DEFAULT_NETWORK_ALL; bucketOut.mMetered = Bucket.METERED_ALL; bucketOut.mRoaming = Bucket.ROAMING_ALL; bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart; diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 853b00331a4dc18ece05b4cf4d068156ef4deb96..5576e86edd8a35bbd2ca4550567feedc91ce12ec 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -60,10 +60,11 @@ import android.util.Log; * {@link #queryDetailsForUid}

    * {@link #queryDetails}

    * These queries do not aggregate over time but do aggregate over state, metered and roaming. - * Therefore there can be multiple buckets for a particular key but all Bucket's state is going to - * be {@link NetworkStats.Bucket#STATE_ALL}, all Bucket's metered is going to be - * {@link NetworkStats.Bucket#METERED_ALL}, and all Bucket's roaming is going to be - * {@link NetworkStats.Bucket#ROAMING_ALL}. + * Therefore there can be multiple buckets for a particular key. However, all Buckets will have + * {@code state} {@link NetworkStats.Bucket#STATE_ALL}, + * {@code defaultNetwork} {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * {@code metered } {@link NetworkStats.Bucket#METERED_ALL}, + * {@code roaming} {@link NetworkStats.Bucket#ROAMING_ALL}. *

    * NOTE: Calling {@link #querySummaryForDevice} or accessing stats for apps other than the * calling app requires the permission {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, @@ -130,13 +131,26 @@ public class NetworkStatsManager { } } + /** @hide */ + public Bucket querySummaryForDevice(NetworkTemplate template, + long startTime, long endTime) throws SecurityException, RemoteException { + Bucket bucket = null; + NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); + bucket = stats.getDeviceSummaryForNetwork(); + + stats.close(); + return bucket; + } + /** * Query network usage statistics summaries. Result is summarised data usage for the whole * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and * roaming. This means the bucket's start and end timestamp are going to be the same as the * 'startTime' and 'endTime' parameters. State is going to be * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL}, - * tag {@link NetworkStats.Bucket#TAG_NONE}, metered {@link NetworkStats.Bucket#METERED_ALL}, + * tag {@link NetworkStats.Bucket#TAG_NONE}, + * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered {@link NetworkStats.Bucket#METERED_ALL}, * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}. * * @param networkType As defined in {@link ConnectivityManager}, e.g. @@ -160,12 +174,7 @@ public class NetworkStatsManager { return null; } - Bucket bucket = null; - NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); - bucket = stats.getDeviceSummaryForNetwork(); - - stats.close(); - return bucket; + return querySummaryForDevice(template, startTime, endTime); } /** @@ -209,10 +218,10 @@ public class NetworkStatsManager { /** * Query network usage statistics summaries. Result filtered to include only uids belonging to * calling user. Result is aggregated over time, hence all buckets will have the same start and - * end timestamps. Not aggregated over state, uid, metered, or roaming. This means buckets' - * start and end timestamps are going to be the same as the 'startTime' and 'endTime' - * parameters. State, uid, metered, and roaming are going to vary, and tag is going to be the - * same. + * end timestamps. Not aggregated over state, uid, default network, metered, or roaming. This + * means buckets' start and end timestamps are going to be the same as the 'startTime' and + * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to + * be the same. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -258,9 +267,10 @@ public class NetworkStatsManager { * belonging to calling user. Result is aggregated over state but not aggregated over time. * This means buckets' start and end timestamps are going to be between 'startTime' and * 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL}, uid the - * same as the 'uid' parameter and tag the same as 'tag' parameter. metered is going to be - * {@link NetworkStats.Bucket#METERED_ALL}, and roaming is going to be - * {@link NetworkStats.Bucket#ROAMING_ALL}. + * same as the 'uid' parameter and tag the same as 'tag' parameter. + * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and + * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. *

    Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. @@ -301,9 +311,10 @@ public class NetworkStatsManager { * metered, nor roaming. This means buckets' start and end timestamps are going to be between * 'startTime' and 'endTime' parameters. State is going to be * {@link NetworkStats.Bucket#STATE_ALL}, uid will vary, - * tag {@link NetworkStats.Bucket#TAG_NONE}, metered is going to be - * {@link NetworkStats.Bucket#METERED_ALL}, and roaming is going to be - * {@link NetworkStats.Bucket#ROAMING_ALL}. + * tag {@link NetworkStats.Bucket#TAG_NONE}, + * default network is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, + * and roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. *

    Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. @@ -335,6 +346,37 @@ public class NetworkStatsManager { return result; } + /** @hide */ + public void registerUsageCallback(NetworkTemplate template, int networkType, + long thresholdBytes, UsageCallback callback, @Nullable Handler handler) { + checkNotNull(callback, "UsageCallback cannot be null"); + + final Looper looper; + if (handler == null) { + looper = Looper.myLooper(); + } else { + looper = handler.getLooper(); + } + + DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET, + template, thresholdBytes); + try { + CallbackHandler callbackHandler = new CallbackHandler(looper, networkType, + template.getSubscriberId(), callback); + callback.request = mService.registerUsageCallback( + mContext.getOpPackageName(), request, new Messenger(callbackHandler), + new Binder()); + if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request); + + if (callback.request == null) { + Log.e(TAG, "Request from callback is null; should not happen"); + } + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when registering callback"); + throw e.rethrowFromSystemServer(); + } + } + /** * Registers to receive notifications about data usage on specified networks. * @@ -363,15 +405,7 @@ public class NetworkStatsManager { */ public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, UsageCallback callback, @Nullable Handler handler) { - checkNotNull(callback, "UsageCallback cannot be null"); - - final Looper looper; - if (handler == null) { - looper = Looper.myLooper(); - } else { - looper = handler.getLooper(); - } - + NetworkTemplate template = createTemplate(networkType, subscriberId); if (DBG) { Log.d(TAG, "registerUsageCallback called with: {" + " networkType=" + networkType @@ -379,25 +413,7 @@ public class NetworkStatsManager { + " thresholdBytes=" + thresholdBytes + " }"); } - - NetworkTemplate template = createTemplate(networkType, subscriberId); - DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET, - template, thresholdBytes); - try { - CallbackHandler callbackHandler = new CallbackHandler(looper, networkType, - subscriberId, callback); - callback.request = mService.registerUsageCallback( - mContext.getOpPackageName(), request, new Messenger(callbackHandler), - new Binder()); - if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request); - - if (callback.request == null) { - Log.e(TAG, "Request from callback is null; should not happen"); - } - } catch (RemoteException e) { - if (DBG) Log.d(TAG, "Remote exception when registering callback"); - throw e.rethrowFromSystemServer(); - } + registerUsageCallback(template, networkType, thresholdBytes, callback, handler); } /** diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index f04e9074ec4aa40e3094357916df3c762b69ba0d..edb992bd265c35c93571f0989206d559b8dd56f4 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -106,6 +106,12 @@ public final class UsageEvents implements Parcelable { */ public static final int NOTIFICATION_SEEN = 10; + /** + * An event type denoting a change in App Standby Bucket. + * @hide + */ + public static final int STANDBY_BUCKET_CHANGED = 11; + /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -170,6 +176,13 @@ public final class UsageEvents implements Parcelable { */ public String[] mContentAnnotations; + /** + * The app standby bucket assigned. + * Only present for {@link #STANDBY_BUCKET_CHANGED} event types + * {@hide} + */ + public int mBucket; + /** @hide */ @EventFlags public int mFlags; @@ -189,6 +202,7 @@ public final class UsageEvents implements Parcelable { mContentType = orig.mContentType; mContentAnnotations = orig.mContentAnnotations; mFlags = orig.mFlags; + mBucket = orig.mBucket; } /** @@ -399,6 +413,9 @@ public final class UsageEvents implements Parcelable { p.writeString(event.mContentType); p.writeStringArray(event.mContentAnnotations); break; + case Event.STANDBY_BUCKET_CHANGED: + p.writeInt(event.mBucket); + break; } } @@ -442,6 +459,9 @@ public final class UsageEvents implements Parcelable { eventOut.mContentType = p.readString(); eventOut.mContentAnnotations = p.createStringArray(); break; + case Event.STANDBY_BUCKET_CHANGED: + eventOut.mBucket = p.readInt(); + break; } } diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 5cd0981f72e32addc7bb5fd7c1087a5ff03e01a4..5d6a989ffef84135a0f63d40b9e9f37b57283e6f 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -146,6 +146,14 @@ public abstract class UsageStatsManagerInternal { * allowed to do work even if they're idle or in a low bucket. */ public abstract void onParoleStateChanged(boolean isParoleOn); + + /** + * Optional callback to inform the listener that the app has transitioned into + * an active state due to user interaction. + */ + public void onUserInteractionStarted(String packageName, @UserIdInt int userId) { + // No-op by default + } } /** Backup/Restore API */ @@ -175,6 +183,12 @@ public abstract class UsageStatsManagerInternal { */ public abstract void setActiveAdminApps(Set adminApps, int userId); + /** + * Called by DevicePolicyManagerService during boot to inform that admin data is loaded and + * pushed to UsageStatsService. + */ + public abstract void onAdminDataAvailable(); + /** * Return usage stats. * @@ -206,4 +220,17 @@ public abstract class UsageStatsManagerInternal { * indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}. */ public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId); + + /** + * Report a few data points about an app's job state at the current time. + * + * @param packageName the app whose job state is being described + * @param userId which user the app is associated with + * @param numDeferredJobs the number of pending jobs that were deferred + * due to bucketing policy + * @param timeSinceLastJobRun number of milliseconds since the last time one of + * this app's jobs was executed + */ + public abstract void reportAppJobState(String packageName, @UserIdInt int userId, + int numDeferredJobs, long timeSinceLastJobRun); } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 35a21a4eaf9fb2127f923a3b30055d9f7651dcbb..b255a43cbe3066fb1e4c4d26f92243be2b236648 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -300,11 +300,7 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** - * Initiate connection to a profile of the remote bluetooth device. - * - *

    Currently, the system supports only 1 connection to the - * A2DP profile. The API will automatically disconnect connected - * devices before connecting. + * Initiate connection to a profile of the remote Bluetooth device. * *

    This API returns false in scenarios like the profile on the * device is already connected or Bluetooth is not turned on. @@ -699,15 +695,17 @@ public final class BluetoothA2dp implements BluetoothProfile { /** * Gets the current codec status (configuration and capability). * + * @param device the remote Bluetooth device. If null, use the current + * active A2DP Bluetooth device. * @return the current codec status * @hide */ - public BluetoothCodecStatus getCodecStatus() { - if (DBG) Log.d(TAG, "getCodecStatus"); + public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { + if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); try { mServiceLock.readLock().lock(); if (mService != null && isEnabled()) { - return mService.getCodecStatus(); + return mService.getCodecStatus(device); } if (mService == null) { Log.w(TAG, "Proxy not attached to service"); @@ -724,15 +722,18 @@ public final class BluetoothA2dp implements BluetoothProfile { /** * Sets the codec configuration preference. * + * @param device the remote Bluetooth device. If null, use the current + * active A2DP Bluetooth device. * @param codecConfig the codec configuration preference * @hide */ - public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) { - if (DBG) Log.d(TAG, "setCodecConfigPreference"); + public void setCodecConfigPreference(BluetoothDevice device, + BluetoothCodecConfig codecConfig) { + if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); try { mServiceLock.readLock().lock(); if (mService != null && isEnabled()) { - mService.setCodecConfigPreference(codecConfig); + mService.setCodecConfigPreference(device, codecConfig); } if (mService == null) Log.w(TAG, "Proxy not attached to service"); return; @@ -747,36 +748,42 @@ public final class BluetoothA2dp implements BluetoothProfile { /** * Enables the optional codecs. * + * @param device the remote Bluetooth device. If null, use the currect + * active A2DP Bluetooth device. * @hide */ - public void enableOptionalCodecs() { - if (DBG) Log.d(TAG, "enableOptionalCodecs"); - enableDisableOptionalCodecs(true); + public void enableOptionalCodecs(BluetoothDevice device) { + if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); + enableDisableOptionalCodecs(device, true); } /** * Disables the optional codecs. * + * @param device the remote Bluetooth device. If null, use the currect + * active A2DP Bluetooth device. * @hide */ - public void disableOptionalCodecs() { - if (DBG) Log.d(TAG, "disableOptionalCodecs"); - enableDisableOptionalCodecs(false); + public void disableOptionalCodecs(BluetoothDevice device) { + if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); + enableDisableOptionalCodecs(device, false); } /** * Enables or disables the optional codecs. * + * @param device the remote Bluetooth device. If null, use the currect + * active A2DP Bluetooth device. * @param enable if true, enable the optional codecs, other disable them */ - private void enableDisableOptionalCodecs(boolean enable) { + private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) { try { mServiceLock.readLock().lock(); if (mService != null && isEnabled()) { if (enable) { - mService.enableOptionalCodecs(); + mService.enableOptionalCodecs(device); } else { - mService.disableOptionalCodecs(); + mService.disableOptionalCodecs(device); } } if (mService == null) Log.w(TAG, "Proxy not attached to service"); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 3290d57f13a38fe72a826912cafc7971692bd37d..bc7823b00f49f91f9e8ba192bcd83d642ce3de79 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -79,8 +79,9 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * {@link BluetoothDevice} objects representing all paired devices with * {@link #getBondedDevices()}; start device discovery with * {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to - * listen for incoming connection requests with - * {@link #listenUsingRfcommWithServiceRecord(String, UUID)}; or start a scan for + * listen for incoming RFComm connection requests with {@link + * #listenUsingRfcommWithServiceRecord(String, UUID)}; listen for incoming L2CAP Connection-oriented + * Channels (CoC) connection requests with listenUsingL2capCoc(int)}; or start a scan for * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}. *

    *

    This class is thread safe.

    @@ -209,6 +210,14 @@ public final class BluetoothAdapter { */ public static final int STATE_BLE_TURNING_OFF = 16; + /** + * UUID of the GATT Read Characteristics for LE_PSM value. + * + * @hide + */ + public static final UUID LE_PSM_CHARACTERISTIC_UUID = + UUID.fromString("2d410339-82b6-42aa-b34e-e2e01df8cc1a"); + /** * Human-readable string helper for AdapterState * @@ -429,7 +438,7 @@ public final class BluetoothAdapter { * Intent used to broadcast the change in connection state of the local * Bluetooth adapter to a profile of the remote device. When the adapter is * not connected to any profiles of any remote devices and it attempts a - * connection to a profile this intent will sent. Once connected, this intent + * connection to a profile this intent will be sent. Once connected, this intent * will not be sent for any more connection attempts to any profiles of any * remote device. When the adapter disconnects from the last profile its * connected to of any remote device, this intent will be sent. @@ -1674,6 +1683,27 @@ public final class BluetoothAdapter { return 0; } + /** + * Get the maximum number of connected audio devices. + * + * @return the maximum number of connected audio devices + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public int getMaxConnectedAudioDevices() { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.getMaxConnectedAudioDevices(); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to get getMaxConnectedAudioDevices, error: ", e); + } finally { + mServiceLock.readLock().unlock(); + } + return 1; + } + /** * Return true if hardware has entries available for matching beacons * @@ -2139,7 +2169,9 @@ public final class BluetoothAdapter { min16DigitPin); int errno = socket.mSocket.bindListen(); if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - socket.setChannel(socket.mSocket.getPort()); + int assignedChannel = socket.mSocket.getPort(); + if (DBG) Log.d(TAG, "listenUsingL2capOn: set assigned channel to " + assignedChannel); + socket.setChannel(assignedChannel); } if (errno != 0) { //TODO(BT): Throw the same exception error code @@ -2180,12 +2212,18 @@ public final class BluetoothAdapter { * @hide */ public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException { + Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port); BluetoothServerSocket socket = new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, false, false, port, false, - false); + false); int errno = socket.mSocket.bindListen(); if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - socket.setChannel(socket.mSocket.getPort()); + int assignedChannel = socket.mSocket.getPort(); + if (DBG) { + Log.d(TAG, "listenUsingInsecureL2capOn: set assigned channel to " + + assignedChannel); + } + socket.setChannel(assignedChannel); } if (errno != 0) { //TODO(BT): Throw the same exception error code @@ -2744,4 +2782,103 @@ public final class BluetoothAdapter { scanner.stopScan(scanCallback); } } + + /** + * Create a secure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and + * assign a dynamic protocol/service multiplexer (PSM) value. This socket can be used to listen + * for incoming connections. + *

    A remote device connecting to this socket will be authenticated and communication on this + * socket will be encrypted. + *

    Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening + * {@link BluetoothServerSocket}. + *

    The system will assign a dynamic PSM value. This PSM value can be read from the {#link + * BluetoothServerSocket#getPsm()} and this value will be released when this server socket is + * closed, Bluetooth is turned off, or the application exits unexpectedly. + *

    The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is + * defined and performed by the application. + *

    Use {@link BluetoothDevice#createL2capCocSocket(int, int)} to connect to this server + * socket from another Android device that is given the PSM value. + * + * @param transport Bluetooth transport to use, must be {@link BluetoothDevice#TRANSPORT_LE} + * @return an L2CAP CoC BluetoothServerSocket + * @throws IOException on error, for example Bluetooth not available, or insufficient + * permissions, or unable to start this CoC + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public BluetoothServerSocket listenUsingL2capCoc(int transport) + throws IOException { + if (transport != BluetoothDevice.TRANSPORT_LE) { + throw new IllegalArgumentException("Unsupported transport: " + transport); + } + BluetoothServerSocket socket = + new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, true, true, + SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false); + int errno = socket.mSocket.bindListen(); + if (errno != 0) { + throw new IOException("Error: " + errno); + } + + int assignedPsm = socket.mSocket.getPort(); + if (assignedPsm == 0) { + throw new IOException("Error: Unable to assign PSM value"); + } + if (DBG) { + Log.d(TAG, "listenUsingL2capCoc: set assigned PSM to " + + assignedPsm); + } + socket.setChannel(assignedPsm); + + return socket; + } + + /** + * Create an insecure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and + * assign a dynamic PSM value. This socket can be used to listen for incoming connections. + *

    The link key is not required to be authenticated, i.e the communication may be vulnerable + * to man-in-the-middle attacks. Use {@link #listenUsingL2capCoc}, if an encrypted and + * authenticated communication channel is desired. + *

    Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening + * {@link BluetoothServerSocket}. + *

    The system will assign a dynamic protocol/service multiplexer (PSM) value. This PSM value + * can be read from the {#link BluetoothServerSocket#getPsm()} and this value will be released + * when this server socket is closed, Bluetooth is turned off, or the application exits + * unexpectedly. + *

    The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is + * defined and performed by the application. + *

    Use {@link BluetoothDevice#createInsecureL2capCocSocket(int, int)} to connect to this + * server socket from another Android device that is given the PSM value. + * + * @param transport Bluetooth transport to use, must be {@link BluetoothDevice#TRANSPORT_LE} + * @return an L2CAP CoC BluetoothServerSocket + * @throws IOException on error, for example Bluetooth not available, or insufficient + * permissions, or unable to start this CoC + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public BluetoothServerSocket listenUsingInsecureL2capCoc(int transport) + throws IOException { + if (transport != BluetoothDevice.TRANSPORT_LE) { + throw new IllegalArgumentException("Unsupported transport: " + transport); + } + BluetoothServerSocket socket = + new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, false, false, + SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false); + int errno = socket.mSocket.bindListen(); + if (errno != 0) { + throw new IOException("Error: " + errno); + } + + int assignedPsm = socket.mSocket.getPort(); + if (assignedPsm == 0) { + throw new IOException("Error: Unable to assign PSM value"); + } + if (DBG) { + Log.d(TAG, "listenUsingInsecureL2capOn: set assigned PSM to " + + assignedPsm); + } + socket.setChannel(assignedPsm); + + return socket; + } } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 9b736b7e5f9e82f2577872db6572fbbf878b981b..ac21395c2fa72a80eaef50506dcbdeded7e044e6 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1921,4 +1921,75 @@ public final class BluetoothDevice implements Parcelable { } return null; } + + /** + * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can + * be used to start a secure outgoing connection to the remote device with the same dynamic + * protocol/service multiplexer (PSM) value. + *

    This is designed to be used with {@link BluetoothAdapter#listenUsingL2capCoc(int)} for + * peer-peer Bluetooth applications. + *

    Use {@link BluetoothSocket#connect} to initiate the outgoing connection. + *

    Application using this API is responsible for obtaining PSM value from remote device. + *

    The remote device will be authenticated and communication on this socket will be + * encrypted. + *

    Use this socket if an authenticated socket link is possible. Authentication refers + * to the authentication of the link key to prevent man-in-the-middle type of attacks. When a + * secure socket connection is not possible, use {#link createInsecureLeL2capCocSocket(int, + * int)}. + * + * @param transport Bluetooth transport to use, must be {@link #TRANSPORT_LE} + * @param psm dynamic PSM value from remote device + * @return a CoC #BluetoothSocket ready for an outgoing connection + * @throws IOException on error, for example Bluetooth not available, or insufficient + * permissions + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public BluetoothSocket createL2capCocSocket(int transport, int psm) throws IOException { + if (!isBluetoothEnabled()) { + Log.e(TAG, "createL2capCocSocket: Bluetooth is not enabled"); + throw new IOException(); + } + if (transport != BluetoothDevice.TRANSPORT_LE) { + throw new IllegalArgumentException("Unsupported transport: " + transport); + } + if (DBG) Log.d(TAG, "createL2capCocSocket: transport=" + transport + ", psm=" + psm); + return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, true, true, this, psm, + null); + } + + /** + * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can + * be used to start a secure outgoing connection to the remote device with the same dynamic + * protocol/service multiplexer (PSM) value. + *

    This is designed to be used with {@link BluetoothAdapter#listenUsingInsecureL2capCoc(int)} + * for peer-peer Bluetooth applications. + *

    Use {@link BluetoothSocket#connect} to initiate the outgoing connection. + *

    Application using this API is responsible for obtaining PSM value from remote device. + *

    The communication channel may not have an authenticated link key, i.e. it may be subject + * to man-in-the-middle attacks. Use {@link #createL2capCocSocket(int, int)} if an encrypted and + * authenticated communication channel is possible. + * + * @param transport Bluetooth transport to use, must be {@link #TRANSPORT_LE} + * @param psm dynamic PSM value from remote device + * @return a CoC #BluetoothSocket ready for an outgoing connection + * @throws IOException on error, for example Bluetooth not available, or insufficient + * permissions + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public BluetoothSocket createInsecureL2capCocSocket(int transport, int psm) throws IOException { + if (!isBluetoothEnabled()) { + Log.e(TAG, "createInsecureL2capCocSocket: Bluetooth is not enabled"); + throw new IOException(); + } + if (transport != BluetoothDevice.TRANSPORT_LE) { + throw new IllegalArgumentException("Unsupported transport: " + transport); + } + if (DBG) { + Log.d(TAG, "createInsecureL2capCocSocket: transport=" + transport + ", psm=" + psm); + } + return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, false, false, this, psm, + null); + } } diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java index dc00d63043fc96221c422fc0ae53cbd03a587f80..d46b2e37467bd065f23e827029060d31e8aac79c 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java @@ -73,17 +73,18 @@ public final class BluetoothHeadsetClientCall implements Parcelable { private final boolean mOutgoing; private final UUID mUUID; private final long mCreationElapsedMilli; + private final boolean mInBandRing; /** * Creates BluetoothHeadsetClientCall instance. */ public BluetoothHeadsetClientCall(BluetoothDevice device, int id, int state, String number, - boolean multiParty, boolean outgoing) { - this(device, id, UUID.randomUUID(), state, number, multiParty, outgoing); + boolean multiParty, boolean outgoing, boolean inBandRing) { + this(device, id, UUID.randomUUID(), state, number, multiParty, outgoing, inBandRing); } public BluetoothHeadsetClientCall(BluetoothDevice device, int id, UUID uuid, int state, - String number, boolean multiParty, boolean outgoing) { + String number, boolean multiParty, boolean outgoing, boolean inBandRing) { mDevice = device; mId = id; mUUID = uuid; @@ -91,6 +92,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { mNumber = number != null ? number : ""; mMultiParty = multiParty; mOutgoing = outgoing; + mInBandRing = inBandRing; mCreationElapsedMilli = SystemClock.elapsedRealtime(); } @@ -200,6 +202,16 @@ public final class BluetoothHeadsetClientCall implements Parcelable { return mOutgoing; } + /** + * Checks if the ringtone will be generated by the connected phone + * + * @return true if in band ring is enabled, false otherwise. + */ + public boolean isInBandRing() { + return mInBandRing; + } + + @Override public String toString() { return toString(false); @@ -253,6 +265,8 @@ public final class BluetoothHeadsetClientCall implements Parcelable { builder.append(mMultiParty); builder.append(", mOutgoing: "); builder.append(mOutgoing); + builder.append(", mInBandRing: "); + builder.append(mInBandRing); builder.append("}"); return builder.toString(); } @@ -266,7 +280,8 @@ public final class BluetoothHeadsetClientCall implements Parcelable { public BluetoothHeadsetClientCall createFromParcel(Parcel in) { return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null), in.readInt(), UUID.fromString(in.readString()), in.readInt(), - in.readString(), in.readInt() == 1, in.readInt() == 1); + in.readString(), in.readInt() == 1, in.readInt() == 1, + in.readInt() == 1); } @Override @@ -284,6 +299,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { out.writeString(mNumber); out.writeInt(mMultiParty ? 1 : 0); out.writeInt(mOutgoing ? 1 : 0); + out.writeInt(mInBandRing ? 1 : 0); } @Override diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index 58d090dc287b43de89cd1b08a73be5d92089aa9a..ebb7f187aea5ba217443f853b11445eba780ab7a 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -68,6 +68,7 @@ import java.io.IOException; public final class BluetoothServerSocket implements Closeable { private static final String TAG = "BluetoothServerSocket"; + private static final boolean DBG = false; /*package*/ final BluetoothSocket mSocket; private Handler mHandler; private int mMessage; @@ -169,6 +170,7 @@ public final class BluetoothServerSocket implements Closeable { * close any {@link BluetoothSocket} received from {@link #accept()}. */ public void close() throws IOException { + if (DBG) Log.d(TAG, "BluetoothServerSocket:close() called. mChannel=" + mChannel); synchronized (this) { if (mHandler != null) { mHandler.obtainMessage(mMessage).sendToTarget(); @@ -196,6 +198,20 @@ public final class BluetoothServerSocket implements Closeable { return mChannel; } + /** + * Returns the assigned dynamic protocol/service multiplexer (PSM) value for the listening L2CAP + * Connection-oriented Channel (CoC) server socket. This server socket must be returned by the + * {#link BluetoothAdapter.listenUsingL2capCoc(int)} or {#link + * BluetoothAdapter.listenUsingInsecureL2capCoc(int)}. The returned value is undefined if this + * method is called on non-L2CAP server sockets. + * + * @return the assigned PSM or LE_PSM value depending on transport + * @hide + */ + public int getPsm() { + return mChannel; + } + /** * Sets the channel on which future sockets are bound. * Currently used only when a channel is auto generated. @@ -227,6 +243,10 @@ public final class BluetoothServerSocket implements Closeable { sb.append("TYPE_L2CAP"); break; } + case BluetoothSocket.TYPE_L2CAP_LE: { + sb.append("TYPE_L2CAP_LE"); + break; + } case BluetoothSocket.TYPE_SCO: { sb.append("TYPE_SCO"); break; diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 0569913435a8046ac35563aea98dc34efd221c7a..09f96840f91850a420f73d7d36a21fed80c1f14a 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -99,6 +99,16 @@ public final class BluetoothSocket implements Closeable { /** L2CAP socket */ public static final int TYPE_L2CAP = 3; + /** L2CAP socket on BR/EDR transport + * @hide + */ + public static final int TYPE_L2CAP_BREDR = TYPE_L2CAP; + + /** L2CAP socket on LE transport + * @hide + */ + public static final int TYPE_L2CAP_LE = 4; + /*package*/ static final int EBADFD = 77; /*package*/ static final int EADDRINUSE = 98; @@ -417,6 +427,7 @@ public final class BluetoothSocket implements Closeable { return -1; } try { + if (DBG) Log.d(TAG, "bindListen(): mPort=" + mPort + ", mType=" + mType); mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName, mUuid, mPort, getSecurityFlags()); } catch (RemoteException e) { @@ -451,7 +462,7 @@ public final class BluetoothSocket implements Closeable { mSocketState = SocketState.LISTENING; } } - if (DBG) Log.d(TAG, "channel: " + channel); + if (DBG) Log.d(TAG, "bindListen(): channel=" + channel + ", mPort=" + mPort); if (mPort <= -1) { mPort = channel; } // else ASSERT(mPort == channel) @@ -515,7 +526,7 @@ public final class BluetoothSocket implements Closeable { /*package*/ int read(byte[] b, int offset, int length) throws IOException { int ret = 0; if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length); - if (mType == TYPE_L2CAP) { + if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { int bytesToRead = length; if (VDBG) { Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length @@ -558,7 +569,7 @@ public final class BluetoothSocket implements Closeable { // Rfcomm uses dynamic allocation, and should not have any bindings // to the actual message length. if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length); - if (mType == TYPE_L2CAP) { + if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { if (length <= mMaxTxPacketSize) { mSocketOS.write(b, offset, length); } else { @@ -702,7 +713,7 @@ public final class BluetoothSocket implements Closeable { } private void createL2capRxBuffer() { - if (mType == TYPE_L2CAP) { + if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { // Allocate the buffer to use for reads. if (VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize); mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]); diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java index 2629929e91ceb871af3974d6d75a69fb58f6a4a6..5a1216b79b8295aa1f7906a3984a7176f1c195ad 100644 --- a/core/java/android/content/AbstractThreadedSyncAdapter.java +++ b/core/java/android/content/AbstractThreadedSyncAdapter.java @@ -21,6 +21,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Process; +import android.os.RemoteException; import android.os.Trace; import android.util.Log; @@ -165,6 +166,12 @@ public abstract class AbstractThreadedSyncAdapter { } private class ISyncAdapterImpl extends ISyncAdapter.Stub { + @Override + public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb) + throws RemoteException { + cb.onUnsyncableAccountDone(AbstractThreadedSyncAdapter.this.onUnsyncableAccount()); + } + @Override public void startSync(ISyncContext syncContext, String authority, Account account, Bundle extras) { @@ -373,6 +380,26 @@ public abstract class AbstractThreadedSyncAdapter { return mISyncAdapterImpl.asBinder(); } + /** + * Allows to defer syncing until all accounts are properly set up. + * + *

    Called when a account / authority pair + *

      + *
    • that can be handled by this adapter
    • + *
    • {@link ContentResolver#requestSync(SyncRequest) is synced}
    • + *
    • and the account/provider {@link ContentResolver#getIsSyncable(Account, String) has + * unknown state (<0)}.
    • + *
    + * + *

    This might be called on a different service connection as {@link #onPerformSync}. + * + * @return If {@code false} syncing is deferred. Returns {@code true} by default, i.e. by + * default syncing starts immediately. + */ + public boolean onUnsyncableAccount() { + return true; + } + /** * Perform a sync for this account. SyncAdapter-specific parameters may * be specified in extras, which is guaranteed to not be null. Invocations diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index f69e76445aa70a84e9b566056d8965a9e9e0d739..f2eb4a0285a492e334eb97ce79acb4321c364fd3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -32,6 +32,7 @@ import android.annotation.StyleableRes; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; @@ -1834,13 +1835,17 @@ public abstract class Context { * See {@link android.content.Context#startActivity(Intent, Bundle)} * Context.startActivity(Intent, Bundle)} for more details. * + * @return The corresponding flag {@link ActivityManager#START_CANCELED}, + * {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was + * successful. + * * @throws ActivityNotFoundException   * * @see #startActivities(Intent[]) * @see PackageManager#resolveActivity */ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -3024,7 +3029,8 @@ public abstract class Context { //@hide: INCIDENT_SERVICE, //@hide: STATS_COMPANION_SERVICE, COMPANION_DEVICE_SERVICE, - CROSS_PROFILE_APPS_SERVICE + CROSS_PROFILE_APPS_SERVICE, + //@hide: SYSTEM_UPDATE_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3240,6 +3246,17 @@ public abstract class Context { */ public static final String RECOVERY_SERVICE = "recovery"; + /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.os.SystemUpdateManager} for accessing the system update + * manager service. + * + * @see #getSystemService(String) + * @hide + */ + @SystemApi + public static final String SYSTEM_UPDATE_SERVICE = "system_update"; + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.view.WindowManager} for accessing the system's window @@ -4109,7 +4126,7 @@ public abstract class Context { public static final String STATS_COMPANION_SERVICE = "statscompanion"; /** - * Use with {@link #getSystemService(String)} to retrieve an {@link android.stats.StatsManager}. + * Use with {@link #getSystemService(String)} to retrieve an {@link android.app.StatsManager}. * @hide */ @SystemApi @@ -4146,12 +4163,22 @@ 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) */ public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.se.omapi.ISecureElementService} + * for accessing the SecureElementService. + * + * @hide + */ + @SystemApi + public static final String SECURE_ELEMENT_SERVICE = "secure_element"; + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 67de4fe6bc4bd2110a519d06c5fdb37e383c577d..8c1293e5b2983f2ec7de13538fccc465453b87e0 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -418,8 +418,8 @@ public class ContextWrapper extends Context { /** @hide */ @Override - public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { - mBase.startActivitiesAsUser(intents, options, userHandle); + public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + return mBase.startActivitiesAsUser(intents, options, userHandle); } @Override diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl index 4660527925c525ad739ad23fd190e3eddaf62320..0eb581e6b5854b8be094315d23bc8d5d3664fca5 100644 --- a/core/java/android/content/ISyncAdapter.aidl +++ b/core/java/android/content/ISyncAdapter.aidl @@ -19,12 +19,21 @@ package android.content; import android.accounts.Account; import android.os.Bundle; import android.content.ISyncContext; +import android.content.ISyncAdapterUnsyncableAccountCallback; /** * Interface used to control the sync activity on a SyncAdapter * @hide */ oneway interface ISyncAdapter { + /** + * Called before {@link #startSync}. This allows the adapter to defer syncs until the + * adapter is ready for the account + * + * @param cb If called back with {@code false} accounts are not synced. + */ + void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb); + /** * Initiate a sync for this account. SyncAdapter-specific parameters may * be specified in extras, which is guaranteed to not be null. diff --git a/core/java/android/content/ISyncAdapterUnsyncableAccountCallback.aidl b/core/java/android/content/ISyncAdapterUnsyncableAccountCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a738ac2785c523526d5fcdba80fade3d9696ab8b --- /dev/null +++ b/core/java/android/content/ISyncAdapterUnsyncableAccountCallback.aidl @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * Callback for {@link ISyncAdapter#onUnsyncableAccount} + * @hide + */ +oneway interface ISyncAdapterUnsyncableAccountCallback { + /** + * Deliver the result for {@link ISyncAdapter#onUnsyncableAccount} + * + * @param isReady Iff {@code false} account is not synced. + */ + void onUnsyncableAccountDone(boolean isReady); +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6e9970980e96c7160bfaaadf9c6596311ef727f0..b3c8737a283112bdecc7c4a39afcfa305fa58e28 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -50,6 +50,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.OpenableColumns; +import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -3516,7 +3517,10 @@ 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"; @@ -3926,6 +3930,14 @@ public class Intent implements Parcelable, Cloneable { */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER"; + /** + * Indicates the preferred entry-point activity when an application is launched from a Car + * launcher. If not present, Car launcher can optionally use {@link #CATEGORY_LAUNCHER} as a + * fallback, or exclude the application entirely. + * @hide + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_CAR_LAUNCHER = "android.intent.category.CAR_LAUNCHER"; /** * Indicates a Leanback settings activity to be displayed in the Leanback launcher. * @hide @@ -4461,7 +4473,15 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION"; /** - * A {@link Bundle} of metadata that describes the instanta application that needs to be + * An array of {@link Bundle}s containing details about resolved instant apps.. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_BUNDLES = + "android.intent.extra.INSTANT_APP_BUNDLES"; + + /** + * A {@link Bundle} of metadata that describes the instant application that needs to be * installed. This data is populated from the response to * {@link android.content.pm.InstantAppResolveInfo#getExtras()} as provided by the registered * instant application resolver. @@ -4470,6 +4490,16 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS"; + /** + * A boolean value indicating that the instant app resolver was unable to state with certainty + * that it did or did not have an app for the sanitized {@link Intent} defined at + * {@link #EXTRA_INTENT}. + * @hide + */ + @SystemApi + public static final String EXTRA_UNKNOWN_INSTANT_APP = + "android.intent.extra.UNKNOWN_INSTANT_APP"; + /** * The version code of the app to install components from. * @deprecated Use {@link #EXTRA_LONG_VERSION_CODE). @@ -5018,6 +5048,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_GRANT_PREFIX_URI_PERMISSION, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, + FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5061,6 +5092,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, + FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5464,6 +5496,14 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000; + + /** + * If set, resolution of this intent may take place via an instant app not + * yet on the device if there does not yet exist an app on device to + * resolve it. + */ + public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800; + /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. @@ -7017,7 +7057,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if none was found. * * @deprecated @@ -7035,7 +7075,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, boolean) @@ -7052,7 +7092,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, byte) @@ -7069,7 +7109,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, short) @@ -7086,7 +7126,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, char) @@ -7103,7 +7143,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, int) @@ -7120,7 +7160,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, long) @@ -7137,7 +7177,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra(), + * @return the value of an item previously added with putExtra(), * or the default value if no such item is present * * @see #putExtra(String, float) @@ -7154,7 +7194,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, double) @@ -7169,7 +7209,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no String value was found. * * @see #putExtra(String, String) @@ -7183,7 +7223,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no CharSequence value was found. * * @see #putExtra(String, CharSequence) @@ -7197,7 +7237,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Parcelable value was found. * * @see #putExtra(String, Parcelable) @@ -7211,7 +7251,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Parcelable[] value was found. * * @see #putExtra(String, Parcelable[]) @@ -7225,8 +7265,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList value was found. + * @return the value of an item previously added with + * putParcelableArrayListExtra(), or null if no + * ArrayList value was found. * * @see #putParcelableArrayListExtra(String, ArrayList) */ @@ -7239,7 +7280,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Serializable value was found. * * @see #putExtra(String, Serializable) @@ -7253,8 +7294,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList value was found. + * @return the value of an item previously added with + * putIntegerArrayListExtra(), or null if no + * ArrayList value was found. * * @see #putIntegerArrayListExtra(String, ArrayList) */ @@ -7267,8 +7309,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList value was found. + * @return the value of an item previously added with + * putStringArrayListExtra(), or null if no + * ArrayList value was found. * * @see #putStringArrayListExtra(String, ArrayList) */ @@ -7281,8 +7324,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList value was found. + * @return the value of an item previously added with + * putCharSequenceArrayListExtra, or null if no + * ArrayList value was found. * * @see #putCharSequenceArrayListExtra(String, ArrayList) */ @@ -7295,7 +7339,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no boolean array value was found. * * @see #putExtra(String, boolean[]) @@ -7309,7 +7353,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no byte array value was found. * * @see #putExtra(String, byte[]) @@ -7323,7 +7367,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no short array value was found. * * @see #putExtra(String, short[]) @@ -7337,7 +7381,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no char array value was found. * * @see #putExtra(String, char[]) @@ -7351,7 +7395,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no int array value was found. * * @see #putExtra(String, int[]) @@ -7365,7 +7409,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no long array value was found. * * @see #putExtra(String, long[]) @@ -7379,7 +7423,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no float array value was found. * * @see #putExtra(String, float[]) @@ -7393,7 +7437,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no double array value was found. * * @see #putExtra(String, double[]) @@ -7407,7 +7451,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no String array value was found. * * @see #putExtra(String, String[]) @@ -7421,7 +7465,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no CharSequence array value was found. * * @see #putExtra(String, CharSequence[]) @@ -7435,7 +7479,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Bundle value was found. * * @see #putExtra(String, Bundle) @@ -7449,7 +7493,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no IBinder value was found. * * @see #putExtra(String, IBinder) @@ -7469,7 +7513,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue The default value to return in case no item is * associated with the key 'name' * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or defaultValue if none was found. * * @see #putExtra @@ -9440,7 +9484,7 @@ public class Intent implements Parcelable, Cloneable { proto.write(IntentProto.PACKAGE, mPackage); } if (comp && mComponent != null) { - proto.write(IntentProto.COMPONENT, mComponent.flattenToShortString()); + mComponent.writeToProto(proto, IntentProto.COMPONENT); } if (mSourceBounds != null) { proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString()); @@ -10013,6 +10057,25 @@ public class Intent implements Parcelable, Cloneable { } } + /** @hide */ + public boolean hasWebURI() { + if (getData() == null) { + return false; + } + final String scheme = getScheme(); + if (TextUtils.isEmpty(scheme)) { + return false; + } + return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); + } + + /** @hide */ + public boolean isBrowsableWebIntent() { + return ACTION_VIEW.equals(mAction) + && hasCategory(CATEGORY_BROWSABLE) + && hasWebURI(); + } + /** * @hide */ diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 15e119b20a2e39755c61ceeb9f7f03840cd56e23..f6697e8148a0f182d7dc63ec247a155a8f3769bd 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +34,7 @@ import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.Printer; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import com.android.internal.util.ArrayUtils; @@ -602,6 +602,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_VENDOR = 1 << 18; + /** + * Value for {@linl #privateFlags}: whether this app is pre-installed on the + * product partition of the system image. + * @hide + */ + public static final int PRIVATE_FLAG_PRODUCT = 1 << 19; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = { PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE, @@ -619,6 +626,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_OEM, PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE, PRIVATE_FLAG_PRIVILEGED, + PRIVATE_FLAG_PRODUCT, PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER, PRIVATE_FLAG_STATIC_SHARED_LIBRARY, PRIVATE_FLAG_VENDOR, @@ -1184,6 +1192,105 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { super.dumpBack(pw, prefix); } + /** {@hide} */ + public void writeToProto(ProtoOutputStream proto, long fieldId, int dumpFlags) { + long token = proto.start(fieldId); + super.writeToProto(proto, ApplicationInfoProto.PACKAGE); + proto.write(ApplicationInfoProto.PERMISSION, permission); + proto.write(ApplicationInfoProto.PROCESS_NAME, processName); + proto.write(ApplicationInfoProto.UID, uid); + proto.write(ApplicationInfoProto.FLAGS, flags); + proto.write(ApplicationInfoProto.PRIVATE_FLAGS, privateFlags); + proto.write(ApplicationInfoProto.THEME, theme); + proto.write(ApplicationInfoProto.SOURCE_DIR, sourceDir); + if (!Objects.equals(sourceDir, publicSourceDir)) { + proto.write(ApplicationInfoProto.PUBLIC_SOURCE_DIR, publicSourceDir); + } + if (!ArrayUtils.isEmpty(splitSourceDirs)) { + for (String dir : splitSourceDirs) { + proto.write(ApplicationInfoProto.SPLIT_SOURCE_DIRS, dir); + } + } + if (!ArrayUtils.isEmpty(splitPublicSourceDirs) + && !Arrays.equals(splitSourceDirs, splitPublicSourceDirs)) { + for (String dir : splitPublicSourceDirs) { + proto.write(ApplicationInfoProto.SPLIT_PUBLIC_SOURCE_DIRS, dir); + } + } + if (resourceDirs != null) { + for (String dir : resourceDirs) { + proto.write(ApplicationInfoProto.RESOURCE_DIRS, dir); + } + } + proto.write(ApplicationInfoProto.DATA_DIR, dataDir); + proto.write(ApplicationInfoProto.CLASS_LOADER_NAME, classLoaderName); + if (!ArrayUtils.isEmpty(splitClassLoaderNames)) { + for (String name : splitClassLoaderNames) { + proto.write(ApplicationInfoProto.SPLIT_CLASS_LOADER_NAMES, name); + } + } + + long versionToken = proto.start(ApplicationInfoProto.VERSION); + proto.write(ApplicationInfoProto.Version.ENABLED, enabled); + proto.write(ApplicationInfoProto.Version.MIN_SDK_VERSION, minSdkVersion); + proto.write(ApplicationInfoProto.Version.TARGET_SDK_VERSION, targetSdkVersion); + proto.write(ApplicationInfoProto.Version.VERSION_CODE, versionCode); + proto.write(ApplicationInfoProto.Version.TARGET_SANDBOX_VERSION, targetSandboxVersion); + proto.end(versionToken); + + if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) { + long detailToken = proto.start(ApplicationInfoProto.DETAIL); + if (className != null) { + proto.write(ApplicationInfoProto.Detail.CLASS_NAME, className); + } + proto.write(ApplicationInfoProto.Detail.TASK_AFFINITY, taskAffinity); + proto.write(ApplicationInfoProto.Detail.REQUIRES_SMALLEST_WIDTH_DP, + requiresSmallestWidthDp); + proto.write(ApplicationInfoProto.Detail.COMPATIBLE_WIDTH_LIMIT_DP, + compatibleWidthLimitDp); + proto.write(ApplicationInfoProto.Detail.LARGEST_WIDTH_LIMIT_DP, + largestWidthLimitDp); + if (seInfo != null) { + proto.write(ApplicationInfoProto.Detail.SEINFO, seInfo); + proto.write(ApplicationInfoProto.Detail.SEINFO_USER, seInfoUser); + } + proto.write(ApplicationInfoProto.Detail.DEVICE_PROTECTED_DATA_DIR, + deviceProtectedDataDir); + proto.write(ApplicationInfoProto.Detail.CREDENTIAL_PROTECTED_DATA_DIR, + credentialProtectedDataDir); + if (sharedLibraryFiles != null) { + for (String f : sharedLibraryFiles) { + proto.write(ApplicationInfoProto.Detail.SHARED_LIBRARY_FILES, f); + } + } + if (manageSpaceActivityName != null) { + proto.write(ApplicationInfoProto.Detail.MANAGE_SPACE_ACTIVITY_NAME, + manageSpaceActivityName); + } + if (descriptionRes != 0) { + proto.write(ApplicationInfoProto.Detail.DESCRIPTION_RES, descriptionRes); + } + if (uiOptions != 0) { + proto.write(ApplicationInfoProto.Detail.UI_OPTIONS, uiOptions); + } + proto.write(ApplicationInfoProto.Detail.SUPPORTS_RTL, hasRtlSupport()); + if (fullBackupContent > 0) { + proto.write(ApplicationInfoProto.Detail.CONTENT, "@xml/" + fullBackupContent); + } else { + proto.write(ApplicationInfoProto.Detail.IS_FULL_BACKUP, fullBackupContent == 0); + } + if (networkSecurityConfigRes != 0) { + proto.write(ApplicationInfoProto.Detail.NETWORK_SECURITY_CONFIG_RES, + networkSecurityConfigRes); + } + if (category != CATEGORY_UNDEFINED) { + proto.write(ApplicationInfoProto.Detail.CATEGORY, category); + } + proto.end(detailToken); + } + proto.end(token); + } + /** * @return true if "supportsRtl" has been set to true in the AndroidManifest * @hide @@ -1489,6 +1596,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } + /** + * @hide + */ + public boolean isAllowedToUseHiddenApi() { + return isSystemApp(); + } + /** * @hide */ @@ -1594,8 +1708,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** @hide */ - public boolean isTargetingDeprecatedSdkVersion() { - return targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT; + public boolean isProduct() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0; } /** diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java index 6bdcefbe974e5f46373fab018f488ff94bfe5385..202df50dda6f88c852af97dfff7ceb3895dcf4db 100644 --- a/core/java/android/content/pm/AuxiliaryResolveInfo.java +++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java @@ -21,6 +21,10 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; + +import java.util.Collections; +import java.util.List; /** * Auxiliary application resolution response. @@ -31,56 +35,95 @@ import android.content.IntentFilter; * hasn't been installed. * @hide */ -public final class AuxiliaryResolveInfo extends IntentFilter { - /** Resolved information returned from the external instant resolver */ - public final InstantAppResolveInfo resolveInfo; - /** The resolved package. Copied from {@link #resolveInfo}. */ - public final String packageName; +public final class AuxiliaryResolveInfo { /** The activity to launch if there's an installation failure. */ public final ComponentName installFailureActivity; - /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ - public final String splitName; /** Whether or not instant resolution needs the second phase */ public final boolean needsPhaseTwo; /** Opaque token to track the instant application resolution */ public final String token; - /** The version code of the package */ - public final long versionCode; /** An intent to start upon failure to install */ public final Intent failureIntent; + /** The matching filters for this resolve info. */ + public final List filters; /** Create a response for installing an instant application. */ - public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo, - @NonNull IntentFilter orig, - @Nullable String splitName, - @NonNull String token, + public AuxiliaryResolveInfo(@NonNull String token, boolean needsPhase2, - @Nullable Intent failureIntent) { - super(orig); - this.resolveInfo = resolveInfo; - this.packageName = resolveInfo.getPackageName(); - this.splitName = splitName; + @Nullable Intent failureIntent, + @Nullable List filters) { this.token = token; this.needsPhaseTwo = needsPhase2; - this.versionCode = resolveInfo.getVersionCode(); this.failureIntent = failureIntent; + this.filters = filters; this.installFailureActivity = null; } /** Create a response for installing a split on demand. */ - public AuxiliaryResolveInfo(@NonNull String packageName, - @Nullable String splitName, - @Nullable ComponentName failureActivity, - long versionCode, - @Nullable Intent failureIntent) { + public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, + @Nullable Intent failureIntent, + @Nullable List filters) { super(); - this.packageName = packageName; this.installFailureActivity = failureActivity; - this.splitName = splitName; - this.versionCode = versionCode; - this.resolveInfo = null; + this.filters = filters; this.token = null; this.needsPhaseTwo = false; this.failureIntent = failureIntent; } + + /** Create a response for installing a split on demand. */ + public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, + String packageName, long versionCode, String splitName) { + this(failureActivity, null, Collections.singletonList( + new AuxiliaryResolveInfo.AuxiliaryFilter(packageName, versionCode, splitName))); + } + + /** @hide */ + public static final class AuxiliaryFilter extends IntentFilter { + /** Resolved information returned from the external instant resolver */ + public final InstantAppResolveInfo resolveInfo; + /** The resolved package. Copied from {@link #resolveInfo}. */ + public final String packageName; + /** The version code of the package */ + public final long versionCode; + /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ + public final String splitName; + /** The extras to pass on to the installer for this filter. */ + public final Bundle extras; + + public AuxiliaryFilter(IntentFilter orig, InstantAppResolveInfo resolveInfo, + String splitName, Bundle extras) { + super(orig); + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.versionCode = resolveInfo.getLongVersionCode(); + this.splitName = splitName; + this.extras = extras; + } + + public AuxiliaryFilter(InstantAppResolveInfo resolveInfo, + String splitName, Bundle extras) { + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.versionCode = resolveInfo.getLongVersionCode(); + this.splitName = splitName; + this.extras = extras; + } + + public AuxiliaryFilter(String packageName, long versionCode, String splitName) { + this.resolveInfo = null; + this.packageName = packageName; + this.versionCode = versionCode; + this.splitName = splitName; + this.extras = null; + } + + @Override + public String toString() { + return "AuxiliaryFilter{" + + "packageName='" + packageName + '\'' + + ", versionCode=" + versionCode + + ", splitName='" + splitName + '\'' + '}'; + } + } } \ No newline at end of file diff --git a/core/java/android/content/pm/crossprofile/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java similarity index 92% rename from core/java/android/content/pm/crossprofile/CrossProfileApps.java rename to core/java/android/content/pm/CrossProfileApps.java index 414c13894f80f4482512d5935a6c6eccea63cf1b..7d5d6090788c8694602abe38d294757f25e8d075 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 similarity index 96% rename from core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl rename to core/java/android/content/pm/ICrossProfileApps.aidl index 227f91f5d3e19a4d2129e6b6bdae7b5cbe26c42d..e79deb96838b3d5fee1799cdaeb4132e20d8960a 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/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index cce6b848e6aea1c72284eb124df7a062bc73d22d..379bff49c9116a979baa3614798844fcbf1d440a 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -656,4 +656,8 @@ interface IPackageManager { void setHarmfulAppWarning(String packageName, CharSequence warning, int userId); CharSequence getHarmfulAppWarning(String packageName, int userId); + + boolean hasSigningCertificate(String packageName, in byte[] signingCertificate, int flags); + + boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags); } diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 19cb9323ba935b3f136279ad089968d2dbbfb76d..112c5dae6731f4bae2f7c2e97f78df7ae2ca70fa 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Intent; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -26,11 +27,35 @@ import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; /** - * Information about an instant application. + * Describes an externally resolvable instant application. There are three states that this class + * can represent:

    + *

      + *
    • + * The first, usable only for non http/s intents, implies that the resolver cannot + * immediately resolve this intent and would prefer that resolution be deferred to the + * instant app installer. Represent this state with {@link #InstantAppResolveInfo(Bundle)}. + * If the {@link android.content.Intent} has the scheme set to http/s and a set of digest + * prefixes were passed into one of the resolve methods in + * {@link android.app.InstantAppResolverService}, this state cannot be used. + *
    • + *
    • + * The second represents a partial match and is constructed with any of the other + * constructors. By setting one or more of the {@link Nullable}arguments to null, you + * communicate to the resolver in response to + * {@link android.app.InstantAppResolverService#onGetInstantAppResolveInfo(Intent, int[], + * String, InstantAppResolverService.InstantAppResolutionCallback)} + * that you need a 2nd round of resolution to complete the request. + *
    • + *
    • + * The third represents a complete match and is constructed with all @Nullable parameters + * populated. + *
    • + *
    * @hide */ @SystemApi @@ -38,6 +63,8 @@ public final class InstantAppResolveInfo implements Parcelable { /** Algorithm that will be used to generate the domain digest */ private static final String SHA_ALGORITHM = "SHA-256"; + private static final byte[] EMPTY_DIGEST = new byte[0]; + private final InstantAppDigest mDigest; private final String mPackageName; /** The filters used to match domain */ @@ -46,15 +73,30 @@ public final class InstantAppResolveInfo implements Parcelable { private final long mVersionCode; /** Data about the app that should be passed along to the Instant App installer on resolve */ private final Bundle mExtras; + /** + * A flag that indicates that the resolver is aware that an app may match, but would prefer + * that the installer get the sanitized intent to decide. This should not be used for + * resolutions that include a host and will be ignored in such cases. + */ + private final boolean mShouldLetInstallerDecide; + /** Constructor for intent-based InstantApp resolution results. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, int versionCode) { this(digest, packageName, filters, (long) versionCode, null /* extras */); } + /** Constructor for intent-based InstantApp resolution results with extras. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, long versionCode, @Nullable Bundle extras) { + this(digest, packageName, filters, versionCode, extras, false); + } + + /** Constructor for intent-based InstantApp resolution results with extras. */ + private InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, + @Nullable List filters, long versionCode, + @Nullable Bundle extras, boolean shouldLetInstallerDecide) { // validate arguments if ((packageName == null && (filters != null && filters.size() != 0)) || (packageName != null && (filters == null || filters.size() == 0))) { @@ -62,7 +104,7 @@ public final class InstantAppResolveInfo implements Parcelable { } mDigest = digest; if (filters != null) { - mFilters = new ArrayList(filters.size()); + mFilters = new ArrayList<>(filters.size()); mFilters.addAll(filters); } else { mFilters = null; @@ -70,25 +112,48 @@ public final class InstantAppResolveInfo implements Parcelable { mPackageName = packageName; mVersionCode = versionCode; mExtras = extras; + mShouldLetInstallerDecide = shouldLetInstallerDecide; } + /** Constructor for intent-based InstantApp resolution results by hostname. */ public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName, @Nullable List filters) { this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/, null /* extras */); } + /** + * Constructor that creates a "let the installer decide" response with optional included + * extras. + */ + public InstantAppResolveInfo(@Nullable Bundle extras) { + this(InstantAppDigest.UNDEFINED, null, null, -1, extras, true); + } + InstantAppResolveInfo(Parcel in) { - mDigest = in.readParcelable(null /*loader*/); - mPackageName = in.readString(); - mFilters = new ArrayList(); - in.readList(mFilters, null /*loader*/); - mVersionCode = in.readLong(); + mShouldLetInstallerDecide = in.readBoolean(); mExtras = in.readBundle(); + if (mShouldLetInstallerDecide) { + mDigest = InstantAppDigest.UNDEFINED; + mPackageName = null; + mFilters = Collections.emptyList(); + mVersionCode = -1; + } else { + mDigest = in.readParcelable(null /*loader*/); + mPackageName = in.readString(); + mFilters = new ArrayList<>(); + in.readList(mFilters, null /*loader*/); + mVersionCode = in.readLong(); + } + } + + /** Returns true if the installer should be notified that it should query for packages. */ + public boolean shouldLetInstallerDecide() { + return mShouldLetInstallerDecide; } public byte[] getDigestBytes() { - return mDigest.getDigestBytes()[0]; + return mDigest.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST; } public int getDigestPrefix() { @@ -127,11 +192,15 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + out.writeBoolean(mShouldLetInstallerDecide); + out.writeBundle(mExtras); + if (mShouldLetInstallerDecide) { + return; + } out.writeParcelable(mDigest, flags); out.writeString(mPackageName); out.writeList(mFilters); out.writeLong(mVersionCode); - out.writeBundle(mExtras); } public static final Parcelable.Creator CREATOR @@ -159,7 +228,9 @@ public final class InstantAppResolveInfo implements Parcelable { @SystemApi public static final class InstantAppDigest implements Parcelable { private static final int DIGEST_MASK = 0xfffff000; - private static final int DIGEST_PREFIX_COUNT = 5; + + public static final InstantAppDigest UNDEFINED = + new InstantAppDigest(new byte[][]{}, new int[]{}); /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; /** The first 4 bytes of the domain hashes */ @@ -186,6 +257,11 @@ public final class InstantAppResolveInfo implements Parcelable { } } + private InstantAppDigest(byte[][] digestBytes, int[] prefix) { + this.mDigestPrefix = prefix; + this.mDigestBytes = digestBytes; + } + private static byte[][] generateDigest(String hostName, int maxDigests) { ArrayList digests = new ArrayList<>(); try { diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..81e4105febee218b9b0cb44e9f729ecedd3e1f7f --- /dev/null +++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java @@ -0,0 +1,48 @@ +/* + * 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; + +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + +import android.content.pm.PackageParser.Package; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is + * included by default. + * + *

    This is separated out so that it can be conditionally included at build time depending on + * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at + * build time, and remove org.apache.http.legacy from the bootclasspath pass + * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included + * and the + * + * @hide + */ +@VisibleForTesting +public class OrgApacheHttpLegacyUpdater extends PackageSharedLibraryUpdater { + + @Override + public void updatePackage(Package pkg) { + // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library + // to be accessible so this maintains backward compatibility by adding the + // org.apache.http.legacy library to those packages. + if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) { + prefixRequiredLibrary(pkg, ORG_APACHE_HTTP_LEGACY); + } + } +} diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java index 8014c94d198df31429669d3d14ea633c2f8ddb1b..67e4cd56136101cde3bfc9569eb2dca2b35eabc9 100644 --- a/core/java/android/content/pm/PackageBackwardCompatibility.java +++ b/core/java/android/content/pm/PackageBackwardCompatibility.java @@ -16,15 +16,18 @@ package android.content.pm; -import android.annotation.NonNull; -import android.annotation.Nullable; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_MOCK; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + import android.content.pm.PackageParser.Package; -import android.os.Build; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; /** * Modifies {@link Package} in order to maintain backwards compatibility. @@ -32,13 +35,83 @@ import java.util.ArrayList; * @hide */ @VisibleForTesting -public class PackageBackwardCompatibility { +public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { + + private static final String TAG = PackageBackwardCompatibility.class.getSimpleName(); + + private static final PackageBackwardCompatibility INSTANCE; - private static final String ANDROID_TEST_MOCK = "android.test.mock"; + static { + final List packageUpdaters = new ArrayList<>(); - private static final String ANDROID_TEST_RUNNER = "android.test.runner"; + // Attempt to load and add the optional updater that will only be available when + // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that + // will remove any references to org.apache.http.library from the package so that it does + // not try and load the library when it is on the bootclasspath. + boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters, + "android.content.pm.OrgApacheHttpLegacyUpdater", + RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new); + + packageUpdaters.add(new AndroidTestRunnerSplitUpdater()); + + PackageSharedLibraryUpdater[] updaterArray = packageUpdaters + .toArray(new PackageSharedLibraryUpdater[0]); + INSTANCE = new PackageBackwardCompatibility( + bootClassPathContainsOAHL, updaterArray); + } + + /** + * Add an optional {@link PackageSharedLibraryUpdater} instance to the list, if it could not be + * found then add a default instance instead. + * + * @param packageUpdaters the list to update. + * @param className the name of the optional class. + * @param defaultUpdater the supplier of the default instance. + * @return true if the optional updater was added false otherwise. + */ + private static boolean addOptionalUpdater(List packageUpdaters, + String className, Supplier defaultUpdater) { + Class clazz; + try { + clazz = (PackageBackwardCompatibility.class.getClassLoader() + .loadClass(className) + .asSubclass(PackageSharedLibraryUpdater.class)); + Log.i(TAG, "Loaded " + className); + } catch (ClassNotFoundException e) { + Log.i(TAG, "Could not find " + className + ", ignoring"); + clazz = null; + } - private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy"; + boolean usedOptional = false; + PackageSharedLibraryUpdater updater; + if (clazz == null) { + updater = defaultUpdater.get(); + } else { + try { + updater = clazz.getConstructor().newInstance(); + usedOptional = true; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Could not create instance of " + className, e); + } + } + packageUpdaters.add(updater); + return usedOptional; + } + + @VisibleForTesting + public static PackageSharedLibraryUpdater getInstance() { + return INSTANCE; + } + + private final boolean mBootClassPathContainsOAHL; + + private final PackageSharedLibraryUpdater[] mPackageUpdaters; + + public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL, + PackageSharedLibraryUpdater[] packageUpdaters) { + this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL; + this.mPackageUpdaters = packageUpdaters; + } /** * Modify the shared libraries in the supplied {@link Package} to maintain backwards @@ -48,52 +121,56 @@ public class PackageBackwardCompatibility { */ @VisibleForTesting public static void modifySharedLibraries(Package pkg) { - ArrayList usesLibraries = pkg.usesLibraries; - ArrayList usesOptionalLibraries = pkg.usesOptionalLibraries; - - // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library - // to be accessible so this maintains backward compatibility by adding the - // org.apache.http.legacy library to those packages. - if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) { - boolean apacheHttpLegacyPresent = isLibraryPresent( - usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY); - if (!apacheHttpLegacyPresent) { - usesLibraries = prefix(usesLibraries, APACHE_HTTP_LEGACY); - } - } + INSTANCE.updatePackage(pkg); + } - // android.test.runner has a dependency on android.test.mock so if android.test.runner - // is present but android.test.mock is not then add android.test.mock. - boolean androidTestMockPresent = isLibraryPresent( - usesLibraries, usesOptionalLibraries, ANDROID_TEST_MOCK); - if (ArrayUtils.contains(usesLibraries, ANDROID_TEST_RUNNER) && !androidTestMockPresent) { - usesLibraries.add(ANDROID_TEST_MOCK); + @Override + public void updatePackage(Package pkg) { + for (PackageSharedLibraryUpdater packageUpdater : mPackageUpdaters) { + packageUpdater.updatePackage(pkg); } - if (ArrayUtils.contains(usesOptionalLibraries, ANDROID_TEST_RUNNER) - && !androidTestMockPresent) { - usesOptionalLibraries.add(ANDROID_TEST_MOCK); - } - - pkg.usesLibraries = usesLibraries; - pkg.usesOptionalLibraries = usesOptionalLibraries; } - private static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(Package pkg) { - int targetSdkVersion = pkg.applicationInfo.targetSdkVersion; - return targetSdkVersion <= Build.VERSION_CODES.O_MR1; + /** + * True if the org.apache.http.legacy is on the bootclasspath, false otherwise. + */ + @VisibleForTesting + public static boolean bootClassPathContainsOAHL() { + return INSTANCE.mBootClassPathContainsOAHL; } - private static boolean isLibraryPresent(ArrayList usesLibraries, - ArrayList usesOptionalLibraries, String apacheHttpLegacy) { - return ArrayUtils.contains(usesLibraries, apacheHttpLegacy) - || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy); + /** + * Add android.test.mock dependency for any APK that depends on android.test.runner. + * + *

    This is needed to maintain backwards compatibility as in previous versions of Android the + * android.test.runner library included the classes from android.test.mock which have since + * been split out into a separate library. + * + * @hide + */ + @VisibleForTesting + public static class AndroidTestRunnerSplitUpdater extends PackageSharedLibraryUpdater { + + @Override + public void updatePackage(Package pkg) { + // android.test.runner has a dependency on android.test.mock so if android.test.runner + // is present but android.test.mock is not then add android.test.mock. + prefixImplicitDependency(pkg, ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK); + } } - private static @NonNull ArrayList prefix(@Nullable ArrayList cur, T val) { - if (cur == null) { - cur = new ArrayList<>(); + /** + * Remove any usages of org.apache.http.legacy from the shared library as the library is on the + * bootclasspath. + */ + @VisibleForTesting + public static class RemoveUnnecessaryOrgApacheHttpLegacyLibrary + extends PackageSharedLibraryUpdater { + + @Override + public void updatePackage(Package pkg) { + removeLibrary(pkg, ORG_APACHE_HTTP_LEGACY); } - cur.add(0, val); - return cur; + } } diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 5a91e94781d7ec0120408306f3295b68b4f63749..09a46b8acf4ba211e7b64e2019e8f5524f18dea1 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -16,14 +16,10 @@ package android.content.pm; -import android.annotation.IntDef; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Overall information about the contents of a package. This corresponds * to all of the information collected from AndroidManifest.xml. @@ -246,9 +242,44 @@ public class PackageInfo implements Parcelable { * equivalent to being signed with certificates B and A. This means that * in case multiple signatures are reported you cannot assume the one at * the first position to be the same across updates. + * + * Deprecated This has been replaced by the + * {@link PackageInfo#signingCertificateHistory} field, which takes into + * account signing certificate rotation. For backwards compatibility in + * the event of signing certificate rotation, this will return the oldest + * reported signing certificate, so that an application will appear to + * callers as though no rotation occurred. + * + * @deprecated use {@code signingCertificateHistory} instead */ + @Deprecated public Signature[] signatures; - + + /** + * Array of all signatures arrays read from the package file, potentially + * including past signing certificates no longer used after signing + * certificate rotation. Though signing certificate rotation is only + * available for apps with a single signing certificate, this provides an + * array of arrays so that packages signed with multiple signing + * certificates can still return all signers. This is only filled in if + * the flag {@link PackageManager#GET_SIGNING_CERTIFICATES} was set. + * + * A package must be singed with at least one certificate, which is at + * position zero in the array. An application may be signed by multiple + * certificates, which would be in the array at position zero in an + * indeterminate order. A package may also have a history of certificates + * due to signing certificate rotation. In this case, the array will be + * populated by a series of single-entry arrays corresponding to a signing + * certificate of the package. + * + * Note: Signature ordering is not guaranteed to be + * stable which means that a package signed with certificates A and B is + * equivalent to being signed with certificates B and A. This means that + * in case multiple signatures are reported you cannot assume the one at + * the first position will be the same across updates. + */ + public Signature[][] signingCertificateHistory; + /** * Application specified preferred configuration * {@link android.R.styleable#AndroidManifestUsesConfiguration @@ -335,28 +366,9 @@ public class PackageInfo implements Parcelable { public int overlayPriority; /** - * Flag for use with {@link #mOverlayFlags}. Marks the overlay as static, meaning it cannot - * be enabled/disabled at runtime. - */ - static final int FLAG_OVERLAY_STATIC = 1 << 1; - - /** - * Flag for use with {@link #mOverlayFlags}. Marks the overlay as trusted (not 3rd party). - */ - static final int FLAG_OVERLAY_TRUSTED = 1 << 2; - - @IntDef(flag = true, prefix = "FLAG_OVERLAY_", value = { - FLAG_OVERLAY_STATIC, - FLAG_OVERLAY_TRUSTED - }) - @Retention(RetentionPolicy.SOURCE) - @interface OverlayFlags {} - - /** - * Modifiers that affect the state of this overlay. See {@link #FLAG_OVERLAY_STATIC}, - * {@link #FLAG_OVERLAY_TRUSTED}. + * Whether the overlay is static, meaning it cannot be enabled/disabled at runtime. */ - @OverlayFlags int mOverlayFlags; + boolean mOverlayIsStatic; /** * The user-visible SDK version (ex. 26) of the framework against which the application claims @@ -389,7 +401,7 @@ public class PackageInfo implements Parcelable { * @hide */ public boolean isOverlayPackage() { - return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_TRUSTED) != 0; + return overlayTarget != null; } /** @@ -398,7 +410,7 @@ public class PackageInfo implements Parcelable { * @hide */ public boolean isStaticOverlayPackage() { - return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_STATIC) != 0; + return overlayTarget != null && mOverlayIsStatic; } @Override @@ -453,7 +465,7 @@ public class PackageInfo implements Parcelable { dest.writeString(requiredAccountType); dest.writeString(overlayTarget); dest.writeInt(overlayPriority); - dest.writeInt(mOverlayFlags); + dest.writeBoolean(mOverlayIsStatic); dest.writeInt(compileSdkVersion); dest.writeString(compileSdkVersionCodename); } @@ -508,7 +520,7 @@ public class PackageInfo implements Parcelable { requiredAccountType = source.readString(); overlayTarget = source.readString(); overlayPriority = source.readInt(); - mOverlayFlags = source.readInt(); + mOverlayIsStatic = source.readBoolean(); compileSdkVersion = source.readInt(); compileSdkVersionCodename = source.readString(); diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 11830c294116d4acacc3e85d0c2fcc8aa6dc7b2f..2c0c6ad0723e0aed8d38f518d43d94a909328f8b 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -19,7 +19,6 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.res.XmlResourceParser; - import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcel; @@ -28,6 +27,8 @@ import android.text.Html; import android.text.TextPaint; import android.text.TextUtils; import android.util.Printer; +import android.util.proto.ProtoOutputStream; + import java.text.Collator; import java.util.Comparator; @@ -386,6 +387,24 @@ public class PackageItemInfo { dest.writeInt(showUserIcon); } + /** + * @hide + */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + if (name != null) { + proto.write(PackageItemInfoProto.NAME, name); + } + proto.write(PackageItemInfoProto.PACKAGE_NAME, packageName); + if (labelRes != 0 || nonLocalizedLabel != null || icon != 0 || banner != 0) { + proto.write(PackageItemInfoProto.LABEL_RES, labelRes); + proto.write(PackageItemInfoProto.NON_LOCALIZED_LABEL, nonLocalizedLabel.toString()); + proto.write(PackageItemInfoProto.ICON, icon); + proto.write(PackageItemInfoProto.BANNER, banner); + } + proto.end(token); + } + protected PackageItemInfo(Parcel source) { name = source.readString(); packageName = source.readString(); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index deb8dfb1109454d7818e2c90f5d1a9888d95d7c8..0b4231fbc9163e5ccf255d749b5066fd1ff4e2cf 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -133,6 +133,7 @@ public abstract class PackageManager { GET_SERVICES, GET_SHARED_LIBRARY_FILES, GET_SIGNATURES, + GET_SIGNING_CERTIFICATES, GET_URI_PERMISSION_PATTERNS, MATCH_UNINSTALLED_PACKAGES, MATCH_DISABLED_COMPONENTS, @@ -272,7 +273,10 @@ public abstract class PackageManager { /** * {@link PackageInfo} flag: return information about the * signatures included in the package. + * + * @deprecated use {@code GET_SIGNING_CERTIFICATES} instead */ + @Deprecated public static final int GET_SIGNATURES = 0x00000040; /** @@ -487,6 +491,14 @@ public abstract class PackageManager { */ public static final int MATCH_STATIC_SHARED_LIBRARIES = 0x04000000; + /** + * {@link PackageInfo} flag: return the signing certificates associated with + * this package. Each entry is a signing certificate that the package + * has proven it is authorized to use, usually a past signing certificate from + * which it has rotated. + */ + public static final int GET_SIGNING_CERTIFICATES = 0x08000000; + /** * Internal flag used to indicate that a system component has done their * homework and verified that they correctly handle packages and components @@ -1297,6 +1309,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, @@ -1744,6 +1765,16 @@ public abstract class PackageManager { public static final String FEATURE_CAMERA_CAPABILITY_RAW = "android.hardware.camera.capability.raw"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one + * of the cameras on the device supports the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING + * MOTION_TRACKING} capability level. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_AR = + "android.hardware.camera.ar"; + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device is capable of communicating with @@ -1919,6 +1950,14 @@ public abstract class PackageManager { *

  • Minor version number in bits 21-12
  • *
  • Patch version number in bits 11-0
  • * + * A version of 1.1.0 or higher also indicates: + *
      + *
    • The {@code VK_ANDROID_external_memory_android_hardware_buffer} extension is + * supported.
    • + *
    • {@code SYNC_FD} external semaphore and fence handles are supported.
    • + *
    • {@code VkPhysicalDeviceSamplerYcbcrConversionFeatures::samplerYcbcrConversion} is + * supported.
    • + *
    */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version"; @@ -2535,31 +2574,22 @@ public abstract class PackageManager { * Devices declaring this feature must include an application implementing a * {@link android.service.vr.VrListenerService} that can be targeted by VR applications via * {@link android.app.Activity#setVrModeEnabled}. + * @deprecated use {@link #FEATURE_VR_MODE_HIGH_PERFORMANCE} instead. */ + @Deprecated @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VR_MODE = "android.software.vr.mode"; /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: - * The device implements {@link #FEATURE_VR_MODE} but additionally meets extra CDD requirements - * to provide a high-quality VR experience. In general, devices declaring this feature will - * additionally: - *
      - *
    • Deliver consistent performance at a high framerate over an extended period of time - * for typical VR application CPU/GPU workloads with a minimal number of frame drops for VR - * applications that have called - * {@link android.view.Window#setSustainedPerformanceMode}.
    • - *
    • Implement {@link #FEATURE_HIFI_SENSORS} and have a low sensor latency.
    • - *
    • Include optimizations to lower display persistence while running VR applications.
    • - *
    • Implement an optimized render path to minimize latency to draw to the device's main - * display.
    • - *
    • Include the following EGL extensions: EGL_ANDROID_create_native_client_buffer, - * EGL_ANDROID_front_buffer_auto_refresh, EGL_EXT_protected_content, - * EGL_KHR_mutable_render_buffer, EGL_KHR_reusable_sync, and EGL_KHR_wait_sync.
    • - *
    • Provide at least one CPU core that is reserved for use solely by the top, foreground - * VR application process for critical render threads while such an application is - * running.
    • - *
    + * The device implements an optimized mode for virtual reality (VR) applications that handles + * stereoscopic rendering of notifications, disables most monocular system UI components + * while a VR application has user focus and meets extra CDD requirements to provide a + * high-quality VR experience. + * Devices declaring this feature must include an application implementing a + * {@link android.service.vr.VrListenerService} that can be targeted by VR applications via + * {@link android.app.Activity#setVrModeEnabled}. + * and must meet CDD requirements to provide a high-quality VR experience. */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE @@ -2581,6 +2611,14 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device has a StrongBox hardware-backed Keystore. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_STRONGBOX_KEYSTORE = + "android.hardware.strongbox_keystore"; + /** * Action to external storage service to clean out removed apps. * @hide @@ -3043,6 +3081,21 @@ public abstract class PackageManager { */ public abstract @Nullable Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName); + /** + * Return a "good" intent to launch a front-door Car activity in a + * package, for use for example to implement an "open" button when browsing + * through packages. The current implementation will look for a main + * activity in the category {@link Intent#CATEGORY_CAR_LAUNCHER}, or + * return null if no main car activities are found. + * + * @param packageName The name of the package to inspect. + * @return Returns either a fully-qualified Intent that can be used to launch + * the main Car activity in the package, or null if the package + * does not contain such an activity. + * @hide + */ + public abstract @Nullable Intent getCarLaunchIntentForPackage(@NonNull String packageName); + /** * Return an array of all of the POSIX secondary group IDs that have been * assigned to the given package. @@ -3766,7 +3819,7 @@ public abstract class PackageManager { public abstract int getInstantAppCookieMaxBytes(); /** - * @deprecated + * deprecated * @hide */ public abstract int getInstantAppCookieMaxSize(); @@ -4713,7 +4766,7 @@ public abstract class PackageManager { PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); if ((flags & GET_SIGNATURES) != 0) { - PackageParser.collectCertificates(pkg, 0); + PackageParser.collectCertificates(pkg, false /* skipVerify */); } PackageUserState state = new PackageUserState(); return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); @@ -5627,6 +5680,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); } } @@ -5671,6 +5726,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; @@ -5896,4 +5952,60 @@ public abstract class PackageManager { public CharSequence getHarmfulAppWarning(@NonNull String packageName) { throw new UnsupportedOperationException("getHarmfulAppWarning not implemented in subclass"); } + + /** @hide */ + @IntDef(prefix = { "CERT_INPUT_" }, value = { + CERT_INPUT_RAW_X509, + CERT_INPUT_SHA256 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CertificateInputType {} + + /** + * Certificate input bytes: the input bytes represent an encoded X.509 Certificate which could + * be generated using an {@code CertificateFactory} + */ + public static final int CERT_INPUT_RAW_X509 = 0; + + /** + * Certificate input bytes: the input bytes represent the SHA256 output of an encoded X.509 + * Certificate. + */ + public static final int CERT_INPUT_SHA256 = 1; + + /** + * Searches the set of signing certificates by which the given package has proven to have been + * signed. This should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES} + * since it takes into account the possibility of signing certificate rotation, except in the + * case of packages that are signed by multiple certificates, for which signing certificate + * rotation is not supported. + * + * @param packageName package whose signing certificates to check + * @param certificate signing certificate for which to search + * @param type representation of the {@code certificate} + * @return true if this package was or is signed by exactly the certificate {@code certificate} + */ + public boolean hasSigningCertificate( + String packageName, byte[] certificate, @CertificateInputType int type) { + throw new UnsupportedOperationException( + "hasSigningCertificate not implemented in subclass"); + } + + /** + * Searches the set of signing certificates by which the given uid has proven to have been + * signed. This should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES} + * since it takes into account the possibility of signing certificate rotation, except in the + * case of packages that are signed by multiple certificates, for which signing certificate + * rotation is not supported. + * + * @param uid package whose signing certificates to check + * @param certificate signing certificate for which to search + * @param type representation of the {@code certificate} + * @return true if this package was or is signed by exactly the certificate {@code certificate} + */ + public boolean hasSigningCertificate( + int uid, byte[] certificate, @CertificateInputType int type) { + throw new UnsupportedOperationException( + "hasSigningCertificate not implemented in subclass"); + } } diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 2c45b8d8b30e1eee428bbd0835e378a50b5a7351..6f093ba8d005c2d60a06d81a1aba672b9ab546ff 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 a18f22e1184baad19bb0f9a600677a37e9a4d01c..3d26af1f15bba8f8ac5f295d61553bf275682ee3 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -239,6 +239,9 @@ public class PackageParser { SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED); } + /** @hide */ + public static final String APK_FILE_EXTENSION = ".apk"; + /** @hide */ public static class NewPermissionInfo { public final String name; @@ -613,7 +616,7 @@ public class PackageParser { } public static boolean isApkPath(String path) { - return path.endsWith(".apk"); + return path.endsWith(APK_FILE_EXTENSION); } /** @@ -676,15 +679,7 @@ public class PackageParser { pi.requiredAccountType = p.mRequiredAccountType; pi.overlayTarget = p.mOverlayTarget; pi.overlayPriority = p.mOverlayPriority; - - if (p.mIsStaticOverlay) { - pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_STATIC; - } - - if (p.mTrustedOverlay) { - pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_TRUSTED; - } - + pi.mOverlayIsStatic = p.mOverlayIsStatic; pi.compileSdkVersion = p.mCompileSdkVersion; pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename; pi.firstInstallTime = firstInstallTime; @@ -798,13 +793,40 @@ public class PackageParser { } } } + // deprecated method of getting signing certificates if ((flags&PackageManager.GET_SIGNATURES) != 0) { - if (p.mSigningDetails.hasSignatures()) { + if (p.mSigningDetails.hasPastSigningCertificates()) { + // Package has included signing certificate rotation information. Return the oldest + // cert so that programmatic checks keep working even if unaware of key rotation. + pi.signatures = new Signature[1]; + pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0]; + } else if (p.mSigningDetails.hasSignatures()) { + // otherwise keep old behavior int numberOfSigs = p.mSigningDetails.signatures.length; pi.signatures = new Signature[numberOfSigs]; System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs); } } + + // replacement for GET_SIGNATURES + if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) { + if (p.mSigningDetails.hasPastSigningCertificates()) { + // Package has included signing certificate rotation information. Convert each + // entry to an array + int numberOfSigs = p.mSigningDetails.pastSigningCertificates.length; + pi.signingCertificateHistory = new Signature[numberOfSigs][]; + for (int i = 0; i < numberOfSigs; i++) { + pi.signingCertificateHistory[i] = + new Signature[] { p.mSigningDetails.pastSigningCertificates[i] }; + } + } else if (p.mSigningDetails.hasSignatures()) { + // otherwise keep old behavior + int numberOfSigs = p.mSigningDetails.signatures.length; + pi.signingCertificateHistory = new Signature[1][numberOfSigs]; + System.arraycopy(p.mSigningDetails.signatures, 0, + pi.signingCertificateHistory[0], 0, numberOfSigs); + } + } return pi; } @@ -1474,9 +1496,9 @@ public class PackageParser { * populating {@link Package#mSigningDetails}. Also asserts that all APK * contents are signed correctly and consistently. */ - public static void collectCertificates(Package pkg, @ParseFlags int parseFlags) + public static void collectCertificates(Package pkg, boolean skipVerify) throws PackageParserException { - collectCertificatesInternal(pkg, parseFlags); + collectCertificatesInternal(pkg, skipVerify); final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { Package childPkg = pkg.childPackages.get(i); @@ -1484,17 +1506,17 @@ public class PackageParser { } } - private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags) + private static void collectCertificatesInternal(Package pkg, boolean skipVerify) throws PackageParserException { pkg.mSigningDetails = SigningDetails.UNKNOWN; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags); + collectCertificates(pkg, new File(pkg.baseCodePath), skipVerify); if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { for (int i = 0; i < pkg.splitCodePaths.length; i++) { - collectCertificates(pkg, new File(pkg.splitCodePaths[i]), parseFlags); + collectCertificates(pkg, new File(pkg.splitCodePaths[i]), skipVerify); } } } finally { @@ -1502,7 +1524,7 @@ public class PackageParser { } } - private static void collectCertificates(Package pkg, File apkFile, @ParseFlags int parseFlags) + private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); @@ -1512,7 +1534,7 @@ public class PackageParser { minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; } SigningDetails verified; - if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) { + if (skipVerify) { // systemDir APKs are already trusted, save time by not verifying verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts( apkPath, minSignatureScheme); @@ -1592,9 +1614,10 @@ public class PackageParser { if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { // TODO: factor signature related items out of Package object final Package tempPkg = new Package((String) null); + final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { - collectCertificates(tempPkg, apkFile, flags); + collectCertificates(tempPkg, apkFile, skipVerify); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -2055,7 +2078,7 @@ public class PackageParser { pkg.mOverlayPriority = sa.getInt( com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority, 0); - pkg.mIsStaticOverlay = sa.getBoolean( + pkg.mOverlayIsStatic = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic, false); final String propName = sa.getString( @@ -5681,23 +5704,74 @@ public class PackageParser { @Nullable public final ArraySet publicKeys; + /** + * Collection of {@code Signature} objects, each of which is formed from a former signing + * certificate of this APK before it was changed by signing certificate rotation. + */ + @Nullable + public final Signature[] pastSigningCertificates; + + /** + * Flags for the {@code pastSigningCertificates} collection, which indicate the capabilities + * the including APK wishes to grant to its past signing certificates. + */ + @Nullable + public final int[] pastSigningCertificatesFlags; + /** A representation of unknown signing details. Use instead of null. */ public static final SigningDetails UNKNOWN = - new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null); + new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null, null, null); @VisibleForTesting public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion, - ArraySet keys) { + ArraySet keys, Signature[] pastSigningCertificates, + int[] pastSigningCertificatesFlags) { this.signatures = signatures; this.signatureSchemeVersion = signatureSchemeVersion; this.publicKeys = keys; + this.pastSigningCertificates = pastSigningCertificates; + this.pastSigningCertificatesFlags = pastSigningCertificatesFlags; + } + + public SigningDetails(Signature[] signatures, + @SignatureSchemeVersion int signatureSchemeVersion, + Signature[] pastSigningCertificates, int[] pastSigningCertificatesFlags) + throws CertificateException { + this(signatures, signatureSchemeVersion, toSigningKeys(signatures), + pastSigningCertificates, pastSigningCertificatesFlags); } public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion) throws CertificateException { - this(signatures, signatureSchemeVersion, toSigningKeys(signatures)); + this(signatures, signatureSchemeVersion, + null, null); + } + + public SigningDetails(SigningDetails orig) { + if (orig != null) { + if (orig.signatures != null) { + this.signatures = orig.signatures.clone(); + } else { + this.signatures = null; + } + this.signatureSchemeVersion = orig.signatureSchemeVersion; + this.publicKeys = new ArraySet<>(orig.publicKeys); + if (orig.pastSigningCertificates != null) { + this.pastSigningCertificates = orig.pastSigningCertificates.clone(); + this.pastSigningCertificatesFlags = orig.pastSigningCertificatesFlags.clone(); + } else { + this.pastSigningCertificates = null; + this.pastSigningCertificatesFlags = null; + } + } else { + this.signatures = null; + this.signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN; + this.publicKeys = null; + this.pastSigningCertificates = null; + this.pastSigningCertificatesFlags = null; + } } /** Returns true if the signing details have one or more signatures. */ @@ -5705,6 +5779,11 @@ public class PackageParser { return signatures != null && signatures.length > 0; } + /** Returns true if the signing details have past signing certificates. */ + public boolean hasPastSigningCertificates() { + return pastSigningCertificates != null && pastSigningCertificates.length > 0; + } + /** Returns true if the signatures in this and other match exactly. */ public boolean signaturesMatchExactly(SigningDetails other) { return Signature.areExactMatch(this.signatures, other.signatures); @@ -5725,6 +5804,8 @@ public class PackageParser { dest.writeTypedArray(this.signatures, flags); dest.writeInt(this.signatureSchemeVersion); dest.writeArraySet(this.publicKeys); + dest.writeTypedArray(this.pastSigningCertificates, flags); + dest.writeIntArray(this.pastSigningCertificatesFlags); } protected SigningDetails(Parcel in) { @@ -5732,6 +5813,8 @@ public class PackageParser { this.signatures = in.createTypedArray(Signature.CREATOR); this.signatureSchemeVersion = in.readInt(); this.publicKeys = (ArraySet) in.readArraySet(boot); + this.pastSigningCertificates = in.createTypedArray(Signature.CREATOR); + this.pastSigningCertificatesFlags = in.createIntArray(); } public static final Creator CREATOR = new Creator() { @@ -5758,8 +5841,23 @@ public class PackageParser { if (signatureSchemeVersion != that.signatureSchemeVersion) return false; if (!Signature.areExactMatch(signatures, that.signatures)) return false; - return publicKeys != null ? publicKeys.equals(that.publicKeys) - : that.publicKeys == null; + if (publicKeys != null) { + if (!publicKeys.equals((that.publicKeys))) { + return false; + } + } else if (that.publicKeys != null) { + return false; + } + + // can't use Signature.areExactMatch() because order matters with the past signing certs + if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) { + return false; + } + if (!Arrays.equals(pastSigningCertificatesFlags, that.pastSigningCertificatesFlags)) { + return false; + } + + return true; } @Override @@ -5767,8 +5865,77 @@ public class PackageParser { int result = +Arrays.hashCode(signatures); result = 31 * result + signatureSchemeVersion; result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0); + result = 31 * result + Arrays.hashCode(pastSigningCertificates); + result = 31 * result + Arrays.hashCode(pastSigningCertificatesFlags); return result; } + + /** + * Builder of {@code SigningDetails} instances. + */ + public static class Builder { + private Signature[] mSignatures; + private int mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN; + private Signature[] mPastSigningCertificates; + private int[] mPastSigningCertificatesFlags; + + public Builder() { + } + + /** get signing certificates used to sign the current APK */ + public Builder setSignatures(Signature[] signatures) { + mSignatures = signatures; + return this; + } + + /** set the signature scheme version used to sign the APK */ + public Builder setSignatureSchemeVersion(int signatureSchemeVersion) { + mSignatureSchemeVersion = signatureSchemeVersion; + return this; + } + + /** set the signing certificates by which the APK proved it can be authenticated */ + public Builder setPastSigningCertificates(Signature[] pastSigningCertificates) { + mPastSigningCertificates = pastSigningCertificates; + return this; + } + + /** set the flags for the {@code pastSigningCertificates} */ + public Builder setPastSigningCertificatesFlags(int[] pastSigningCertificatesFlags) { + mPastSigningCertificatesFlags = pastSigningCertificatesFlags; + return this; + } + + private void checkInvariants() { + // must have signatures and scheme version set + if (mSignatures == null) { + throw new IllegalStateException("SigningDetails requires the current signing" + + " certificates."); + } + + // pastSigningCerts and flags must match up + boolean pastMismatch = false; + if (mPastSigningCertificates != null && mPastSigningCertificatesFlags != null) { + if (mPastSigningCertificates.length != mPastSigningCertificatesFlags.length) { + pastMismatch = true; + } + } else if (!(mPastSigningCertificates == null + && mPastSigningCertificatesFlags == null)) { + pastMismatch = true; + } + if (pastMismatch) { + throw new IllegalStateException("SigningDetails must have a one to one mapping " + + "between pastSigningCertificates and pastSigningCertificatesFlags"); + } + } + /** build a {@code SigningDetails} object */ + public SigningDetails build() + throws CertificateException { + checkInvariants(); + return new SigningDetails(mSignatures, mSignatureSchemeVersion, + mPastSigningCertificates, mPastSigningCertificatesFlags); + } + } } /** @@ -5920,8 +6087,7 @@ public class PackageParser { public String mOverlayTarget; public int mOverlayPriority; - public boolean mIsStaticOverlay; - public boolean mTrustedOverlay; + public boolean mOverlayIsStatic; public int mCompileSdkVersion; public String mCompileSdkVersionCodename; @@ -6227,6 +6393,11 @@ public class PackageParser { return applicationInfo.isVendor(); } + /** @hide */ + public boolean isProduct() { + return applicationInfo.isProduct(); + } + /** @hide */ public boolean isPrivileged() { return applicationInfo.isPrivilegedApp(); @@ -6285,6 +6456,31 @@ public class PackageParser { + " " + packageName + "}"; } + public String dumpState_temp() { + String flags = ""; + flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : ""); + flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : ""); + if ("".equals(flags)) { + flags = "-"; + } + String privFlags = ""; + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : ""); + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : ""); + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : ""); + if ("".equals(privFlags)) { + privFlags = "-"; + } + return "Package{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + packageName + + ", ver:" + getLongVersionCode() + + ", path: " + codePath + + ", flags: " + flags + + ", privFlags: " + privFlags + + ", extra: " + (mExtras == null ? "<>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}") + + "}"; + } + @Override public int describeContents() { return 0; @@ -6426,8 +6622,7 @@ public class PackageParser { mRequiredAccountType = dest.readString(); mOverlayTarget = dest.readString(); mOverlayPriority = dest.readInt(); - mIsStaticOverlay = (dest.readInt() == 1); - mTrustedOverlay = (dest.readInt() == 1); + mOverlayIsStatic = (dest.readInt() == 1); mCompileSdkVersion = dest.readInt(); mCompileSdkVersionCodename = dest.readString(); mUpgradeKeySets = (ArraySet) dest.readArraySet(boot); @@ -6550,8 +6745,7 @@ public class PackageParser { dest.writeString(mRequiredAccountType); dest.writeString(mOverlayTarget); dest.writeInt(mOverlayPriority); - dest.writeInt(mIsStaticOverlay ? 1 : 0); - dest.writeInt(mTrustedOverlay ? 1 : 0); + dest.writeInt(mOverlayIsStatic ? 1 : 0); dest.writeInt(mCompileSdkVersion); dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mUpgradeKeySets); diff --git a/core/java/android/content/pm/PackageSharedLibraryUpdater.java b/core/java/android/content/pm/PackageSharedLibraryUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..fa894320b7d0728830e3a654cd05de89bb85a048 --- /dev/null +++ b/core/java/android/content/pm/PackageSharedLibraryUpdater.java @@ -0,0 +1,107 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Build; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; + +/** + * Base for classes that update a {@link PackageParser.Package}'s shared libraries. + * + * @hide + */ +@VisibleForTesting +public abstract class PackageSharedLibraryUpdater { + + /** + * Update the package's shared libraries. + * + * @param pkg the package to update. + */ + public abstract void updatePackage(PackageParser.Package pkg); + + static void removeLibrary(PackageParser.Package pkg, String libraryName) { + pkg.usesLibraries = ArrayUtils.remove(pkg.usesLibraries, libraryName); + pkg.usesOptionalLibraries = + ArrayUtils.remove(pkg.usesOptionalLibraries, libraryName); + } + + static @NonNull + ArrayList prefix(@Nullable ArrayList cur, T val) { + if (cur == null) { + cur = new ArrayList<>(); + } + cur.add(0, val); + return cur; + } + + private static boolean isLibraryPresent(ArrayList usesLibraries, + ArrayList usesOptionalLibraries, String apacheHttpLegacy) { + return ArrayUtils.contains(usesLibraries, apacheHttpLegacy) + || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy); + } + + static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(PackageParser.Package pkg) { + int targetSdkVersion = pkg.applicationInfo.targetSdkVersion; + return targetSdkVersion <= Build.VERSION_CODES.O_MR1; + } + + /** + * Add an implicit dependency. + * + *

    If the package has an existing dependency on {@code existingLibrary} then prefix it with + * the {@code implicitDependency} if it is not already in the list of libraries. + * + * @param pkg the {@link PackageParser.Package} to update. + * @param existingLibrary the existing library. + * @param implicitDependency the implicit dependency to add + */ + void prefixImplicitDependency(PackageParser.Package pkg, String existingLibrary, + String implicitDependency) { + ArrayList usesLibraries = pkg.usesLibraries; + ArrayList usesOptionalLibraries = pkg.usesOptionalLibraries; + + if (!isLibraryPresent(usesLibraries, usesOptionalLibraries, implicitDependency)) { + if (ArrayUtils.contains(usesLibraries, existingLibrary)) { + prefix(usesLibraries, implicitDependency); + } else if (ArrayUtils.contains(usesOptionalLibraries, existingLibrary)) { + prefix(usesOptionalLibraries, implicitDependency); + } + + pkg.usesLibraries = usesLibraries; + pkg.usesOptionalLibraries = usesOptionalLibraries; + } + } + + void prefixRequiredLibrary(PackageParser.Package pkg, String libraryName) { + ArrayList usesLibraries = pkg.usesLibraries; + ArrayList usesOptionalLibraries = pkg.usesOptionalLibraries; + + boolean alreadyPresent = isLibraryPresent( + usesLibraries, usesOptionalLibraries, libraryName); + if (!alreadyPresent) { + usesLibraries = prefix(usesLibraries, libraryName); + + pkg.usesLibraries = usesLibraries; + } + } +} diff --git a/core/java/android/content/pm/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java new file mode 100644 index 0000000000000000000000000000000000000000..3fbf664b1a836818086ce5b86d5efb30cfe6627d --- /dev/null +++ b/core/java/android/content/pm/SharedLibraryNames.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * A set of shared library names + * + * @hide + */ +public class SharedLibraryNames { + + static final String ANDROID_TEST_MOCK = "android.test.mock"; + + static final String ANDROID_TEST_RUNNER = "android.test.runner"; + + static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy"; +} diff --git a/core/java/android/content/pm/dex/ArtManager.java b/core/java/android/content/pm/dex/ArtManager.java index 201cd8d32cc1cc28a8d561bca2db6bcbc2dec46c..41293981102fc1533e2fdb3b25f483639d5650b3 100644 --- a/core/java/android/content/pm/dex/ArtManager.java +++ b/core/java/android/content/pm/dex/ArtManager.java @@ -16,16 +16,22 @@ package android.content.pm.dex; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; +import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Slog; +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + /** * Class for retrieving various kinds of information related to the runtime artifacts of * packages that are currently installed on the device. @@ -43,6 +49,20 @@ public class ArtManager { /** The snapshot failed because of an internal error (e.g. error during opening profiles). */ public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2; + /** Constant used for applications profiles. */ + public static final int PROFILE_APPS = 0; + /** Constant used for the boot image profile. */ + public static final int PROFILE_BOOT_IMAGE = 1; + + /** @hide */ + @IntDef(flag = true, prefix = { "PROFILE_" }, value = { + PROFILE_APPS, + PROFILE_BOOT_IMAGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProfileType {} + + private IArtManager mArtManager; /** @@ -53,41 +73,59 @@ public class ArtManager { } /** - * Snapshots the runtime profile for an apk belonging to the package {@code packageName}. - * The apk is identified by {@code codePath}. The calling process must have - * {@code android.permission.READ_RUNTIME_PROFILE} permission. + * Snapshots a runtime profile according to the {@code profileType} parameter. + * + * If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot + * the profile for for an apk belonging to the package {@code packageName}. + * The apk is identified by {@code codePath}. + * + * If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot + * the profile for the boot image. In this case {@code codePath can be null}. The parameters + * {@code packageName} and {@code codePath} are ignored. + *u + * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission. + * + * The result will be posted on the {@code executor} using the given {@code callback}. + * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}. * - * The result will be posted on {@code handler} using the given {@code callback}. - * The profile being available as a read-only {@link android.os.ParcelFileDescriptor}. + * This method will throw {@link IllegalStateException} if + * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given + * {@code profileType}. * - * @param packageName the target package name - * @param codePath the code path for which the profile should be retrieved + * @param profileType the type of profile that should be snapshot (boot image or app) + * @param packageName the target package name or null if the target is the boot image + * @param codePath the code path for which the profile should be retrieved or null if + * the target is the boot image * @param callback the callback which should be used for the result - * @param handler the handler which should be used to post the result + * @param executor the executor which should be used to post the result */ @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES) - public void snapshotRuntimeProfile(@NonNull String packageName, @NonNull String codePath, - @NonNull SnapshotRuntimeProfileCallback callback, @NonNull Handler handler) { + public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName, + @Nullable String codePath, @NonNull @CallbackExecutor Executor executor, + @NonNull SnapshotRuntimeProfileCallback callback) { Slog.d(TAG, "Requesting profile snapshot for " + packageName + ":" + codePath); SnapshotRuntimeProfileCallbackDelegate delegate = - new SnapshotRuntimeProfileCallbackDelegate(callback, handler.getLooper()); + new SnapshotRuntimeProfileCallbackDelegate(callback, executor); try { - mArtManager.snapshotRuntimeProfile(packageName, codePath, delegate); + mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } } /** - * Returns true if runtime profiles are enabled, false otherwise. + * Returns true if runtime profiles are enabled for the given type, false otherwise. * * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission. + * + * @param profileType can be either {@link ArtManager#PROFILE_APPS} + * or {@link ArtManager#PROFILE_BOOT_IMAGE} */ @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES) - public boolean isRuntimeProfilingEnabled() { + public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) { try { - return mArtManager.isRuntimeProfilingEnabled(); + return mArtManager.isRuntimeProfilingEnabled(profileType); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } @@ -116,41 +154,57 @@ public class ArtManager { } private static class SnapshotRuntimeProfileCallbackDelegate - extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub - implements Handler.Callback { - private static final int MSG_SNAPSHOT_OK = 1; - private static final int MSG_ERROR = 2; + extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub { private final ArtManager.SnapshotRuntimeProfileCallback mCallback; - private final Handler mHandler; + private final Executor mExecutor; private SnapshotRuntimeProfileCallbackDelegate( - ArtManager.SnapshotRuntimeProfileCallback callback, Looper looper) { + ArtManager.SnapshotRuntimeProfileCallback callback, Executor executor) { mCallback = callback; - mHandler = new Handler(looper, this); + mExecutor = executor; } @Override - public void onSuccess(ParcelFileDescriptor profileReadFd) { - mHandler.obtainMessage(MSG_SNAPSHOT_OK, profileReadFd).sendToTarget(); + public void onSuccess(final ParcelFileDescriptor profileReadFd) { + mExecutor.execute(() -> mCallback.onSuccess(profileReadFd)); } @Override public void onError(int errCode) { - mHandler.obtainMessage(MSG_ERROR, errCode, 0).sendToTarget(); + mExecutor.execute(() -> mCallback.onError(errCode)); } + } - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_SNAPSHOT_OK: - mCallback.onSuccess((ParcelFileDescriptor) msg.obj); - break; - case MSG_ERROR: - mCallback.onError(msg.arg1); - break; - default: return false; - } - return true; - } + /** + * Return the profile name for the given split. If {@code splitName} is null the + * method returns the profile name for the base apk. + * + * @hide + */ + public static String getProfileName(String splitName) { + return splitName == null ? "primary.prof" : splitName + ".split.prof"; + } + + /** + * Return the path to the current profile corresponding to given package and split. + * + * @hide + */ + public static String getCurrentProfilePath(String packageName, int userId, String splitName) { + File profileDir = Environment.getDataProfilesDePackageDirectory(userId, packageName); + return new File(profileDir, getProfileName(splitName)).getAbsolutePath(); + } + + /** + * Return the snapshot profile file for the given package and profile name. + * + * KEEP in sync with installd dexopt.cpp. + * TODO(calin): inject the snapshot profile name from PM to avoid the dependency. + * + * @hide + */ + public static File getProfileSnapshotFileForName(String packageName, String profileName) { + File profileDir = Environment.getDataRefProfilesDePackageDirectory(packageName); + return new File(profileDir, profileName + ".snapshot"); } } 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 0000000000000000000000000000000000000000..5d10b8826b0071dfb1525a7864fdde9c0444ab6a --- /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 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 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 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 loose + * matching based on the extension ('foo.dm' will match 'foo.apk' or 'foo'). + * + * This should only be used for code paths extracted from a package structure after the naming + * was enforced in the installer. + */ + private static Map buildPackageApkToDexMetadataMap( + List codePaths) { + ArrayMap result = new ArrayMap<>(); + for (int i = codePaths.size() - 1; i >= 0; i--) { + String codePath = codePaths.get(i); + String dexMetadataPath = buildDexMetadataPathForFile(new File(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 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 apks = new ArrayList<>(); + for (int i = 0; i < paths.length; i++) { + if (PackageParser.isApkPath(paths[i])) { + apks.add(paths[i]); + } + } + ArrayList 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/content/pm/dex/IArtManager.aidl b/core/java/android/content/pm/dex/IArtManager.aidl index 8cbb452344b23868794c4f8f63c04d2f50c447ee..6abfdbab6e2682f5517624f03b710fd60cd576a9 100644 --- a/core/java/android/content/pm/dex/IArtManager.aidl +++ b/core/java/android/content/pm/dex/IArtManager.aidl @@ -25,20 +25,34 @@ import android.content.pm.dex.ISnapshotRuntimeProfileCallback; */ interface IArtManager { /** - * Snapshots the runtime profile for an apk belonging to the package {@param packageName}. - * The apk is identified by {@param codePath}. The calling process must have - * {@code android.permission.READ_RUNTIME_PROFILE} permission. + * Snapshots a runtime profile according to the {@code profileType} parameter. * - * The result will be posted on {@param callback} with the profile being available as a - * read-only {@link android.os.ParcelFileDescriptor}. - */ - oneway void snapshotRuntimeProfile(in String packageName, - in String codePath, in ISnapshotRuntimeProfileCallback callback); - - /** - * Returns true if runtime profiles are enabled, false otherwise. + * If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot + * the profile for for an apk belonging to the package {@code packageName}. + * The apk is identified by {@code codePath}. + * + * If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot + * the profile for the boot image. In this case {@code codePath can be null}. The parameters + * {@code packageName} and {@code codePath} are ignored. * * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission. + * + * The result will be posted on the {@code executor} using the given {@code callback}. + * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}. + * + * This method will throw {@link IllegalStateException} if + * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given + * {@code profileType}. */ - boolean isRuntimeProfilingEnabled(); + oneway void snapshotRuntimeProfile(int profileType, in String packageName, + in String codePath, in ISnapshotRuntimeProfileCallback callback); + + /** + * Returns true if runtime profiles are enabled for the given type, false otherwise. + * The type can be can be either {@code ArtManager.PROFILE_APPS} + * or {@code ArtManager.PROFILE_BOOT_IMAGE}. + * + * @param profileType + */ + boolean isRuntimeProfilingEnabled(int profileType); } diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index f84ec65fe0aec90bb85f64c55649ff4b6c97a343..a748f4d2cbce9c5dc9d2931397a4fccc08b70ddd 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -28,6 +28,7 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.SparseIntArray; +import dalvik.annotation.optimization.FastNative; import dalvik.system.CloseGuard; /** @@ -62,28 +63,43 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private static native void nativeDispose(long windowPtr); private static native void nativeWriteToParcel(long windowPtr, Parcel parcel); + private static native String nativeGetName(long windowPtr); + private static native byte[] nativeGetBlob(long windowPtr, int row, int column); + private static native String nativeGetString(long windowPtr, int row, int column); + private static native void nativeCopyStringToBuffer(long windowPtr, int row, int column, + CharArrayBuffer buffer); + private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column); + private static native boolean nativePutString(long windowPtr, String value, + int row, int column); + + // Below native methods don't do unconstrained work, so are FastNative for performance + + @FastNative private static native void nativeClear(long windowPtr); + @FastNative private static native int nativeGetNumRows(long windowPtr); + @FastNative private static native boolean nativeSetNumColumns(long windowPtr, int columnNum); + @FastNative private static native boolean nativeAllocRow(long windowPtr); + @FastNative private static native void nativeFreeLastRow(long windowPtr); + @FastNative private static native int nativeGetType(long windowPtr, int row, int column); - private static native byte[] nativeGetBlob(long windowPtr, int row, int column); - private static native String nativeGetString(long windowPtr, int row, int column); + @FastNative private static native long nativeGetLong(long windowPtr, int row, int column); + @FastNative private static native double nativeGetDouble(long windowPtr, int row, int column); - private static native void nativeCopyStringToBuffer(long windowPtr, int row, int column, - CharArrayBuffer buffer); - private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column); - private static native boolean nativePutString(long windowPtr, String value, int row, int column); + @FastNative private static native boolean nativePutLong(long windowPtr, long value, int row, int column); + @FastNative private static native boolean nativePutDouble(long windowPtr, double value, int row, int column); + @FastNative private static native boolean nativePutNull(long windowPtr, int row, int column); - private static native String nativeGetName(long windowPtr); /** * Creates a new empty cursor window and gives it a name. diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 49f357e6cb10a46f56af3fe9418bf3a6ba56eff2..a2991e6e9cb626065e80ca4924bbc4d949c2bb9f 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -66,7 +66,7 @@ public abstract class SQLiteOpenHelper { * created or opened until one of {@link #getWritableDatabase} or * {@link #getReadableDatabase} is called. * - * @param context to use to open or create the database + * @param context to use for locating paths to the the database * @param name of the database file, or null for an in-memory database * @param factory to use for creating cursor objects, or null for the default * @param version number of the database (starting at 1); if the database is older, @@ -86,7 +86,7 @@ public abstract class SQLiteOpenHelper { *

    Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be * used to handle corruption when sqlite reports database corruption.

    * - * @param context to use to open or create the database + * @param context to use for locating paths to the the database * @param name of the database file, or null for an in-memory database * @param factory to use for creating cursor objects, or null for the default * @param version number of the database (starting at 1); if the database is older, @@ -107,7 +107,7 @@ public abstract class SQLiteOpenHelper { * created or opened until one of {@link #getWritableDatabase} or * {@link #getReadableDatabase} is called. * - * @param context to use to open or create the database + * @param context to use for locating paths to the the database * @param name of the database file, or null for an in-memory database * @param version number of the database (starting at 1); if the database is older, * {@link #onUpgrade} will be used to upgrade the database; if the database is @@ -128,7 +128,7 @@ public abstract class SQLiteOpenHelper { * minimumSupportedVersion is found, it is simply deleted and a new database is created with the * given name and version * - * @param context to use to open or create the database + * @param context to use for locating paths to the the database * @param name the name of the database file, null for a temporary in-memory database * @param factory to use for creating cursor objects, null for default * @param version the required version of the database diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index 7866b52cc68ccb1e2e16905ce4cd8b3d5f26f377..9aa3f40a8f0a1019d38a79c1bdac880454a70358 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -25,11 +25,11 @@ import android.os.Parcelable; import dalvik.annotation.optimization.FastNative; import dalvik.system.CloseGuard; +import libcore.util.NativeAllocationRegistry; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import libcore.util.NativeAllocationRegistry; - /** * HardwareBuffer wraps a native AHardwareBuffer object, which is a low-level object * representing a memory buffer accessible by various hardware units. HardwareBuffer allows sharing @@ -42,18 +42,25 @@ import libcore.util.NativeAllocationRegistry; public final class HardwareBuffer implements Parcelable, AutoCloseable { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "RGB", "BLOB" }, value = { + @IntDef(prefix = { "RGB", "BLOB", "D_", "DS_", "S_" }, value = { RGBA_8888, RGBA_FP16, RGBA_1010102, RGBX_8888, RGB_888, RGB_565, - BLOB + BLOB, + D_16, + D_24, + DS_24UI8, + D_FP32, + DS_FP32UI8, + S_UI8, }) public @interface Format { } + @Format /** Format: 8 bits each red, green, blue, alpha */ public static final int RGBA_8888 = 1; /** Format: 8 bits each red, green, blue, alpha, alpha is always 0xFF */ @@ -68,6 +75,18 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { public static final int RGBA_1010102 = 0x2b; /** Format: opaque format used for raw data transfer; must have a height of 1 */ public static final int BLOB = 0x21; + /** Format: 16 bits depth */ + public static final int D_16 = 0x30; + /** Format: 24 bits depth */ + public static final int D_24 = 0x31; + /** Format: 24 bits depth, 8 bits stencil */ + public static final int DS_24UI8 = 0x32; + /** Format: 32 bits depth */ + public static final int D_FP32 = 0x33; + /** Format: 32 bits depth, 8 bits stencil */ + public static final int DS_FP32UI8 = 0x34; + /** Format: 8 bits stencil */ + public static final int S_UI8 = 0x35; // Note: do not rename, this field is used by native code private long mNativeObject; @@ -82,9 +101,11 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { @LongDef(flag = true, value = {USAGE_CPU_READ_RARELY, USAGE_CPU_READ_OFTEN, USAGE_CPU_WRITE_RARELY, USAGE_CPU_WRITE_OFTEN, USAGE_GPU_SAMPLED_IMAGE, USAGE_GPU_COLOR_OUTPUT, USAGE_PROTECTED_CONTENT, USAGE_VIDEO_ENCODE, - USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA}) + USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA, USAGE_GPU_CUBE_MAP, + USAGE_GPU_MIPMAP_COMPLETE}) public @interface Usage {}; + @Usage /** Usage: The buffer will sometimes be read by the CPU */ public static final long USAGE_CPU_READ_RARELY = 2; /** Usage: The buffer will often be read by the CPU */ @@ -107,6 +128,10 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { public static final long USAGE_SENSOR_DIRECT_DATA = 1 << 23; /** Usage: The buffer will be used as a shader storage or uniform buffer object */ public static final long USAGE_GPU_DATA_BUFFER = 1 << 24; + /** Usage: The buffer will be used as a cube map texture */ + public static final long USAGE_GPU_CUBE_MAP = 1 << 25; + /** Usage: The buffer contains a complete mipmap hierarchy */ + public static final long USAGE_GPU_MIPMAP_COMPLETE = 1 << 26; // The approximate size of a native AHardwareBuffer object. private static final long NATIVE_HARDWARE_BUFFER_SIZE = 232; @@ -118,15 +143,9 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { * * @param width The width in pixels of the buffer * @param height The height in pixels of the buffer - * @param format The format of each pixel, one of {@link #RGBA_8888}, {@link #RGBA_FP16}, - * {@link #RGBX_8888}, {@link #RGB_565}, {@link #RGB_888}, {@link #RGBA_1010102}, {@link #BLOB} + * @param format The @Format of each pixel * @param layers The number of layers in the buffer - * @param usage Flags describing how the buffer will be used, one of - * {@link #USAGE_CPU_READ_RARELY}, {@link #USAGE_CPU_READ_OFTEN}, - * {@link #USAGE_CPU_WRITE_RARELY}, {@link #USAGE_CPU_WRITE_OFTEN}, - * {@link #USAGE_GPU_SAMPLED_IMAGE}, {@link #USAGE_GPU_COLOR_OUTPUT}, - * {@link #USAGE_GPU_DATA_BUFFER}, {@link #USAGE_PROTECTED_CONTENT}, - * {@link #USAGE_SENSOR_DIRECT_DATA}, {@link #USAGE_VIDEO_ENCODE} + * @param usage The @Usage flags describing how the buffer will be used * @return A HardwareBuffer instance if successful, or throws an * IllegalArgumentException if the dimensions passed are invalid (either zero, negative, or * too large to allocate), if the format is not supported, if the requested number of layers @@ -154,7 +173,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { if (nativeObject == 0) { throw new IllegalArgumentException("Unable to create a HardwareBuffer, either the " + "dimensions passed were too large, too many image layers were requested, " + - "or an invalid set of usage flags was passed"); + "or an invalid set of usage flags or invalid format was passed"); } return new HardwareBuffer(nativeObject); } @@ -206,8 +225,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { } /** - * Returns the format of this buffer, one of {@link #RGBA_8888}, {@link #RGBA_FP16}, - * {@link #RGBX_8888}, {@link #RGB_565}, {@link #RGB_888}, {@link #RGBA_1010102}, {@link #BLOB}. + * Returns the @Format of this buffer. */ @Format public int getFormat() { @@ -338,6 +356,12 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { case RGB_565: case RGB_888: case BLOB: + case D_16: + case D_24: + case DS_24UI8: + case D_FP32: + case DS_FP32UI8: + case S_UI8: return true; } return false; diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 7fb0c89e7f92358e396346e41152ad080118d3fb..7297426130ac4a2994c226f02dbfcd6cf60b4d83 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -22,7 +22,9 @@ import android.os.Build; /** * Class representing a sensor. Use {@link SensorManager#getSensorList} to get - * the list of available Sensors. + * the list of available sensors. For more information about Android sensors, + * read the + * Motion Sensors guide.

    * * @see SensorManager * @see SensorEventListener diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 1201ef48220a2f72b3250ec06ed4d9bf148a7614..96d043c2a894cf97e16218de660d9605f3c46430 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -22,9 +22,11 @@ import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.TypeReference; import android.util.Rational; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -171,6 +173,7 @@ public final class CameraCharacteristics extends CameraMetadata> mKeys; private List> mAvailableRequestKeys; private List> mAvailableSessionKeys; + private List> mAvailablePhysicalRequestKeys; private List> mAvailableResultKeys; /** @@ -313,6 +316,45 @@ public final class CameraCharacteristics extends CameraMetadataReturns a subset of {@link #getAvailableCaptureRequestKeys} keys that can + * be overriden for physical devices backing a logical multi-camera.

    + * + *

    This is a subset of android.request.availableRequestKeys which contains a list + * of keys that can be overriden using {@link CaptureRequest.Builder#setPhysicalCameraKey }. + * The respective value of such request key can be obtained by calling + * {@link CaptureRequest.Builder#getPhysicalCameraKey }. Capture requests that contain + * individual physical device requests must be built via + * {@link android.hardware.camera2.CameraDevice#createCaptureRequest(int, Set)}. + * Such extended capture requests can be passed only to + * {@link CameraCaptureSession#capture } or {@link CameraCaptureSession#captureBurst } and + * not to {@link CameraCaptureSession#setRepeatingRequest } or + * {@link CameraCaptureSession#setRepeatingBurst }.

    + * + *

    The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.

    + * + *

    Each key is only listed once in the list. The order of the keys is undefined.

    + * + * @return List of keys that can be overriden in individual physical device requests. + * In case the camera device doesn't support such keys the list can be null. + */ + @SuppressWarnings({"unchecked"}) + public List> getAvailablePhysicalCameraRequestKeys() { + if (mAvailableSessionKeys == null) { + Object crKey = CaptureRequest.Key.class; + Class> crKeyTyped = (Class>)crKey; + + int[] filterTags = get(REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS); + if (filterTags == null) { + return null; + } + mAvailablePhysicalRequestKeys = + getAvailableKeyList(CaptureRequest.class, crKeyTyped, filterTags); + } + return mAvailablePhysicalRequestKeys; + } + /** * Returns the list of keys supported by this {@link CameraDevice} for querying * with a {@link CaptureRequest}. @@ -407,6 +449,47 @@ public final class CameraCharacteristics extends CameraMetadataA camera device is a logical camera if it has + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability. If the camera device + * doesn't have the capability, the return value will be an empty list.

    + * + *

    The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.

    + * + *

    Each physical camera id is only listed once in the list. The order of the keys + * is undefined.

    + * + * @return List of physical camera ids for this logical camera device. + */ + @NonNull + public List getPhysicalCameraIds() { + int[] availableCapabilities = get(REQUEST_AVAILABLE_CAPABILITIES); + if (availableCapabilities == null) { + throw new AssertionError("android.request.availableCapabilities must be non-null " + + "in the characteristics"); + } + + if (!ArrayUtils.contains(availableCapabilities, + REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { + return Collections.emptyList(); + } + byte[] physicalCamIds = get(LOGICAL_MULTI_CAMERA_PHYSICAL_IDS); + + String physicalCamIdString = null; + try { + physicalCamIdString = new String(physicalCamIds, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + throw new AssertionError("android.logicalCam.physicalIds must be UTF-8 string"); + } + String[] physicalCameraIdList = physicalCamIdString.split("\0"); + + return Collections.unmodifiableList(Arrays.asList(physicalCameraIdList)); + } + /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * The key entries below this point are generated from metadata * definitions in /system/media/camera/docs. Do not modify by hand or @@ -1579,6 +1662,7 @@ public final class CameraCharacteristics extends CameraMetadata{@link #REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT} *
  • {@link #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO CONSTRAINED_HIGH_SPEED_VIDEO}
  • *
  • {@link #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}
  • + *
  • {@link #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA LOGICAL_MULTI_CAMERA}
  • *

    *

    This key is available on all devices.

    * @@ -1594,6 +1678,7 @@ public final class CameraCharacteristics extends CameraMetadata REQUEST_AVAILABLE_CAPABILITIES = @@ -1700,6 +1785,30 @@ public final class CameraCharacteristics extends CameraMetadata REQUEST_AVAILABLE_SESSION_KEYS = new Key("android.request.availableSessionKeys", int[].class); + /** + *

    A subset of the available request keys that can be overriden for + * physical devices backing a logical multi-camera.

    + *

    This is a subset of android.request.availableRequestKeys which contains a list + * of keys that can be overriden using {@link CaptureRequest.Builder#setPhysicalCameraKey }. + * The respective value of such request key can be obtained by calling + * {@link CaptureRequest.Builder#getPhysicalCameraKey }. Capture requests that contain + * individual physical device requests must be built via + * {@link android.hardware.camera2.CameraDevice#createCaptureRequest(int, Set)}. + * Such extended capture requests can be passed only to + * {@link CameraCaptureSession#capture } or {@link CameraCaptureSession#captureBurst } and + * not to {@link CameraCaptureSession#setRepeatingRequest } or + * {@link CameraCaptureSession#setRepeatingBurst }.

    + *

    Optional - This value may be {@code null} on some devices.

    + *

    Limited capability - + * 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

    + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS = + new Key("android.request.availablePhysicalCameraRequestKeys", int[].class); + /** *

    The list of image formats that are supported by this * camera device for output streams.

    @@ -2869,6 +2978,21 @@ public final class CameraCharacteristics extends CameraMetadata STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES = new Key("android.statistics.info.availableLensShadingMapModes", int[].class); + /** + *

    List of OIS data output modes for {@link CaptureRequest#STATISTICS_OIS_DATA_MODE android.statistics.oisDataMode} that + * are supported by this camera device.

    + *

    If no OIS data output is available for this camera device, this key will + * contain only OFF.

    + *

    Range of valid values:
    + * Any value listed in {@link CaptureRequest#STATISTICS_OIS_DATA_MODE android.statistics.oisDataMode}

    + *

    Optional - This value may be {@code null} on some devices.

    + * + * @see CaptureRequest#STATISTICS_OIS_DATA_MODE + */ + @PublicKey + public static final Key STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES = + new Key("android.statistics.info.availableOisDataModes", int[].class); + /** *

    Maximum number of supported points in the * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.

    @@ -2978,6 +3102,7 @@ public final class CameraCharacteristics extends CameraMetadata{@link #INFO_SUPPORTED_HARDWARE_LEVEL_FULL FULL} *
  • {@link #INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}
  • *
  • {@link #INFO_SUPPORTED_HARDWARE_LEVEL_3 3}
  • + *
  • {@link #INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL EXTERNAL}
  • *

    *

    This key is available on all devices.

    * @@ -2991,6 +3116,7 @@ public final class CameraCharacteristics extends CameraMetadata INFO_SUPPORTED_HARDWARE_LEVEL = @@ -3167,6 +3293,54 @@ public final class CameraCharacteristics extends CameraMetadata DEPTH_DEPTH_IS_EXCLUSIVE = new Key("android.depth.depthIsExclusive", boolean.class); + /** + *

    String containing the ids of the underlying physical cameras.

    + *

    For a logical camera, this is concatenation of all underlying physical camera ids. + * The null terminator for physical camera id must be preserved so that the whole string + * can be tokenized using '\0' to generate list of physical camera ids.

    + *

    For example, if the physical camera ids of the logical camera are "2" and "3", the + * value of this tag will be ['2', '\0', '3', '\0'].

    + *

    The number of physical camera ids must be no less than 2.

    + *

    Units: UTF-8 null-terminated string

    + *

    Optional - This value may be {@code null} on some devices.

    + *

    Limited capability - + * 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

    + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key LOGICAL_MULTI_CAMERA_PHYSICAL_IDS = + new Key("android.logicalMultiCamera.physicalIds", byte[].class); + + /** + *

    The accuracy of frame timestamp synchronization between physical cameras

    + *

    The accuracy of the frame timestamp synchronization determines the physical cameras' + * ability to start exposure at the same time. If the sensorSyncType is CALIBRATED, + * the physical camera sensors usually run in master-slave mode so that their shutter + * time is synchronized. For APPROXIMATE sensorSyncType, the camera sensors usually run in + * master-master mode, and there could be offset between their start of exposure.

    + *

    In both cases, all images generated for a particular capture request still carry the same + * timestamps, so that they can be used to look up the matching frame number and + * onCaptureStarted callback.

    + *

    Possible values: + *

      + *
    • {@link #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE APPROXIMATE}
    • + *
    • {@link #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED CALIBRATED}
    • + *

    + *

    Optional - This value may be {@code null} on some devices.

    + *

    Limited capability - + * 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

    + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE + * @see #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED + */ + @PublicKey + public static final Key LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE = + new Key("android.logicalMultiCamera.sensorSyncType", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index ce1fba798c0e2680f57b9a1353e15bd425ac00c0..40ee8348ac4cbafbdaaa36d97bd552c429af6ef1 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -31,6 +31,7 @@ import android.os.Handler; import android.view.Surface; import java.util.List; +import java.util.Set; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -183,7 +184,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 +427,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. * * * * * - * + * *
    MOTION_TRACKING-capability additional guaranteed configurations
    Target 1Target 2Target 3Sample use case(s)
    TypeMax sizeTypeMax sizeTypeMax size
    {@code PRIV}{@code PREVIEW} {@code YUV }{@code FULL FOV 640} {@code YUV }{@code MAXIMUM} Live preview with a tracking YUV output and a maximum-resolution YUV for still captures.
    {@code YUV}{@code PREVIEW} {@code YUV }{@code FULL FOV 640} {@code JPEG}{@code MAX 30FPS} Preview with a tracking YUV output and a as-large-as-possible JPEG for still captures.

    *

    * @@ -899,16 +905,51 @@ 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 + */ + @NonNull + public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) + throws CameraAccessException; + + /** + *

    Create a {@link CaptureRequest.Builder} for new capture requests, + * initialized with template for a target use case. This methods allows + * clients to pass physical camera ids which can be used to customize the + * request for a specific physical camera. The settings are chosen + * to be the best options for the specific logical camera device. If + * additional physical camera ids are passed, then they will also use the + * same settings template. Requests containing individual physical camera + * settings can be passed only to {@link CameraCaptureSession#capture} or + * {@link CameraCaptureSession#captureBurst} and not to + * {@link CameraCaptureSession#setRepeatingRequest} or + * {@link CameraCaptureSession#setRepeatingBurst}

    + * + * @param templateType An enumeration selecting the use case for this request. Not all template + * types are supported on every device. See the documentation for each template type for + * details. + * @param physicalCameraIdSet A set of physical camera ids that can be used to customize + * the request for a specific physical camera. + * @return a builder for a capture request, initialized with default + * settings for that template, and no output streams + * + * @throws IllegalArgumentException if the templateType is not supported by + * this device, or one of the physical id arguments matches with logical camera id. + * @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 + * @see CaptureRequest.Builder#setKey + * @see CaptureRequest.Builder#getKey */ @NonNull - public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) - throws CameraAccessException; + public CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType, + Set physicalCameraIdSet) throws CameraAccessException { + throw new UnsupportedOperationException("Subclasses must override this method"); + } /** *

    Create a {@link CaptureRequest.Builder} for a new reprocess {@link CaptureRequest} from a diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 90bf896c2225b119e8dd6d9e001d16a3e8b290be..a2bc91e0cda6adefd7ca9986cb5b6e6368fd14a5 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -996,7 +996,12 @@ public final class CameraManager { return; } - Integer oldStatus = mDeviceStatus.put(id, status); + Integer oldStatus; + if (status == ICameraServiceListener.STATUS_NOT_PRESENT) { + oldStatus = mDeviceStatus.remove(id); + } else { + oldStatus = mDeviceStatus.put(id, status); + } if (oldStatus != null && oldStatus == status) { if (DEBUG) { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 1c7f2896b167e84b3c598e8d4718d25229b2e062..e7c8961186ec364a961d1a0a61332628fbf8191c 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -845,6 +845,53 @@ public abstract class CameraMetadata { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; + /** + *

    The camera device is a logical camera backed by two or more physical cameras that are + * also exposed to the application.

    + *

    This capability requires the camera device to support the following:

    + *
      + *
    • This camera device must list the following static metadata entries in {@link android.hardware.camera2.CameraCharacteristics }:
        + *
      • android.logicalMultiCamera.physicalIds
      • + *
      • {@link CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE android.logicalMultiCamera.sensorSyncType}
      • + *
      + *
    • + *
    • The underlying physical cameras' static metadata must list the following entries, + * so that the application can correlate pixels from the physical streams:
        + *
      • {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}
      • + *
      • {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}
      • + *
      • {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}
      • + *
      • {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}
      • + *
      • {@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion}
      • + *
      + *
    • + *
    • The logical camera device must be LIMITED or higher device.
    • + *
    + *

    Both the logical camera device and its underlying physical devices support the + * mandatory stream combinations required for their device levels.

    + *

    Additionally, for each guaranteed stream combination, the logical camera supports:

    + *
      + *
    • Replacing one logical {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888} + * or raw stream with two physical streams of the same size and format, each from a + * separate physical camera, given that the size and format are supported by both + * physical cameras.
    • + *
    • Adding two raw streams, each from one physical camera, if the logical camera doesn't + * advertise RAW capability, but the underlying physical cameras do. This is usually + * the case when the physical cameras have different sensor sizes.
    • + *
    + *

    Using physical streams in place of a logical stream of the same size and format will + * not slow down the frame rate of the capture, as long as the minimum frame duration + * of the physical and logical streams are the same.

    + * + * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION + * @see CameraCharacteristics#LENS_POSE_REFERENCE + * @see CameraCharacteristics#LENS_POSE_ROTATION + * @see CameraCharacteristics#LENS_POSE_TRANSLATION + * @see CameraCharacteristics#LENS_RADIAL_DISTORTION + * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // @@ -1134,6 +1181,38 @@ public abstract class CameraMetadata { */ public static final int INFO_SUPPORTED_HARDWARE_LEVEL_3 = 3; + /** + *

    This camera device is backed by an external camera connected to this Android device.

    + *

    The device has capability identical to a LIMITED level device, with the following + * exceptions:

    + *
      + *
    • The device may not report lens/sensor related information such as
        + *
      • {@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}
      • + *
      • {@link CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE android.lens.info.hyperfocalDistance}
      • + *
      • {@link CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE android.sensor.info.physicalSize}
      • + *
      • {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}
      • + *
      • {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern}
      • + *
      • {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}
      • + *
      • {@link CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW android.sensor.rollingShutterSkew}
      • + *
      + *
    • + *
    • The device will report 0 for {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}
    • + *
    • The device has less guarantee on stable framerate, as the framerate partly depends + * on the external camera being used.
    • + *
    + * + * @see CaptureRequest#LENS_FOCAL_LENGTH + * @see CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE + * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN + * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT + * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE + * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL + * @see CameraCharacteristics#SENSOR_ORIENTATION + * @see CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + */ + public static final int INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL = 4; + // // Enumeration values for CameraCharacteristics#SYNC_MAX_LATENCY // @@ -1159,6 +1238,26 @@ public abstract class CameraMetadata { */ public static final int SYNC_MAX_LATENCY_UNKNOWN = -1; + // + // Enumeration values for CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + // + + /** + *

    A software mechanism is used to synchronize between the physical cameras. As a result, + * the timestamp of an image from a physical stream is only an approximation of the + * image sensor start-of-exposure time.

    + * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + */ + public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; + + /** + *

    The camera device supports frame timestamp synchronization at the hardware level, + * and the timestamp of a physical stream image accurately reflects its + * start-of-exposure time.

    + * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + */ + public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED = 1; + // // Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE // @@ -1359,6 +1458,20 @@ public abstract class CameraMetadata { */ public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; + /** + *

    An external flash has been turned on.

    + *

    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.

    + *

    When the external flash is turned off, AE mode should be changed to one of the + * other available AE modes.

    + *

    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.

    + * @see CaptureRequest#CONTROL_AE_MODE + */ + public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; + // // Enumeration values for CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER // @@ -2551,6 +2664,22 @@ public abstract class CameraMetadata { */ public static final int STATISTICS_LENS_SHADING_MAP_MODE_ON = 1; + // + // Enumeration values for CaptureRequest#STATISTICS_OIS_DATA_MODE + // + + /** + *

    Do not include OIS data in the capture result.

    + * @see CaptureRequest#STATISTICS_OIS_DATA_MODE + */ + public static final int STATISTICS_OIS_DATA_MODE_OFF = 0; + + /** + *

    Include OIS data in the capture result.

    + * @see CaptureRequest#STATISTICS_OIS_DATA_MODE + */ + public static final int STATISTICS_OIS_DATA_MODE_ON = 1; + // // Enumeration values for CaptureRequest#TONEMAP_MODE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index cf27c704f7e540763442ab61bc32229249dee9fe..481b7649610a704f0e3f31f65a3b1a54f9eceed4 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -33,9 +33,11 @@ import android.view.Surface; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; - +import java.util.Set; /** *

    An immutable package of settings and outputs needed to capture a single @@ -219,7 +221,11 @@ public final class CaptureRequest extends CameraMetadata> private static final ArraySet mEmptySurfaceSet = new ArraySet(); - private final CameraMetadataNative mSettings; + private String mLogicalCameraId; + private CameraMetadataNative mLogicalCameraSettings; + private final HashMap mPhysicalCameraSettings = + new HashMap(); + private boolean mIsReprocess; // If this request is part of constrained high speed request list that was created by // {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList} @@ -236,8 +242,6 @@ public final class CaptureRequest extends CameraMetadata> * Used by Binder to unparcel this object only. */ private CaptureRequest() { - mSettings = new CameraMetadataNative(); - setNativeInstance(mSettings); mIsReprocess = false; mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE; } @@ -249,8 +253,14 @@ public final class CaptureRequest extends CameraMetadata> */ @SuppressWarnings("unchecked") private CaptureRequest(CaptureRequest source) { - mSettings = new CameraMetadataNative(source.mSettings); - setNativeInstance(mSettings); + mLogicalCameraId = new String(source.mLogicalCameraId); + for (Map.Entry entry : + source.mPhysicalCameraSettings.entrySet()) { + mPhysicalCameraSettings.put(new String(entry.getKey()), + new CameraMetadataNative(entry.getValue())); + } + mLogicalCameraSettings = mPhysicalCameraSettings.get(mLogicalCameraId); + setNativeInstance(mLogicalCameraSettings); mSurfaceSet.addAll(source.mSurfaceSet); mIsReprocess = source.mIsReprocess; mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList; @@ -272,16 +282,35 @@ public final class CaptureRequest extends CameraMetadata> * reprocess capture request to the same session where * the {@link TotalCaptureResult}, used to create the reprocess * capture, came from. + * @param logicalCameraId Camera Id of the actively open camera that instantiates the + * Builder. + * + * @param physicalCameraIdSet A set of physical camera ids that can be used to customize + * the request for a specific physical camera. * * @throws IllegalArgumentException If creating a reprocess capture request with an invalid - * reprocessableSessionId. + * reprocessableSessionId, or multiple physical cameras. * * @see CameraDevice#createReprocessCaptureRequest */ private CaptureRequest(CameraMetadataNative settings, boolean isReprocess, - int reprocessableSessionId) { - mSettings = CameraMetadataNative.move(settings); - setNativeInstance(mSettings); + int reprocessableSessionId, String logicalCameraId, Set physicalCameraIdSet) { + if ((physicalCameraIdSet != null) && isReprocess) { + throw new IllegalArgumentException("Create a reprocess capture request with " + + "with more than one physical camera is not supported!"); + } + + mLogicalCameraId = logicalCameraId; + mLogicalCameraSettings = CameraMetadataNative.move(settings); + mPhysicalCameraSettings.put(mLogicalCameraId, mLogicalCameraSettings); + if (physicalCameraIdSet != null) { + for (String physicalId : physicalCameraIdSet) { + mPhysicalCameraSettings.put(physicalId, new CameraMetadataNative( + mLogicalCameraSettings)); + } + } + + setNativeInstance(mLogicalCameraSettings); mIsReprocess = isReprocess; if (isReprocess) { if (reprocessableSessionId == CameraCaptureSession.SESSION_ID_NONE) { @@ -309,7 +338,7 @@ public final class CaptureRequest extends CameraMetadata> */ @Nullable public T get(Key key) { - return mSettings.get(key); + return mLogicalCameraSettings.get(key); } /** @@ -319,7 +348,7 @@ public final class CaptureRequest extends CameraMetadata> @SuppressWarnings("unchecked") @Override protected T getProtected(Key key) { - return (T) mSettings.get(key); + return (T) mLogicalCameraSettings.get(key); } /** @@ -403,7 +432,7 @@ public final class CaptureRequest extends CameraMetadata> * @hide */ public CameraMetadataNative getNativeCopy() { - return new CameraMetadataNative(mSettings); + return new CameraMetadataNative(mLogicalCameraSettings); } /** @@ -444,14 +473,16 @@ public final class CaptureRequest extends CameraMetadata> return other != null && Objects.equals(mUserTag, other.mUserTag) && mSurfaceSet.equals(other.mSurfaceSet) - && mSettings.equals(other.mSettings) + && mPhysicalCameraSettings.equals(other.mPhysicalCameraSettings) + && mLogicalCameraId.equals(other.mLogicalCameraId) + && mLogicalCameraSettings.equals(other.mLogicalCameraSettings) && mIsReprocess == other.mIsReprocess && mReprocessableSessionId == other.mReprocessableSessionId; } @Override public int hashCode() { - return HashCodeHelpers.hashCodeGeneric(mSettings, mSurfaceSet, mUserTag); + return HashCodeHelpers.hashCodeGeneric(mPhysicalCameraSettings, mSurfaceSet, mUserTag); } public static final Parcelable.Creator CREATOR = @@ -479,8 +510,25 @@ public final class CaptureRequest extends CameraMetadata> * @hide */ private void readFromParcel(Parcel in) { - mSettings.readFromParcel(in); - setNativeInstance(mSettings); + int physicalCameraCount = in.readInt(); + if (physicalCameraCount <= 0) { + throw new RuntimeException("Physical camera count" + physicalCameraCount + + " should always be positive"); + } + + //Always start with the logical camera id + mLogicalCameraId = in.readString(); + mLogicalCameraSettings = new CameraMetadataNative(); + mLogicalCameraSettings.readFromParcel(in); + setNativeInstance(mLogicalCameraSettings); + mPhysicalCameraSettings.put(mLogicalCameraId, mLogicalCameraSettings); + for (int i = 1; i < physicalCameraCount; i++) { + String physicalId = in.readString(); + CameraMetadataNative physicalCameraSettings = new CameraMetadataNative(); + physicalCameraSettings.readFromParcel(in); + mPhysicalCameraSettings.put(physicalId, physicalCameraSettings); + } + mIsReprocess = (in.readInt() == 0) ? false : true; mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE; @@ -509,7 +557,19 @@ public final class CaptureRequest extends CameraMetadata> @Override public void writeToParcel(Parcel dest, int flags) { - mSettings.writeToParcel(dest, flags); + int physicalCameraCount = mPhysicalCameraSettings.size(); + dest.writeInt(physicalCameraCount); + //Logical camera id and settings always come first. + dest.writeString(mLogicalCameraId); + mLogicalCameraSettings.writeToParcel(dest, flags); + for (Map.Entry entry : mPhysicalCameraSettings.entrySet()) { + if (entry.getKey().equals(mLogicalCameraId)) { + continue; + } + dest.writeString(entry.getKey()); + entry.getValue().writeToParcel(dest, flags); + } + dest.writeInt(mIsReprocess ? 1 : 0); synchronized (mSurfacesLock) { @@ -541,6 +601,14 @@ public final class CaptureRequest extends CameraMetadata> return Collections.unmodifiableCollection(mSurfaceSet); } + /** + * Retrieves the logical camera id. + * @hide + */ + public String getLogicalCameraId() { + return mLogicalCameraId; + } + /** * @hide */ @@ -633,14 +701,20 @@ public final class CaptureRequest extends CameraMetadata> * submits a reprocess capture request to the same session * where the {@link TotalCaptureResult}, used to create the * reprocess capture, came from. + * @param logicalCameraId Camera Id of the actively open camera that instantiates the + * Builder. + * @param physicalCameraIdSet A set of physical camera ids that can be used to customize + * the request for a specific physical camera. * * @throws IllegalArgumentException If creating a reprocess capture request with an invalid * reprocessableSessionId. * @hide */ public Builder(CameraMetadataNative template, boolean reprocess, - int reprocessableSessionId) { - mRequest = new CaptureRequest(template, reprocess, reprocessableSessionId); + int reprocessableSessionId, String logicalCameraId, + Set physicalCameraIdSet) { + mRequest = new CaptureRequest(template, reprocess, reprocessableSessionId, + logicalCameraId, physicalCameraIdSet); } /** @@ -682,7 +756,7 @@ public final class CaptureRequest extends CameraMetadata> * type to the key. */ public void set(@NonNull Key key, T value) { - mRequest.mSettings.set(key, value); + mRequest.mLogicalCameraSettings.set(key, value); } /** @@ -696,7 +770,71 @@ public final class CaptureRequest extends CameraMetadata> */ @Nullable public T get(Key key) { - return mRequest.mSettings.get(key); + return mRequest.mLogicalCameraSettings.get(key); + } + + /** + * Set a capture request field to a value. The field definitions can be + * found in {@link CaptureRequest}. + * + *

    Setting a field to {@code null} will remove that field from the capture request. + * Unless the field is optional, removing it will likely produce an error from the camera + * device when the request is submitted.

    + * + *

    This method can be called for logical camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to + * {@link CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of + * physical devices that are backing the logical camera. The camera Id included in the + * 'physicalCameraId' argument selects an individual physical device that will receive + * the customized capture request field.

    + * + * @throws IllegalArgumentException if the physical camera id is not valid + * + * @param key The metadata field to write. + * @param value The value to set the field to, which must be of a matching + * @param physicalCameraId A valid physical camera Id. The valid camera Ids can be obtained + * via calls to {@link CameraCharacteristics#getPhysicalCameraIds}. + * @return The builder object. + * type to the key. + */ + public Builder setPhysicalCameraKey(@NonNull Key key, T value, + @NonNull String physicalCameraId) { + if (!mRequest.mPhysicalCameraSettings.containsKey(physicalCameraId)) { + throw new IllegalArgumentException("Physical camera id: " + physicalCameraId + + " is not valid!"); + } + + mRequest.mPhysicalCameraSettings.get(physicalCameraId).set(key, value); + + return this; + } + + /** + * Get a capture request field value for a specific physical camera Id. The field + * definitions can be found in {@link CaptureRequest}. + * + *

    This method can be called for logical camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to + * {@link CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of + * physical devices that are backing the logical camera. The camera Id included in the + * 'physicalCameraId' argument selects an individual physical device and returns + * its specific capture request field.

    + * + * @throws IllegalArgumentException if the key or physical camera id were not valid + * + * @param key The metadata field to read. + * @param physicalCameraId A valid physical camera Id. The valid camera Ids can be obtained + * via calls to {@link CameraCharacteristics#getPhysicalCameraIds}. + * @return The value of that key, or {@code null} if the field is not set. + */ + @Nullable + public T getPhysicalCameraKey(Key key,@NonNull String physicalCameraId) { + if (!mRequest.mPhysicalCameraSettings.containsKey(physicalCameraId)) { + throw new IllegalArgumentException("Physical camera id: " + physicalCameraId + + " is not valid!"); + } + + return mRequest.mPhysicalCameraSettings.get(physicalCameraId).get(key); } /** @@ -748,7 +886,7 @@ public final class CaptureRequest extends CameraMetadata> * @hide */ public boolean isEmpty() { - return mRequest.mSettings.isEmpty(); + return mRequest.mLogicalCameraSettings.isEmpty(); } } @@ -1076,6 +1214,7 @@ public final class CaptureRequest extends CameraMetadata> *
  • {@link #CONTROL_AE_MODE_ON_AUTO_FLASH ON_AUTO_FLASH}
  • *
  • {@link #CONTROL_AE_MODE_ON_ALWAYS_FLASH ON_ALWAYS_FLASH}
  • *
  • {@link #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE ON_AUTO_FLASH_REDEYE}
  • + *
  • {@link #CONTROL_AE_MODE_ON_EXTERNAL_FLASH ON_EXTERNAL_FLASH}
  • *

    *

    Available values for this device:
    * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES android.control.aeAvailableModes}

    @@ -1093,6 +1232,7 @@ public final class CaptureRequest extends CameraMetadata> * @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 CONTROL_AE_MODE = @@ -2616,6 +2756,29 @@ public final class CaptureRequest extends CameraMetadata> public static final Key STATISTICS_LENS_SHADING_MAP_MODE = new Key("android.statistics.lensShadingMapMode", int.class); + /** + *

    Whether the camera device outputs the OIS data in output + * result metadata.

    + *

    When set to ON, + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}, android.statistics.oisShiftPixelX, + * android.statistics.oisShiftPixelY will provide OIS data in the output result metadata.

    + *

    Possible values: + *

      + *
    • {@link #STATISTICS_OIS_DATA_MODE_OFF OFF}
    • + *
    • {@link #STATISTICS_OIS_DATA_MODE_ON ON}
    • + *

    + *

    Available values for this device:
    + * android.Statistics.info.availableOisDataModes

    + *

    Optional - This value may be {@code null} on some devices.

    + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @see #STATISTICS_OIS_DATA_MODE_OFF + * @see #STATISTICS_OIS_DATA_MODE_ON + */ + @PublicKey + public static final Key STATISTICS_OIS_DATA_MODE = + new Key("android.statistics.oisDataMode", int.class); + /** *

    Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index b6b0c907a8f93b5cfa16f47de6bcfefaa9a1d9d6..d730fa8a31cf09f33ab03bfc473cfd13eb10523a 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> { *

  • {@link #CONTROL_AE_MODE_ON_AUTO_FLASH ON_AUTO_FLASH}
  • *
  • {@link #CONTROL_AE_MODE_ON_ALWAYS_FLASH ON_ALWAYS_FLASH}
  • *
  • {@link #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE ON_AUTO_FLASH_REDEYE}
  • + *
  • {@link #CONTROL_AE_MODE_ON_EXTERNAL_FLASH ON_EXTERNAL_FLASH}
  • *

    *

    Available values for this device:
    * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES android.control.aeAvailableModes}

    @@ -708,6 +709,7 @@ public class CaptureResult extends CameraMetadata> { * @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 CONTROL_AE_MODE = @@ -877,7 +879,7 @@ public class CaptureResult extends CameraMetadata> { * * * - *

    When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON_*:

    + *

    When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON*:

    * * * @@ -998,10 +1000,13 @@ public class CaptureResult extends CameraMetadata> { * * *
    + *

    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.

    *

    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.

    - *

    For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions + *

    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:

    * @@ -1072,6 +1077,7 @@ public class CaptureResult extends CameraMetadata> { * 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

    * + * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES * @see CaptureRequest#CONTROL_AE_LOCK * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER @@ -2197,8 +2203,6 @@ public class CaptureResult extends CameraMetadata> { * significant illumination change, this value will be set to DETECTED for a single capture * result. Otherwise the value will be NOT_DETECTED. The threshold for detection is similar * to what would trigger a new passive focus scan to begin in CONTINUOUS autofocus modes.

    - *

    afSceneChange may be DETECTED only if afMode is AF_MODE_CONTINUOUS_VIDEO or - * AF_MODE_CONTINUOUS_PICTURE. In other AF modes, afSceneChange must be NOT_DETECTED.

    *

    This key will be available if the camera device advertises this key via {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.

    *

    Possible values: *

      @@ -3904,6 +3908,76 @@ public class CaptureResult extends CameraMetadata> { public static final Key STATISTICS_LENS_SHADING_MAP_MODE = new Key("android.statistics.lensShadingMapMode", int.class); + /** + *

      Whether the camera device outputs the OIS data in output + * result metadata.

      + *

      When set to ON, + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}, android.statistics.oisShiftPixelX, + * android.statistics.oisShiftPixelY will provide OIS data in the output result metadata.

      + *

      Possible values: + *

        + *
      • {@link #STATISTICS_OIS_DATA_MODE_OFF OFF}
      • + *
      • {@link #STATISTICS_OIS_DATA_MODE_ON ON}
      • + *

      + *

      Available values for this device:
      + * android.Statistics.info.availableOisDataModes

      + *

      Optional - This value may be {@code null} on some devices.

      + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @see #STATISTICS_OIS_DATA_MODE_OFF + * @see #STATISTICS_OIS_DATA_MODE_ON + */ + @PublicKey + public static final Key STATISTICS_OIS_DATA_MODE = + new Key("android.statistics.oisDataMode", int.class); + + /** + *

      An array of timestamps of OIS samples, in nanoseconds.

      + *

      The array contains the timestamps of OIS samples. The timestamps are in the same + * timebase as and comparable to {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp}.

      + *

      Units: nanoseconds

      + *

      Optional - This value may be {@code null} on some devices.

      + * + * @see CaptureResult#SENSOR_TIMESTAMP + */ + @PublicKey + public static final Key STATISTICS_OIS_TIMESTAMPS = + new Key("android.statistics.oisTimestamps", long[].class); + + /** + *

      An array of shifts of OIS samples, in x direction.

      + *

      The array contains the amount of shifts in x direction, in pixels, based on OIS samples. + * A positive value is a shift from left to right in active array coordinate system. For + * example, if the optical center is (1000, 500) in active array coordinates, an shift of + * (3, 0) puts the new optical center at (1003, 500).

      + *

      The number of shifts must match the number of timestamps in + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}.

      + *

      Units: Pixels in active array.

      + *

      Optional - This value may be {@code null} on some devices.

      + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + */ + @PublicKey + public static final Key STATISTICS_OIS_X_SHIFTS = + new Key("android.statistics.oisXShifts", float[].class); + + /** + *

      An array of shifts of OIS samples, in y direction.

      + *

      The array contains the amount of shifts in y direction, in pixels, based on OIS samples. + * A positive value is a shift from top to bottom in active array coordinate system. For + * example, if the optical center is (1000, 500) in active array coordinates, an shift of + * (0, 5) puts the new optical center at (1000, 505).

      + *

      The number of shifts must match the number of timestamps in + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}.

      + *

      Units: Pixels in active array.

      + *

      Optional - This value may be {@code null} on some devices.

      + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + */ + @PublicKey + public static final Key STATISTICS_OIS_Y_SHIFTS = + new Key("android.statistics.oisYShifts", float[].class); + /** *

      Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index 657745c80e913bfc67678481a10e0f089abb7347..bae2d04b0a24d6befc0f640cce5201912258485e 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -17,11 +17,14 @@ package android.hardware.camera2; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; /** @@ -44,6 +47,12 @@ import java.util.List; * as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on * a case-by-case basis.

      * + *

      For a logical multi-camera device, if the CaptureRequest contains a surface for an underlying + * physical camera, the corresponding {@link TotalCaptureResult} object will include the metadata + * for that physical camera. And its keys and values can be accessed by + * {@link #getPhysicalCameraKey}. If all requested surfaces are for the logical camera, no + * metadata for physical camera will be included.

      + * *

      {@link TotalCaptureResult} objects are immutable.

      * * @see CameraDevice.CaptureCallback#onCaptureCompleted @@ -52,6 +61,8 @@ public final class TotalCaptureResult extends CaptureResult { private final List mPartialResults; private final int mSessionId; + // The map between physical camera id and capture result + private final HashMap mPhysicalCaptureResults; /** * Takes ownership of the passed-in camera metadata and the partial results @@ -60,7 +71,8 @@ public final class TotalCaptureResult extends CaptureResult { * @hide */ public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, - CaptureResultExtras extras, List partials, int sessionId) { + CaptureResultExtras extras, List partials, int sessionId, + PhysicalCaptureResultInfo physicalResults[]) { super(results, parent, extras); if (partials == null) { @@ -70,6 +82,12 @@ public final class TotalCaptureResult extends CaptureResult { } mSessionId = sessionId; + + mPhysicalCaptureResults = new HashMap(); + for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) { + mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(), + onePhysicalResult.getCameraMetadata()); + } } /** @@ -83,6 +101,7 @@ public final class TotalCaptureResult extends CaptureResult { mPartialResults = new ArrayList<>(); mSessionId = CameraCaptureSession.SESSION_ID_NONE; + mPhysicalCaptureResults = new HashMap(); } /** @@ -111,4 +130,38 @@ public final class TotalCaptureResult extends CaptureResult { public int getSessionId() { return mSessionId; } + + /** + * Get a capture result field value for a particular physical camera id. + * + *

      The field definitions can be found in {@link CaptureResult}.

      + * + *

      This function can be called for logical camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to {@link + * CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of physical devices that + * are backing the logical camera. The camera id included in physicalCameraId argument + * selects an individual physical device, and returns its specific capture result field.

      + * + *

      This function should only be called if one or more streams from the underlying + * 'physicalCameraId' was requested by the corresponding capture request.

      + * + * @throws IllegalArgumentException if the key was not valid, or the physicalCameraId is not + * applicable to the current camera, or a stream from 'physicalCameraId' is not requested by the + * corresponding capture request. + * + * @param key The result field to read. + * @param physicalCameraId The physical camera the result originates from. + * + * @return The value of that key, or {@code null} if the field is not set. + */ + @Nullable + public T getPhysicalCameraKey(Key key, @NonNull String physicalCameraId) { + if (!mPhysicalCaptureResults.containsKey(physicalCameraId)) { + throw new IllegalArgumentException( + "No TotalCaptureResult exists for physical camera " + physicalCameraId); + } + + CameraMetadataNative physicalMetadata = mPhysicalCaptureResults.get(physicalCameraId); + return physicalMetadata.get(key); + } } diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java index 8c4dbfa58d051f501d8497dc8a28074be7b58de2..06c2c25ab6bbb465b1a8d4b740f301952e5ce628 100644 --- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java @@ -94,8 +94,8 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl // Note that after this step, the requestMetadata is mutated (swapped) and can not be used // for next request builder creation. CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder( - requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); - + requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + request.getLogicalCameraId(), /*physicalCameraIdSet*/ null); // Carry over userTag, as native metadata doesn't have this field. singleTargetRequestBuilder.setTag(request.getTag()); @@ -120,7 +120,8 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl // CaptureRequest.Builder creation. requestMetadata = new CameraMetadataNative(request.getNativeCopy()); doubleTargetRequestBuilder = new CaptureRequest.Builder( - requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + request.getLogicalCameraId(), /*physicalCameraIdSet*/null); doubleTargetRequestBuilder.setTag(request.getTag()); doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index f1ffb890eecd1ecbbcff62888803d58c73b1c796..511fa43a544132761dce9e5e558b090ef62c4767 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -16,27 +16,25 @@ package android.hardware.camera2.impl; -import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; +import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; -import android.graphics.ImageFormat; +import android.hardware.ICameraService; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; -import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.OutputConfiguration; -import android.hardware.camera2.params.ReprocessFormatsMap; import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.SubmitInfo; import android.hardware.camera2.utils.SurfaceUtils; -import android.hardware.ICameraService; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -51,16 +49,15 @@ import android.view.Surface; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.LinkedList; +import java.util.List; +import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicBoolean; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -714,6 +711,38 @@ public class CameraDeviceImpl extends CameraDevice request.set(CaptureRequest.CONTROL_ENABLE_ZSL, newValue); } + @Override + public CaptureRequest.Builder createCaptureRequest(int templateType, + Set physicalCameraIdSet) + throws CameraAccessException { + synchronized(mInterfaceLock) { + checkIfCameraClosedOrInError(); + + for (String physicalId : physicalCameraIdSet) { + if (physicalId == getId()) { + throw new IllegalStateException("Physical id matches the logical id!"); + } + } + + CameraMetadataNative templatedRequest = null; + + templatedRequest = mRemoteDevice.createDefaultRequest(templateType); + + // If app target SDK is older than O, or it's not a still capture template, enableZsl + // must be false in the default request. + if (mAppTargetSdkVersion < Build.VERSION_CODES.O || + templateType != TEMPLATE_STILL_CAPTURE) { + overrideEnableZsl(templatedRequest, false); + } + + CaptureRequest.Builder builder = new CaptureRequest.Builder( + templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + getId(), physicalCameraIdSet); + + return builder; + } + } + @Override public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { @@ -732,7 +761,8 @@ public class CameraDeviceImpl extends CameraDevice } CaptureRequest.Builder builder = new CaptureRequest.Builder( - templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + getId(), /*physicalCameraIdSet*/ null); return builder; } @@ -748,7 +778,7 @@ public class CameraDeviceImpl extends CameraDevice CameraMetadataNative(inputResult.getNativeCopy()); return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, - inputResult.getSessionId()); + inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null); } } @@ -958,7 +988,8 @@ public class CameraDeviceImpl extends CameraDevice // callback is valid handler = checkHandler(handler, callback); - // Make sure that there all requests have at least 1 surface; all surfaces are non-null + // Make sure that there all requests have at least 1 surface; all surfaces are non-null; + // the surface isn't a physical stream surface for reprocessing request for (CaptureRequest request : requestList) { if (request.getTargets().isEmpty()) { throw new IllegalArgumentException( @@ -969,6 +1000,17 @@ public class CameraDeviceImpl extends CameraDevice if (surface == null) { throw new IllegalArgumentException("Null Surface targets are not allowed"); } + + for (int i = 0; i < mConfiguredOutputs.size(); i++) { + OutputConfiguration configuration = mConfiguredOutputs.valueAt(i); + if (configuration.isForPhysicalCamera() + && configuration.getSurfaces().contains(surface)) { + if (request.isReprocess()) { + throw new IllegalArgumentException( + "Reprocess request on physical stream is not allowed"); + } + } + } } } @@ -1798,34 +1840,36 @@ public class CameraDeviceImpl extends CameraDevice case ERROR_CAMERA_DISCONNECTED: CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected); break; - default: - Log.e(TAG, "Unknown error from camera device: " + errorCode); - // no break - case ERROR_CAMERA_DEVICE: - case ERROR_CAMERA_SERVICE: - mInError = true; - final int publicErrorCode = (errorCode == ERROR_CAMERA_DEVICE) ? - StateCallback.ERROR_CAMERA_DEVICE : - StateCallback.ERROR_CAMERA_SERVICE; - Runnable r = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()) { - mDeviceCallback.onError(CameraDeviceImpl.this, publicErrorCode); - } - } - }; - CameraDeviceImpl.this.mDeviceHandler.post(r); - break; case ERROR_CAMERA_REQUEST: case ERROR_CAMERA_RESULT: case ERROR_CAMERA_BUFFER: onCaptureErrorLocked(errorCode, resultExtras); break; + case ERROR_CAMERA_DEVICE: + scheduleNotifyError(StateCallback.ERROR_CAMERA_DEVICE); + break; + case ERROR_CAMERA_DISABLED: + scheduleNotifyError(StateCallback.ERROR_CAMERA_DISABLED); + break; + default: + Log.e(TAG, "Unknown error from camera device: " + errorCode); + scheduleNotifyError(StateCallback.ERROR_CAMERA_SERVICE); } } } + private void scheduleNotifyError(int code) { + mInError = true; + CameraDeviceImpl.this.mDeviceHandler.post(obtainRunnable( + CameraDeviceCallbacks::notifyError, this, code)); + } + + private void notifyError(int code) { + if (!CameraDeviceImpl.this.isClosed()) { + mDeviceCallback.onError(CameraDeviceImpl.this, code); + } + } + @Override public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) { if (DEBUG) { @@ -1920,7 +1964,8 @@ public class CameraDeviceImpl extends CameraDevice @Override public void onResultReceived(CameraMetadataNative result, - CaptureResultExtras resultExtras) throws RemoteException { + CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[]) + throws RemoteException { int requestId = resultExtras.getRequestId(); long frameNumber = resultExtras.getFrameNumber(); @@ -2027,7 +2072,8 @@ public class CameraDeviceImpl extends CameraDevice request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); final int subsequenceId = resultExtras.getSubsequenceId(); final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, - request, resultExtras, partialResults, holder.getSessionId()); + request, resultExtras, partialResults, holder.getSessionId(), + physicalResults); // Final capture result resultDispatch = new Runnable() { @Override @@ -2042,9 +2088,11 @@ public class CameraDeviceImpl extends CameraDevice NANO_PER_SECOND/fpsRange.getUpper()); CameraMetadataNative resultLocal = new CameraMetadataNative(resultCopy); + // No logical multi-camera support for batched output mode. TotalCaptureResult resultInBatch = new TotalCaptureResult( resultLocal, holder.getRequest(i), resultExtras, - partialResults, holder.getSessionId()); + partialResults, holder.getSessionId(), + new PhysicalCaptureResultInfo[0]); holder.getCallback().onCaptureCompleted( CameraDeviceImpl.this, diff --git a/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..30eaf1373c8923926234aaa1330327205387df8f --- /dev/null +++ b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.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 android.hardware.camera2.impl; + +import android.hardware.camera2.impl.CameraMetadataNative; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +public class PhysicalCaptureResultInfo implements Parcelable { + private String cameraId; + private CameraMetadataNative cameraMetadata; + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public PhysicalCaptureResultInfo createFromParcel(Parcel in) { + return new PhysicalCaptureResultInfo(in); + } + + @Override + public PhysicalCaptureResultInfo[] newArray(int size) { + return new PhysicalCaptureResultInfo[size]; + } + }; + + private PhysicalCaptureResultInfo(Parcel in) { + readFromParcel(in); + } + + public PhysicalCaptureResultInfo(String cameraId, CameraMetadataNative cameraMetadata) { + this.cameraId = cameraId; + this.cameraMetadata = cameraMetadata; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(cameraId); + cameraMetadata.writeToParcel(dest, flags); + } + + public void readFromParcel(Parcel in) { + cameraId = in.readString(); + cameraMetadata = new CameraMetadataNative(); + cameraMetadata.readFromParcel(in); + } + + public String getCameraId() { + return cameraId; + } + + public CameraMetadataNative getCameraMetadata() { + return cameraMetadata; + } +} diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index eccab7500abfea01faf1c7bc7926a25438f91b1f..bc7b1260751e5f115dc4e1e6a21a95a110fa723d 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -26,6 +26,7 @@ import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.SubmitInfo; import android.os.ConditionVariable; @@ -249,7 +250,8 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { @Override public void onResultReceived(final CameraMetadataNative result, - final CaptureResultExtras resultExtras) { + final CaptureResultExtras resultExtras, + PhysicalCaptureResultInfo physicalResults[]) { Object[] resultArray = new Object[] { result, resultExtras }; Message msg = getHandler().obtainMessage(RESULT_RECEIVED, /*obj*/ resultArray); @@ -320,7 +322,8 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { Object[] resultArray = (Object[]) msg.obj; CameraMetadataNative result = (CameraMetadataNative) resultArray[0]; CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1]; - mCallbacks.onResultReceived(result, resultExtras); + mCallbacks.onResultReceived(result, resultExtras, + new PhysicalCaptureResultInfo[0]); break; } case PREPARED: { diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index cb59fd14f5c5bd0877e38349b9b85bb5a135563a..e7f2134de0ee454a89e1f265e37a4a7357a1dfb1 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -23,6 +23,7 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.impl.CameraDeviceImpl; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.ArrayUtils; @@ -253,7 +254,8 @@ public class LegacyCameraDevice implements AutoCloseable { holder.getRequestId()); } try { - mDeviceCallbacks.onResultReceived(result, extras); + mDeviceCallbacks.onResultReceived(result, extras, + new PhysicalCaptureResultInfo[0]); } catch (RemoteException e) { throw new IllegalStateException( "Received remote exception during onCameraError callback: ", e); diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index a85b5f710696542464cdacbf43bb56a69856724b..f47cd665fd9c193d3a74d2ab7c43cf737142a68a 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -31,13 +31,12 @@ import android.util.Log; import android.util.Size; import android.view.Surface; -import java.util.Arrays; -import java.util.List; -import java.util.Collections; -import java.util.ArrayList; - import static com.android.internal.util.Preconditions.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * A class for describing camera output, which contains a {@link Surface} and its specific * configuration for creating capture session. @@ -266,6 +265,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredGenerationId = surface.getGenerationId(); mIsDeferredConfig = false; mIsShared = false; + mPhysicalCameraId = null; } /** @@ -319,6 +319,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredGenerationId = 0; mIsDeferredConfig = true; mIsShared = false; + mPhysicalCameraId = null; } /** @@ -348,8 +349,9 @@ public final class OutputConfiguration implements Parcelable { * * *

      To enable surface sharing, this function must be called before {@link - * CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link - * CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.

      + * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link + * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function after + * {@link CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.

      * *

      Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration. * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView, @@ -359,6 +361,44 @@ public final class OutputConfiguration implements Parcelable { mIsShared = true; } + /** + * Set the id of the physical camera for this OutputConfiguration + * + *

      In the case one logical camera is made up of multiple physical cameras, it could be + * desirable for the camera application to request streams from individual physical cameras. + * This call achieves it by mapping the OutputConfiguration to the physical camera id.

      + * + *

      The valid physical camera id can be queried by {@link + * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}. + *

      + * + *

      Passing in a null physicalCameraId means that the OutputConfiguration is for a logical + * stream.

      + * + *

      This function must be called before {@link + * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link + * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function + * after {@link CameraDevice#createCaptureSessionByOutputConfigurations} or {@link + * CameraDevice#createReprocessableCaptureSessionByConfigurations} has no effect.

      + * + *

      The surface belonging to a physical camera OutputConfiguration must not be used as input + * or output of a reprocessing request.

      + */ + public void setPhysicalCameraId(@Nullable String physicalCameraId) { + mPhysicalCameraId = physicalCameraId; + } + + /** + * Check if this configuration is for a physical camera. + * + *

      This returns true if the output configuration was for a physical camera making up a + * logical multi camera via {@link OutputConfiguration#setPhysicalCameraId}.

      + * @hide + */ + public boolean isForPhysicalCamera() { + return (mPhysicalCameraId != null); + } + /** * Check if this configuration has deferred configuration. * @@ -487,6 +527,7 @@ public final class OutputConfiguration implements Parcelable { this.mConfiguredGenerationId = other.mConfiguredGenerationId; this.mIsDeferredConfig = other.mIsDeferredConfig; this.mIsShared = other.mIsShared; + this.mPhysicalCameraId = other.mPhysicalCameraId; } /** @@ -502,6 +543,7 @@ public final class OutputConfiguration implements Parcelable { boolean isShared = source.readInt() == 1; ArrayList surfaces = new ArrayList(); source.readTypedList(surfaces, Surface.CREATOR); + String physicalCameraId = source.readString(); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); @@ -524,6 +566,7 @@ public final class OutputConfiguration implements Parcelable { StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); mConfiguredGenerationId = 0; } + mPhysicalCameraId = physicalCameraId; } /** @@ -622,6 +665,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mIsDeferredConfig ? 1 : 0); dest.writeInt(mIsShared ? 1 : 0); dest.writeTypedList(mSurfaces); + dest.writeString(mPhysicalCameraId); } /** @@ -675,13 +719,15 @@ public final class OutputConfiguration implements Parcelable { if (mIsDeferredConfig) { return HashCodeHelpers.hashCode( mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, - mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0); + mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); } return HashCodeHelpers.hashCode( mRotation, mSurfaces.hashCode(), mConfiguredGenerationId, mConfiguredSize.hashCode(), mConfiguredFormat, - mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0); + mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); } private static final String TAG = "OutputConfiguration"; @@ -701,4 +747,6 @@ public final class OutputConfiguration implements Parcelable { private final boolean mIsDeferredConfig; // Flag indicating if this config has shared surfaces private boolean mIsShared; + // The physical camera id that this output configuration is for. + private String mPhysicalCameraId; } diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java index 0a08353cbe4ca35a7bbe51283babc35e9e79d2ea..2301824cb9dc1aee110766a3e26546d3c12790b2 100644 --- a/core/java/android/hardware/display/BrightnessChangeEvent.java +++ b/core/java/android/hardware/display/BrightnessChangeEvent.java @@ -16,6 +16,8 @@ package android.hardware.display; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -23,51 +25,65 @@ import android.os.Parcelable; * Data about a brightness settings change. * * {@see DisplayManager.getBrightnessEvents()} - * TODO make this SystemAPI * @hide */ +@SystemApi +@TestApi public final class BrightnessChangeEvent implements Parcelable { /** Brightness in nits */ - public float brightness; + public final float brightness; /** Timestamp of the change {@see System.currentTimeMillis()} */ - public long timeStamp; + public final long timeStamp; /** Package name of focused activity when brightness was changed. * This will be null if the caller of {@see DisplayManager.getBrightnessEvents()} * does not have access to usage stats {@see UsageStatsManager} */ - public String packageName; + public final String packageName; /** User id of of the user running when brightness was changed. * @hide */ - public int userId; + public final int userId; /** Lux values of recent sensor data */ - public float[] luxValues; + public final float[] luxValues; /** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */ - public long[] luxTimestamps; + public final long[] luxTimestamps; /** Most recent battery level when brightness was changed or Float.NaN */ - public float batteryLevel; + public final float batteryLevel; /** Color filter active to provide night mode */ - public boolean nightMode; + public final boolean nightMode; /** If night mode color filter is active this will be the temperature in kelvin */ - public int colorTemperature; + public final int colorTemperature; - /** Brightness level before slider adjustment */ - public float lastBrightness; + /** Brightness le vel before slider adjustment */ + public final float lastBrightness; - public BrightnessChangeEvent() { + /** @hide */ + private BrightnessChangeEvent(float brightness, long timeStamp, String packageName, + int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel, + boolean nightMode, int colorTemperature, float lastBrightness) { + this.brightness = brightness; + this.timeStamp = timeStamp; + this.packageName = packageName; + this.userId = userId; + this.luxValues = luxValues; + this.luxTimestamps = luxTimestamps; + this.batteryLevel = batteryLevel; + this.nightMode = nightMode; + this.colorTemperature = colorTemperature; + this.lastBrightness = lastBrightness; } /** @hide */ - public BrightnessChangeEvent(BrightnessChangeEvent other) { + public BrightnessChangeEvent(BrightnessChangeEvent other, boolean redactPackage) { this.brightness = other.brightness; this.timeStamp = other.timeStamp; - this.packageName = other.packageName; + this.packageName = redactPackage ? null : other.packageName; this.userId = other.userId; this.luxValues = other.luxValues; this.luxTimestamps = other.luxTimestamps; @@ -118,4 +134,85 @@ public final class BrightnessChangeEvent implements Parcelable { dest.writeInt(colorTemperature); dest.writeFloat(lastBrightness); } + + /** @hide */ + public static class Builder { + private float mBrightness; + private long mTimeStamp; + private String mPackageName; + private int mUserId; + private float[] mLuxValues; + private long[] mLuxTimestamps; + private float mBatteryLevel; + private boolean mNightMode; + private int mColorTemperature; + private float mLastBrightness; + + /** {@see BrightnessChangeEvent#brightness} */ + public Builder setBrightness(float brightness) { + mBrightness = brightness; + return this; + } + + /** {@see BrightnessChangeEvent#timeStamp} */ + public Builder setTimeStamp(long timeStamp) { + mTimeStamp = timeStamp; + return this; + } + + /** {@see BrightnessChangeEvent#packageName} */ + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + /** {@see BrightnessChangeEvent#userId} */ + public Builder setUserId(int userId) { + mUserId = userId; + return this; + } + + /** {@see BrightnessChangeEvent#luxValues} */ + public Builder setLuxValues(float[] luxValues) { + mLuxValues = luxValues; + return this; + } + + /** {@see BrightnessChangeEvent#luxTimestamps} */ + public Builder setLuxTimestamps(long[] luxTimestamps) { + mLuxTimestamps = luxTimestamps; + return this; + } + + /** {@see BrightnessChangeEvent#batteryLevel} */ + public Builder setBatteryLevel(float batteryLevel) { + mBatteryLevel = batteryLevel; + return this; + } + + /** {@see BrightnessChangeEvent#nightMode} */ + public Builder setNightMode(boolean nightMode) { + mNightMode = nightMode; + return this; + } + + /** {@see BrightnessChangeEvent#colorTemperature} */ + public Builder setColorTemperature(int colorTemperature) { + mColorTemperature = colorTemperature; + return this; + } + + /** {@see BrightnessChangeEvent#lastBrightness} */ + public Builder setLastBrightness(float lastBrightness) { + mLastBrightness = lastBrightness; + return this; + } + + /** Builds a BrightnessChangeEvent */ + public BrightnessChangeEvent build() { + return new BrightnessChangeEvent(mBrightness, mTimeStamp, + mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel, + mNightMode, mColorTemperature, mLastBrightness); + } + } } diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java index 6c3be816e87be0681f43f1dbc64730d59501f0cd..67e97bfd3d62a2a4354b998b706674f109639f45 100644 --- a/core/java/android/hardware/display/BrightnessConfiguration.java +++ b/core/java/android/hardware/display/BrightnessConfiguration.java @@ -16,6 +16,9 @@ package android.hardware.display; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; @@ -23,15 +26,20 @@ import android.util.Pair; import com.android.internal.util.Preconditions; import java.util.Arrays; +import java.util.Objects; /** @hide */ +@SystemApi +@TestApi public final class BrightnessConfiguration implements Parcelable { private final float[] mLux; private final float[] mNits; + private final String mDescription; - private BrightnessConfiguration(float[] lux, float[] nits) { + private BrightnessConfiguration(float[] lux, float[] nits, String description) { mLux = lux; mNits = nits; + mDescription = description; } /** @@ -47,10 +55,19 @@ public final class BrightnessConfiguration implements Parcelable { return Pair.create(Arrays.copyOf(mLux, mLux.length), Arrays.copyOf(mNits, mNits.length)); } + /** + * Returns description string. + * @hide + */ + public String getDescription() { + return mDescription; + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeFloatArray(mLux); dest.writeFloatArray(mNits); + dest.writeString(mDescription); } @Override @@ -68,7 +85,9 @@ public final class BrightnessConfiguration implements Parcelable { } sb.append("(").append(mLux[i]).append(", ").append(mNits[i]).append(")"); } - sb.append("]}"); + sb.append("], '"); + sb.append(mDescription); + sb.append("'}"); return sb.toString(); } @@ -77,6 +96,7 @@ public final class BrightnessConfiguration implements Parcelable { int result = 1; result = result * 31 + Arrays.hashCode(mLux); result = result * 31 + Arrays.hashCode(mNits); + result = result * 31 + mDescription.hashCode(); return result; } @@ -89,16 +109,17 @@ public final class BrightnessConfiguration implements Parcelable { return false; } final BrightnessConfiguration other = (BrightnessConfiguration) o; - return Arrays.equals(mLux, other.mLux) && Arrays.equals(mNits, other.mNits); + return Arrays.equals(mLux, other.mLux) && Arrays.equals(mNits, other.mNits) + && Objects.equals(mDescription, other.mDescription); } public static final Creator CREATOR = new Creator() { public BrightnessConfiguration createFromParcel(Parcel in) { - Builder builder = new Builder(); float[] lux = in.createFloatArray(); float[] nits = in.createFloatArray(); - builder.setCurve(lux, nits); + Builder builder = new Builder(lux, nits); + builder.setDescription(in.readString()); return builder.build(); } @@ -113,6 +134,29 @@ public final class BrightnessConfiguration implements Parcelable { public static class Builder { private float[] mCurveLux; private float[] mCurveNits; + private String mDescription; + + /** + * STOPSHIP remove when app has stopped using this. + * @hide + */ + public Builder() { + } + + /** + * Constructs the builder with the control points for the brightness curve. + * + * Brightness curves must have strictly increasing ambient brightness values in lux and + * monotonically increasing display brightness values in nits. In addition, the initial + * control point must be 0 lux. + * + * @throws IllegalArgumentException if the initial control point is not at 0 lux. + * @throws IllegalArgumentException if the lux levels are not strictly increasing. + * @throws IllegalArgumentException if the nit levels are not monotonically increasing. + */ + public Builder(float[] lux, float[] nits) { + setCurve(lux, nits); + } /** * Sets the control points for the brightness curve. @@ -124,6 +168,9 @@ public final class BrightnessConfiguration implements Parcelable { * @throws IllegalArgumentException if the initial control point is not at 0 lux. * @throws IllegalArgumentException if the lux levels are not strictly increasing. * @throws IllegalArgumentException if the nit levels are not monotonically increasing. + * + * STOPSHIP remove when app has stopped using this. + * @hide */ public Builder setCurve(float[] lux, float[] nits) { Preconditions.checkNotNull(lux); @@ -146,6 +193,17 @@ public final class BrightnessConfiguration implements Parcelable { return this; } + /** + * Set description of the brightness curve. + * + * @param description brief text describing the curve pushed. It maybe truncated + * and will not be displayed in the UI + */ + public Builder setDescription(@Nullable String description) { + mDescription = description; + return this; + } + /** * Builds the {@link BrightnessConfiguration}. * @@ -155,7 +213,7 @@ public final class BrightnessConfiguration implements Parcelable { if (mCurveLux == null || mCurveNits == null) { throw new IllegalStateException("A curve must be set!"); } - return new BrightnessConfiguration(mCurveLux, mCurveNits); + return new BrightnessConfiguration(mCurveLux, mCurveNits, mDescription); } private static void checkMonotonic(float[] vals, boolean strictlyIncreasing, String name) { diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 97e9b9c2e2f46135b560a6875609b2f8e7ae5af1..4de4880b7c171f194f51678715b7a12d92c07926 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.KeyguardManager; import android.content.Context; import android.graphics.Point; @@ -622,6 +623,8 @@ public final class DisplayManager { * Fetch {@link BrightnessChangeEvent}s. * @hide until we make it a system api. */ + @SystemApi + @TestApi @RequiresPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public List getBrightnessEvents() { return mGlobal.getBrightnessEvents(mContext.getOpPackageName()); @@ -632,8 +635,11 @@ public final class DisplayManager { * * @hide */ + @SystemApi + @TestApi + @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(BrightnessConfiguration c) { - setBrightnessConfigurationForUser(c, UserHandle.myUserId()); + setBrightnessConfigurationForUser(c, UserHandle.myUserId(), mContext.getPackageName()); } /** @@ -644,8 +650,37 @@ public final class DisplayManager { * * @hide */ - public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId) { - mGlobal.setBrightnessConfigurationForUser(c, userId); + public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId, + String packageName) { + mGlobal.setBrightnessConfigurationForUser(c, userId, packageName); + } + + /** + * Temporarily sets the brightness of the display. + *

      + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + *

      + * + * @param brightness The brightness value from 0 to 255. + * + * @hide Requires signature permission. + */ + public void setTemporaryBrightness(int brightness) { + mGlobal.setTemporaryBrightness(brightness); + } + + /** + * Temporarily sets the auto brightness adjustment factor. + *

      + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + *

      + * + * @param adjustment The adjustment factor from -1.0 to 1.0. + * + * @hide Requires signature permission. + */ + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + mGlobal.setTemporaryAutoBrightnessAdjustment(adjustment); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index cbb5a7de7db8d9eed4f0106d79b706b14d916eb9..2d5f5e04148685c6aafaaf995144506e7b5d458b 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -480,9 +480,46 @@ public final class DisplayManagerGlobal { * * @hide */ - public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId) { + public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userId, + String packageName) { try { - mDm.setBrightnessConfigurationForUser(c, userId); + mDm.setBrightnessConfigurationForUser(c, userId, packageName); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Temporarily sets the brightness of the display. + *

      + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + *

      + * + * @param brightness The brightness value from 0 to 255. + * + * @hide Requires signature permission. + */ + public void setTemporaryBrightness(int brightness) { + try { + mDm.setTemporaryBrightness(brightness); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Temporarily sets the auto brightness adjustment factor. + *

      + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + *

      + * + * @param adjustment The adjustment factor from -1.0 to 1.0. + * + * @hide Requires signature permission. + */ + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + try { + mDm.setTemporaryAutoBrightnessAdjustment(adjustment); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 3f6dd2e757edb8a6530d48d1da0e7bc548b68978..1cfad4f0168f42e453991c45326e6749c1180f15 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -178,6 +178,11 @@ public abstract class DisplayManagerInternal { */ public abstract void persistBrightnessSliderEvents(); + /** + * Notifies the display manager that resource overlays have changed. + */ + public abstract void onOverlayChanged(); + /** * Describes the requested power state of the display. * @@ -209,23 +214,12 @@ public abstract class DisplayManagerInternal { // nearby, turning it off temporarily until the object is moved away. public boolean useProximitySensor; - // The desired screen brightness in the range 0 (minimum / off) to 255 (brightest). - // The display power controller may choose to clamp the brightness. - // When auto-brightness is enabled, this field should specify a nominal default - // value to use while waiting for the light sensor to report enough data. - public int screenBrightness; - - // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter). - public float screenAutoBrightnessAdjustment; + // An override of the screen brightness. Set to -1 is used if there's no override. + public int screenBrightnessOverride; - // Set to true if screenBrightness and screenAutoBrightnessAdjustment were both - // set by the user as opposed to being programmatically controlled by apps. - public boolean brightnessSetByUser; - - // Set to true if screenBrightness or screenAutoBrightnessAdjustment are being set - // temporarily. This is typically set while the user has their finger on the brightness - // control, before they've selected the final brightness value. - public boolean brightnessIsTemporary; + // An override of the screen auto-brightness adjustment factor in the range -1 (dimmer) to + // 1 (brighter). Set to Float.NaN if there's no override. + public float screenAutoBrightnessAdjustmentOverride; // If true, enables automatic brightness control. public boolean useAutoBrightness; @@ -257,10 +251,10 @@ public abstract class DisplayManagerInternal { public DisplayPowerRequest() { policy = POLICY_BRIGHT; useProximitySensor = false; - screenBrightness = PowerManager.BRIGHTNESS_ON; - screenAutoBrightnessAdjustment = 0.0f; - screenLowPowerBrightnessFactor = 0.5f; + screenBrightnessOverride = -1; useAutoBrightness = false; + screenAutoBrightnessAdjustmentOverride = Float.NaN; + screenLowPowerBrightnessFactor = 0.5f; blockScreenOn = false; dozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; dozeScreenState = Display.STATE_UNKNOWN; @@ -281,12 +275,10 @@ public abstract class DisplayManagerInternal { public void copyFrom(DisplayPowerRequest other) { policy = other.policy; useProximitySensor = other.useProximitySensor; - screenBrightness = other.screenBrightness; - screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment; - screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor; - brightnessSetByUser = other.brightnessSetByUser; - brightnessIsTemporary = other.brightnessIsTemporary; + screenBrightnessOverride = other.screenBrightnessOverride; useAutoBrightness = other.useAutoBrightness; + screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride; + screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor; blockScreenOn = other.blockScreenOn; lowPowerMode = other.lowPowerMode; boostScreenBrightness = other.boostScreenBrightness; @@ -304,13 +296,12 @@ public abstract class DisplayManagerInternal { return other != null && policy == other.policy && useProximitySensor == other.useProximitySensor - && screenBrightness == other.screenBrightness - && screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment + && screenBrightnessOverride == other.screenBrightnessOverride + && useAutoBrightness == other.useAutoBrightness + && floatEquals(screenAutoBrightnessAdjustmentOverride, + other.screenAutoBrightnessAdjustmentOverride) && screenLowPowerBrightnessFactor == other.screenLowPowerBrightnessFactor - && brightnessSetByUser == other.brightnessSetByUser - && brightnessIsTemporary == other.brightnessIsTemporary - && useAutoBrightness == other.useAutoBrightness && blockScreenOn == other.blockScreenOn && lowPowerMode == other.lowPowerMode && boostScreenBrightness == other.boostScreenBrightness @@ -318,6 +309,10 @@ public abstract class DisplayManagerInternal { && dozeScreenState == other.dozeScreenState; } + private boolean floatEquals(float f1, float f2) { + return f1 == f2 || Float.isNaN(f1) && Float.isNaN(f2); + } + @Override public int hashCode() { return 0; // don't care @@ -327,12 +322,11 @@ public abstract class DisplayManagerInternal { public String toString() { return "policy=" + policyToString(policy) + ", useProximitySensor=" + useProximitySensor - + ", screenBrightness=" + screenBrightness - + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment - + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor - + ", brightnessSetByUser=" + brightnessSetByUser - + ", brightnessIsTemporary=" + brightnessIsTemporary + + ", screenBrightnessOverride=" + screenBrightnessOverride + ", useAutoBrightness=" + useAutoBrightness + + ", screenAutoBrightnessAdjustmentOverride=" + + screenAutoBrightnessAdjustmentOverride + + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor + ", blockScreenOn=" + blockScreenOn + ", lowPowerMode=" + lowPowerMode + ", boostScreenBrightness=" + boostScreenBrightness diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 61c42e1ab491a5d83a7ffb4c7ba54218f70f3dee..13599cfa0b7d58495b3e9b53220727ab5aaed3d9 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -90,5 +90,12 @@ interface IDisplayManager { // Sets the global brightness configuration for a given user. Requires // CONFIGURE_DISPLAY_BRIGHTNESS, and INTERACT_ACROSS_USER if the user being configured is not // the same as the calling user. - void setBrightnessConfigurationForUser(in BrightnessConfiguration c, int userId); + void setBrightnessConfigurationForUser(in BrightnessConfiguration c, int userId, + String packageName); + + // Temporarily sets the display brightness. + void setTemporaryBrightness(int brightness); + + // Temporarily sets the auto brightness adjustment factor. + void setTemporaryAutoBrightnessAdjustment(float adjustment); } diff --git a/core/java/android/hardware/fingerprint/FingerprintDialog.java b/core/java/android/hardware/fingerprint/FingerprintDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..6b7fab773b43ab30f4faa571b1653e97d823689b --- /dev/null +++ b/core/java/android/hardware/fingerprint/FingerprintDialog.java @@ -0,0 +1,293 @@ +/* + * 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.hardware.fingerprint; + +import static android.Manifest.permission.USE_FINGERPRINT; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.content.DialogInterface; +import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; +import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; +import android.hardware.fingerprint.IFingerprintDialogReceiver; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.text.TextUtils; + +import java.util.concurrent.Executor; + +/** + * A class that manages a system-provided fingerprint dialog. + */ +public class FingerprintDialog { + + /** + * @hide + */ + public static final String KEY_TITLE = "title"; + /** + * @hide + */ + public static final String KEY_SUBTITLE = "subtitle"; + /** + * @hide + */ + public static final String KEY_DESCRIPTION = "description"; + /** + * @hide + */ + public static final String KEY_POSITIVE_TEXT = "positive_text"; + /** + * @hide + */ + public static final String KEY_NEGATIVE_TEXT = "negative_text"; + + /** + * Error/help message will show for this amount of time. + * For error messages, the dialog will also be dismissed after this amount of time. + * Error messages will be propagated back to the application via AuthenticationCallback + * after this amount of time. + * @hide + */ + public static final int HIDE_DIALOG_DELAY = 3000; // ms + /** + * @hide + */ + public static final int DISMISSED_REASON_POSITIVE = 1; + + /** + * @hide + */ + public static final int DISMISSED_REASON_NEGATIVE = 2; + + /** + * @hide + */ + public static final int DISMISSED_REASON_USER_CANCEL = 3; + + private static class ButtonInfo { + Executor executor; + DialogInterface.OnClickListener listener; + ButtonInfo(Executor ex, DialogInterface.OnClickListener l) { + executor = ex; + listener = l; + } + } + + /** + * A builder that collects arguments, to be shown on the system-provided fingerprint dialog. + **/ + public static class Builder { + private final Bundle bundle; + private ButtonInfo positiveButtonInfo; + private ButtonInfo negativeButtonInfo; + + /** + * Creates a builder for a fingerprint dialog. + */ + public Builder() { + bundle = new Bundle(); + } + + /** + * Required: Set the title to display. + * @param title + * @return + */ + public Builder setTitle(@NonNull CharSequence title) { + bundle.putCharSequence(KEY_TITLE, title); + return this; + } + + /** + * Optional: Set the subtitle to display. + * @param subtitle + * @return + */ + public Builder setSubtitle(@NonNull CharSequence subtitle) { + bundle.putCharSequence(KEY_SUBTITLE, subtitle); + return this; + } + + /** + * Optional: Set the description to display. + * @param description + * @return + */ + public Builder setDescription(@NonNull CharSequence description) { + bundle.putCharSequence(KEY_DESCRIPTION, description); + return this; + } + + /** + * Optional: Set the text for the positive button. If not set, the positive button + * will not show. + * @param text + * @return + * @hide + */ + public Builder setPositiveButton(@NonNull CharSequence text, + @NonNull @CallbackExecutor Executor executor, + @NonNull DialogInterface.OnClickListener listener) { + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Text must be set and non-empty"); + } + if (executor == null) { + throw new IllegalArgumentException("Executor must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + bundle.putCharSequence(KEY_POSITIVE_TEXT, text); + positiveButtonInfo = new ButtonInfo(executor, listener); + return this; + } + + /** + * Required: Set the text for the negative button. + * @param text + * @return + */ + public Builder setNegativeButton(@NonNull CharSequence text, + @NonNull @CallbackExecutor Executor executor, + @NonNull DialogInterface.OnClickListener listener) { + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Text must be set and non-empty"); + } + if (executor == null) { + throw new IllegalArgumentException("Executor must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + bundle.putCharSequence(KEY_NEGATIVE_TEXT, text); + negativeButtonInfo = new ButtonInfo(executor, listener); + return this; + } + + /** + * Creates a {@link FingerprintDialog} with the arguments supplied to this builder. + * @param context + * @return a {@link FingerprintDialog} + * @throws IllegalArgumentException if any of the required fields are not set. + */ + public FingerprintDialog build(Context context) { + final CharSequence title = bundle.getCharSequence(KEY_TITLE); + final CharSequence negative = bundle.getCharSequence(KEY_NEGATIVE_TEXT); + + if (TextUtils.isEmpty(title)) { + throw new IllegalArgumentException("Title must be set and non-empty"); + } else if (TextUtils.isEmpty(negative)) { + throw new IllegalArgumentException("Negative text must be set and non-empty"); + } + return new FingerprintDialog(context, bundle, positiveButtonInfo, negativeButtonInfo); + } + } + + private FingerprintManager mFingerprintManager; + private Bundle mBundle; + private ButtonInfo mPositiveButtonInfo; + private ButtonInfo mNegativeButtonInfo; + + IFingerprintDialogReceiver mDialogReceiver = new IFingerprintDialogReceiver.Stub() { + @Override + public void onDialogDismissed(int reason) { + // Check the reason and invoke OnClickListener(s) if necessary + if (reason == DISMISSED_REASON_POSITIVE) { + mPositiveButtonInfo.executor.execute(() -> { + mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); + }); + } else if (reason == DISMISSED_REASON_NEGATIVE) { + mNegativeButtonInfo.executor.execute(() -> { + mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); + }); + } + } + }; + + private FingerprintDialog(Context context, Bundle bundle, + ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) { + mBundle = bundle; + mPositiveButtonInfo = positiveButtonInfo; + mNegativeButtonInfo = negativeButtonInfo; + mFingerprintManager = context.getSystemService(FingerprintManager.class); + } + + /** + * This call warms up the fingerprint hardware, displays a system-provided dialog, + * and starts scanning for a fingerprint. It terminates when + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when + * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, + * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user + * dismisses the system-provided dialog, at which point the crypto object becomes invalid. + * This operation can be canceled by using the provided cancel object. The application will + * receive authentication errors through {@link AuthenticationCallback}, and button events + * through the corresponding callback set in + * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. + * It is safe to reuse the {@link FingerprintDialog} object, and calling + * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} + * while an existing authentication attempt is occurring will stop the previous client and + * start a new authentication. The interrupted client will receive a cancelled notification + * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * + * @throws IllegalArgumentException if any of the arguments are null + * + * @param crypto object associated with the call + * @param cancel an object that can be used to cancel authentication + * @param executor an executor to handle callback events + * @param callback an object to receive authentication events + */ + @RequiresPermission(USE_FINGERPRINT) + public void authenticate(@NonNull FingerprintManager.CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull FingerprintManager.AuthenticationCallback callback) { + mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver, + callback); + } + + /** + * This call warms up the fingerprint hardware, displays a system-provided dialog, + * and starts scanning for a fingerprint. It terminates when + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when + * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, + * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user + * dismisses the system-provided dialog. This operation can be canceled by using the provided + * cancel object. The application will receive authentication errors through + * {@link AuthenticationCallback}, and button events through the corresponding callback set in + * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. + * It is safe to reuse the {@link FingerprintDialog} object, and calling + * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)} + * while an existing authentication attempt is occurring will stop the previous client and + * start a new authentication. The interrupted client will receive a cancelled notification + * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}. + * + * @throws IllegalArgumentException if any of the arguments are null + * + * @param cancel an object that can be used to cancel authentication + * @param executor an executor to handle callback events + * @param callback an object to receive authentication events + */ + @RequiresPermission(USE_FINGERPRINT) + public void authenticate(@NonNull CancellationSignal cancel, + @NonNull @CallbackExecutor Executor executor, + @NonNull FingerprintManager.AuthenticationCallback callback) { + mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback); + } +} diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 987718a82c47ab4604383d6b1f20b21fae9b2364..62d92c4ac385cc0ea4bd4faea21caa397324d6a2 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -16,6 +16,11 @@ package android.hardware.fingerprint; +import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.MANAGE_FINGERPRINT; +import static android.Manifest.permission.USE_FINGERPRINT; + +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -23,6 +28,7 @@ import android.annotation.SystemService; import android.app.ActivityManager; import android.content.Context; import android.os.Binder; +import android.os.Bundle; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; @@ -38,14 +44,11 @@ import android.util.Slog; import java.security.Signature; import java.util.List; +import java.util.concurrent.Executor; import javax.crypto.Cipher; import javax.crypto.Mac; -import static android.Manifest.permission.INTERACT_ACROSS_USERS; -import static android.Manifest.permission.MANAGE_FINGERPRINT; -import static android.Manifest.permission.USE_FINGERPRINT; - /** * A class that coordinates access to the fingerprint hardware. */ @@ -204,6 +207,7 @@ public class FingerprintManager { private CryptoObject mCryptoObject; private Fingerprint mRemovalFingerprint; private Handler mHandler; + private Executor mExecutor; private class OnEnrollCancelListener implements OnCancelListener { @Override @@ -505,7 +509,9 @@ public class FingerprintManager { } /** - * Per-user version + * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, + * CancellationSignal, int, AuthenticationCallback, Handler)} + * @param userId the user ID that the fingerprint hardware will authenticate for. * @hide */ @RequiresPermission(USE_FINGERPRINT) @@ -530,7 +536,7 @@ public class FingerprintManager { mCryptoObject = crypto; long sessionId = crypto != null ? crypto.getOpId() : 0; mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); + mContext.getOpPackageName(), null /* bundle */, null /* receiver */); } catch (RemoteException e) { Log.w(TAG, "Remote exception while authenticating: ", e); if (callback != null) { @@ -542,6 +548,111 @@ public class FingerprintManager { } } + /** + * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, + * CancellationSignal, Bundle, Executor, IFingerprintDialogReceiver, AuthenticationCallback)} + * @param userId the user ID that the fingerprint hardware will authenticate for. + */ + private void authenticate(int userId, + @Nullable CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull Bundle bundle, + @NonNull @CallbackExecutor Executor executor, + @NonNull IFingerprintDialogReceiver receiver, + @NonNull AuthenticationCallback callback) { + mCryptoObject = crypto; + if (cancel.isCanceled()) { + Log.w(TAG, "authentication already canceled"); + return; + } else { + cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto)); + } + + if (mService != null) { + try { + mExecutor = executor; + mAuthenticationCallback = callback; + final long sessionId = crypto != null ? crypto.getOpId() : 0; + mService.authenticate(mToken, sessionId, userId, mServiceReceiver, + 0 /* flags */, mContext.getOpPackageName(), bundle, receiver); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception while authenticating", e); + mExecutor.execute(() -> { + callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE, + getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); + }); + } + } + } + + /** + * Private method, see {@link FingerprintDialog#authenticate(CancellationSignal, Executor, + * AuthenticationCallback)} + * @param cancel + * @param executor + * @param callback + * @hide + */ + public void authenticate( + @NonNull CancellationSignal cancel, + @NonNull Bundle bundle, + @NonNull @CallbackExecutor Executor executor, + @NonNull IFingerprintDialogReceiver receiver, + @NonNull AuthenticationCallback callback) { + if (cancel == null) { + throw new IllegalArgumentException("Must supply a cancellation signal"); + } + if (bundle == null) { + throw new IllegalArgumentException("Must supply a bundle"); + } + if (executor == null) { + throw new IllegalArgumentException("Must supply an executor"); + } + if (receiver == null) { + throw new IllegalArgumentException("Must supply a receiver"); + } + if (callback == null) { + throw new IllegalArgumentException("Must supply a calback"); + } + authenticate(UserHandle.myUserId(), null, cancel, bundle, executor, receiver, callback); + } + + /** + * Private method, see {@link FingerprintDialog#authenticate(CryptoObject, CancellationSignal, + * Executor, AuthenticationCallback)} + * @param crypto + * @param cancel + * @param executor + * @param callback + * @hide + */ + public void authenticate(@NonNull CryptoObject crypto, + @NonNull CancellationSignal cancel, + @NonNull Bundle bundle, + @NonNull @CallbackExecutor Executor executor, + @NonNull IFingerprintDialogReceiver receiver, + @NonNull AuthenticationCallback callback) { + if (crypto == null) { + throw new IllegalArgumentException("Must supply a crypto object"); + } + if (cancel == null) { + throw new IllegalArgumentException("Must supply a cancellation signal"); + } + if (bundle == null) { + throw new IllegalArgumentException("Must supply a bundle"); + } + if (executor == null) { + throw new IllegalArgumentException("Must supply an executor"); + } + if (receiver == null) { + throw new IllegalArgumentException("Must supply a receiver"); + } + if (callback == null) { + throw new IllegalArgumentException("Must supply a calback"); + } + authenticate(UserHandle.myUserId(), crypto, cancel, bundle, executor, receiver, callback); + } + /** * Request fingerprint enrollment. This call warms up the fingerprint hardware * and starts scanning for fingerprints. Progress will be indicated by callbacks to the @@ -929,63 +1040,63 @@ public class FingerprintManager { } } - private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) { - // emulate HAL 2.1 behavior and send real errMsgId - final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR - ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId; - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentError(clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationError(clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } else if (mRemovalCallback != null) { - mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } else if (mEnumerateCallback != null) { - mEnumerateCallback.onEnumerateError(clientErrMsgId, - getErrorString(errMsgId, vendorCode)); - } - } - private void sendEnrollResult(Fingerprint fp, int remaining) { if (mEnrollmentCallback != null) { mEnrollmentCallback.onEnrollmentProgress(remaining); } } + }; - private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) { - if (mAuthenticationCallback != null) { - final AuthenticationResult result = - new AuthenticationResult(mCryptoObject, fp, userId); - mAuthenticationCallback.onAuthenticationSucceeded(result); - } + private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) { + if (mAuthenticationCallback != null) { + final AuthenticationResult result = + new AuthenticationResult(mCryptoObject, fp, userId); + mAuthenticationCallback.onAuthenticationSucceeded(result); } + } - private void sendAuthenticatedFailed() { - if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationFailed(); - } + private void sendAuthenticatedFailed() { + if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationFailed(); } + } - private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) { - if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); - } - final String msg = getAcquiredString(acquireInfo, vendorCode); - if (msg == null) { - return; - } - // emulate HAL 2.1 behavior and send real acquiredInfo - final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR - ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo; - if (mEnrollmentCallback != null) { - mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); - } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); - } + private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) { + if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); } - }; + final String msg = getAcquiredString(acquireInfo, vendorCode); + if (msg == null) { + return; + } + // emulate HAL 2.1 behavior and send real acquiredInfo + final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR + ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo; + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); + } + } + + private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) { + // emulate HAL 2.1 behavior and send real errMsgId + final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR + ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId; + if (mEnrollmentCallback != null) { + mEnrollmentCallback.onEnrollmentError(clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } else if (mAuthenticationCallback != null) { + mAuthenticationCallback.onAuthenticationError(clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } else if (mRemovalCallback != null) { + mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } else if (mEnumerateCallback != null) { + mEnumerateCallback.onEnumerateError(clientErrMsgId, + getErrorString(errMsgId, vendorCode)); + } + } /** * @hide @@ -1023,7 +1134,10 @@ public class FingerprintManager { } } - private String getErrorString(int errMsg, int vendorCode) { + /** + * @hide + */ + public String getErrorString(int errMsg, int vendorCode) { switch (errMsg) { case FINGERPRINT_ERROR_UNABLE_TO_PROCESS: return mContext.getString( @@ -1043,6 +1157,9 @@ public class FingerprintManager { case FINGERPRINT_ERROR_LOCKOUT_PERMANENT: return mContext.getString( com.android.internal.R.string.fingerprint_error_lockout_permanent); + case FINGERPRINT_ERROR_USER_CANCELED: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_user_canceled); case FINGERPRINT_ERROR_VENDOR: { String[] msgArray = mContext.getResources().getStringArray( com.android.internal.R.array.fingerprint_error_vendor); @@ -1055,7 +1172,10 @@ public class FingerprintManager { return null; } - private String getAcquiredString(int acquireInfo, int vendorCode) { + /** + * @hide + */ + public String getAcquiredString(int acquireInfo, int vendorCode) { switch (acquireInfo) { case FINGERPRINT_ACQUIRED_GOOD: return null; @@ -1096,22 +1216,47 @@ public class FingerprintManager { @Override // binder call public void onAcquired(long deviceId, int acquireInfo, int vendorCode) { - mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendAcquiredResult(deviceId, acquireInfo, vendorCode); + }); + } else { + mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, + deviceId).sendToTarget(); + } } @Override // binder call public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) { - mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendAuthenticatedSucceeded(fp, userId); + }); + } else { + mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget(); + } } @Override // binder call public void onAuthenticationFailed(long deviceId) { - mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendAuthenticatedFailed(); + }); + } else { + mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); + } } @Override // binder call public void onError(long deviceId, int error, int vendorCode) { - mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget(); + if (mExecutor != null) { + mExecutor.execute(() -> { + sendErrorResult(deviceId, error, vendorCode); + }); + } else { + mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget(); + } } @Override // binder call diff --git a/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl new file mode 100644 index 0000000000000000000000000000000000000000..13e79741e543be4b595468a7a7c4ebb3dd6d21ef --- /dev/null +++ b/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl @@ -0,0 +1,28 @@ +/* + * 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.hardware.fingerprint; + +import android.hardware.fingerprint.Fingerprint; +import android.os.Bundle; +import android.os.UserHandle; + +/** + * Communication channel from the FingerprintDialog (SysUI) back to AuthenticationClient. + * @hide + */ +oneway interface IFingerprintDialogReceiver { + void onDialogDismissed(int reason); +} diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 4879d54768fb840b80493e7e36d9abb7d5dce26c..f1502e489c1113a5452748fdd729fb5b60817e6c 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -17,6 +17,7 @@ package android.hardware.fingerprint; import android.os.Bundle; import android.hardware.fingerprint.IFingerprintClientActiveCallback; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback; import android.hardware.fingerprint.Fingerprint; @@ -29,7 +30,8 @@ import java.util.List; interface IFingerprintService { // Authenticate the given sessionId with a fingerprint void authenticate(IBinder token, long sessionId, int userId, - IFingerprintServiceReceiver receiver, int flags, String opPackageName); + IFingerprintServiceReceiver receiver, int flags, String opPackageName, + in Bundle bundle, IFingerprintDialogReceiver dialogReceiver); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 1de8882e057a2516195a5e6384041bd078f49e6d..fdea5a2f93518d63efa44ff7fc1da7a0b48ae7fc 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1246,7 +1246,7 @@ public final class InputManager { int repeat; if (effect instanceof VibrationEffect.OneShot) { VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; - pattern = new long[] { 0, oneShot.getTiming() }; + pattern = new long[] { 0, oneShot.getDuration() }; repeat = -1; } else if (effect instanceof VibrationEffect.Waveform) { VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; diff --git a/core/java/android/hardware/location/ContextHubMessage.java b/core/java/android/hardware/location/ContextHubMessage.java index 2a4ad0013d4be92501e94d7a1f9b4ca8ead247db..f85ce3eed82e3841ae91e836c6d093942b9d38c8 100644 --- a/core/java/android/hardware/location/ContextHubMessage.java +++ b/core/java/android/hardware/location/ContextHubMessage.java @@ -34,12 +34,11 @@ import java.util.Arrays; @SystemApi @Deprecated public class ContextHubMessage { + private static final int DEBUG_LOG_NUM_BYTES = 16; private int mType; private int mVersion; private byte[]mData; - private static final String TAG = "ContextHubMessage"; - /** * Get the message type * @@ -136,4 +135,28 @@ public class ContextHubMessage { return new ContextHubMessage[size]; } }; + + @Override + public String toString() { + int length = mData.length; + + String ret = + "ContextHubMessage[type = " + mType + ", length = " + mData.length + " bytes]("; + if (length > 0) { + ret += "data = 0x"; + } + for (int i = 0; i < Math.min(length, DEBUG_LOG_NUM_BYTES); i++) { + ret += Byte.toHexString(mData[i], true /* upperCase */); + + if ((i + 1) % 4 == 0) { + ret += " "; + } + } + if (length > DEBUG_LOG_NUM_BYTES) { + ret += "..."; + } + ret += ")"; + + return ret; + } } diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java index 716a1946c54017eb92a12f45519e75063e342f6b..66352581dac3985605bd00a4faf42b6ec4737c08 100644 --- a/core/java/android/hardware/location/NanoAppMessage.java +++ b/core/java/android/hardware/location/NanoAppMessage.java @@ -28,6 +28,7 @@ import android.os.Parcelable; */ @SystemApi public final class NanoAppMessage implements Parcelable { + private static final int DEBUG_LOG_NUM_BYTES = 16; private long mNanoAppId; private int mMessageType; private byte[] mMessageBody; @@ -142,4 +143,29 @@ public final class NanoAppMessage implements Parcelable { return new NanoAppMessage[size]; } }; + + @Override + public String toString() { + int length = mMessageBody.length; + + String ret = "NanoAppMessage[type = " + mMessageType + ", length = " + mMessageBody.length + + " bytes, " + (mIsBroadcasted ? "broadcast" : "unicast") + ", nanoapp = 0x" + + Long.toHexString(mNanoAppId) + "]("; + if (length > 0) { + ret += "data = 0x"; + } + for (int i = 0; i < Math.min(length, DEBUG_LOG_NUM_BYTES); i++) { + ret += Byte.toHexString(mMessageBody[i], true /* upperCase */); + + if ((i + 1) % 4 == 0) { + ret += " "; + } + } + if (length > DEBUG_LOG_NUM_BYTES) { + ret += "..."; + } + ret += ")"; + + return ret; + } } diff --git a/core/java/android/hardware/radio/Announcement.aidl b/core/java/android/hardware/radio/Announcement.aidl new file mode 100644 index 0000000000000000000000000000000000000000..eeb5951ee782b6f5625b1579bef8187607c73ebf --- /dev/null +++ b/core/java/android/hardware/radio/Announcement.aidl @@ -0,0 +1,20 @@ +/** + * 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.hardware.radio; + +/** @hide */ +parcelable Announcement; diff --git a/core/java/android/hardware/radio/Announcement.java b/core/java/android/hardware/radio/Announcement.java new file mode 100644 index 0000000000000000000000000000000000000000..166fe6043f3d9730019e93ed005d860bd5bfa485 --- /dev/null +++ b/core/java/android/hardware/radio/Announcement.java @@ -0,0 +1,133 @@ +/** + * 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.hardware.radio; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** + * @hide + */ +@SystemApi +public final class Announcement implements Parcelable { + + /** DAB alarm, RDS emergency program type (PTY 31). */ + public static final int TYPE_EMERGENCY = 1; + /** DAB warning. */ + public static final int TYPE_WARNING = 2; + /** DAB road traffic, RDS TA, HD Radio transportation. */ + public static final int TYPE_TRAFFIC = 3; + /** Weather. */ + public static final int TYPE_WEATHER = 4; + /** News. */ + public static final int TYPE_NEWS = 5; + /** DAB event, special event. */ + public static final int TYPE_EVENT = 6; + /** DAB sport report, RDS sports. */ + public static final int TYPE_SPORT = 7; + /** All others. */ + public static final int TYPE_MISC = 8; + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_EMERGENCY, + TYPE_WARNING, + TYPE_TRAFFIC, + TYPE_WEATHER, + TYPE_NEWS, + TYPE_EVENT, + TYPE_SPORT, + TYPE_MISC, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + /** + * Listener of announcement list events. + */ + public interface OnListUpdatedListener { + /** + * An event called whenever a list of active announcements change. + * + * The entire list is sent each time a new announcement appears or any ends broadcasting. + * + * @param activeAnnouncements a full list of active announcements + */ + void onListUpdated(Collection activeAnnouncements); + } + + @NonNull private final ProgramSelector mSelector; + @Type private final int mType; + @NonNull private final Map mVendorInfo; + + /** @hide */ + public Announcement(@NonNull ProgramSelector selector, @Type int type, + @NonNull Map vendorInfo) { + mSelector = Objects.requireNonNull(selector); + mType = Objects.requireNonNull(type); + mVendorInfo = Objects.requireNonNull(vendorInfo); + } + + private Announcement(@NonNull Parcel in) { + mSelector = in.readTypedObject(ProgramSelector.CREATOR); + mType = in.readInt(); + mVendorInfo = Utils.readStringMap(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedObject(mSelector, 0); + dest.writeInt(mType); + Utils.writeStringMap(dest, mVendorInfo); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public Announcement createFromParcel(Parcel in) { + return new Announcement(in); + } + + public Announcement[] newArray(int size) { + return new Announcement[size]; + } + }; + + public @NonNull ProgramSelector getSelector() { + return mSelector; + } + + public @Type int getType() { + return mType; + } + + public @NonNull Map getVendorInfo() { + return mVendorInfo; + } +} diff --git a/core/java/android/hardware/radio/IAnnouncementListener.aidl b/core/java/android/hardware/radio/IAnnouncementListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b4d974a37ede1699294119c55f9d8feb7471e5d5 --- /dev/null +++ b/core/java/android/hardware/radio/IAnnouncementListener.aidl @@ -0,0 +1,24 @@ +/** + * 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.hardware.radio; + +import android.hardware.radio.Announcement; + +/** {@hide} */ +oneway interface IAnnouncementListener { + void onListUpdated(in List activeAnnouncements); +} diff --git a/core/java/android/hardware/radio/ICloseHandle.aidl b/core/java/android/hardware/radio/ICloseHandle.aidl new file mode 100644 index 0000000000000000000000000000000000000000..576c03b0db843085a7793842cfcf0677759ed980 --- /dev/null +++ b/core/java/android/hardware/radio/ICloseHandle.aidl @@ -0,0 +1,22 @@ +/** + * 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.hardware.radio; + +/** {@hide} */ +interface ICloseHandle { + void close(); +} diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl index c43fd2615e4440e68f17a96ddac164a620ae14b4..9349cf7d0c327e98da05d781fdc417b3acc2b8e8 100644 --- a/core/java/android/hardware/radio/IRadioService.aidl +++ b/core/java/android/hardware/radio/IRadioService.aidl @@ -16,6 +16,8 @@ package android.hardware.radio; +import android.hardware.radio.IAnnouncementListener; +import android.hardware.radio.ICloseHandle; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; @@ -30,4 +32,7 @@ interface IRadioService { ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio, in ITunerCallback callback); + + ICloseHandle addAnnouncementListener(in int[] enabledTypes, + in IAnnouncementListener listener); } diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl index ca380769954bc3bd02e4f40093a0de5ea6ea0ddd..429f1f351a937f0a2510116da5aea2a332c41c40 100644 --- a/core/java/android/hardware/radio/ITuner.aidl +++ b/core/java/android/hardware/radio/ITuner.aidl @@ -17,6 +17,7 @@ package android.hardware.radio; import android.graphics.Bitmap; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; @@ -63,8 +64,6 @@ interface ITuner { void cancelAnnouncement(); - RadioManager.ProgramInfo getProgramInformation(); - Bitmap getImage(int id); /** @@ -73,14 +72,8 @@ interface ITuner { */ boolean startBackgroundScan(); - /** - * @param vendorFilter Vendor-specific filter, must be Map - * @return the list, or null if scan is in progress - * @throws IllegalArgumentException if invalid arguments are passed - * @throws IllegalStateException if the scan has not been started, client may - * call startBackgroundScan to fix this. - */ - List getProgramList(in Map vendorFilter); + void startProgramListUpdates(in ProgramList.Filter filter); + void stopProgramListUpdates(); boolean isConfigFlagSupported(int flag); boolean isConfigFlagSet(int flag); @@ -97,6 +90,4 @@ interface ITuner { * @return Vendor-specific key-value pairs, must be Map */ Map getParameters(in List keys); - - boolean isAntennaConnected(); } diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl index 775e25c7e7cf93da8f2ad66b219bd69499876416..b32daa5a7609751d2f9dace4cbeb108f928c47a8 100644 --- a/core/java/android/hardware/radio/ITunerCallback.aidl +++ b/core/java/android/hardware/radio/ITunerCallback.aidl @@ -16,12 +16,15 @@ package android.hardware.radio; +import android.hardware.radio.ProgramList; +import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; /** {@hide} */ oneway interface ITunerCallback { void onError(int status); + void onTuneFailed(int result, in ProgramSelector selector); void onConfigurationChanged(in RadioManager.BandConfig config); void onCurrentProgramInfoChanged(in RadioManager.ProgramInfo info); void onTrafficAnnouncement(boolean active); @@ -30,6 +33,7 @@ oneway interface ITunerCallback { void onBackgroundScanAvailabilityChange(boolean isAvailable); void onBackgroundScanComplete(); void onProgramListChanged(); + void onProgramListUpdated(in ProgramList.Chunk chunk); /** * @param parameters Vendor-specific key-value pairs, must be Map diff --git a/core/java/android/hardware/radio/ProgramList.aidl b/core/java/android/hardware/radio/ProgramList.aidl new file mode 100644 index 0000000000000000000000000000000000000000..34b7f97558c74ecf61ccd3ae07b353e2f694d3e9 --- /dev/null +++ b/core/java/android/hardware/radio/ProgramList.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.radio; + +/** @hide */ +parcelable ProgramList.Filter; + +/** @hide */ +parcelable ProgramList.Chunk; diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java new file mode 100644 index 0000000000000000000000000000000000000000..b2aa9ba532a95c6d736c972511e909984ca2d8c0 --- /dev/null +++ b/core/java/android/hardware/radio/ProgramList.java @@ -0,0 +1,427 @@ +/** + * 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.hardware.radio; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +/** + * @hide + */ +@SystemApi +public final class ProgramList implements AutoCloseable { + + private final Object mLock = new Object(); + private final Map mPrograms = + new HashMap<>(); + + private final List mListCallbacks = new ArrayList<>(); + private final List mOnCompleteListeners = new ArrayList<>(); + private OnCloseListener mOnCloseListener; + private boolean mIsClosed = false; + private boolean mIsComplete = false; + + ProgramList() {} + + /** + * Callback for list change operations. + */ + public abstract static class ListCallback { + /** + * Called when item was modified or added to the list. + */ + public void onItemChanged(@NonNull ProgramSelector.Identifier id) { } + + /** + * Called when item was removed from the list. + */ + public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { } + } + + /** + * Listener of list complete event. + */ + public interface OnCompleteListener { + /** + * Called when the list turned complete (i.e. when the scan process + * came to an end). + */ + void onComplete(); + } + + interface OnCloseListener { + void onClose(); + } + + /** + * Registers list change callback with executor. + */ + public void registerListCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull ListCallback callback) { + registerListCallback(new ListCallback() { + public void onItemChanged(@NonNull ProgramSelector.Identifier id) { + executor.execute(() -> callback.onItemChanged(id)); + } + + public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { + executor.execute(() -> callback.onItemRemoved(id)); + } + }); + } + + /** + * Registers list change callback. + */ + public void registerListCallback(@NonNull ListCallback callback) { + synchronized (mLock) { + if (mIsClosed) return; + mListCallbacks.add(Objects.requireNonNull(callback)); + } + } + + /** + * Unregisters list change callback. + */ + public void unregisterListCallback(@NonNull ListCallback callback) { + synchronized (mLock) { + if (mIsClosed) return; + mListCallbacks.remove(Objects.requireNonNull(callback)); + } + } + + /** + * Adds list complete event listener with executor. + */ + public void addOnCompleteListener(@NonNull @CallbackExecutor Executor executor, + @NonNull OnCompleteListener listener) { + addOnCompleteListener(() -> executor.execute(listener::onComplete)); + } + + /** + * Adds list complete event listener. + */ + public void addOnCompleteListener(@NonNull OnCompleteListener listener) { + synchronized (mLock) { + if (mIsClosed) return; + mOnCompleteListeners.add(Objects.requireNonNull(listener)); + if (mIsComplete) listener.onComplete(); + } + } + + /** + * Removes list complete event listener. + */ + public void removeOnCompleteListener(@NonNull OnCompleteListener listener) { + synchronized (mLock) { + if (mIsClosed) return; + mOnCompleteListeners.remove(Objects.requireNonNull(listener)); + } + } + + void setOnCloseListener(@Nullable OnCloseListener listener) { + synchronized (mLock) { + if (mOnCloseListener != null) { + throw new IllegalStateException("Close callback is already set"); + } + mOnCloseListener = listener; + } + } + + /** + * Disables list updates and releases all resources. + */ + public void close() { + synchronized (mLock) { + if (mIsClosed) return; + mIsClosed = true; + mPrograms.clear(); + mListCallbacks.clear(); + mOnCompleteListeners.clear(); + if (mOnCloseListener != null) { + mOnCloseListener.onClose(); + mOnCloseListener = null; + } + } + } + + void apply(@NonNull Chunk chunk) { + synchronized (mLock) { + if (mIsClosed) return; + + mIsComplete = false; + + if (chunk.isPurge()) { + new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id)); + } + + chunk.getRemoved().stream().forEach(id -> removeLocked(id)); + chunk.getModified().stream().forEach(info -> putLocked(info)); + + if (chunk.isComplete()) { + mIsComplete = true; + mOnCompleteListeners.forEach(cb -> cb.onComplete()); + } + } + } + + private void putLocked(@NonNull RadioManager.ProgramInfo value) { + ProgramSelector.Identifier key = value.getSelector().getPrimaryId(); + mPrograms.put(Objects.requireNonNull(key), value); + ProgramSelector.Identifier sel = value.getSelector().getPrimaryId(); + mListCallbacks.forEach(cb -> cb.onItemChanged(sel)); + } + + private void removeLocked(@NonNull ProgramSelector.Identifier key) { + RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key)); + if (removed == null) return; + ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId(); + mListCallbacks.forEach(cb -> cb.onItemRemoved(sel)); + } + + /** + * Converts the program list in its current shape to the static List<>. + * + * @return the new List<> object; it won't receive any further updates + */ + public @NonNull List toList() { + synchronized (mLock) { + return mPrograms.values().stream().collect(Collectors.toList()); + } + } + + /** + * Returns the program with a specified primary identifier. + * + * @param id primary identifier of a program to fetch + * @return the program info, or null if there is no such program on the list + */ + public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { + synchronized (mLock) { + return mPrograms.get(Objects.requireNonNull(id)); + } + } + + /** + * Filter for the program list. + */ + public static final class Filter implements Parcelable { + private final @NonNull Set mIdentifierTypes; + private final @NonNull Set mIdentifiers; + private final boolean mIncludeCategories; + private final boolean mExcludeModifications; + private final @Nullable Map mVendorFilter; + + /** + * Constructor of program list filter. + * + * Arrays passed to this constructor become owned by this object, do not modify them later. + * + * @param identifierTypes see getIdentifierTypes() + * @param identifiers see getIdentifiers() + * @param includeCategories see areCategoriesIncluded() + * @param excludeModifications see areModificationsExcluded() + */ + public Filter(@NonNull Set identifierTypes, + @NonNull Set identifiers, + boolean includeCategories, boolean excludeModifications) { + mIdentifierTypes = Objects.requireNonNull(identifierTypes); + mIdentifiers = Objects.requireNonNull(identifiers); + mIncludeCategories = includeCategories; + mExcludeModifications = excludeModifications; + mVendorFilter = null; + } + + /** + * @hide for framework use only + */ + public Filter(@Nullable Map vendorFilter) { + mIdentifierTypes = Collections.emptySet(); + mIdentifiers = Collections.emptySet(); + mIncludeCategories = false; + mExcludeModifications = false; + mVendorFilter = vendorFilter; + } + + private Filter(@NonNull Parcel in) { + mIdentifierTypes = Utils.createIntSet(in); + mIdentifiers = Utils.createSet(in, ProgramSelector.Identifier.CREATOR); + mIncludeCategories = in.readByte() != 0; + mExcludeModifications = in.readByte() != 0; + mVendorFilter = Utils.readStringMap(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + Utils.writeIntSet(dest, mIdentifierTypes); + Utils.writeSet(dest, mIdentifiers); + dest.writeByte((byte) (mIncludeCategories ? 1 : 0)); + dest.writeByte((byte) (mExcludeModifications ? 1 : 0)); + Utils.writeStringMap(dest, mVendorFilter); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Filter createFromParcel(Parcel in) { + return new Filter(in); + } + + public Filter[] newArray(int size) { + return new Filter[size]; + } + }; + + /** + * @hide for framework use only + */ + public Map getVendorFilter() { + return mVendorFilter; + } + + /** + * Returns the list of identifier types that satisfy the filter. + * + * If the program list entry contains at least one identifier of the type + * listed, it satisfies this condition. + * + * Empty list means no filtering on identifier type. + * + * @return the list of accepted identifier types, must not be modified + */ + public @NonNull Set getIdentifierTypes() { + return mIdentifierTypes; + } + + /** + * Returns the list of identifiers that satisfy the filter. + * + * If the program list entry contains at least one listed identifier, + * it satisfies this condition. + * + * Empty list means no filtering on identifier. + * + * @return the list of accepted identifiers, must not be modified + */ + public @NonNull Set getIdentifiers() { + return mIdentifiers; + } + + /** + * Checks, if non-tunable entries that define tree structure on the + * program list (i.e. DAB ensembles) should be included. + */ + public boolean areCategoriesIncluded() { + return mIncludeCategories; + } + + /** + * Checks, if updates on entry modifications should be disabled. + * + * If true, 'modified' vector of ProgramListChunk must contain list + * additions only. Once the program is added to the list, it's not + * updated anymore. + */ + public boolean areModificationsExcluded() { + return mExcludeModifications; + } + } + + /** + * @hide This is a transport class used for internal communication between + * Broadcast Radio Service and RadioManager. + * Do not use it directly. + */ + public static final class Chunk implements Parcelable { + private final boolean mPurge; + private final boolean mComplete; + private final @NonNull Set mModified; + private final @NonNull Set mRemoved; + + public Chunk(boolean purge, boolean complete, + @Nullable Set modified, + @Nullable Set removed) { + mPurge = purge; + mComplete = complete; + mModified = (modified != null) ? modified : Collections.emptySet(); + mRemoved = (removed != null) ? removed : Collections.emptySet(); + } + + private Chunk(@NonNull Parcel in) { + mPurge = in.readByte() != 0; + mComplete = in.readByte() != 0; + mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR); + mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mPurge ? 1 : 0)); + dest.writeByte((byte) (mComplete ? 1 : 0)); + Utils.writeSet(dest, mModified); + Utils.writeSet(dest, mRemoved); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Chunk createFromParcel(Parcel in) { + return new Chunk(in); + } + + public Chunk[] newArray(int size) { + return new Chunk[size]; + } + }; + + public boolean isPurge() { + return mPurge; + } + + public boolean isComplete() { + return mComplete; + } + + public @NonNull Set getModified() { + return mModified; + } + + public @NonNull Set getRemoved() { + return mRemoved; + } + } +} diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 2211cee9b3151336e6f045dfb4b816a2ed5a8bc1..0294a29b828216bf119d0ed59be8639eb486a1e4 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -27,6 +27,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -59,24 +60,58 @@ import java.util.stream.Stream; */ @SystemApi public final class ProgramSelector implements Parcelable { - /** Analogue AM radio (with or without RDS). */ + /** Invalid program type. + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated + public static final int PROGRAM_TYPE_INVALID = 0; + /** Analogue AM radio (with or without RDS). + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_AM = 1; - /** analogue FM radio (with or without RDS). */ + /** analogue FM radio (with or without RDS). + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_FM = 2; - /** AM HD Radio. */ + /** AM HD Radio. + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_AM_HD = 3; - /** FM HD Radio. */ + /** FM HD Radio. + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_FM_HD = 4; - /** Digital audio broadcasting. */ + /** Digital audio broadcasting. + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_DAB = 5; - /** Digital Radio Mondiale. */ + /** Digital Radio Mondiale. + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_DRMO = 6; - /** SiriusXM Satellite Radio. */ + /** SiriusXM Satellite Radio. + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_SXM = 7; - /** Vendor-specific, not synced across devices. */ + /** Vendor-specific, not synced across devices. + * @deprecated use {@link ProgramIdentifier} instead + */ + @Deprecated public static final int PROGRAM_TYPE_VENDOR_START = 1000; + /** @deprecated use {@link ProgramIdentifier} instead */ + @Deprecated public static final int PROGRAM_TYPE_VENDOR_END = 1999; + /** @deprecated use {@link ProgramIdentifier} instead */ + @Deprecated @IntDef(prefix = { "PROGRAM_TYPE_" }, value = { + PROGRAM_TYPE_INVALID, PROGRAM_TYPE_AM, PROGRAM_TYPE_FM, PROGRAM_TYPE_AM_HD, @@ -89,6 +124,7 @@ public final class ProgramSelector implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface ProgramType {} + public static final int IDENTIFIER_TYPE_INVALID = 0; /** kHz */ public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; /** 16bit */ @@ -109,18 +145,46 @@ public final class ProgramSelector implements Parcelable { * * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS), * as opposed to HD Radio standard (where it's 1-based). + * + * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead */ + @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; /** - * 24bit compound primary identifier for DAB. + * 64bit additional identifier for HD Radio. + * + * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not + * globally unique. To provide a best-effort solution, a short version of + * station name may be carried as additional identifier and may be used + * by the tuner hardware to double-check tuning. + * + * The name is limited to the first 8 A-Z0-9 characters (lowercase letters + * must be converted to uppercase). Encoded in little-endian ASCII: + * the first character of the name is the LSB. + * + * For example: "Abc" is encoded as 0x434241. + */ + public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; + /** + * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT} + */ + public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; + /** + * 28bit compound primary identifier for Digital Audio Broadcasting. * * Consists of (from the LSB): * - 16bit: SId; - * - 8bit: ECC code. + * - 8bit: ECC code; + * - 4bit: SCIdS. + * + * SCIdS (Service Component Identifier within the Service) value + * of 0 represents the main service, while 1 and above represents + * secondary services. + * * The remaining bits should be set to zeros when writing on the chip side * and ignored when read. */ - public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; + public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC; /** 16bit */ public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; /** 12bit */ @@ -131,7 +195,11 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; /** kHz */ public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; - /** 1: AM, 2:FM */ + /** + * 1: AM, 2:FM + * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead + */ + @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; /** 32bit */ public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; @@ -145,13 +213,29 @@ public final class ProgramSelector implements Parcelable { * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must * not be used in any program type other than 1015). */ - public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START; - public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END; + public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START; + /** + * @see {@link IDENTIFIER_TYPE_VENDOR_START} + */ + public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END; + /** + * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead + */ + @Deprecated + public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START; + /** + * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead + */ + @Deprecated + public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END; @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = { + IDENTIFIER_TYPE_INVALID, IDENTIFIER_TYPE_AMFM_FREQUENCY, IDENTIFIER_TYPE_RDS_PI, IDENTIFIER_TYPE_HD_STATION_ID_EXT, IDENTIFIER_TYPE_HD_SUBCHANNEL, + IDENTIFIER_TYPE_HD_STATION_NAME, + IDENTIFIER_TYPE_DAB_SID_EXT, IDENTIFIER_TYPE_DAB_SIDECC, IDENTIFIER_TYPE_DAB_ENSEMBLE, IDENTIFIER_TYPE_DAB_SCID, @@ -162,7 +246,7 @@ public final class ProgramSelector implements Parcelable { IDENTIFIER_TYPE_SXM_SERVICE_ID, IDENTIFIER_TYPE_SXM_CHANNEL, }) - @IntRange(from = IDENTIFIER_TYPE_VENDOR_PRIMARY_START, to = IDENTIFIER_TYPE_VENDOR_PRIMARY_END) + @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END) @Retention(RetentionPolicy.SOURCE) public @interface IdentifierType {} @@ -201,7 +285,9 @@ public final class ProgramSelector implements Parcelable { * Type of a radio technology. * * @return program type. + * @deprecated use {@link getPrimaryId} instead */ + @Deprecated public @ProgramType int getProgramType() { return mProgramType; } @@ -268,12 +354,47 @@ public final class ProgramSelector implements Parcelable { * Vendor identifiers are passed as-is to the HAL implementation, * preserving elements order. * - * @return a array of vendor identifiers, must not be modified. + * @return an array of vendor identifiers, must not be modified. + * @deprecated for HAL 1.x compatibility; + * HAL 2.x uses standard primary/secondary lists for vendor IDs */ + @Deprecated public @NonNull long[] getVendorIds() { return mVendorIds; } + /** + * Creates an equivalent ProgramSelector with a given secondary identifier preferred. + * + * Used to point to a specific physical identifier for technologies that may broadcast the same + * program on different channels. For example, with a DAB program broadcasted over multiple + * ensembles, the radio hardware may select the one with the strongest signal. The UI may select + * preferred ensemble though, so the radio hardware may try to use it in the first place. + * + * This is a best-effort hint for the tuner, not a guaranteed behavior. + * + * Setting the given secondary identifier as preferred means filtering out other secondary + * identifiers of its type and adding it to the list. + * + * @param preferred preferred secondary identifier + * @return a new ProgramSelector with a given secondary identifier preferred + */ + public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) { + int preferredType = preferred.getType(); + Identifier[] secondaryIds = Stream.concat( + // remove other identifiers of that type + Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType), + // add preferred identifier instead + Stream.of(preferred)).toArray(Identifier[]::new); + + return new ProgramSelector( + mProgramType, + mPrimaryId, + secondaryIds, + mVendorIds + ); + } + /** * Builds new ProgramSelector for AM/FM frequency. * @@ -423,6 +544,10 @@ public final class ProgramSelector implements Parcelable { private final long mValue; public Identifier(@IdentifierType int type, long value) { + if (type == IDENTIFIER_TYPE_HD_STATION_NAME) { + // see getType + type = IDENTIFIER_TYPE_HD_SUBCHANNEL; + } mType = type; mValue = value; } @@ -433,6 +558,13 @@ public final class ProgramSelector implements Parcelable { * @return type of an identifier. */ public @IdentifierType int getType() { + if (mType == IDENTIFIER_TYPE_HD_SUBCHANNEL && mValue > 10) { + /* HD_SUBCHANNEL and HD_STATION_NAME use the same identifier type, but they differ + * in possible values: sub channel is 0-7, station name is greater than ASCII space + * code (32). + */ + return IDENTIFIER_TYPE_HD_STATION_NAME; + } return mType; } diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index b740f1430157fcb2cbd55335399a6d5a65995308..b00f6033976e1b75996b53c9359255f2cafe8511 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -17,8 +17,10 @@ package android.hardware.radio; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -32,14 +34,19 @@ import android.os.ServiceManager.ServiceNotFoundException; import android.text.TextUtils; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; import java.util.stream.Collectors; /** @@ -185,25 +192,6 @@ public class RadioManager { @Retention(RetentionPolicy.SOURCE) public @interface ConfigFlag {} - private static void writeStringMap(@NonNull Parcel dest, @NonNull Map map) { - dest.writeInt(map.size()); - for (Map.Entry entry : map.entrySet()) { - dest.writeString(entry.getKey()); - dest.writeString(entry.getValue()); - } - } - - private static @NonNull Map readStringMap(@NonNull Parcel in) { - int size = in.readInt(); - Map map = new HashMap<>(); - while (size-- > 0) { - String key = in.readString(); - String value = in.readString(); - map.put(key, value); - } - return map; - } - /***************************************************************************** * Lists properties, options and radio bands supported by a given broadcast radio module. * Each module has a unique ID used to address it when calling RadioManager APIs. @@ -415,7 +403,7 @@ public class RadioManager { mIsBgScanSupported = in.readInt() == 1; mSupportedProgramTypes = arrayToSet(in.createIntArray()); mSupportedIdentifierTypes = arrayToSet(in.createIntArray()); - mVendorInfo = readStringMap(in); + mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator CREATOR @@ -445,7 +433,7 @@ public class RadioManager { dest.writeInt(mIsBgScanSupported ? 1 : 0); dest.writeIntArray(setToArray(mSupportedProgramTypes)); dest.writeIntArray(setToArray(mSupportedIdentifierTypes)); - writeStringMap(dest, mVendorInfo); + Utils.writeStringMap(dest, mVendorInfo); } @Override @@ -1399,34 +1387,44 @@ public class RadioManager { }; } - /** Radio program information returned by - * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */ + /** Radio program information. */ public static class ProgramInfo implements Parcelable { - // sourced from hardware/interfaces/broadcastradio/1.1/types.hal + // sourced from hardware/interfaces/broadcastradio/2.0/types.hal private static final int FLAG_LIVE = 1 << 0; private static final int FLAG_MUTED = 1 << 1; private static final int FLAG_TRAFFIC_PROGRAM = 1 << 2; private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3; + private static final int FLAG_TUNED = 1 << 4; + private static final int FLAG_STEREO = 1 << 5; @NonNull private final ProgramSelector mSelector; - private final boolean mTuned; - private final boolean mStereo; - private final boolean mDigital; - private final int mFlags; - private final int mSignalStrength; - private final RadioMetadata mMetadata; + @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo; + @Nullable private final ProgramSelector.Identifier mPhysicallyTunedTo; + @NonNull private final Collection mRelatedContent; + private final int mInfoFlags; + private final int mSignalQuality; + @Nullable private final RadioMetadata mMetadata; @NonNull private final Map mVendorInfo; - ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo, - boolean digital, int signalStrength, RadioMetadata metadata, int flags, - Map vendorInfo) { - mSelector = selector; - mTuned = tuned; - mStereo = stereo; - mDigital = digital; - mFlags = flags; - mSignalStrength = signalStrength; + /** @hide */ + public ProgramInfo(@NonNull ProgramSelector selector, + @Nullable ProgramSelector.Identifier logicallyTunedTo, + @Nullable ProgramSelector.Identifier physicallyTunedTo, + @Nullable Collection relatedContent, + int infoFlags, int signalQuality, @Nullable RadioMetadata metadata, + @Nullable Map vendorInfo) { + mSelector = Objects.requireNonNull(selector); + mLogicallyTunedTo = logicallyTunedTo; + mPhysicallyTunedTo = physicallyTunedTo; + if (relatedContent == null) { + mRelatedContent = Collections.emptyList(); + } else { + Preconditions.checkCollectionElementsNotNull(relatedContent, "relatedContent"); + mRelatedContent = relatedContent; + } + mInfoFlags = infoFlags; + mSignalQuality = signalQuality; mMetadata = metadata; mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo; } @@ -1440,6 +1438,51 @@ public class RadioManager { return mSelector; } + /** + * Identifier currently used for program selection. + * + * This identifier can be used to determine which technology is + * currently being used for reception. + * + * Some program selectors contain tuning information for different radio + * technologies (i.e. FM RDS and DAB). For example, user may tune using + * a ProgramSelector with RDS_PI primary identifier, but the tuner hardware + * may choose to use DAB technology to make actual tuning. This identifier + * must reflect that. + */ + public @Nullable ProgramSelector.Identifier getLogicallyTunedTo() { + return mLogicallyTunedTo; + } + + /** + * Identifier currently used by hardware to physically tune to a channel. + * + * Some radio technologies broadcast the same program on multiple channels, + * i.e. with RDS AF the same program may be broadcasted on multiple + * alternative frequencies; the same DAB program may be broadcast on + * multiple ensembles. This identifier points to the channel to which the + * radio hardware is physically tuned to. + */ + public @Nullable ProgramSelector.Identifier getPhysicallyTunedTo() { + return mPhysicallyTunedTo; + } + + /** + * Primary identifiers of related contents. + * + * Some radio technologies provide pointers to other programs that carry + * related content (i.e. DAB soft-links). This field is a list of pointers + * to other programs on the program list. + * + * Please note, that these identifiers does not have to exist on the program + * list - i.e. DAB tuner may provide information on FM RDS alternatives + * despite not supporting FM RDS. If the system has multiple tuners, another + * one may have it on its list. + */ + public @Nullable Collection getRelatedContent() { + return mRelatedContent; + } + /** Main channel expressed in units according to band type. * Currently all defined band types express channels as frequency in kHz * @return the program channel @@ -1474,19 +1517,28 @@ public class RadioManager { * @return {@code true} if currently tuned, {@code false} otherwise. */ public boolean isTuned() { - return mTuned; + return (mInfoFlags & FLAG_TUNED) != 0; } + /** {@code true} if the received program is stereo * @return {@code true} if stereo, {@code false} otherwise. */ public boolean isStereo() { - return mStereo; + return (mInfoFlags & FLAG_STEREO) != 0; } + /** {@code true} if the received program is digital (e.g HD radio) * @return {@code true} if digital, {@code false} otherwise. + * @deprecated Use {@link getLogicallyTunedTo()} instead. */ + @Deprecated public boolean isDigital() { - return mDigital; + ProgramSelector.Identifier id = mLogicallyTunedTo; + if (id == null) id = mSelector.getPrimaryId(); + + int type = id.getType(); + return (type != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY + && type != ProgramSelector.IDENTIFIER_TYPE_RDS_PI); } /** @@ -1495,7 +1547,7 @@ public class RadioManager { * usually targetted at reduced latency. */ public boolean isLive() { - return (mFlags & FLAG_LIVE) != 0; + return (mInfoFlags & FLAG_LIVE) != 0; } /** @@ -1505,7 +1557,7 @@ public class RadioManager { * It does NOT mean the user has muted audio. */ public boolean isMuted() { - return (mFlags & FLAG_MUTED) != 0; + return (mInfoFlags & FLAG_MUTED) != 0; } /** @@ -1513,7 +1565,7 @@ public class RadioManager { * regularily. */ public boolean isTrafficProgram() { - return (mFlags & FLAG_TRAFFIC_PROGRAM) != 0; + return (mInfoFlags & FLAG_TRAFFIC_PROGRAM) != 0; } /** @@ -1521,15 +1573,18 @@ public class RadioManager { * at the very moment. */ public boolean isTrafficAnnouncementActive() { - return (mFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0; + return (mInfoFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0; } - /** Signal strength indicator from 0 (no signal) to 100 (excellent) - * @return the signal strength indication. + /** + * Signal quality (as opposed to the name) indication from 0 (no signal) + * to 100 (excellent) + * @return the signal quality indication. */ public int getSignalStrength() { - return mSignalStrength; + return mSignalQuality; } + /** Metadata currently received from this station. * null if no metadata have been received * @return current meta data received from this program. @@ -1553,18 +1608,14 @@ public class RadioManager { } private ProgramInfo(Parcel in) { - mSelector = in.readParcelable(null); - mTuned = in.readByte() == 1; - mStereo = in.readByte() == 1; - mDigital = in.readByte() == 1; - mSignalStrength = in.readInt(); - if (in.readByte() == 1) { - mMetadata = RadioMetadata.CREATOR.createFromParcel(in); - } else { - mMetadata = null; - } - mFlags = in.readInt(); - mVendorInfo = readStringMap(in); + mSelector = Objects.requireNonNull(in.readTypedObject(ProgramSelector.CREATOR)); + mLogicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR); + mPhysicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR); + mRelatedContent = in.createTypedArrayList(ProgramSelector.Identifier.CREATOR); + mInfoFlags = in.readInt(); + mSignalQuality = in.readInt(); + mMetadata = in.readTypedObject(RadioMetadata.CREATOR); + mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator CREATOR @@ -1580,19 +1631,14 @@ public class RadioManager { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mSelector, 0); - dest.writeByte((byte)(mTuned ? 1 : 0)); - dest.writeByte((byte)(mStereo ? 1 : 0)); - dest.writeByte((byte)(mDigital ? 1 : 0)); - dest.writeInt(mSignalStrength); - if (mMetadata == null) { - dest.writeByte((byte)0); - } else { - dest.writeByte((byte)1); - mMetadata.writeToParcel(dest, flags); - } - dest.writeInt(mFlags); - writeStringMap(dest, mVendorInfo); + dest.writeTypedObject(mSelector, flags); + dest.writeTypedObject(mLogicallyTunedTo, flags); + dest.writeTypedObject(mPhysicallyTunedTo, flags); + Utils.writeTypedCollection(dest, mRelatedContent); + dest.writeInt(mInfoFlags); + dest.writeInt(mSignalQuality); + dest.writeTypedObject(mMetadata, flags); + Utils.writeStringMap(dest, mVendorInfo); } @Override @@ -1602,52 +1648,38 @@ public class RadioManager { @Override public String toString() { - return "ProgramInfo [mSelector=" + mSelector - + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital - + ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength - + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString())) + return "ProgramInfo" + + " [selector=" + mSelector + + ", logicallyTunedTo=" + Objects.toString(mLogicallyTunedTo) + + ", physicallyTunedTo=" + Objects.toString(mPhysicallyTunedTo) + + ", relatedContent=" + mRelatedContent.size() + + ", infoFlags=" + mInfoFlags + + ", mSignalQuality=" + mSignalQuality + + ", mMetadata=" + Objects.toString(mMetadata) + "]"; } @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mSelector.hashCode(); - result = prime * result + (mTuned ? 1 : 0); - result = prime * result + (mStereo ? 1 : 0); - result = prime * result + (mDigital ? 1 : 0); - result = prime * result + mFlags; - result = prime * result + mSignalStrength; - result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode()); - result = prime * result + mVendorInfo.hashCode(); - return result; + return Objects.hash(mSelector, mLogicallyTunedTo, mPhysicallyTunedTo, + mRelatedContent, mInfoFlags, mSignalQuality, mMetadata, mVendorInfo); } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof ProgramInfo)) - return false; + if (this == obj) return true; + if (!(obj instanceof ProgramInfo)) return false; ProgramInfo other = (ProgramInfo) obj; - if (!mSelector.equals(other.getSelector())) return false; - if (mTuned != other.isTuned()) - return false; - if (mStereo != other.isStereo()) - return false; - if (mDigital != other.isDigital()) - return false; - if (mFlags != other.mFlags) - return false; - if (mSignalStrength != other.getSignalStrength()) - return false; - if (mMetadata == null) { - if (other.getMetadata() != null) - return false; - } else if (!mMetadata.equals(other.getMetadata())) - return false; - if (!mVendorInfo.equals(other.mVendorInfo)) return false; + + if (!Objects.equals(mSelector, other.mSelector)) return false; + if (!Objects.equals(mLogicallyTunedTo, other.mLogicallyTunedTo)) return false; + if (!Objects.equals(mPhysicallyTunedTo, other.mPhysicallyTunedTo)) return false; + if (!Objects.equals(mRelatedContent, other.mRelatedContent)) return false; + if (mInfoFlags != other.mInfoFlags) return false; + if (mSignalQuality != other.mSignalQuality) return false; + if (!Objects.equals(mMetadata, other.mMetadata)) return false; + if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false; + return true; } } @@ -1727,7 +1759,70 @@ public class RadioManager { Log.e(TAG, "Failed to open tuner"); return null; } - return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID); + return new TunerAdapter(tuner, halCallback, + config != null ? config.getType() : BAND_INVALID); + } + + private final Map mAnnouncementListeners = + new HashMap<>(); + + /** + * Adds new announcement listener. + * + * @param enabledAnnouncementTypes a set of announcement types to listen to + * @param listener announcement listener + */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + public void addAnnouncementListener(@NonNull Set enabledAnnouncementTypes, + @NonNull Announcement.OnListUpdatedListener listener) { + addAnnouncementListener(cmd -> cmd.run(), enabledAnnouncementTypes, listener); + } + + /** + * Adds new announcement listener with executor. + * + * @param executor the executor + * @param enabledAnnouncementTypes a set of announcement types to listen to + * @param listener announcement listener + */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + public void addAnnouncementListener(@NonNull @CallbackExecutor Executor executor, + @NonNull Set enabledAnnouncementTypes, + @NonNull Announcement.OnListUpdatedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + int[] types = enabledAnnouncementTypes.stream().mapToInt(Integer::intValue).toArray(); + IAnnouncementListener listenerIface = new IAnnouncementListener.Stub() { + public void onListUpdated(List activeAnnouncements) { + executor.execute(() -> listener.onListUpdated(activeAnnouncements)); + } + }; + synchronized (mAnnouncementListeners) { + ICloseHandle closeHandle = null; + try { + closeHandle = mService.addAnnouncementListener(types, listenerIface); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + Objects.requireNonNull(closeHandle); + ICloseHandle oldCloseHandle = mAnnouncementListeners.put(listener, closeHandle); + if (oldCloseHandle != null) Utils.close(oldCloseHandle); + } + } + + /** + * Removes previously registered announcement listener. + * + * @param listener announcement listener, previously registered with + * {@link addAnnouncementListener} + */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) { + Objects.requireNonNull(listener); + synchronized (mAnnouncementListeners) { + ICloseHandle closeHandle = mAnnouncementListeners.remove(listener); + if (closeHandle != null) Utils.close(closeHandle); + } } @NonNull private final Context mContext; diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java index 0d367e7871222cc5d361948dd5046dfb26c4fe03..0edd05533dd3e230f226a692b3a48bb11271a160 100644 --- a/core/java/android/hardware/radio/RadioTuner.java +++ b/core/java/android/hardware/radio/RadioTuner.java @@ -64,7 +64,9 @@ public abstract class RadioTuner { *
    • {@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails,
    • *
    + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated public abstract int setConfiguration(RadioManager.BandConfig config); /** @@ -80,7 +82,10 @@ public abstract class RadioTuner { *
  • {@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails,
  • * + * + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated public abstract int getConfiguration(RadioManager.BandConfig[] config); @@ -228,7 +233,9 @@ public abstract class RadioTuner { *
  • {@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails,
  • * + * @deprecated Use {@link onProgramInfoChanged} callback instead. */ + @Deprecated public abstract int getProgramInformation(RadioManager.ProgramInfo[] info); /** @@ -280,10 +287,28 @@ public abstract class RadioTuner { * @throws IllegalStateException if the scan is in progress or has not been started, * startBackgroundScan() call may fix it. * @throws IllegalArgumentException if the vendorFilter argument is not valid. + * @deprecated Use {@link getDynamicProgramList} instead. */ + @Deprecated public abstract @NonNull List getProgramList(@Nullable Map vendorFilter); + /** + * Get the dynamic list of discovered radio stations. + * + * The list object is updated asynchronously; to get the updates register + * with {@link ProgramList#addListCallback}. + * + * When the returned object is no longer used, it must be closed. + * + * @param filter filter for the list, or null to get the full list. + * @return the dynamic program list object, close it after use + * or {@code null} if program list is not supported by the tuner + */ + public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { + return null; + } + /** * Checks, if the analog playback is forced, see setAnalogForced. * @@ -409,7 +434,10 @@ public abstract class RadioTuner { * Get current antenna connection state for current configuration. * Only valid if a configuration has been applied. * @return {@code true} if the antenna is connected, {@code false} otherwise. + * + * @deprecated Use {@link onAntennaState} callback instead */ + @Deprecated public abstract boolean isAntennaConnected(); /** @@ -428,20 +456,41 @@ public abstract class RadioTuner { public abstract boolean hasControl(); /** Indicates a failure of radio IC or driver. - * The application must close and re open the tuner */ + * The application must close and re open the tuner + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_HARDWARE_FAILURE = 0; /** Indicates a failure of the radio service. - * The application must close and re open the tuner */ + * The application must close and re open the tuner + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_SERVER_DIED = 1; - /** A pending seek or tune operation was cancelled */ + /** A pending seek or tune operation was cancelled + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_CANCELLED = 2; - /** A pending seek or tune operation timed out */ + /** A pending seek or tune operation timed out + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_SCAN_TIMEOUT = 3; - /** The requested configuration could not be applied */ + /** The requested configuration could not be applied + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_CONFIG = 4; - /** Background scan was interrupted due to hardware becoming temporarily unavailable. */ + /** Background scan was interrupted due to hardware becoming temporarily unavailable. + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5; - /** Background scan failed due to other error, ie. HW failure. */ + /** Background scan failed due to other error, ie. HW failure. + * @deprecated See {@link onError} callback. + */ + @Deprecated public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; /** @@ -455,13 +504,29 @@ public abstract class RadioTuner { * status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED}, * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT}, * {@link #ERROR_CONFIG} + * + * @deprecated Use {@link onTuneFailed} for tune, scan and step; + * other use cases (configuration, background scan) are already deprecated. */ public void onError(int status) {} + + /** + * Called when tune, scan or step operation fails. + * + * @param result cause of the failure + * @param selector ProgramSelector argument of tune that failed; + * null for scan and step. + */ + public void onTuneFailed(int result, @Nullable ProgramSelector selector) {} + /** * onConfigurationChanged() is called upon successful completion of * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)} * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)} + * + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated public void onConfigurationChanged(RadioManager.BandConfig config) {} /** diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index 8ad609d008166c871f64cbbedf8d9675a5a925d3..85f3115689cd998f9c46c47d8413c52f40ddb579 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -33,15 +33,18 @@ class TunerAdapter extends RadioTuner { private static final String TAG = "BroadcastRadio.TunerAdapter"; @NonNull private final ITuner mTuner; + @NonNull private final TunerCallbackAdapter mCallback; private boolean mIsClosed = false; private @RadioManager.Band int mBand; - TunerAdapter(ITuner tuner, @RadioManager.Band int band) { - if (tuner == null) { - throw new NullPointerException(); - } - mTuner = tuner; + private ProgramList mLegacyListProxy; + private Map mLegacyListFilter; + + TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback, + @RadioManager.Band int band) { + mTuner = Objects.requireNonNull(tuner); + mCallback = Objects.requireNonNull(callback); mBand = band; } @@ -53,6 +56,10 @@ class TunerAdapter extends RadioTuner { return; } mIsClosed = true; + if (mLegacyListProxy != null) { + mLegacyListProxy.close(); + mLegacyListProxy = null; + } } try { mTuner.close(); @@ -195,15 +202,17 @@ class TunerAdapter extends RadioTuner { @Override public int getProgramInformation(RadioManager.ProgramInfo[] info) { if (info == null || info.length != 1) { - throw new IllegalArgumentException("The argument must be an array of length 1"); + Log.e(TAG, "The argument must be an array of length 1"); + return RadioManager.STATUS_BAD_VALUE; } - try { - info[0] = mTuner.getProgramInformation(); - return RadioManager.STATUS_OK; - } catch (RemoteException e) { - Log.e(TAG, "service died", e); - return RadioManager.STATUS_DEAD_OBJECT; + + RadioManager.ProgramInfo current = mCallback.getCurrentProgramInformation(); + if (current == null) { + Log.w(TAG, "Didn't get program info yet"); + return RadioManager.STATUS_INVALID_OPERATION; } + info[0] = current; + return RadioManager.STATUS_OK; } @Override @@ -227,21 +236,74 @@ class TunerAdapter extends RadioTuner { @Override public @NonNull List getProgramList(@Nullable Map vendorFilter) { - try { - return mTuner.getProgramList(vendorFilter); - } catch (RemoteException e) { - throw new RuntimeException("service died", e); + synchronized (mTuner) { + if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) { + Log.i(TAG, "Program list filter has changed, requesting new list"); + mLegacyListProxy = new ProgramList(); + mLegacyListFilter = vendorFilter; + + mCallback.clearLastCompleteList(); + mCallback.setProgramListObserver(mLegacyListProxy, () -> { }); + try { + mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter)); + } catch (RemoteException ex) { + throw new RuntimeException("service died", ex); + } + } + + List list = mCallback.getLastCompleteList(); + if (list == null) throw new IllegalStateException("Program list is not ready yet"); + return list; + } + } + + @Override + public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { + synchronized (mTuner) { + if (mLegacyListProxy != null) { + mLegacyListProxy.close(); + mLegacyListProxy = null; + } + mLegacyListFilter = null; + + ProgramList list = new ProgramList(); + mCallback.setProgramListObserver(list, () -> { + try { + mTuner.stopProgramListUpdates(); + } catch (RemoteException ex) { + Log.e(TAG, "Couldn't stop program list updates", ex); + } + }); + + try { + mTuner.startProgramListUpdates(filter); + } catch (UnsupportedOperationException ex) { + return null; + } catch (RemoteException ex) { + mCallback.setProgramListObserver(null, () -> { }); + throw new RuntimeException("service died", ex); + } + + return list; } } @Override public boolean isAnalogForced() { - return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG); + try { + return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG); + } catch (UnsupportedOperationException ex) { + throw new IllegalStateException(ex); + } } @Override public void setAnalogForced(boolean isForced) { - setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced); + try { + setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced); + } catch (UnsupportedOperationException ex) { + throw new IllegalStateException(ex); + } } @Override @@ -291,11 +353,7 @@ class TunerAdapter extends RadioTuner { @Override public boolean isAntennaConnected() { - try { - return mTuner.isAntennaConnected(); - } catch (RemoteException e) { - throw new RuntimeException("service died", e); - } + return mCallback.isAntennaConnected(); } @Override diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index a01f658e80f6a1ada74c89fc9ba159214efbb996..7437c40351db37eb47ff8eebd76cc5c69edfa3e8 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -22,7 +22,9 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import java.util.List; import java.util.Map; +import java.util.Objects; /** * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. @@ -30,9 +32,18 @@ import java.util.Map; class TunerCallbackAdapter extends ITunerCallback.Stub { private static final String TAG = "BroadcastRadio.TunerCallbackAdapter"; + private final Object mLock = new Object(); @NonNull private final RadioTuner.Callback mCallback; @NonNull private final Handler mHandler; + @Nullable ProgramList mProgramList; + + // cache for deprecated methods + boolean mIsAntennaConnected = true; + @Nullable List mLastCompleteList; + private boolean mDelayedCompleteCallback = false; + @Nullable RadioManager.ProgramInfo mCurrentProgramInfo; + TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) { mCallback = callback; if (handler == null) { @@ -42,11 +53,88 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } } + void setProgramListObserver(@Nullable ProgramList programList, + @NonNull ProgramList.OnCloseListener closeListener) { + Objects.requireNonNull(closeListener); + synchronized (mLock) { + if (mProgramList != null) { + Log.w(TAG, "Previous program list observer wasn't properly closed, closing it..."); + mProgramList.close(); + } + mProgramList = programList; + if (programList == null) return; + programList.setOnCloseListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) return; + mProgramList = null; + mLastCompleteList = null; + closeListener.onClose(); + } + }); + programList.addOnCompleteListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) return; + mLastCompleteList = programList.toList(); + if (mDelayedCompleteCallback) { + Log.d(TAG, "Sending delayed onBackgroundScanComplete callback"); + sendBackgroundScanCompleteLocked(); + } + } + }); + } + } + + @Nullable List getLastCompleteList() { + synchronized (mLock) { + return mLastCompleteList; + } + } + + void clearLastCompleteList() { + synchronized (mLock) { + mLastCompleteList = null; + } + } + + @Nullable RadioManager.ProgramInfo getCurrentProgramInformation() { + synchronized (mLock) { + return mCurrentProgramInfo; + } + } + + boolean isAntennaConnected() { + return mIsAntennaConnected; + } + @Override public void onError(int status) { mHandler.post(() -> mCallback.onError(status)); } + @Override + public void onTuneFailed(int status, @Nullable ProgramSelector selector) { + mHandler.post(() -> mCallback.onTuneFailed(status, selector)); + + int errorCode; + switch (status) { + case RadioManager.STATUS_PERMISSION_DENIED: + case RadioManager.STATUS_DEAD_OBJECT: + errorCode = RadioTuner.ERROR_SERVER_DIED; + break; + case RadioManager.STATUS_ERROR: + case RadioManager.STATUS_NO_INIT: + case RadioManager.STATUS_BAD_VALUE: + case RadioManager.STATUS_INVALID_OPERATION: + Log.i(TAG, "Got an error with no mapping to the legacy API (" + status + + "), doing a best-effort conversion to ERROR_SCAN_TIMEOUT"); + // fall through + case RadioManager.STATUS_TIMED_OUT: + default: + errorCode = RadioTuner.ERROR_SCAN_TIMEOUT; + } + mHandler.post(() -> mCallback.onError(errorCode)); + } + @Override public void onConfigurationChanged(RadioManager.BandConfig config) { mHandler.post(() -> mCallback.onConfigurationChanged(config)); @@ -59,6 +147,10 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { return; } + synchronized (mLock) { + mCurrentProgramInfo = info; + } + mHandler.post(() -> { mCallback.onProgramInfoChanged(info); @@ -79,6 +171,7 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { @Override public void onAntennaState(boolean connected) { + mIsAntennaConnected = connected; mHandler.post(() -> mCallback.onAntennaState(connected)); } @@ -87,9 +180,22 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable)); } + private void sendBackgroundScanCompleteLocked() { + mDelayedCompleteCallback = false; + mHandler.post(() -> mCallback.onBackgroundScanComplete()); + } + @Override public void onBackgroundScanComplete() { - mHandler.post(() -> mCallback.onBackgroundScanComplete()); + synchronized (mLock) { + if (mLastCompleteList == null) { + Log.i(TAG, "Got onBackgroundScanComplete callback, but the " + + "program list didn't get through yet. Delaying it..."); + mDelayedCompleteCallback = true; + return; + } + sendBackgroundScanCompleteLocked(); + } } @Override @@ -97,6 +203,14 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { mHandler.post(() -> mCallback.onProgramListChanged()); } + @Override + public void onProgramListUpdated(ProgramList.Chunk chunk) { + synchronized (mLock) { + if (mProgramList == null) return; + mProgramList.apply(Objects.requireNonNull(chunk)); + } + } + @Override public void onParametersUpdated(Map parameters) { mHandler.post(() -> mCallback.onParametersUpdated(parameters)); diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..f1b589746a1464c8f02e1273fd6ccda5e8f66f05 --- /dev/null +++ b/core/java/android/hardware/radio/Utils.java @@ -0,0 +1,118 @@ +/** + * 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.hardware.radio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +final class Utils { + private static final String TAG = "BroadcastRadio.utils"; + + static void writeStringMap(@NonNull Parcel dest, @Nullable Map map) { + if (map == null) { + dest.writeInt(0); + return; + } + dest.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeString(entry.getValue()); + } + } + + static @NonNull Map readStringMap(@NonNull Parcel in) { + int size = in.readInt(); + Map map = new HashMap<>(); + while (size-- > 0) { + String key = in.readString(); + String value = in.readString(); + map.put(key, value); + } + return map; + } + + static void writeSet(@NonNull Parcel dest, @Nullable Set set) { + if (set == null) { + dest.writeInt(0); + return; + } + dest.writeInt(set.size()); + set.stream().forEach(elem -> dest.writeTypedObject(elem, 0)); + } + + static Set createSet(@NonNull Parcel in, Parcelable.Creator c) { + int size = in.readInt(); + Set set = new HashSet<>(); + while (size-- > 0) { + set.add(in.readTypedObject(c)); + } + return set; + } + + static void writeIntSet(@NonNull Parcel dest, @Nullable Set set) { + if (set == null) { + dest.writeInt(0); + return; + } + dest.writeInt(set.size()); + set.stream().forEach(elem -> dest.writeInt(Objects.requireNonNull(elem))); + } + + static Set createIntSet(@NonNull Parcel in) { + return createSet(in, new Parcelable.Creator() { + public Integer createFromParcel(Parcel in) { + return in.readInt(); + } + + public Integer[] newArray(int size) { + return new Integer[size]; + } + }); + } + + static void writeTypedCollection(@NonNull Parcel dest, + @Nullable Collection coll) { + ArrayList list = null; + if (coll != null) { + if (coll instanceof ArrayList) { + list = (ArrayList) coll; + } else { + list = new ArrayList<>(coll); + } + } + dest.writeTypedList(list); + } + + static void close(ICloseHandle handle) { + try { + handle.close(); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8937490091d048bdc33553e76ce2b5312850b7d9..a817f33ce7ba0d30dd295ee6ec2e55d59f75d761 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -265,6 +265,10 @@ public class InputMethodService extends AbstractInputMethodService { */ public static final int IME_VISIBLE = 0x2; + // Min and max values for back disposition. + private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; + private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_WILL_DISMISS; + InputMethodManager mImm; int mTheme = 0; @@ -340,42 +344,35 @@ public class InputMethodService extends AbstractInputMethodService { final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; - final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = - new ViewTreeObserver.OnComputeInternalInsetsListener() { - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (isExtractViewShown()) { - // In true fullscreen mode, we just say the window isn't covering - // any content so we don't impact whatever is behind. - View decor = getWindow().getWindow().getDecorView(); - info.contentInsets.top = info.visibleInsets.top - = decor.getHeight(); - info.touchableRegion.setEmpty(); - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); - } else { - onComputeInsets(mTmpInsets); - info.contentInsets.top = mTmpInsets.contentTopInsets; - info.visibleInsets.top = mTmpInsets.visibleTopInsets; - info.touchableRegion.set(mTmpInsets.touchableRegion); - info.setTouchableInsets(mTmpInsets.touchableInsets); - } + final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { + if (isExtractViewShown()) { + // In true fullscreen mode, we just say the window isn't covering + // any content so we don't impact whatever is behind. + View decor = getWindow().getWindow().getDecorView(); + info.contentInsets.top = info.visibleInsets.top = decor.getHeight(); + info.touchableRegion.setEmpty(); + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + } else { + onComputeInsets(mTmpInsets); + info.contentInsets.top = mTmpInsets.contentTopInsets; + info.visibleInsets.top = mTmpInsets.visibleTopInsets; + info.touchableRegion.set(mTmpInsets.touchableRegion); + info.setTouchableInsets(mTmpInsets.touchableInsets); } }; - final View.OnClickListener mActionClickListener = new View.OnClickListener() { - public void onClick(View v) { - final EditorInfo ei = getCurrentInputEditorInfo(); - final InputConnection ic = getCurrentInputConnection(); - if (ei != null && ic != null) { - if (ei.actionId != 0) { - ic.performEditorAction(ei.actionId); - } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION) - != EditorInfo.IME_ACTION_NONE) { - ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION); - } + final View.OnClickListener mActionClickListener = v -> { + final EditorInfo ei = getCurrentInputEditorInfo(); + final InputConnection ic = getCurrentInputConnection(); + if (ei != null && ic != null) { + if (ei.actionId != 0) { + ic.performEditorAction(ei.actionId); + } else if ((ei.imeOptions & EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE) { + ic.performEditorAction(ei.imeOptions & EditorInfo.IME_MASK_ACTION); } } }; - + /** * Concrete implementation of * {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides @@ -508,9 +505,8 @@ public class InputMethodService extends AbstractInputMethodService { } clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. - boolean showing = isInputViewShown(); mImm.setImeWindowStatus(mToken, mStartInputToken, - IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition); + mapToImeWindowStatus(isInputViewShown()), mBackDisposition); if (resultReceiver != null) { resultReceiver.send(wasVis != isInputViewShown() ? InputMethodManager.RESULT_SHOWN @@ -896,20 +892,20 @@ public class InputMethodService extends AbstractInputMethodService { mWindow.getWindow().setWindowAnimations( com.android.internal.R.style.Animation_InputMethodFancy); } - mFullscreenArea = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea); + mFullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea); mExtractViewHidden = false; - mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea); + mExtractFrame = mRootView.findViewById(android.R.id.extractArea); mExtractView = null; mExtractEditText = null; mExtractAccessories = null; mExtractAction = null; mFullscreenApplied = false; - - mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea); - mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea); + + mCandidatesFrame = mRootView.findViewById(android.R.id.candidatesArea); + mInputFrame = mRootView.findViewById(android.R.id.inputArea); mInputView = null; mIsInputViewShown = false; - + mExtractFrame.setVisibility(View.GONE); mCandidatesVisibility = getCandidatesHiddenVisibility(); mCandidatesFrame.setVisibility(mCandidatesVisibility); @@ -1021,7 +1017,16 @@ public class InputMethodService extends AbstractInputMethodService { } public void setBackDisposition(int disposition) { + if (disposition == mBackDisposition) { + return; + } + if (disposition > BACK_DISPOSITION_MAX || disposition < BACK_DISPOSITION_MIN) { + Log.e(TAG, "Invalid back disposition value (" + disposition + ") specified."); + return; + } mBackDisposition = disposition; + mImm.setImeWindowStatus(mToken, mStartInputToken, mapToImeWindowStatus(isInputViewShown()), + mBackDisposition); } public int getBackDisposition() { @@ -1088,33 +1093,6 @@ public class InputMethodService extends AbstractInputMethodService { mImm.setInputMethodAndSubtypeInternal(mToken, id, subtype); } - /** - * Close/hide the input method's soft input area, so the user no longer - * sees it or can interact with it. This can only be called - * from the currently active input method, as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY}, - * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set. - */ - public void hideSoftInputFromInputMethod(int flags) { - mImm.hideSoftInputFromInputMethodInternal(mToken, flags); - } - - /** - * Show the input method's soft input area, so the user - * sees the input method window and can interact with it. - * This can only be called from the currently active input method, - * as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} or - * {@link InputMethodManager#SHOW_FORCED} bit set. - */ - public void showSoftInputFromInputMethod(int flags) { - mImm.showSoftInputFromInputMethodInternal(mToken, flags); - } - /** * Force switch to the last used input method and subtype. If the last input method didn't have * any subtypes, the framework will simply switch to the last input method with no subtype @@ -1461,17 +1439,17 @@ public class InputMethodService extends AbstractInputMethodService { public int getCandidatesHiddenVisibility() { return isExtractViewShown() ? View.GONE : View.INVISIBLE; } - + public void showStatusIcon(@DrawableRes int iconResId) { mStatusIcon = iconResId; - mImm.showStatusIcon(mToken, getPackageName(), iconResId); + mImm.showStatusIconInternal(mToken, getPackageName(), iconResId); } - + public void hideStatusIcon() { mStatusIcon = 0; - mImm.hideStatusIcon(mToken); + mImm.hideStatusIconInternal(mToken); } - + /** * Force switch to a new input method, as identified by id. This * input method will be destroyed, and the requested one started on the @@ -1480,9 +1458,9 @@ public class InputMethodService extends AbstractInputMethodService { * @param id Unique identifier of the new input method ot start. */ public void switchInputMethod(String id) { - mImm.setInputMethod(mToken, id); + mImm.setInputMethodInternal(mToken, id); } - + public void setExtractView(View view) { mExtractFrame.removeAllViews(); mExtractFrame.addView(view, new FrameLayout.LayoutParams( @@ -1490,13 +1468,13 @@ public class InputMethodService extends AbstractInputMethodService { ViewGroup.LayoutParams.MATCH_PARENT)); mExtractView = view; if (view != null) { - mExtractEditText = (ExtractEditText)view.findViewById( + mExtractEditText = view.findViewById( com.android.internal.R.id.inputExtractEditText); mExtractEditText.setIME(this); mExtractAction = view.findViewById( com.android.internal.R.id.inputExtractAction); if (mExtractAction != null) { - mExtractAccessories = (ViewGroup)view.findViewById( + mExtractAccessories = view.findViewById( com.android.internal.R.id.inputExtractAccessories); } startExtractingText(false); @@ -1745,7 +1723,7 @@ public class InputMethodService extends AbstractInputMethodService { // Rethrow the exception to preserve the existing behavior. Some IMEs may have directly // called this method and relied on this exception for some clean-up tasks. // TODO: Give developers a clear guideline of whether it's OK to call this method or - // InputMethodManager#showSoftInputFromInputMethod() should always be used instead. + // InputMethodService#requestShowSelf(int) should always be used instead. throw e; } finally { // TODO: Is it OK to set true when we get BadTokenException? @@ -1796,7 +1774,7 @@ public class InputMethodService extends AbstractInputMethodService { startExtractingText(false); } - final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0); + final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown()); if (previousImeWindowStatus != nextImeWindowStatus) { mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus, mBackDisposition); @@ -1923,6 +1901,7 @@ public class InputMethodService extends AbstractInputMethodService { mInputStarted = false; mStartedInputConnection = null; mCurCompletions = null; + mBackDisposition = BACK_DISPOSITION_DEFAULT; } void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { @@ -2067,27 +2046,30 @@ public class InputMethodService extends AbstractInputMethodService { /** * Close this input method's soft input area, removing it from the display. - * The input method will continue running, but the user can no longer use - * it to generate input by touching the screen. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY - * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set. + * + * The input method will continue running, but the user can no longer use it to generate input + * by touching the screen. + * + * @see InputMethodManager#HIDE_IMPLICIT_ONLY + * @see InputMethodManager#HIDE_NOT_ALWAYS + * @param flags Provides additional operating flags. */ public void requestHideSelf(int flags) { - mImm.hideSoftInputFromInputMethod(mToken, flags); + mImm.hideSoftInputFromInputMethodInternal(mToken, flags); } - + /** - * Show the input method. This is a call back to the - * IMF to handle showing the input method. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_FORCED - * InputMethodManager.} bit set. + * Show the input method's soft input area, so the user sees the input method window and can + * interact with it. + * + * @see InputMethodManager#SHOW_IMPLICIT + * @see InputMethodManager#SHOW_FORCED + * @param flags Provides additional operating flags. */ - private void requestShowSelf(int flags) { - mImm.showSoftInputFromInputMethod(mToken, flags); + public void requestShowSelf(int flags) { + mImm.showSoftInputFromInputMethodInternal(mToken, flags); } - + private boolean handleBack(boolean doIt) { if (mShowInputRequested) { // If the soft input area is shown, back closes it and we @@ -2135,7 +2117,11 @@ public class InputMethodService extends AbstractInputMethodService { * them to perform navigation in the underlying application. */ public boolean onKeyDown(int keyCode, KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + if (mBackDisposition == BACK_DISPOSITION_WILL_NOT_DISMISS) { + return false; + } final ExtractEditText eet = getExtractEditTextIfVisible(); if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) { return true; @@ -2754,7 +2740,7 @@ public class InputMethodService extends AbstractInputMethodService { * application. * This cannot be {@code null}. * @param inputConnection {@link InputConnection} with which - * {@link InputConnection#commitContent(InputContentInfo, Bundle)} will be called. + * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} will be called. * @hide */ @Override @@ -2769,6 +2755,10 @@ public class InputMethodService extends AbstractInputMethodService { mImm.exposeContent(mToken, inputContentInfo, getCurrentInputEditorInfo()); } + private static int mapToImeWindowStatus(boolean isInputViewShown) { + return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0); + } + /** * Performs a dump of the InputMethodService's internal state. Override * to add your own information to the dump. diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 13b9206b12ee83a4e740598911f39cbb28c5a3f4..2306e5fa3dc1910038757a4f171a5fee61f808d2 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -61,6 +61,7 @@ import java.util.Map; * @attr ref android.R.styleable#KeyboardView_keyBackground * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset + * @attr ref android.R.styleable#KeyboardView_keyPreviewHeight * @attr ref android.R.styleable#KeyboardView_labelTextSize * @attr ref android.R.styleable#KeyboardView_keyTextSize * @attr ref android.R.styleable#KeyboardView_keyTextColor diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 11d338d05c6864d816adddebde0f6280faac084b..166342dd4e6dc87b7d20da297ac1bb68693d750b 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3763,4 +3763,20 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } + + /** + * The network watchlist is a list of domains and IP addresses that are associated with + * potentially harmful apps. This method returns the hash of the watchlist currently + * used by the system. + * + * @return Hash of network watchlist config file. Null if config does not exist. + */ + public byte[] getNetworkWatchlistConfigHash() { + try { + return mService.getNetworkWatchlistConfigHash(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get watchlist config hash"); + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a6fe7389bc723ea1d4c581a601b168e94bff1da2..ce95b60dd2db6cf3e55a0400394187a8bf4ec2b6 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -180,4 +180,6 @@ interface IConnectivityManager void stopKeepalive(in Network network, int slot); String getCaptivePortalServerUrl(); + + byte[] getNetworkWatchlistConfigHash(); } diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index d9b57db1807173cb36206449f019dd063994f898..3ce0283d7f23fc45c4fb63b338a25ee55c11d732 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -21,6 +21,7 @@ import android.net.IpSecConfig; import android.net.IpSecUdpEncapResponse; import android.net.IpSecSpiResponse; import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -31,7 +32,7 @@ import android.os.ParcelFileDescriptor; interface IIpSecService { IpSecSpiResponse allocateSecurityParameterIndex( - int direction, in String remoteAddress, int requestedSpi, in IBinder binder); + in String destinationAddress, int requestedSpi, in IBinder binder); void releaseSecurityParameterIndex(int resourceId); @@ -39,11 +40,29 @@ interface IIpSecService void closeUdpEncapsulationSocket(int resourceId); - IpSecTransformResponse createTransportModeTransform(in IpSecConfig c, in IBinder binder); + IpSecTunnelInterfaceResponse createTunnelInterface( + in String localAddr, + in String remoteAddr, + in Network underlyingNetwork, + in IBinder binder); - void deleteTransportModeTransform(int transformId); + void addAddressToTunnelInterface( + int tunnelResourceId, + String localAddr); - void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + void removeAddressFromTunnelInterface( + int tunnelResourceId, + String localAddr); - void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + void deleteTunnelInterface(int resourceId); + + IpSecTransformResponse createTransform(in IpSecConfig c, in IBinder binder); + + void deleteTransform(int transformId); + + void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId); + + void applyTunnelModeTransform(int tunnelResourceId, int direction, int transformResourceId); + + void removeTransportModeTransforms(in ParcelFileDescriptor socket); } diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl index 005dd6e16880de1fcc5c5c306e47b99848ab3944..10667aecd128756a12e421fafbb6e92099f9ca6d 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 7e37432fb06a62a9c6a9f616cc4fdf581a59a87b..476e2f43649ff147f22eb3150eaf5e6a91aa24a1 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/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 95e7f6031c726a5f3aa48d04af45f832617ea693..90e3ffd550b4708e74af383fb5bbb8b492aae560 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -18,6 +18,7 @@ package android.net; import android.net.DataUsageRequest; import android.net.INetworkStatsSession; +import android.net.Network; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; @@ -53,7 +54,7 @@ interface INetworkStatsService { void setUidForeground(int uid, boolean uidForeground); /** Force update of ifaces. */ - void forceUpdateIfaces(); + void forceUpdateIfaces(in Network[] defaultNetworks); /** Force update of statistics. */ void forceUpdate(); diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index 7d752e89e6f60de830ca85b66c251e7756ba416d..c69a4d4c0beed39dbc8f7a73e7506b507739238f 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -256,13 +256,19 @@ public final class IpSecAlgorithm implements Parcelable { return getName().equals(AUTH_CRYPT_AES_GCM); } + // Because encryption keys are sensitive and userdebug builds are used by large user pools + // such as beta testers, we only allow sensitive info such as keys on eng builds. + private static boolean isUnsafeBuild() { + return Build.IS_DEBUGGABLE && Build.IS_ENG; + } + @Override public String toString() { return new StringBuilder() .append("{mName=") .append(mName) .append(", mKey=") - .append(Build.IS_DEBUGGABLE ? HexDump.toHexString(mKey) : "") + .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "") .append(", mTruncLenBits=") .append(mTruncLenBits) .append("}") diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index f54ceb5c142a189f8ad815e519b81e58c01dd9a4..6a262e2c87cad08a1bec3822cd660e7a5ea73c42 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -32,59 +32,29 @@ public final class IpSecConfig implements Parcelable { // MODE_TRANSPORT or MODE_TUNNEL private int mMode = IpSecTransform.MODE_TRANSPORT; - // Needs to be valid only for tunnel mode // Preventing this from being null simplifies Java->Native binder - private String mLocalAddress = ""; + private String mSourceAddress = ""; // Preventing this from being null simplifies Java->Native binder - private String mRemoteAddress = ""; + private String mDestinationAddress = ""; // The underlying Network that represents the "gateway" Network // for outbound packets. It may also be used to select packets. private Network mNetwork; - /** - * This class captures the parameters that specifically apply to inbound or outbound traffic. - */ - public static class Flow { - // Minimum requirements for identifying a transform - // SPI identifying the IPsec flow in packet processing - // and a remote IP address - private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID; - - // Encryption Algorithm - private IpSecAlgorithm mEncryption; - - // Authentication Algorithm - private IpSecAlgorithm mAuthentication; - - // Authenticated Encryption Algorithm - private IpSecAlgorithm mAuthenticatedEncryption; - - @Override - public String toString() { - return new StringBuilder() - .append("{mSpiResourceId=") - .append(mSpiResourceId) - .append(", mEncryption=") - .append(mEncryption) - .append(", mAuthentication=") - .append(mAuthentication) - .append(", mAuthenticatedEncryption=") - .append(mAuthenticatedEncryption) - .append("}") - .toString(); - } - - static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) { - if (lhs == null || rhs == null) return (lhs == rhs); - return (lhs.mSpiResourceId == rhs.mSpiResourceId - && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption) - && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)); - } - } - - private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()}; + // Minimum requirements for identifying a transform + // SPI identifying the IPsec SA in packet processing + // and a destination IP address + private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID; + + // Encryption Algorithm + private IpSecAlgorithm mEncryption; + + // Authentication Algorithm + private IpSecAlgorithm mAuthentication; + + // Authenticated Encryption Algorithm + private IpSecAlgorithm mAuthenticatedEncryption; // For tunnel mode IPv4 UDP Encapsulation // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE @@ -95,41 +65,46 @@ public final class IpSecConfig implements Parcelable { // An interval, in seconds between the NattKeepalive packets private int mNattKeepaliveInterval; + // XFRM mark and mask + private int mMarkValue; + private int mMarkMask; + /** Set the mode for this IPsec transform */ public void setMode(int mode) { mMode = mode; } - /** Set the local IP address for Tunnel mode */ - public void setLocalAddress(String localAddress) { - mLocalAddress = localAddress; + /** Set the source IP addres for this IPsec transform */ + public void setSourceAddress(String sourceAddress) { + mSourceAddress = sourceAddress; } - /** Set the remote IP address for this IPsec transform */ - public void setRemoteAddress(String remoteAddress) { - mRemoteAddress = remoteAddress; + /** Set the destination IP address for this IPsec transform */ + public void setDestinationAddress(String destinationAddress) { + mDestinationAddress = destinationAddress; } - /** Set the SPI for a given direction by resource ID */ - public void setSpiResourceId(int direction, int resourceId) { - mFlow[direction].mSpiResourceId = resourceId; + /** Set the SPI by resource ID */ + public void setSpiResourceId(int resourceId) { + mSpiResourceId = resourceId; } - /** Set the encryption algorithm for a given direction */ - public void setEncryption(int direction, IpSecAlgorithm encryption) { - mFlow[direction].mEncryption = encryption; + /** Set the encryption algorithm */ + public void setEncryption(IpSecAlgorithm encryption) { + mEncryption = encryption; } - /** Set the authentication algorithm for a given direction */ - public void setAuthentication(int direction, IpSecAlgorithm authentication) { - mFlow[direction].mAuthentication = authentication; + /** Set the authentication algorithm */ + public void setAuthentication(IpSecAlgorithm authentication) { + mAuthentication = authentication; } - /** Set the authenticated encryption algorithm for a given direction */ - public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) { - mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption; + /** Set the authenticated encryption algorithm */ + public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) { + mAuthenticatedEncryption = authenticatedEncryption; } + /** Set the underlying network that will carry traffic for this transform */ public void setNetwork(Network network) { mNetwork = network; } @@ -150,33 +125,41 @@ public final class IpSecConfig implements Parcelable { mNattKeepaliveInterval = interval; } + public void setMarkValue(int mark) { + mMarkValue = mark; + } + + public void setMarkMask(int mask) { + mMarkMask = mask; + } + // Transport or Tunnel public int getMode() { return mMode; } - public String getLocalAddress() { - return mLocalAddress; + public String getSourceAddress() { + return mSourceAddress; } - public int getSpiResourceId(int direction) { - return mFlow[direction].mSpiResourceId; + public int getSpiResourceId() { + return mSpiResourceId; } - public String getRemoteAddress() { - return mRemoteAddress; + public String getDestinationAddress() { + return mDestinationAddress; } - public IpSecAlgorithm getEncryption(int direction) { - return mFlow[direction].mEncryption; + public IpSecAlgorithm getEncryption() { + return mEncryption; } - public IpSecAlgorithm getAuthentication(int direction) { - return mFlow[direction].mAuthentication; + public IpSecAlgorithm getAuthentication() { + return mAuthentication; } - public IpSecAlgorithm getAuthenticatedEncryption(int direction) { - return mFlow[direction].mAuthenticatedEncryption; + public IpSecAlgorithm getAuthenticatedEncryption() { + return mAuthenticatedEncryption; } public Network getNetwork() { @@ -199,6 +182,14 @@ public final class IpSecConfig implements Parcelable { return mNattKeepaliveInterval; } + public int getMarkValue() { + return mMarkValue; + } + + public int getMarkMask() { + return mMarkMask; + } + // Parcelable Methods @Override @@ -209,21 +200,19 @@ public final class IpSecConfig implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mMode); - out.writeString(mLocalAddress); - out.writeString(mRemoteAddress); + out.writeString(mSourceAddress); + out.writeString(mDestinationAddress); out.writeParcelable(mNetwork, flags); - out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags); - out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags); + out.writeInt(mSpiResourceId); + out.writeParcelable(mEncryption, flags); + out.writeParcelable(mAuthentication, flags); + out.writeParcelable(mAuthenticatedEncryption, flags); out.writeInt(mEncapType); out.writeInt(mEncapSocketResourceId); out.writeInt(mEncapRemotePort); out.writeInt(mNattKeepaliveInterval); + out.writeInt(mMarkValue); + out.writeInt(mMarkMask); } @VisibleForTesting @@ -231,27 +220,22 @@ public final class IpSecConfig implements Parcelable { private IpSecConfig(Parcel in) { mMode = in.readInt(); - mLocalAddress = in.readString(); - mRemoteAddress = in.readString(); + mSourceAddress = in.readString(); + mDestinationAddress = in.readString(); mNetwork = (Network) in.readParcelable(Network.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt(); - mFlow[IpSecTransform.DIRECTION_IN].mEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mAuthentication = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt(); - mFlow[IpSecTransform.DIRECTION_OUT].mEncryption = + mSpiResourceId = in.readInt(); + mEncryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication = + mAuthentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption = + mAuthenticatedEncryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); mEncapType = in.readInt(); mEncapSocketResourceId = in.readInt(); mEncapRemotePort = in.readInt(); mNattKeepaliveInterval = in.readInt(); + mMarkValue = in.readInt(); + mMarkMask = in.readInt(); } @Override @@ -260,10 +244,10 @@ public final class IpSecConfig implements Parcelable { strBuilder .append("{mMode=") .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT") - .append(", mLocalAddress=") - .append(mLocalAddress) - .append(", mRemoteAddress=") - .append(mRemoteAddress) + .append(", mSourceAddress=") + .append(mSourceAddress) + .append(", mDestinationAddress=") + .append(mDestinationAddress) .append(", mNetwork=") .append(mNetwork) .append(", mEncapType=") @@ -274,10 +258,18 @@ public final class IpSecConfig implements Parcelable { .append(mEncapRemotePort) .append(", mNattKeepaliveInterval=") .append(mNattKeepaliveInterval) - .append(", mFlow[OUT]=") - .append(mFlow[IpSecTransform.DIRECTION_OUT]) - .append(", mFlow[IN]=") - .append(mFlow[IpSecTransform.DIRECTION_IN]) + .append("{mSpiResourceId=") + .append(mSpiResourceId) + .append(", mEncryption=") + .append(mEncryption) + .append(", mAuthentication=") + .append(mAuthentication) + .append(", mAuthenticatedEncryption=") + .append(mAuthenticatedEncryption) + .append(", mMarkValue=") + .append(mMarkValue) + .append(", mMarkMask=") + .append(mMarkMask) .append("}"); return strBuilder.toString(); @@ -299,17 +291,20 @@ public final class IpSecConfig implements Parcelable { public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) { if (lhs == null || rhs == null) return (lhs == rhs); return (lhs.mMode == rhs.mMode - && lhs.mLocalAddress.equals(rhs.mLocalAddress) - && lhs.mRemoteAddress.equals(rhs.mRemoteAddress) + && lhs.mSourceAddress.equals(rhs.mSourceAddress) + && lhs.mDestinationAddress.equals(rhs.mDestinationAddress) && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork)) || (lhs.mNetwork == rhs.mNetwork)) && lhs.mEncapType == rhs.mEncapType && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId && lhs.mEncapRemotePort == rhs.mEncapRemotePort && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval - && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT], - rhs.mFlow[IpSecTransform.DIRECTION_OUT]) - && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN], - rhs.mFlow[IpSecTransform.DIRECTION_IN])); + && lhs.mSpiResourceId == rhs.mSpiResourceId + && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption) + && IpSecAlgorithm.equals( + lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption) + && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication) + && lhs.mMarkValue == rhs.mMarkValue + && lhs.mMarkMask == rhs.mMarkMask); } } diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 34cfa9b2153d6e90dd234237901631fd35c7a940..24a078fccc1d62b7e7045d7f97435d1eb0f2f90d 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,7 +17,9 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; @@ -33,6 +35,8 @@ import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; @@ -52,6 +56,23 @@ import java.net.Socket; public final class IpSecManager { private static final String TAG = "IpSecManager"; + /** + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic towards the host. + */ + public static final int DIRECTION_IN = 0; + + /** + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic from the host. + */ + public static final int DIRECTION_OUT = 1; + + /** @hide */ + @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) + @Retention(RetentionPolicy.SOURCE) + public @interface PolicyDirection {} + /** * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * @@ -125,7 +146,7 @@ public final class IpSecManager { */ public static final class SecurityParameterIndex implements AutoCloseable { private final IIpSecService mService; - private final InetAddress mRemoteAddress; + private final InetAddress mDestinationAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; private int mResourceId = INVALID_RESOURCE_ID; @@ -164,14 +185,14 @@ public final class IpSecManager { } private SecurityParameterIndex( - @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi) + @NonNull IIpSecService service, InetAddress destinationAddress, int spi) throws ResourceUnavailableException, SpiUnavailableException { mService = service; - mRemoteAddress = remoteAddress; + mDestinationAddress = destinationAddress; try { IpSecSpiResponse result = mService.allocateSecurityParameterIndex( - direction, remoteAddress.getHostAddress(), spi, new Binder()); + destinationAddress.getHostAddress(), spi, new Binder()); if (result == null) { throw new NullPointerException("Received null response from IpSecService"); @@ -216,25 +237,23 @@ public final class IpSecManager { } /** - * Reserve a random SPI for traffic bound to or from the specified remote address. + * Reserve a random SPI for traffic bound to or from the specified destination address. * *

    If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. * @return the reserved SecurityParameterIndex - * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated - * for this user - * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user */ - public SecurityParameterIndex allocateSecurityParameterIndex( - int direction, InetAddress remoteAddress) throws ResourceUnavailableException { + public SecurityParameterIndex allocateSecurityParameterIndex(InetAddress destinationAddress) + throws ResourceUnavailableException { try { return new SecurityParameterIndex( mService, - direction, - remoteAddress, + destinationAddress, IpSecManager.INVALID_SECURITY_PARAMETER_INDEX); } catch (SpiUnavailableException unlikely) { throw new ResourceUnavailableException("No SPIs available"); @@ -242,26 +261,27 @@ public final class IpSecManager { } /** - * Reserve the requested SPI for traffic bound to or from the specified remote address. + * Reserve the requested SPI for traffic bound to or from the specified destination address. * *

    If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. * @param requestedSpi the requested SPI, or '0' to allocate a random SPI * @return the reserved SecurityParameterIndex - * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated - * for this user - * @throws SpiUnavailableException indicating that the requested SPI could not be reserved + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user + * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be + * reserved */ public SecurityParameterIndex allocateSecurityParameterIndex( - int direction, InetAddress remoteAddress, int requestedSpi) + InetAddress destinationAddress, int requestedSpi) throws SpiUnavailableException, ResourceUnavailableException { if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) { throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI"); } - return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi); + return new SecurityParameterIndex(mService, destinationAddress, requestedSpi); } /** @@ -269,14 +289,14 @@ public final class IpSecManager { * *

    This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * *

    For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * *

    Rekey Procedure

    * @@ -287,15 +307,14 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a stream socket + * @param direction the policy direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied - * @hide */ - public void applyTransportModeTransform(Socket socket, IpSecTransform transform) + public void applyTransportModeTransform( + Socket socket, int direction, IpSecTransform transform) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { - applyTransportModeTransform(pfd, transform); - } + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); } /** @@ -303,14 +322,14 @@ public final class IpSecManager { * *

    This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * *

    For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * *

    Rekey Procedure

    * @@ -321,15 +340,13 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a datagram socket + * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied - * @hide */ - public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) - throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { - applyTransportModeTransform(pfd, transform); - } + public void applyTransportModeTransform( + DatagramSocket socket, int direction, IpSecTransform transform) throws IOException { + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); } /** @@ -337,14 +354,14 @@ public final class IpSecManager { * *

    This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * *

    For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * *

    Rekey Procedure

    * @@ -355,24 +372,17 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a socket file descriptor + * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied */ - public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) + public void applyTransportModeTransform( + FileDescriptor socket, int direction, IpSecTransform transform) throws IOException { // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor() - // constructor takes control and closes the user's FD when we exit the method - // This is behaviorally the same as the other versions, but the PFD constructor does not - // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup(). + // constructor takes control and closes the user's FD when we exit the method. try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { - applyTransportModeTransform(pfd, transform); - } - } - - /* Call down to activate a transform */ - private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { - try { - mService.applyTransportModeTransform(pfd, transform.getResourceId()); + mService.applyTransportModeTransform(pfd, direction, transform.getResourceId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -396,75 +406,56 @@ public final class IpSecManager { /** * Remove an IPsec transform from a stream socket. * - *

    Once removed, traffic on the socket will not be encrypted. This operation will succeed - * regardless of the state of the transform. Removing a transform from a socket allows the - * socket to be reused for communication in the clear. + *

    Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. * *

    If an {@code IpSecTransform} object applied to this socket was deallocated by calling * {@link IpSecTransform#close()}, then communication on the socket will fail until this method * is called. * * @param socket a socket that previously had a transform applied to it - * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket - * @hide */ - public void removeTransportModeTransform(Socket socket, IpSecTransform transform) + public void removeTransportModeTransforms(Socket socket) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { - removeTransportModeTransform(pfd, transform); - } + removeTransportModeTransforms(socket.getFileDescriptor$()); } /** * Remove an IPsec transform from a datagram socket. * - *

    Once removed, traffic on the socket will not be encrypted. This operation will succeed - * regardless of the state of the transform. Removing a transform from a socket allows the - * socket to be reused for communication in the clear. + *

    Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. * *

    If an {@code IpSecTransform} object applied to this socket was deallocated by calling * {@link IpSecTransform#close()}, then communication on the socket will fail until this method * is called. * * @param socket a socket that previously had a transform applied to it - * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket - * @hide */ - public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) + public void removeTransportModeTransforms(DatagramSocket socket) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { - removeTransportModeTransform(pfd, transform); - } + removeTransportModeTransforms(socket.getFileDescriptor$()); } /** * Remove an IPsec transform from a socket. * - *

    Once removed, traffic on the socket will not be encrypted. This operation will succeed - * regardless of the state of the transform. Removing a transform from a socket allows the - * socket to be reused for communication in the clear. + *

    Once removed, traffic on the socket will not be encrypted. Removing transforms from a + * socket allows the socket to be reused for communication in the clear. * *

    If an {@code IpSecTransform} object applied to this socket was deallocated by calling * {@link IpSecTransform#close()}, then communication on the socket will fail until this method * is called. * * @param socket a socket that previously had a transform applied to it - * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket */ - public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) + public void removeTransportModeTransforms(FileDescriptor socket) throws IOException { try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { - removeTransportModeTransform(pfd, transform); - } - } - - /* Call down to remove a transform */ - private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { - try { - mService.removeTransportModeTransform(pfd, transform.getResourceId()); + mService.removeTransportModeTransforms(pfd); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -634,6 +625,170 @@ public final class IpSecManager { return new UdpEncapsulationSocket(mService, 0); } + /** + * This class represents an IpSecTunnelInterface + * + *

    IpSecTunnelInterface objects track tunnel interfaces that serve as + * local endpoints for IPsec tunnels. + * + *

    Creating an IpSecTunnelInterface creates a device to which IpSecTransforms may be + * applied to provide IPsec security to packets sent through the tunnel. While a tunnel + * cannot be used in standalone mode within Android, the higher layers may use the tunnel + * to create Network objects which are accessible to the Android system. + * @hide + */ + @SystemApi + public static final class IpSecTunnelInterface implements AutoCloseable { + private final IIpSecService mService; + private final InetAddress mRemoteAddress; + private final InetAddress mLocalAddress; + private final Network mUnderlyingNetwork; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private String mInterfaceName; + private int mResourceId = INVALID_RESOURCE_ID; + + /** Get the underlying SPI held by this object. */ + public String getInterfaceName() { + return mInterfaceName; + } + + /** + * Add an address to the IpSecTunnelInterface + * + *

    Add an address which may be used as the local inner address for + * tunneled traffic. + * + * @param address the local address for traffic inside the tunnel + * @throws IOException if the address could not be added + * @hide + */ + public void addAddress(LinkAddress address) throws IOException { + } + + /** + * Remove an address from the IpSecTunnelInterface + * + *

    Remove an address which was previously added to the IpSecTunnelInterface + * + * @param address to be removed + * @throws IOException if the address could not be removed + * @hide + */ + public void removeAddress(LinkAddress address) throws IOException { + } + + private IpSecTunnelInterface(@NonNull IIpSecService service, + @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress, + @NonNull Network underlyingNetwork) + throws ResourceUnavailableException, IOException { + mService = service; + mLocalAddress = localAddress; + mRemoteAddress = remoteAddress; + mUnderlyingNetwork = underlyingNetwork; + + try { + IpSecTunnelInterfaceResponse result = + mService.createTunnelInterface( + localAddress.getHostAddress(), + remoteAddress.getHostAddress(), + underlyingNetwork, + new Binder()); + switch (result.status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more tunnel interfaces may be allocated by this requester."); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + result.status); + } + mResourceId = result.resourceId; + mInterfaceName = result.interfaceName; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.open("constructor"); + } + + /** + * Delete an IpSecTunnelInterface + * + *

    Calling close will deallocate the IpSecTunnelInterface and all of its system + * resources. Any packets bound for this interface either inbound or outbound will + * all be lost. + */ + @Override + public void close() { + try { + mService.deleteTunnelInterface(mResourceId); + mResourceId = INVALID_RESOURCE_ID; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.close(); + } + + /** Check that the Interface was closed properly. */ + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + /** @hide */ + @VisibleForTesting + public int getResourceId() { + return mResourceId; + } + } + + /** + * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic. + * + *

    An application that creates tunnels is responsible for cleaning up the tunnel when the + * underlying network goes away, and the onLost() callback is received. + * + * @param localAddress The local addres of the tunnel + * @param remoteAddress The local addres of the tunnel + * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel. + * This network should almost certainly be a network such as WiFi with an L2 address. + * @return a new {@link IpSecManager#IpSecTunnelInterface} with the specified properties + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open + * @hide + */ + @SystemApi + public IpSecTunnelInterface createIpSecTunnelInterface(@NonNull InetAddress localAddress, + @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork) + throws ResourceUnavailableException, IOException { + return new IpSecTunnelInterface(mService, localAddress, remoteAddress, underlyingNetwork); + } + + /** + * Apply a transform to the IpSecTunnelInterface + * + * @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied + * transform. + * @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which + * the transform will be used. + * @param transform an {@link IpSecTransform} created in tunnel mode + * @throws IOException indicating that the transform could not be applied due to a lower + * layer failure. + * @hide + */ + @SystemApi + public void applyTunnelModeTransform(IpSecTunnelInterface tunnel, int direction, + IpSecTransform transform) throws IOException { + try { + mService.applyTunnelModeTransform( + tunnel.getResourceId(), direction, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Construct an instance of IpSecManager within an application context. * diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 102ba6d94faa05e31b30738f2d1723f1c6f48e61..9ccdbe2b1bdc9ad3b35b4677e66ac6edec1eedf0 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -17,11 +17,14 @@ package android.net; import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -38,13 +41,11 @@ import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; /** - * This class represents an IPsec transform, which comprises security associations in one or both - * directions. + * This class represents a transform, which roughly corresponds to an IPsec Security Association. * *

    Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} - * object encapsulates the properties and state of an inbound and outbound IPsec security - * association. That includes, but is not limited to, algorithm choice, key material, and allocated - * system resources. + * object encapsulates the properties and state of an IPsec security association. That includes, + * but is not limited to, algorithm choice, key material, and allocated system resources. * * @see RFC 4301, Security Architecture for the * Internet Protocol @@ -52,23 +53,6 @@ import java.net.InetAddress; public final class IpSecTransform implements AutoCloseable { private static final String TAG = "IpSecTransform"; - /** - * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute - * applies to traffic towards the host. - */ - public static final int DIRECTION_IN = 0; - - /** - * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute - * applies to traffic from the host. - */ - public static final int DIRECTION_OUT = 1; - - /** @hide */ - @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) - @Retention(RetentionPolicy.SOURCE) - public @interface TransformDirection {} - /** @hide */ public static final int MODE_TRANSPORT = 0; @@ -143,18 +127,10 @@ public final class IpSecTransform implements AutoCloseable { synchronized (this) { try { IIpSecService svc = getIpSecService(); - IpSecTransformResponse result = - svc.createTransportModeTransform(mConfig, new Binder()); + IpSecTransformResponse result = svc.createTransform(mConfig, new Binder()); int status = result.status; checkResultStatus(status); mResourceId = result.resourceId; - - /* Keepalive will silently fail if not needed by the config; but, if needed and - * it fails to start, we need to bail because a transform will not be reliable - * to use if keepalive is expected to offload and fails. - */ - // FIXME: if keepalive fails, we need to fail spectacularly - startKeepalive(mContext); Log.d(TAG, "Added Transform with Id " + mResourceId); mCloseGuard.open("build"); } catch (RemoteException e) { @@ -170,7 +146,7 @@ public final class IpSecTransform implements AutoCloseable { * *

    Deactivating a transform while it is still applied to a socket will result in errors on * that socket. Make sure to remove transforms by calling {@link - * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a + * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a * socket will not deactivate it (because one transform may be applied to multiple sockets). * *

    It is safe to call this method on a transform that has already been deactivated. @@ -184,13 +160,9 @@ public final class IpSecTransform implements AutoCloseable { return; } try { - /* Order matters here because the keepalive is best-effort but could fail in some - * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we - * still want to clear out the transform. - */ IIpSecService svc = getIpSecService(); - svc.deleteTransportModeTransform(mResourceId); - stopKeepalive(); + svc.deleteTransform(mResourceId); + stopNattKeepalive(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { @@ -218,42 +190,35 @@ public final class IpSecTransform implements AutoCloseable { private final Context mContext; private final CloseGuard mCloseGuard = CloseGuard.get(); private ConnectivityManager.PacketKeepalive mKeepalive; - private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; - private Object mKeepaliveSyncLock = new Object(); - private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback = + private Handler mCallbackHandler; + private final ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback = new ConnectivityManager.PacketKeepaliveCallback() { @Override public void onStarted() { - synchronized (mKeepaliveSyncLock) { - mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS; - mKeepaliveSyncLock.notifyAll(); + synchronized (this) { + mCallbackHandler.post(() -> mUserKeepaliveCallback.onStarted()); } } @Override public void onStopped() { - synchronized (mKeepaliveSyncLock) { - mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; - mKeepaliveSyncLock.notifyAll(); + synchronized (this) { + mKeepalive = null; + mCallbackHandler.post(() -> mUserKeepaliveCallback.onStopped()); } } @Override public void onError(int error) { - synchronized (mKeepaliveSyncLock) { - mKeepaliveStatus = error; - mKeepaliveSyncLock.notifyAll(); + synchronized (this) { + mKeepalive = null; + mCallbackHandler.post(() -> mUserKeepaliveCallback.onError(error)); } } }; - /* Package */ - void startKeepalive(Context c) { - if (mConfig.getNattKeepaliveInterval() != 0) { - Log.wtf(TAG, "Keepalive not yet supported."); - } - } + private NattKeepaliveCallback mUserKeepaliveCallback; /** @hide */ @VisibleForTesting @@ -261,9 +226,93 @@ public final class IpSecTransform implements AutoCloseable { return mResourceId; } - /* Package */ - void stopKeepalive() { - return; + /** + * A callback class to provide status information regarding a NAT-T keepalive session + * + *

    Use this callback to receive status information regarding a NAT-T keepalive session + * by registering it when calling {@link #startNattKeepalive}. + * + * @hide + */ + @SystemApi + public static class NattKeepaliveCallback { + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = 1; + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = 2; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = 3; + + /** The requested keepalive was successfully started. */ + public void onStarted() {} + /** The keepalive was successfully stopped. */ + public void onStopped() {} + /** An error occurred. */ + public void onError(int error) {} + } + + /** + * Start a NAT-T keepalive session for the current transform. + * + * For a transform that is using UDP encapsulated IPv4, NAT-T offloading provides + * a power efficient mechanism of sending NAT-T packets at a specified interval. + * + * @param userCallback a {@link #NattKeepaliveCallback} to receive asynchronous status + * information about the requested NAT-T keepalive session. + * @param intervalSeconds the interval between NAT-T keepalives being sent. The + * the allowed range is between 20 and 3600 seconds. + * @param handler a handler on which to post callbacks when received. + * + * @hide + */ + @SystemApi + public void startNattKeepalive(@NonNull NattKeepaliveCallback userCallback, + int intervalSeconds, @NonNull Handler handler) throws IOException { + checkNotNull(userCallback); + if (intervalSeconds < 20 || intervalSeconds > 3600) { + throw new IllegalArgumentException("Invalid NAT-T keepalive interval"); + } + checkNotNull(handler); + if (mResourceId == INVALID_RESOURCE_ID) { + throw new IllegalStateException( + "Packet keepalive cannot be started for an inactive transform"); + } + + synchronized (mKeepaliveCallback) { + if (mKeepaliveCallback != null) { + throw new IllegalStateException("Keepalive already active"); + } + + mUserKeepaliveCallback = userCallback; + ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + mKeepalive = cm.startNattKeepalive( + mConfig.getNetwork(), intervalSeconds, mKeepaliveCallback, + NetworkUtils.numericToInetAddress(mConfig.getSourceAddress()), + 4500, // FIXME urgently, we need to get the port number from the Encap socket + NetworkUtils.numericToInetAddress(mConfig.getDestinationAddress())); + mCallbackHandler = handler; + } + } + + /** + * Stop an ongoing NAT-T keepalive session. + * + * Calling this API will request that an ongoing NAT-T keepalive session be terminated. + * If this API is not called when a Transform is closed, the underlying NAT-T session will + * be terminated automatically. + * + * @hide + */ + @SystemApi + public void stopNattKeepalive() { + synchronized (mKeepaliveCallback) { + if (mKeepalive == null) { + Log.e(TAG, "No active keepalive to stop"); + return; + } + mKeepalive.stop(); + } } /** This class is used to build {@link IpSecTransform} objects. */ @@ -272,99 +321,49 @@ public final class IpSecTransform implements AutoCloseable { private IpSecConfig mConfig; /** - * Set the encryption algorithm for the given direction. - * - *

    If encryption is set for a direction without also providing an SPI for that direction, - * creation of an {@code IpSecTransform} will fail when attempting to build the transform. + * Set the encryption algorithm. * *

    Encryption is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. */ - public IpSecTransform.Builder setEncryption( - @TransformDirection int direction, IpSecAlgorithm algo) { + public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. - mConfig.setEncryption(direction, algo); + Preconditions.checkNotNull(algo); + mConfig.setEncryption(algo); return this; } /** - * Set the authentication (integrity) algorithm for the given direction. - * - *

    If authentication is set for a direction without also providing an SPI for that - * direction, creation of an {@code IpSecTransform} will fail when attempting to build the - * transform. + * Set the authentication (integrity) algorithm. * *

    Authentication is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. */ - public IpSecTransform.Builder setAuthentication( - @TransformDirection int direction, IpSecAlgorithm algo) { + public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. - mConfig.setAuthentication(direction, algo); + Preconditions.checkNotNull(algo); + mConfig.setAuthentication(algo); return this; } /** - * Set the authenticated encryption algorithm for the given direction. + * Set the authenticated encryption algorithm. * - *

    If an authenticated encryption algorithm is set for a given direction without also - * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when - * attempting to build the transform. - * - *

    The Authenticated Encryption (AE) class of algorithms are also known as Authenticated - * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as - * referred to in RFC 4301). + *

    The Authenticated Encryption (AE) class of algorithms are also known as + * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode + * algorithms (as referred to in + * RFC 4301). * *

    Authenticated encryption is mutually exclusive with encryption and authentication. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to * be applied. */ - public IpSecTransform.Builder setAuthenticatedEncryption( - @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.setAuthenticatedEncryption(direction, algo); - return this; - } - - /** - * Set the SPI for the given direction. - * - *

    Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies - * packets to a given destination address. To prevent SPI collisions, values should be - * reserved by calling {@link IpSecManager#allocateSecurityParameterIndex}. - * - *

    If the SPI and algorithms are omitted for one direction, traffic in that direction - * will not be encrypted or authenticated. - * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} - * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed - * traffic - */ - public IpSecTransform.Builder setSpi( - @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { - if (spi.getResourceId() == INVALID_RESOURCE_ID) { - throw new IllegalArgumentException("Invalid SecurityParameterIndex"); - } - mConfig.setSpiResourceId(direction, spi.getResourceId()); - return this; - } - - /** - * Set the {@link Network} which will carry tunneled traffic. - * - *

    Restricts the transformed traffic to a particular {@link Network}. This is required - * for tunnel mode, otherwise tunneled traffic would be sent on the default network. - * - * @hide - */ - @SystemApi - public IpSecTransform.Builder setUnderlyingNetwork(Network net) { - mConfig.setNetwork(net); + public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { + Preconditions.checkNotNull(algo); + mConfig.setAuthenticatedEncryption(algo); return this; } @@ -382,7 +381,8 @@ public final class IpSecTransform implements AutoCloseable { * encapsulated traffic. In the case of IKEv2, this should be port 4500. */ public IpSecTransform.Builder setIpv4Encapsulation( - IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + Preconditions.checkNotNull(localSocket); mConfig.setEncapType(ENCAP_ESPINUDP); if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); @@ -392,26 +392,6 @@ public final class IpSecTransform implements AutoCloseable { return this; } - // TODO: Decrease the minimum keepalive to maybe 10? - // TODO: Probably a better exception to throw for NATTKeepalive failure - // TODO: Specify the needed NATT keepalive permission. - /** - * Set NAT-T keepalives to be sent with a given interval. - * - *

    This will set power-efficient keepalive packets to be sent by the system. If NAT-T - * keepalive is requested but cannot be activated, then creation of an {@link - * IpSecTransform} will fail when calling the build method. - * - * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be - * between 20s and 3600s. - * @hide - */ - @SystemApi - public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) { - mConfig.setNattKeepaliveInterval(intervalSeconds); - return this; - } - /** * Build a transport mode {@link IpSecTransform}. * @@ -419,24 +399,33 @@ public final class IpSecTransform implements AutoCloseable { * will not affect any network traffic until it has been applied to one or more sockets. * * @see IpSecManager#applyTransportModeTransform - * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use - * this transform + * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use + * this transform; this address must belong to the Network used by all sockets that + * utilize this transform; if provided, then only traffic originating from the + * specified source address will be processed. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic * @throws IllegalArgumentException indicating that a particular combination of transform * properties is invalid - * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are - * active + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI * collides with an existing transform * @throws IOException indicating other errors */ - public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) + public IpSecTransform buildTransportModeTransform( + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { - if (remoteAddress == null) { - throw new IllegalArgumentException("Remote address may not be null or empty!"); + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } mConfig.setMode(MODE_TRANSPORT); - mConfig.setRemoteAddress(remoteAddress.getHostAddress()); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); // FIXME: modifying a builder after calling build can change the built transform. return new IpSecTransform(mContext, mConfig).activate(); } @@ -445,26 +434,34 @@ public final class IpSecTransform implements AutoCloseable { * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some * parameters have interdependencies that are checked at build time. * - * @param localAddress the {@link InetAddress} that provides the local endpoint for this + * @param sourceAddress the {@link InetAddress} that provides the source address for this * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. - * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this - * IPsec tunnel. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic * @throws IllegalArgumentException indicating that a particular combination of transform * properties is invalid. + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors * @hide */ + @SystemApi public IpSecTransform buildTunnelModeTransform( - InetAddress localAddress, InetAddress remoteAddress) { - if (localAddress == null) { - throw new IllegalArgumentException("Local address may not be null or empty!"); - } - if (remoteAddress == null) { - throw new IllegalArgumentException("Remote address may not be null or empty!"); + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) + throws IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException, IOException { + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } - mConfig.setLocalAddress(localAddress.getHostAddress()); - mConfig.setRemoteAddress(remoteAddress.getHostAddress()); mConfig.setMode(MODE_TUNNEL); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); return new IpSecTransform(mContext, mConfig); } diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7239221415cec2398f7510dfa77bcb15412e9d4e --- /dev/null +++ b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +/** @hide */ +parcelable IpSecTunnelInterfaceResponse; diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/core/java/android/net/IpSecTunnelInterfaceResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..c23d831a4435d73459d0bb232cf1b19c5db09255 --- /dev/null +++ b/core/java/android/net/IpSecTunnelInterfaceResponse.java @@ -0,0 +1,78 @@ +/* + * 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; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class is used to return an IpSecTunnelInterface resource Id and and corresponding status + * from the IpSecService to an IpSecTunnelInterface object. + * + * @hide + */ +public final class IpSecTunnelInterfaceResponse implements Parcelable { + private static final String TAG = "IpSecTunnelInterfaceResponse"; + + public final int resourceId; + public final String interfaceName; + public final int status; + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + out.writeString(interfaceName); + } + + public IpSecTunnelInterfaceResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + interfaceName = ""; + } + + public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) { + status = inStatus; + resourceId = inResourceId; + interfaceName = inInterfaceName; + } + + private IpSecTunnelInterfaceResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + interfaceName = in.readString(); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) { + return new IpSecTunnelInterfaceResponse(in); + } + + public IpSecTunnelInterfaceResponse[] newArray(int size) { + return new IpSecTunnelInterfaceResponse[size]; + } + }; +} diff --git a/core/java/android/net/KeepalivePacketData.aidl b/core/java/android/net/KeepalivePacketData.aidl new file mode 100644 index 0000000000000000000000000000000000000000..d456b53fd188071e494d9ea7f9097c05a437a706 --- /dev/null +++ b/core/java/android/net/KeepalivePacketData.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +parcelable KeepalivePacketData; diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java similarity index 61% rename from services/core/java/com/android/server/connectivity/KeepalivePacketData.java rename to core/java/android/net/KeepalivePacketData.java index 2ccfdd1f3136dc74e703cf1060813be427f19e49..08d4ff5da9662678c6cc232dd6376c2bb93aa622 100644 --- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java +++ b/core/java/android/net/KeepalivePacketData.java @@ -14,12 +14,15 @@ * limitations under the License. */ -package com.android.server.connectivity; +package android.net; import android.system.OsConstants; import android.net.ConnectivityManager; -import android.net.NetworkUtils; import android.net.util.IpUtils; +import android.os.Parcel; +import android.os.Parcelable; +import android.system.OsConstants; +import android.util.Log; import java.net.Inet4Address; import java.net.Inet6Address; @@ -35,9 +38,8 @@ import static android.net.ConnectivityManager.PacketKeepalive.*; * * @hide */ -public class KeepalivePacketData { - /** Protocol of the packet to send; one of the OsConstants.ETH_P_* values. */ - public final int protocol; +public class KeepalivePacketData implements Parcelable { + private static final String TAG = "KeepalivePacketData"; /** Source IP address */ public final InetAddress srcAddress; @@ -51,57 +53,57 @@ public class KeepalivePacketData { /** Destination port */ public final int dstPort; - /** Destination MAC address. Can change if routing changes. */ - public byte[] dstMac; - /** Packet data. A raw byte string of packet data, not including the link-layer header. */ - public final byte[] data; + private final byte[] mPacket; private static final int IPV4_HEADER_LENGTH = 20; private static final int UDP_HEADER_LENGTH = 8; + // This should only be constructed via static factory methods, such as + // nattKeepalivePacket protected KeepalivePacketData(InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException { this.srcAddress = srcAddress; this.dstAddress = dstAddress; this.srcPort = srcPort; this.dstPort = dstPort; - this.data = data; + this.mPacket = data; // Check we have two IP addresses of the same family. - if (srcAddress == null || dstAddress == null || - !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) { - throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); - } - - // Set the protocol. - if (this.dstAddress instanceof Inet4Address) { - this.protocol = OsConstants.ETH_P_IP; - } else if (this.dstAddress instanceof Inet6Address) { - this.protocol = OsConstants.ETH_P_IPV6; - } else { + if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName() + .equals(dstAddress.getClass().getName())) { + Log.e(TAG, "Invalid or mismatched InetAddresses in KeepalivePacketData"); throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } // Check the ports. if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) { + Log.e(TAG, "Invalid ports in KeepalivePacketData"); throw new InvalidPacketException(ERROR_INVALID_PORT); } } public static class InvalidPacketException extends Exception { - final public int error; + public final int error; public InvalidPacketException(int error) { this.error = error; } } - /** - * Creates an IPsec NAT-T keepalive packet with the specified parameters. - */ + public byte[] getPacket() { + return mPacket.clone(); + } + public static KeepalivePacketData nattKeepalivePacket( - InetAddress srcAddress, int srcPort, - InetAddress dstAddress, int dstPort) throws InvalidPacketException { + InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) + throws InvalidPacketException { + + // FIXME: remove this and actually support IPv6 keepalives + if (srcAddress instanceof Inet6Address && dstAddress instanceof Inet6Address) { + // Optimistically returning an IPv6 Keepalive Packet with no data, + // which currently only works on cellular + return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, new byte[0]); + } if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); @@ -134,4 +136,39 @@ public class KeepalivePacketData { return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); } + + /* Parcelable Implementation */ + public int describeContents() { + return 0; + } + + /** Write to parcel */ + public void writeToParcel(Parcel out, int flags) { + out.writeString(srcAddress.getHostAddress()); + out.writeString(dstAddress.getHostAddress()); + out.writeInt(srcPort); + out.writeInt(dstPort); + out.writeByteArray(mPacket); + } + + private KeepalivePacketData(Parcel in) { + srcAddress = NetworkUtils.numericToInetAddress(in.readString()); + dstAddress = NetworkUtils.numericToInetAddress(in.readString()); + srcPort = in.readInt(); + dstPort = in.readInt(); + mPacket = in.createByteArray(); + } + + /** Parcelable Creator */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public KeepalivePacketData createFromParcel(Parcel in) { + return new KeepalivePacketData(in); + } + + public KeepalivePacketData[] newArray(int size) { + return new KeepalivePacketData[size]; + } + }; + } diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 4e474c8e478c0ad605b14e9bc523727f5c4236d0..f525b1f37518c9dd109c670f938f8080e7400692 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -50,6 +50,8 @@ public final class LinkProperties implements Parcelable { private String mIfaceName; private ArrayList mLinkAddresses = new ArrayList(); private ArrayList mDnses = new ArrayList(); + private boolean mUsePrivateDns; + private String mPrivateDnsServerName; private String mDomains; private ArrayList mRoutes = new ArrayList(); private ProxyInfo mHttpProxy; @@ -165,6 +167,8 @@ public final class LinkProperties implements Parcelable { mIfaceName = source.getInterfaceName(); for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l); for (InetAddress i : source.getDnsServers()) mDnses.add(i); + mUsePrivateDns = source.mUsePrivateDns; + mPrivateDnsServerName = source.mPrivateDnsServerName; mDomains = source.getDomains(); for (RouteInfo r : source.getRoutes()) mRoutes.add(r); mHttpProxy = (source.getHttpProxy() == null) ? @@ -390,6 +394,59 @@ public final class LinkProperties implements Parcelable { return Collections.unmodifiableList(mDnses); } + /** + * Set whether private DNS is currently in use on this network. + * + * @param usePrivateDns The private DNS state. + * @hide + */ + public void setUsePrivateDns(boolean usePrivateDns) { + mUsePrivateDns = usePrivateDns; + } + + /** + * Returns whether private DNS is currently in use on this network. When + * private DNS is in use, applications must not send unencrypted DNS + * queries as doing so could reveal private user information. Furthermore, + * if private DNS is in use and {@link #getPrivateDnsServerName} is not + * {@code null}, DNS queries must be sent to the specified DNS server. + * + * @return {@code true} if private DNS is in use, {@code false} otherwise. + */ + public boolean isPrivateDnsActive() { + return mUsePrivateDns; + } + + /** + * Set the name of the private DNS server to which private DNS queries + * should be sent when in strict mode. This value should be {@code null} + * when private DNS is off or in opportunistic mode. + * + * @param privateDnsServerName The private DNS server name. + * @hide + */ + public void setPrivateDnsServerName(@Nullable String privateDnsServerName) { + mPrivateDnsServerName = privateDnsServerName; + } + + /** + * Returns the private DNS server name that is in use. If not {@code null}, + * private DNS is in strict mode. In this mode, applications should ensure + * that all DNS queries are encrypted and sent to this hostname and that + * queries are only sent if the hostname's certificate is valid. If + * {@code null} and {@link #isPrivateDnsActive} is {@code true}, private + * DNS is in opportunistic mode, and applications should ensure that DNS + * queries are encrypted and sent to a DNS server returned by + * {@link #getDnsServers}. System DNS will handle each of these cases + * correctly, but applications implementing their own DNS lookups must make + * sure to follow these requirements. + * + * @return The private DNS server name. + */ + public @Nullable String getPrivateDnsServerName() { + return mPrivateDnsServerName; + } + /** * Sets the DNS domain search path used on this link. * @@ -622,6 +679,8 @@ public final class LinkProperties implements Parcelable { mIfaceName = null; mLinkAddresses.clear(); mDnses.clear(); + mUsePrivateDns = false; + mPrivateDnsServerName = null; mDomains = null; mRoutes.clear(); mHttpProxy = null; @@ -649,6 +708,13 @@ public final class LinkProperties implements Parcelable { for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ","; dns += "] "; + String usePrivateDns = "UsePrivateDns: " + mUsePrivateDns + " "; + + String privateDnsServerName = ""; + if (privateDnsServerName != null) { + privateDnsServerName = "PrivateDnsServerName: " + mPrivateDnsServerName + " "; + } + String domainName = "Domains: " + mDomains; String mtu = " MTU: " + mMtu; @@ -671,8 +737,9 @@ public final class LinkProperties implements Parcelable { } stacked += "] "; } - return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu - + tcpBuffSizes + proxy + stacked + "}"; + return "{" + ifaceName + linkAddresses + routes + dns + usePrivateDns + + privateDnsServerName + domainName + mtu + tcpBuffSizes + proxy + + stacked + "}"; } /** @@ -895,6 +962,20 @@ public final class LinkProperties implements Parcelable { mDnses.containsAll(targetDnses) : false; } + /** + * Compares this {@code LinkProperties} private DNS settings against the + * target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalPrivateDns(LinkProperties target) { + return (isPrivateDnsActive() == target.isPrivateDnsActive() + && TextUtils.equals(getPrivateDnsServerName(), + target.getPrivateDnsServerName())); + } + /** * Compares this {@code LinkProperties} Routes against the target * @@ -989,14 +1070,15 @@ public final class LinkProperties implements Parcelable { * stacked interfaces are not so much a property of the link as a * description of connections between links. */ - return isIdenticalInterfaceName(target) && - isIdenticalAddresses(target) && - isIdenticalDnses(target) && - isIdenticalRoutes(target) && - isIdenticalHttpProxy(target) && - isIdenticalStackedLinks(target) && - isIdenticalMtu(target) && - isIdenticalTcpBufferSizes(target); + return isIdenticalInterfaceName(target) + && isIdenticalAddresses(target) + && isIdenticalDnses(target) + && isIdenticalPrivateDns(target) + && isIdenticalRoutes(target) + && isIdenticalHttpProxy(target) + && isIdenticalStackedLinks(target) + && isIdenticalMtu(target) + && isIdenticalTcpBufferSizes(target); } /** @@ -1091,7 +1173,9 @@ public final class LinkProperties implements Parcelable { + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()) + mStackedLinks.hashCode() * 47) + mMtu * 51 - + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode()); + + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode()) + + (mUsePrivateDns ? 57 : 0) + + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode()); } /** @@ -1108,6 +1192,8 @@ public final class LinkProperties implements Parcelable { for(InetAddress d : mDnses) { dest.writeByteArray(d.getAddress()); } + dest.writeBoolean(mUsePrivateDns); + dest.writeString(mPrivateDnsServerName); dest.writeString(mDomains); dest.writeInt(mMtu); dest.writeString(mTcpBufferSizes); @@ -1148,6 +1234,8 @@ public final class LinkProperties implements Parcelable { netProp.addDnsServer(InetAddress.getByAddress(in.createByteArray())); } catch (UnknownHostException e) { } } + netProp.setUsePrivateDns(in.readBoolean()); + netProp.setPrivateDnsServerName(in.readString()); netProp.setDomains(in.readString()); netProp.setMtu(in.readInt()); netProp.setTcpBufferSizes(in.readString()); diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index d6992aaede5f2d13d11049bd4e277325e581b326..287bdc88dd3e4100056c86082999a2bfefd3d8e4 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; @@ -60,7 +61,7 @@ public final class MacAddress implements Parcelable { }) public @interface MacAddressType { } - /** Indicates a MAC address of unknown type. */ + /** @hide Indicates a MAC address of unknown type. */ public static final int TYPE_UNKNOWN = 0; /** Indicates a MAC address is a unicast address. */ public static final int TYPE_UNICAST = 1; @@ -92,7 +93,7 @@ public final class MacAddress implements Parcelable { * * @return the int constant representing the MAC address type of this MacAddress. */ - public @MacAddressType int addressType() { + public @MacAddressType int getAddressType() { if (equals(BROADCAST_ADDRESS)) { return TYPE_BROADCAST; } @@ -120,12 +121,12 @@ public final class MacAddress implements Parcelable { /** * @return a byte array representation of this MacAddress. */ - public byte[] toByteArray() { + public @NonNull byte[] toByteArray() { return byteAddrFromLongAddr(mAddr); } @Override - public String toString() { + public @NonNull String toString() { return stringAddrFromLongAddr(mAddr); } @@ -133,7 +134,7 @@ public final class MacAddress implements Parcelable { * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal * numbers in [0,ff] joined by ':' characters. */ - public String toOuiString() { + public @NonNull String toOuiString() { return String.format( "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff); } @@ -197,7 +198,7 @@ public final class MacAddress implements Parcelable { if (!isMacAddress(addr)) { return TYPE_UNKNOWN; } - return MacAddress.fromBytes(addr).addressType(); + return MacAddress.fromBytes(addr).getAddressType(); } /** @@ -211,7 +212,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static byte[] byteAddrFromStringAddr(String addr) { + public static @NonNull byte[] byteAddrFromStringAddr(String addr) { Preconditions.checkNotNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { @@ -239,7 +240,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static String stringAddrFromByteAddr(byte[] addr) { + public static @NonNull String stringAddrFromByteAddr(byte[] addr) { if (!isMacAddress(addr)) { return null; } @@ -291,7 +292,7 @@ public final class MacAddress implements Parcelable { // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr)) // that avoids the allocation of an intermediary byte[]. - private static String stringAddrFromLongAddr(long addr) { + private static @NonNull String stringAddrFromLongAddr(long addr) { return String.format("%02x:%02x:%02x:%02x:%02x:%02x", (addr >> 40) & 0xff, (addr >> 32) & 0xff, @@ -310,7 +311,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given String representation. * @throws IllegalArgumentException if the given String is not a valid representation. */ - public static MacAddress fromString(String addr) { + public static @NonNull MacAddress fromString(@NonNull String addr) { return new MacAddress(longAddrFromStringAddr(addr)); } @@ -322,7 +323,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given byte array representation. * @throws IllegalArgumentException if the given byte array is not a valid representation. */ - public static MacAddress fromBytes(byte[] addr) { + public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) { return new MacAddress(longAddrFromByteAddr(addr)); } @@ -336,7 +337,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress() { + public static @NonNull MacAddress createRandomUnicastAddress() { return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random()); } @@ -352,7 +353,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) { + public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) { long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); addr = addr | LOCALLY_ASSIGNED_MASK; addr = addr & ~MULTICAST_MASK; diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 1a3ce9124b480cf60174d1d7e294d81bb79a43b6..5df168d20586c65bffde212b62fe1d0c3caffc4d 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -357,13 +357,13 @@ public class Network implements Parcelable { // Multiple Provisioning Domains API recommendations, as made by the // IETF mif working group. // - // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // The handleMagic value MUST be kept in sync with the corresponding // value in the native/android/net.c NDK implementation. if (netId == 0) { return 0L; // make this zero condition obvious for debugging } - final long HANDLE_MAGIC = 0xfacade; - return (((long) netId) << 32) | HANDLE_MAGIC; + final long handleMagic = 0xcafed00dL; + return (((long) netId) << 32) | handleMagic; } // implement the Parcelable interface diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 2dacf8f460bb25e30b618e8e039f0cf4f1a756d5..52a2354840c9c10a6af1bc767ecab4ab79dfa41b 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -17,6 +17,7 @@ package android.net; import android.content.Context; +import android.net.ConnectivityManager.PacketKeepalive; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -26,7 +27,6 @@ import android.util.Log; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; -import android.net.ConnectivityManager.PacketKeepalive; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -100,20 +100,6 @@ public abstract class NetworkAgent extends Handler { */ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; - /** - * Sent by the NetworkAgent to ConnectivityService to add new UID ranges - * to be forced into this Network. For VPNs only. - * obj = UidRange[] to forward - */ - public static final int EVENT_UID_RANGES_ADDED = BASE + 5; - - /** - * Sent by the NetworkAgent to ConnectivityService to remove UID ranges - * from being forced into this Network. For VPNs only. - * obj = UidRange[] to stop forwarding - */ - public static final int EVENT_UID_RANGES_REMOVED = BASE + 6; - /** * Sent by ConnectivityService to the NetworkAgent to inform the agent of the * networks status - whether we could use the network or could not, due to @@ -389,22 +375,6 @@ public abstract class NetworkAgent extends Handler { queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score)); } - /** - * Called by the VPN code when it wants to add ranges of UIDs to be routed - * through the VPN network. - */ - public void addUidRanges(UidRange[] ranges) { - queueOrSendMessage(EVENT_UID_RANGES_ADDED, ranges); - } - - /** - * Called by the VPN code when it wants to remove ranges of UIDs from being routed - * through the VPN network. - */ - public void removeUidRanges(UidRange[] ranges) { - queueOrSendMessage(EVENT_UID_RANGES_REMOVED, ranges); - } - /** * Called by the bearer to indicate this network was manually selected by the user. * This should be called before the NetworkInfo is marked CONNECTED so that this diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 8b03fa81d483e9cc50a9e26b1fa3824ca3927477..8e05cfa96625b905c519e6eae0d870db1048b925 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.net.ConnectivityManager.NetworkCallback; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArraySet; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -29,6 +30,7 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; +import java.util.Set; import java.util.StringJoiner; /** @@ -47,6 +49,7 @@ import java.util.StringJoiner; */ public final class NetworkCapabilities implements Parcelable { private static final String TAG = "NetworkCapabilities"; + private static final int INVALID_UID = -1; /** * @hide @@ -64,6 +67,8 @@ public final class NetworkCapabilities implements Parcelable { mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; mNetworkSpecifier = nc.mNetworkSpecifier; mSignalStrength = nc.mSignalStrength; + mUids = nc.mUids; + mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid; } } @@ -77,6 +82,8 @@ public final class NetworkCapabilities implements Parcelable { mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; mNetworkSpecifier = null; mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; + mUids = null; + mEstablishingVpnAppUid = INVALID_UID; } /** @@ -108,6 +115,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_CAPTIVE_PORTAL, NET_CAPABILITY_NOT_ROAMING, NET_CAPABILITY_FOREGROUND, + NET_CAPABILITY_NOT_CONGESTED, }) public @interface NetCapability { } @@ -235,8 +243,17 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_FOREGROUND = 19; + /** + * Indicates that this network is not congested. + *

    + * When a network is congested, the device should defer network traffic that + * can be done at a later time without breaking developer contracts. + * @hide + */ + public static final int NET_CAPABILITY_NOT_CONGESTED = 20; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_FOREGROUND; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_CONGESTED; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -249,7 +266,8 @@ public final class NetworkCapabilities implements Parcelable { (1 << NET_CAPABILITY_VALIDATED) | (1 << NET_CAPABILITY_CAPTIVE_PORTAL) | (1 << NET_CAPABILITY_NOT_ROAMING) | - (1 << NET_CAPABILITY_FOREGROUND); + (1 << NET_CAPABILITY_FOREGROUND) | + (1 << NET_CAPABILITY_NOT_CONGESTED); /** * Network capabilities that are not allowed in NetworkRequests. This exists because the @@ -387,12 +405,9 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public String describeFirstNonRequestableCapability() { - if (hasCapability(NET_CAPABILITY_VALIDATED)) return "NET_CAPABILITY_VALIDATED"; - if (hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return "NET_CAPABILITY_CAPTIVE_PORTAL"; - if (hasCapability(NET_CAPABILITY_FOREGROUND)) return "NET_CAPABILITY_FOREGROUND"; - // This cannot happen unless the preceding checks are incomplete. - if ((mNetworkCapabilities & NON_REQUESTABLE_CAPABILITIES) != 0) { - return "unknown non-requestable capabilities " + Long.toHexString(mNetworkCapabilities); + final long nonRequestable = (mNetworkCapabilities & NON_REQUESTABLE_CAPABILITIES); + if (nonRequestable != 0) { + return capabilityNameOf(BitUtils.unpackBits(nonRequestable)[0]); } if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth"; if (hasSignalStrength()) return "signalStrength"; @@ -610,6 +625,29 @@ public final class NetworkCapabilities implements Parcelable { return (nc.mTransportTypes == this.mTransportTypes); } + /** + * UID of the app that manages this network, or INVALID_UID if none/unknown. + * + * This field keeps track of the UID of the app that created this network and is in charge + * of managing it. In the practice, it is used to store the UID of VPN apps so it is named + * accordingly, but it may be renamed if other mechanisms are offered for third party apps + * to create networks. + * + * Because this field is only used in the services side (and to avoid apps being able to + * set this to whatever they want), this field is not parcelled and will not be conserved + * across the IPC boundary. + * @hide + */ + private int mEstablishingVpnAppUid = INVALID_UID; + + /** + * Set the UID of the managing app. + * @hide + */ + public void setEstablishingVpnAppUid(final int uid) { + mEstablishingVpnAppUid = uid; + } + /** * Value indicating that link bandwidth is unspecified. * @hide @@ -828,6 +866,174 @@ public final class NetworkCapabilities implements Parcelable { return this.mSignalStrength == nc.mSignalStrength; } + /** + * List of UIDs this network applies to. No restriction if null. + *

    + * This is typically (and at this time, only) used by VPN. This network is only available to + * the UIDs in this list, and it is their default network. Apps in this list that wish to + * bypass the VPN can do so iff the VPN app allows them to or if they are privileged. If this + * member is null, then the network is not restricted by app UID. If it's an empty list, then + * it means nobody can use it. + * As a special exception, the app managing this network (as identified by its UID stored in + * mEstablishingVpnAppUid) can always see this network. This is embodied by a special check in + * satisfiedByUids. That still does not mean the network necessarily applies + * to the app that manages it as determined by #appliesToUid. + *

    + * Please note that in principle a single app can be associated with multiple UIDs because + * each app will have a different UID when it's run as a different (macro-)user. A single + * macro user can only have a single active VPN app at any given time however. + *

    + * Also please be aware this class does not try to enforce any normalization on this. Callers + * can only alter the UIDs by setting them wholesale : this class does not provide any utility + * to add or remove individual UIDs or ranges. If callers have any normalization needs on + * their own (like requiring sortedness or no overlap) they need to enforce it + * themselves. Some of the internal methods also assume this is normalized as in no adjacent + * or overlapping ranges are present. + * + * @hide + */ + private ArraySet mUids = null; + + /** + * Convenience method to set the UIDs this network applies to to a single UID. + * @hide + */ + public NetworkCapabilities setSingleUid(int uid) { + final ArraySet identity = new ArraySet<>(1); + identity.add(new UidRange(uid, uid)); + setUids(identity); + return this; + } + + /** + * Set the list of UIDs this network applies to. + * This makes a copy of the set so that callers can't modify it after the call. + * @hide + */ + public NetworkCapabilities setUids(Set uids) { + if (null == uids) { + mUids = null; + } else { + mUids = new ArraySet<>(uids); + } + return this; + } + + /** + * Get the list of UIDs this network applies to. + * This returns a copy of the set so that callers can't modify the original object. + * @hide + */ + public Set getUids() { + return null == mUids ? null : new ArraySet<>(mUids); + } + + /** + * Test whether this network applies to this UID. + * @hide + */ + public boolean appliesToUid(int uid) { + if (null == mUids) return true; + for (UidRange range : mUids) { + if (range.contains(uid)) { + return true; + } + } + return false; + } + + /** + * Tests if the set of UIDs that this network applies to is the same of the passed set of UIDs. + *

    + * This test only checks whether equal range objects are in both sets. It will + * return false if the ranges are not exactly the same, even if the covered UIDs + * are for an equivalent result. + *

    + * Note that this method is not very optimized, which is fine as long as it's not used very + * often. + *

    + * nc is assumed nonnull. + * + * @hide + */ + @VisibleForTesting + public boolean equalsUids(NetworkCapabilities nc) { + Set comparedUids = nc.mUids; + if (null == comparedUids) return null == mUids; + if (null == mUids) return false; + // Make a copy so it can be mutated to check that all ranges in mUids + // also are in uids. + final Set uids = new ArraySet<>(mUids); + for (UidRange range : comparedUids) { + if (!uids.contains(range)) { + return false; + } + uids.remove(range); + } + return uids.isEmpty(); + } + + /** + * Test whether the passed NetworkCapabilities satisfies the UIDs this capabilities require. + * + * This method is called on the NetworkCapabilities embedded in a request with the + * capabilities of an available network. It checks whether all the UIDs from this listen + * (representing the UIDs that must have access to the network) are satisfied by the UIDs + * in the passed nc (representing the UIDs that this network is available to). + *

    + * As a special exception, the UID that created the passed network (as represented by its + * mEstablishingVpnAppUid field) always satisfies a NetworkRequest requiring it (of LISTEN + * or REQUEST types alike), even if the network does not apply to it. That is so a VPN app + * can see its own network when it listens for it. + *

    + * nc is assumed nonnull. Else, NPE. + * @see #appliesToUid + * @hide + */ + public boolean satisfiedByUids(NetworkCapabilities nc) { + if (null == nc.mUids) return true; // The network satisfies everything. + if (null == mUids) return false; // Not everything allowed but requires everything + for (UidRange requiredRange : mUids) { + if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true; + if (!nc.appliesToUidRange(requiredRange)) { + return false; + } + } + return true; + } + + /** + * Returns whether this network applies to the passed ranges. + * This assumes that to apply, the passed range has to be entirely contained + * within one of the ranges this network applies to. If the ranges are not normalized, + * this method may return false even though all required UIDs are covered because no + * single range contained them all. + * @hide + */ + @VisibleForTesting + public boolean appliesToUidRange(UidRange requiredRange) { + if (null == mUids) return true; + for (UidRange uidRange : mUids) { + if (uidRange.containsRange(requiredRange)) { + return true; + } + } + return false; + } + + /** + * Combine the UIDs this network currently applies to with the UIDs the passed + * NetworkCapabilities apply to. + * nc is assumed nonnull. + */ + private void combineUids(NetworkCapabilities nc) { + if (null == nc.mUids || null == mUids) { + mUids = null; + return; + } + mUids.addAll(nc.mUids); + } + /** * Combine a set of Capabilities to this one. Useful for coming up with the complete set * @hide @@ -838,6 +1044,7 @@ public final class NetworkCapabilities implements Parcelable { combineLinkBandwidths(nc); combineSpecifiers(nc); combineSignalStrength(nc); + combineUids(nc); } /** @@ -850,12 +1057,13 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ private boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc, boolean onlyImmutable) { - return (nc != null && - satisfiedByNetCapabilities(nc, onlyImmutable) && - satisfiedByTransportTypes(nc) && - (onlyImmutable || satisfiedByLinkBandwidths(nc)) && - satisfiedBySpecifier(nc) && - (onlyImmutable || satisfiedBySignalStrength(nc))); + return (nc != null + && satisfiedByNetCapabilities(nc, onlyImmutable) + && satisfiedByTransportTypes(nc) + && (onlyImmutable || satisfiedByLinkBandwidths(nc)) + && satisfiedBySpecifier(nc) + && (onlyImmutable || satisfiedBySignalStrength(nc)) + && (onlyImmutable || satisfiedByUids(nc))); } /** @@ -936,24 +1144,26 @@ public final class NetworkCapabilities implements Parcelable { @Override public boolean equals(Object obj) { if (obj == null || (obj instanceof NetworkCapabilities == false)) return false; - NetworkCapabilities that = (NetworkCapabilities)obj; - return (equalsNetCapabilities(that) && - equalsTransportTypes(that) && - equalsLinkBandwidths(that) && - equalsSignalStrength(that) && - equalsSpecifier(that)); + NetworkCapabilities that = (NetworkCapabilities) obj; + return (equalsNetCapabilities(that) + && equalsTransportTypes(that) + && equalsLinkBandwidths(that) + && equalsSignalStrength(that) + && equalsSpecifier(that) + && equalsUids(that)); } @Override public int hashCode() { - return ((int)(mNetworkCapabilities & 0xFFFFFFFF) + - ((int)(mNetworkCapabilities >> 32) * 3) + - ((int)(mTransportTypes & 0xFFFFFFFF) * 5) + - ((int)(mTransportTypes >> 32) * 7) + - (mLinkUpBandwidthKbps * 11) + - (mLinkDownBandwidthKbps * 13) + - Objects.hashCode(mNetworkSpecifier) * 17 + - (mSignalStrength * 19)); + return ((int) (mNetworkCapabilities & 0xFFFFFFFF) + + ((int) (mNetworkCapabilities >> 32) * 3) + + ((int) (mTransportTypes & 0xFFFFFFFF) * 5) + + ((int) (mTransportTypes >> 32) * 7) + + (mLinkUpBandwidthKbps * 11) + + (mLinkDownBandwidthKbps * 13) + + Objects.hashCode(mNetworkSpecifier) * 17 + + (mSignalStrength * 19) + + Objects.hashCode(mUids) * 23); } @Override @@ -968,6 +1178,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeInt(mLinkDownBandwidthKbps); dest.writeParcelable((Parcelable) mNetworkSpecifier, flags); dest.writeInt(mSignalStrength); + dest.writeArraySet(mUids); } public static final Creator CREATOR = @@ -982,6 +1193,8 @@ public final class NetworkCapabilities implements Parcelable { netCap.mLinkDownBandwidthKbps = in.readInt(); netCap.mNetworkSpecifier = in.readParcelable(null); netCap.mSignalStrength = in.readInt(); + netCap.mUids = (ArraySet) in.readArraySet( + null /* ClassLoader, null for default */); return netCap; } @Override @@ -1014,7 +1227,12 @@ public final class NetworkCapabilities implements Parcelable { String signalStrength = (hasSignalStrength() ? " SignalStrength: " + mSignalStrength : ""); - return "[" + transports + capabilities + upBand + dnBand + specifier + signalStrength + "]"; + String uids = (null != mUids ? " Uids: <" + mUids + ">" : ""); + + String establishingAppUid = " EstablishingAppUid: " + mEstablishingVpnAppUid; + + return "[" + transports + capabilities + upBand + dnBand + specifier + signalStrength + + uids + establishingAppUid + "]"; } /** @hide */ @@ -1080,6 +1298,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL"; case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING"; case NET_CAPABILITY_FOREGROUND: return "FOREGROUND"; + case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED"; default: return Integer.toString(capability); } } diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index d3b35998be13b2d25f0692087b578dcffc8c0e7b..ce2de8554dcc53e5d18f15fa5b0c16aac5e557cd 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -58,21 +58,24 @@ public class NetworkIdentity implements Comparable { final String mNetworkId; final boolean mRoaming; final boolean mMetered; + final boolean mDefaultNetwork; public NetworkIdentity( int type, int subType, String subscriberId, String networkId, boolean roaming, - boolean metered) { + boolean metered, boolean defaultNetwork) { mType = type; mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType; mSubscriberId = subscriberId; mNetworkId = networkId; mRoaming = roaming; mMetered = metered; + mDefaultNetwork = defaultNetwork; } @Override public int hashCode() { - return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered); + return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered, + mDefaultNetwork); } @Override @@ -82,7 +85,8 @@ public class NetworkIdentity implements Comparable { return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming && Objects.equals(mSubscriberId, ident.mSubscriberId) && Objects.equals(mNetworkId, ident.mNetworkId) - && mMetered == ident.mMetered; + && mMetered == ident.mMetered + && mDefaultNetwork == ident.mDefaultNetwork; } return false; } @@ -109,6 +113,7 @@ public class NetworkIdentity implements Comparable { builder.append(", ROAMING"); } builder.append(", metered=").append(mMetered); + builder.append(", defaultNetwork=").append(mDefaultNetwork); return builder.append("}").toString(); } @@ -125,6 +130,7 @@ public class NetworkIdentity implements Comparable { proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId); proto.write(NetworkIdentityProto.ROAMING, mRoaming); proto.write(NetworkIdentityProto.METERED, mMetered); + proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork); proto.end(start); } @@ -153,6 +159,10 @@ public class NetworkIdentity implements Comparable { return mMetered; } + public boolean getDefaultNetwork() { + return mDefaultNetwork; + } + /** * Scrub given IMSI on production builds. */ @@ -183,7 +193,8 @@ public class NetworkIdentity implements Comparable { * Build a {@link NetworkIdentity} from the given {@link NetworkState}, * assuming that any mobile networks are using the current IMSI. */ - public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state) { + public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state, + boolean defaultNetwork) { final int type = state.networkInfo.getType(); final int subType = state.networkInfo.getSubtype(); @@ -216,7 +227,8 @@ public class NetworkIdentity implements Comparable { } } - return new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered); + return new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered, + defaultNetwork); } @Override @@ -237,6 +249,9 @@ public class NetworkIdentity implements Comparable { if (res == 0) { res = Boolean.compare(mMetered, another.mMetered); } + if (res == 0) { + res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork); + } return res; } } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 81c49a339d534dcec4be301593c9412e54b0e458..2c5a021ec42ac1b2c7915b8cac307959b2cc6b97 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -29,7 +29,6 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.os.RemoteException; import android.os.UserHandle; -import android.telephony.SubscriptionPlan; import android.util.DebugUtils; import android.util.Pair; @@ -114,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; @@ -329,7 +331,7 @@ public class NetworkPolicyManager { * to access network when the device is idle or in battery saver mode. Otherwise, false. */ public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) { - return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } /** @@ -337,7 +339,7 @@ public class NetworkPolicyManager { * to access network when the device is in data saver mode. Otherwise, false. */ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) { - return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } public static String resolveNetworkId(WifiConfiguration config) { @@ -348,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/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 171adc054bbe782a0ed969866288e3b86ced7f7c..01b2b39213f9ae820e6400e925152e2e6ca5c418 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -82,6 +82,13 @@ public class NetworkStats implements Parcelable { /** {@link #roaming} value where roaming data is accounted. */ public static final int ROAMING_YES = 1; + /** {@link #onDefaultNetwork} value to account for all default network states. */ + public static final int DEFAULT_NETWORK_ALL = -1; + /** {@link #onDefaultNetwork} value to account for usage while not the default network. */ + public static final int DEFAULT_NETWORK_NO = 0; + /** {@link #onDefaultNetwork} value to account for usage while the default network. */ + public static final int DEFAULT_NETWORK_YES = 1; + /** Denotes a request for stats at the interface level. */ public static final int STATS_PER_IFACE = 0; /** Denotes a request for stats at the interface and UID level. */ @@ -102,6 +109,7 @@ public class NetworkStats implements Parcelable { private int[] tag; private int[] metered; private int[] roaming; + private int[] defaultNetwork; private long[] rxBytes; private long[] rxPackets; private long[] txBytes; @@ -125,6 +133,12 @@ public class NetworkStats implements Parcelable { * getSummary(). */ public int roaming; + /** + * Note that this is only populated w/ the default value when read from /proc or written + * to disk. We merge in the correct value when reporting this value to clients of + * getSummary(). + */ + public int defaultNetwork; public long rxBytes; public long rxPackets; public long txBytes; @@ -142,18 +156,20 @@ public class NetworkStats implements Parcelable { public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { - this(iface, uid, set, tag, METERED_NO, ROAMING_NO, rxBytes, rxPackets, txBytes, - txPackets, operations); + this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, + rxBytes, rxPackets, txBytes, txPackets, operations); } public Entry(String iface, int uid, int set, int tag, int metered, int roaming, - long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { + int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, + long operations) { this.iface = iface; this.uid = uid; this.set = set; this.tag = tag; this.metered = metered; this.roaming = roaming; + this.defaultNetwork = defaultNetwork; this.rxBytes = rxBytes; this.rxPackets = rxPackets; this.txBytes = txBytes; @@ -187,6 +203,7 @@ public class NetworkStats implements Parcelable { builder.append(" tag=").append(tagToString(tag)); builder.append(" metered=").append(meteredToString(metered)); builder.append(" roaming=").append(roamingToString(roaming)); + builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork)); builder.append(" rxBytes=").append(rxBytes); builder.append(" rxPackets=").append(rxPackets); builder.append(" txBytes=").append(txBytes); @@ -200,7 +217,8 @@ public class NetworkStats implements Parcelable { if (o instanceof Entry) { final Entry e = (Entry) o; return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered - && roaming == e.roaming && rxBytes == e.rxBytes && rxPackets == e.rxPackets + && roaming == e.roaming && defaultNetwork == e.defaultNetwork + && rxBytes == e.rxBytes && rxPackets == e.rxPackets && txBytes == e.txBytes && txPackets == e.txPackets && operations == e.operations && iface.equals(e.iface); } @@ -209,7 +227,7 @@ public class NetworkStats implements Parcelable { @Override public int hashCode() { - return Objects.hash(uid, set, tag, metered, roaming, iface); + return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface); } } @@ -224,6 +242,7 @@ public class NetworkStats implements Parcelable { this.tag = new int[initialSize]; this.metered = new int[initialSize]; this.roaming = new int[initialSize]; + this.defaultNetwork = new int[initialSize]; this.rxBytes = new long[initialSize]; this.rxPackets = new long[initialSize]; this.txBytes = new long[initialSize]; @@ -238,6 +257,7 @@ public class NetworkStats implements Parcelable { this.tag = EmptyArray.INT; this.metered = EmptyArray.INT; this.roaming = EmptyArray.INT; + this.defaultNetwork = EmptyArray.INT; this.rxBytes = EmptyArray.LONG; this.rxPackets = EmptyArray.LONG; this.txBytes = EmptyArray.LONG; @@ -256,6 +276,7 @@ public class NetworkStats implements Parcelable { tag = parcel.createIntArray(); metered = parcel.createIntArray(); roaming = parcel.createIntArray(); + defaultNetwork = parcel.createIntArray(); rxBytes = parcel.createLongArray(); rxPackets = parcel.createLongArray(); txBytes = parcel.createLongArray(); @@ -274,6 +295,7 @@ public class NetworkStats implements Parcelable { dest.writeIntArray(tag); dest.writeIntArray(metered); dest.writeIntArray(roaming); + dest.writeIntArray(defaultNetwork); dest.writeLongArray(rxBytes); dest.writeLongArray(rxPackets); dest.writeLongArray(txBytes); @@ -308,10 +330,11 @@ public class NetworkStats implements Parcelable { @VisibleForTesting public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming, - long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { + int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, + long operations) { return addValues(new Entry( - iface, uid, set, tag, metered, roaming, rxBytes, rxPackets, txBytes, txPackets, - operations)); + iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets, + txBytes, txPackets, operations)); } /** @@ -327,6 +350,7 @@ public class NetworkStats implements Parcelable { tag = Arrays.copyOf(tag, newLength); metered = Arrays.copyOf(metered, newLength); roaming = Arrays.copyOf(roaming, newLength); + defaultNetwork = Arrays.copyOf(defaultNetwork, newLength); rxBytes = Arrays.copyOf(rxBytes, newLength); rxPackets = Arrays.copyOf(rxPackets, newLength); txBytes = Arrays.copyOf(txBytes, newLength); @@ -341,6 +365,7 @@ public class NetworkStats implements Parcelable { tag[size] = entry.tag; metered[size] = entry.metered; roaming[size] = entry.roaming; + defaultNetwork[size] = entry.defaultNetwork; rxBytes[size] = entry.rxBytes; rxPackets[size] = entry.rxPackets; txBytes[size] = entry.txBytes; @@ -362,6 +387,7 @@ public class NetworkStats implements Parcelable { entry.tag = tag[i]; entry.metered = metered[i]; entry.roaming = roaming[i]; + entry.defaultNetwork = defaultNetwork[i]; entry.rxBytes = rxBytes[i]; entry.rxPackets = rxPackets[i]; entry.txBytes = txBytes[i]; @@ -416,7 +442,7 @@ public class NetworkStats implements Parcelable { */ public NetworkStats combineValues(Entry entry) { final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered, - entry.roaming); + entry.roaming, entry.defaultNetwork); if (i == -1) { // only create new entry when positive contribution addValues(entry); @@ -444,10 +470,12 @@ public class NetworkStats implements Parcelable { /** * Find first stats index that matches the requested parameters. */ - public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming) { + public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming, + int defaultNetwork) { for (int i = 0; i < size; i++) { if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] && metered == this.metered[i] && roaming == this.roaming[i] + && defaultNetwork == this.defaultNetwork[i] && Objects.equals(iface, this.iface[i])) { return i; } @@ -461,7 +489,7 @@ public class NetworkStats implements Parcelable { */ @VisibleForTesting public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, - int hintIndex) { + int defaultNetwork, int hintIndex) { for (int offset = 0; offset < size; offset++) { final int halfOffset = offset / 2; @@ -475,6 +503,7 @@ public class NetworkStats implements Parcelable { if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] && metered == this.metered[i] && roaming == this.roaming[i] + && defaultNetwork == this.defaultNetwork[i] && Objects.equals(iface, this.iface[i])) { return i; } @@ -489,7 +518,8 @@ public class NetworkStats implements Parcelable { */ public void spliceOperationsFrom(NetworkStats stats) { for (int i = 0; i < size; i++) { - final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i]); + final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i], + defaultNetwork[i]); if (j == -1) { operations[i] = 0; } else { @@ -581,6 +611,7 @@ public class NetworkStats implements Parcelable { entry.tag = TAG_NONE; entry.metered = METERED_ALL; entry.roaming = ROAMING_ALL; + entry.defaultNetwork = DEFAULT_NETWORK_ALL; entry.rxBytes = 0; entry.rxPackets = 0; entry.txBytes = 0; @@ -677,6 +708,7 @@ public class NetworkStats implements Parcelable { entry.tag = left.tag[i]; entry.metered = left.metered[i]; entry.roaming = left.roaming[i]; + entry.defaultNetwork = left.defaultNetwork[i]; entry.rxBytes = left.rxBytes[i]; entry.rxPackets = left.rxPackets[i]; entry.txBytes = left.txBytes[i]; @@ -685,7 +717,7 @@ public class NetworkStats implements Parcelable { // find remote row that matches, and subtract final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, - entry.metered, entry.roaming, i); + entry.metered, entry.roaming, entry.defaultNetwork, i); if (j != -1) { // Found matching row, subtract remote value. entry.rxBytes -= right.rxBytes[j]; @@ -725,6 +757,7 @@ public class NetworkStats implements Parcelable { entry.tag = TAG_NONE; entry.metered = METERED_ALL; entry.roaming = ROAMING_ALL; + entry.defaultNetwork = DEFAULT_NETWORK_ALL; entry.operations = 0L; for (int i = 0; i < size; i++) { @@ -755,6 +788,7 @@ public class NetworkStats implements Parcelable { entry.tag = TAG_NONE; entry.metered = METERED_ALL; entry.roaming = ROAMING_ALL; + entry.defaultNetwork = DEFAULT_NETWORK_ALL; for (int i = 0; i < size; i++) { // skip specific tags, since already counted in TAG_NONE @@ -802,6 +836,7 @@ public class NetworkStats implements Parcelable { pw.print(" tag="); pw.print(tagToString(tag[i])); pw.print(" metered="); pw.print(meteredToString(metered[i])); pw.print(" roaming="); pw.print(roamingToString(roaming[i])); + pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i])); pw.print(" rxBytes="); pw.print(rxBytes[i]); pw.print(" rxPackets="); pw.print(rxPackets[i]); pw.print(" txBytes="); pw.print(txBytes[i]); @@ -900,6 +935,22 @@ public class NetworkStats implements Parcelable { } } + /** + * Return text description of {@link #defaultNetwork} value. + */ + public static String defaultNetworkToString(int defaultNetwork) { + switch (defaultNetwork) { + case DEFAULT_NETWORK_ALL: + return "ALL"; + case DEFAULT_NETWORK_NO: + return "NO"; + case DEFAULT_NETWORK_YES: + return "YES"; + default: + return "UNKNOWN"; + } + } + @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); @@ -1055,6 +1106,7 @@ public class NetworkStats implements Parcelable { tmpEntry.set = set[i]; tmpEntry.metered = metered[i]; tmpEntry.roaming = roaming[i]; + tmpEntry.defaultNetwork = defaultNetwork[i]; combineValues(tmpEntry); if (tag[i] == TAG_NONE) { moved.add(tmpEntry); @@ -1075,6 +1127,7 @@ public class NetworkStats implements Parcelable { moved.iface = underlyingIface; moved.metered = METERED_ALL; moved.roaming = ROAMING_ALL; + moved.defaultNetwork = DEFAULT_NETWORK_ALL; combineValues(moved); // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than @@ -1085,13 +1138,13 @@ public class NetworkStats implements Parcelable { // roaming data after applying these adjustments, by checking the NetworkIdentity of the // underlying iface. int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO); + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); if (idxVpnBackground != -1) { tunSubtract(idxVpnBackground, this, moved); } int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, - METERED_NO, ROAMING_NO); + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); if (idxVpnForeground != -1) { tunSubtract(idxVpnForeground, this, moved); } diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index b307c5d6fc534851cabf328f5745bf2e8de8959f..8efd39a7a785dc2d6e9dc2334854e9e0fe7d2396 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -24,6 +24,15 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED; +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.ROAMING_YES; import static android.net.wifi.WifiInfo.removeDoubleQuotes; import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G; import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G; @@ -191,16 +200,30 @@ public class NetworkTemplate implements Parcelable { private final String mNetworkId; + // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. + private final int mMetered; + private final int mRoaming; + private final int mDefaultNetwork; + public NetworkTemplate(int matchRule, String subscriberId, String networkId) { this(matchRule, subscriberId, new String[] { subscriberId }, networkId); } public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, String networkId) { + this(matchRule, subscriberId, matchSubscriberIds, networkId, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL); + } + + public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, + String networkId, int metered, int roaming, int defaultNetwork) { mMatchRule = matchRule; mSubscriberId = subscriberId; mMatchSubscriberIds = matchSubscriberIds; mNetworkId = networkId; + mMetered = metered; + mRoaming = roaming; + mDefaultNetwork = defaultNetwork; if (!isKnownMatchRule(matchRule)) { Log.e(TAG, "Unknown network template rule " + matchRule @@ -213,6 +236,9 @@ public class NetworkTemplate implements Parcelable { mSubscriberId = in.readString(); mMatchSubscriberIds = in.createStringArray(); mNetworkId = in.readString(); + mMetered = in.readInt(); + mRoaming = in.readInt(); + mDefaultNetwork = in.readInt(); } @Override @@ -221,6 +247,9 @@ public class NetworkTemplate implements Parcelable { dest.writeString(mSubscriberId); dest.writeStringArray(mMatchSubscriberIds); dest.writeString(mNetworkId); + dest.writeInt(mMetered); + dest.writeInt(mRoaming); + dest.writeInt(mDefaultNetwork); } @Override @@ -243,12 +272,23 @@ public class NetworkTemplate implements Parcelable { if (mNetworkId != null) { builder.append(", networkId=").append(mNetworkId); } + if (mMetered != METERED_ALL) { + builder.append(", metered=").append(NetworkStats.meteredToString(mMetered)); + } + if (mRoaming != ROAMING_ALL) { + builder.append(", roaming=").append(NetworkStats.roamingToString(mRoaming)); + } + if (mDefaultNetwork != DEFAULT_NETWORK_ALL) { + builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString( + mDefaultNetwork)); + } return builder.toString(); } @Override public int hashCode() { - return Objects.hash(mMatchRule, mSubscriberId, mNetworkId); + return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming, + mDefaultNetwork); } @Override @@ -257,7 +297,10 @@ public class NetworkTemplate implements Parcelable { final NetworkTemplate other = (NetworkTemplate) obj; return mMatchRule == other.mMatchRule && Objects.equals(mSubscriberId, other.mSubscriberId) - && Objects.equals(mNetworkId, other.mNetworkId); + && Objects.equals(mNetworkId, other.mNetworkId) + && mMetered == other.mMetered + && mRoaming == other.mRoaming + && mDefaultNetwork == other.mDefaultNetwork; } return false; } @@ -300,6 +343,10 @@ public class NetworkTemplate implements Parcelable { * Test if given {@link NetworkIdentity} matches this template. */ public boolean matches(NetworkIdentity ident) { + if (!matchesMetered(ident)) return false; + if (!matchesRoaming(ident)) return false; + if (!matchesDefaultNetwork(ident)) return false; + switch (mMatchRule) { case MATCH_MOBILE_ALL: return matchesMobile(ident); @@ -326,6 +373,24 @@ public class NetworkTemplate implements Parcelable { } } + private boolean matchesMetered(NetworkIdentity ident) { + return (mMetered == METERED_ALL) + || (mMetered == METERED_YES && ident.mMetered) + || (mMetered == METERED_NO && !ident.mMetered); + } + + private boolean matchesRoaming(NetworkIdentity ident) { + return (mRoaming == ROAMING_ALL) + || (mRoaming == ROAMING_YES && ident.mRoaming) + || (mRoaming == ROAMING_NO && !ident.mRoaming); + } + + private boolean matchesDefaultNetwork(NetworkIdentity ident) { + return (mDefaultNetwork == DEFAULT_NETWORK_ALL) + || (mDefaultNetwork == DEFAULT_NETWORK_YES && ident.mDefaultNetwork) + || (mDefaultNetwork == DEFAULT_NETWORK_NO && !ident.mDefaultNetwork); + } + public boolean matchesSubscriberId(String subscriberId) { return ArrayUtils.contains(mMatchSubscriberIds, subscriberId); } diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java index 5425bf534ebdeafbba190d9c4f0cf3025dd5016a..49047d3a0c8739d6244c6da30cb4541d015da97d 100644 --- a/core/java/android/net/NetworkWatchlistManager.java +++ b/core/java/android/net/NetworkWatchlistManager.java @@ -86,4 +86,16 @@ public class NetworkWatchlistManager { e.rethrowFromSystemServer(); } } + + /** + * Get Network Watchlist config file hash. + */ + public byte[] getWatchlistConfigHash() { + try { + return mNetworkWatchlistManager.getWatchlistConfigHash(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get watchlist config hash"); + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/net/metrics/NetworkMetrics.java b/core/java/android/net/metrics/NetworkMetrics.java index 2b662a0c28e2405a00c63c2647099be7303befbe..2425bba9e6683ec0a9e4e7a7a526732e969a0429 100644 --- a/core/java/android/net/metrics/NetworkMetrics.java +++ b/core/java/android/net/metrics/NetworkMetrics.java @@ -96,6 +96,13 @@ public class NetworkMetrics { } } + /** Accumulate a single netd sock_diag poll result reported by netd. */ + public void addTcpStatsResult(int sent, int lost, int rttUs, int sentAckDiffMs) { + pendingSummary.tcpLossRate.count(lost, sent); + pendingSummary.roundTripTimeUs.count(rttUs); + pendingSummary.sentAckTimeDiffenceMs.count(sentAckDiffMs); + } + /** Represents running sums for dns and connect average error counts and average latencies. */ public static class Summary { @@ -109,6 +116,13 @@ public class NetworkMetrics { public final Metrics connectLatencies = new Metrics(); // Blocking and non blocking connect error rate measured in percentage points. public final Metrics connectErrorRate = new Metrics(); + // TCP socket packet loss stats collected from Netlink sock_diag. + public final Metrics tcpLossRate = new Metrics(); + // TCP averaged microsecond round-trip-time stats collected from Netlink sock_diag. + public final Metrics roundTripTimeUs = new Metrics(); + // TCP stats collected from Netlink sock_diag that averages millisecond per-socket + // differences between last packet sent timestamp and last ack received timestamp. + public final Metrics sentAckTimeDiffenceMs = new Metrics(); public Summary(int netId, long transports) { this.netId = netId; @@ -120,6 +134,7 @@ public class NetworkMetrics { dnsErrorRate.merge(that.dnsErrorRate); connectLatencies.merge(that.connectLatencies); connectErrorRate.merge(that.connectErrorRate); + tcpLossRate.merge(that.tcpLossRate); } @Override @@ -135,6 +150,10 @@ public class NetworkMetrics { j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d", (int) connectLatencies.average(), (int) connectLatencies.max, 100 * connectErrorRate.average(), connectErrorRate.count)); + j.add(String.format("tcp avg_loss=%.1f%% total_sent=%d total_lost=%d", + 100 * tcpLossRate.average(), tcpLossRate.count, (int) tcpLossRate.sum)); + j.add(String.format("tcp rtt=%dms", (int) (roundTripTimeUs.average() / 1000))); + j.add(String.format("tcp sent-ack_diff=%dms", (int) sentAckTimeDiffenceMs.average())); return j.toString(); } } @@ -152,7 +171,11 @@ public class NetworkMetrics { } void count(double value) { - count++; + count(value, 1); + } + + void count(double value, int subcount) { + count += subcount; sum += value; max = Math.max(max, value); } diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java index 7277ba34534b3a3dfa24d513c9f97010ae313b37..bb36536fe2ce0251e3cf851f1296d5164c25d73f 100644 --- a/core/java/android/net/metrics/WakeupStats.java +++ b/core/java/android/net/metrics/WakeupStats.java @@ -80,7 +80,7 @@ public class WakeupStats { break; } - switch (ev.dstHwAddr.addressType()) { + switch (ev.dstHwAddr.getAddressType()) { case MacAddress.TYPE_UNICAST: l2UnicastCount++; break; diff --git a/services/net/java/android/net/util/IpUtils.java b/core/java/android/net/util/IpUtils.java similarity index 100% rename from services/net/java/android/net/util/IpUtils.java rename to core/java/android/net/util/IpUtils.java diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 843bdb50dcabafeb9e0058c0d8e08548e77a53ce..a734719afa5dda4515894d9c9346de9bc2c7e0bb 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -157,11 +157,11 @@ public class BatteryManager { // values of the "plugged" field in the ACTION_BATTERY_CHANGED intent. // These must be powers of 2. /** Power source is an AC charger. */ - public static final int BATTERY_PLUGGED_AC = 1; + public static final int BATTERY_PLUGGED_AC = OsProtoEnums.BATTERY_PLUGGED_AC; // = 1 /** Power source is a USB port. */ - public static final int BATTERY_PLUGGED_USB = 2; + public static final int BATTERY_PLUGGED_USB = OsProtoEnums.BATTERY_PLUGGED_USB; // = 2 /** Power source is wireless. */ - public static final int BATTERY_PLUGGED_WIRELESS = 4; + public static final int BATTERY_PLUGGED_WIRELESS = OsProtoEnums.BATTERY_PLUGGED_WIRELESS; // = 4 /** @hide */ public static final int BATTERY_PLUGGED_ANY = diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java index f3a95b90d5c112ada8ef7db22e720f0e15ba53d7..a86237dd271f5217ea021a96ce0c5cea656b4116 100644 --- a/core/java/android/os/BatteryManagerInternal.java +++ b/core/java/android/os/BatteryManagerInternal.java @@ -24,26 +24,63 @@ package android.os; public abstract class BatteryManagerInternal { /** * Returns true if the device is plugged into any of the specified plug types. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract boolean isPowered(int plugTypeSet); /** * Returns the current plug type. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract int getPlugType(); /** * Returns battery level as a percentage. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract int getBatteryLevel(); + /** + * Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct. + * Please note apparently it could be bigger than {@link #getBatteryFullCharge}. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. + * + * @see android.hardware.health.V1_0.HealthInfo#batteryChargeCounter + */ + public abstract int getBatteryChargeCounter(); + + /** + * Battery charge value when it is considered to be "full" in uA-h , as defined in the + * HealthInfo HAL struct. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. + * + * @see android.hardware.health.V1_0.HealthInfo#batteryFullCharge + */ + public abstract int getBatteryFullCharge(); + /** * Returns whether we currently consider the battery level to be low. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract boolean getBatteryLevelLow(); /** * Returns a non-zero value if an unsupported charger is attached. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract int getInvalidCharger(); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index dc271d8639d562786430fd1f92591261f3078dcd..fd0e5ae51892b16e0ffbb3704abe514ff2c6b525 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.job.JobParameters; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.server.ServerProtoEnums; import android.service.batterystats.BatteryStatsServiceDumpProto; import android.telephony.SignalStrength; import android.text.format.DateFormat; @@ -35,6 +36,7 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.location.gnssmetrics.GnssMetrics; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; @@ -335,6 +337,9 @@ public abstract class BatteryStats implements Parcelable { private final StringBuilder mFormatBuilder = new StringBuilder(32); private final Formatter mFormatter = new Formatter(mFormatBuilder); + private static final String CELLULAR_CONTROLLER_NAME = "Cellular"; + private static final String WIFI_CONTROLLER_NAME = "WiFi"; + /** * Indicates times spent by the uid at each cpu frequency in all process states. * @@ -410,6 +415,13 @@ public abstract class BatteryStats implements Parcelable { */ public abstract LongCounter getIdleTimeCounter(); + /** + * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the + * scan state. + */ + public abstract LongCounter getScanTimeCounter(); + + /** * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the * receive state. @@ -683,6 +695,14 @@ public abstract class BatteryStats implements Parcelable { public abstract long[] getCpuFreqTimes(int which); public abstract long[] getScreenOffCpuFreqTimes(int which); + /** + * Returns cpu active time of an uid. + */ + public abstract long getCpuActiveTime(); + /** + * Returns cpu times of an uid on each cluster + */ + public abstract long[] getCpuClusterTimes(); /** * Returns cpu times of an uid at a particular process state. @@ -1500,6 +1520,10 @@ public abstract class BatteryStats implements Parcelable { public static final int STATE2_WIFI_SIGNAL_STRENGTH_SHIFT = 4; public static final int STATE2_WIFI_SIGNAL_STRENGTH_MASK = 0x7 << STATE2_WIFI_SIGNAL_STRENGTH_SHIFT; + // Values for NUM_GPS_SIGNAL_QUALITY_LEVELS + public static final int STATE2_GPS_SIGNAL_QUALITY_SHIFT = 7; + public static final int STATE2_GPS_SIGNAL_QUALITY_MASK = + 0x1 << STATE2_GPS_SIGNAL_QUALITY_SHIFT; public static final int STATE2_POWER_SAVE_FLAG = 1<<31; public static final int STATE2_VIDEO_ON_FLAG = 1<<30; @@ -2031,17 +2055,17 @@ public abstract class BatteryStats implements Parcelable { /** * Constant for device idle mode: not active. */ - public static final int DEVICE_IDLE_MODE_OFF = 0; + public static final int DEVICE_IDLE_MODE_OFF = ServerProtoEnums.DEVICE_IDLE_MODE_OFF; // 0 /** * Constant for device idle mode: active in lightweight mode. */ - public static final int DEVICE_IDLE_MODE_LIGHT = 1; + public static final int DEVICE_IDLE_MODE_LIGHT = ServerProtoEnums.DEVICE_IDLE_MODE_LIGHT; // 1 /** * Constant for device idle mode: active in full mode. */ - public static final int DEVICE_IDLE_MODE_DEEP = 2; + public static final int DEVICE_IDLE_MODE_DEEP = ServerProtoEnums.DEVICE_IDLE_MODE_DEEP; // 2 /** * Returns the time in microseconds that device has been in idle mode while @@ -2088,6 +2112,23 @@ public abstract class BatteryStats implements Parcelable { */ public abstract int getNumConnectivityChange(int which); + + /** + * Returns the time in microseconds that the phone has been running with + * the given GPS signal quality level + * + * {@hide} + */ + public abstract long getGpsSignalQualityTime(int strengthBin, + long elapsedRealtimeUs, int which); + + /** + * Returns the GPS battery drain in mA-ms + * + * {@hide} + */ + public abstract long getGpsBatteryDrainMaMs(); + /** * Returns the time in microseconds that the phone has been on while the device was * running on battery. @@ -2312,6 +2353,9 @@ public abstract class BatteryStats implements Parcelable { WIFI_SUPPL_STATE_NAMES, WIFI_SUPPL_STATE_SHORT_NAMES), new BitDescription(HistoryItem.STATE2_CAMERA_FLAG, "camera", "ca"), new BitDescription(HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG, "ble_scan", "bles"), + new BitDescription(HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK, + HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT, "gps_signal_quality", "Gss", + new String[] { "poor", "good"}, new String[] { "poor", "good"}), }; public static final String[] HISTORY_EVENT_NAMES = new String[] { @@ -2365,6 +2409,14 @@ public abstract class BatteryStats implements Parcelable { */ public abstract long getWifiOnTime(long elapsedRealtimeUs, int which); + /** + * Returns the time in microseconds that wifi has been active while the device was + * running on battery. + * + * {@hide} + */ + public abstract long getWifiActiveTime(long elapsedRealtimeUs, int which); + /** * Returns the time in microseconds that wifi has been on and the driver has * been in the running state while the device was running on battery. @@ -3312,6 +3364,20 @@ public abstract class BatteryStats implements Parcelable { final long sleepTimeMs = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs); + if (controllerName.equals(WIFI_CONTROLLER_NAME)) { + final long scanTimeMs = counter.getScanTimeCounter().getCountLocked(which); + sb.setLength(0); + sb.append(prefix); + sb.append(" "); + sb.append(controllerName); + sb.append(" Scan time: "); + formatTimeMs(sb, scanTimeMs); + sb.append("("); + sb.append(formatRatioLocked(scanTimeMs, totalControllerActivityTimeMs)); + sb.append(")"); + pw.println(sb.toString()); + } + sb.setLength(0); sb.append(prefix); sb.append(" "); @@ -3353,7 +3419,7 @@ public abstract class BatteryStats implements Parcelable { String [] powerLevel; switch(controllerName) { - case "Cellular": + case CELLULAR_CONTROLLER_NAME: powerLevel = new String[] { " less than 0dBm: ", " 0dBm to 8dBm: ", @@ -4641,7 +4707,7 @@ public abstract class BatteryStats implements Parcelable { if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - printControllerActivity(pw, sb, prefix, "Cellular", + printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME, getModemControllerActivity(), which); pw.print(prefix); @@ -4650,6 +4716,16 @@ public abstract class BatteryStats implements Parcelable { sb.append(" Wifi Statistics:"); pw.println(sb.toString()); + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" Wifi kernel active time: "); + final long wifiActiveTime = getWifiActiveTime(rawRealtime, which); + formatTimeMs(sb, wifiActiveTime / 1000); + sb.append("("); sb.append(formatRatioLocked(wifiActiveTime, whichBatteryRealtime)); + sb.append(")"); + pw.println(sb.toString()); + pw.print(" Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes)); pw.print(" Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes)); pw.print(" Wifi packets received: "); pw.println(wifiRxTotalPackets); @@ -4727,7 +4803,45 @@ public abstract class BatteryStats implements Parcelable { if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - printControllerActivity(pw, sb, prefix, "WiFi", getWifiControllerActivity(), which); + printControllerActivity(pw, sb, prefix, WIFI_CONTROLLER_NAME, + getWifiControllerActivity(), which); + + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" GPS Statistics:"); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" GPS signal quality (Top 4 Average CN0):"); + final String[] gpsSignalQualityDescription = new String[]{ + "poor (less than 20 dBHz): ", + "good (greater than 20 dBHz): "}; + final int numGpsSignalQualityBins = Math.min(GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS, + gpsSignalQualityDescription.length); + for (int i=0; i 0) { + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" Battery Drain (mAh): "); + sb.append(Double.toString(((double) gpsBatteryDrainMaMs)/(3600 * 1000))); + pw.println(sb.toString()); + } pw.print(prefix); sb.setLength(0); @@ -5168,8 +5282,8 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - printControllerActivityIfInteresting(pw, sb, prefix + " ", "Modem", - u.getModemControllerActivity(), which); + printControllerActivityIfInteresting(pw, sb, prefix + " ", + CELLULAR_CONTROLLER_NAME, u.getModemControllerActivity(), which); if (wifiRxBytes > 0 || wifiTxBytes > 0 || wifiRxPackets > 0 || wifiTxPackets > 0) { pw.print(prefix); pw.print(" Wi-Fi network: "); @@ -5223,7 +5337,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - printControllerActivityIfInteresting(pw, sb, prefix + " ", "WiFi", + printControllerActivityIfInteresting(pw, sb, prefix + " ", WIFI_CONTROLLER_NAME, u.getWifiControllerActivity(), which); if (btRxBytes > 0 || btTxBytes > 0) { diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java index 94a44ec3729a7215a4305bb541767e8cfe1a8474..dda0ed8a5e2017e3e81e71f75204ea1aeafba0cb 100644 --- a/core/java/android/os/ConfigUpdate.java +++ b/core/java/android/os/ConfigUpdate.java @@ -82,6 +82,14 @@ public final class ConfigUpdate { public static final String ACTION_UPDATE_SMART_SELECTION = "android.intent.action.UPDATE_SMART_SELECTION"; + /** + * Update network watchlist config file. + * @hide + */ + @SystemApi + public static final String ACTION_UPDATE_NETWORK_WATCHLIST + = "android.intent.action.UPDATE_NETWORK_WATCHLIST"; + private ConfigUpdate() { } } diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 848ab88d3cbcbbe28f2e14317e20040985052370..e6069644caa577f2d30396323bd2c5722cb91e57 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -56,10 +56,10 @@ import java.util.Map; *

    Logging Trace Files

    *

    Debug can create log files that give details about an application, such as * a call stack and start/stop times for any running methods. See Traceview: A Graphical Log Viewer for - * information about reading trace files. To start logging trace files, call one - * of the startMethodTracing() methods. To stop tracing, call - * {@link #stopMethodTracing()}. + * href="{@docRoot}studio/profile/traceview.html">Inspect Trace Logs with + * Traceview for information about reading trace files. To start logging + * trace files, call one of the startMethodTracing() methods. To stop tracing, + * call {@link #stopMethodTracing()}. */ public final class Debug { @@ -116,6 +116,8 @@ public final class Debug /** The proportional set size that is swappable for dalvik heap. */ /** @hide We may want to expose this, eventually. */ public int dalvikSwappablePss; + /** @hide The resident set size for dalvik heap. (Without other Dalvik overhead.) */ + public int dalvikRss; /** The private dirty pages used by dalvik heap. */ public int dalvikPrivateDirty; /** The shared dirty pages used by dalvik heap. */ @@ -138,6 +140,8 @@ public final class Debug /** The proportional set size that is swappable for the native heap. */ /** @hide We may want to expose this, eventually. */ public int nativeSwappablePss; + /** @hide The resident set size for the native heap. */ + public int nativeRss; /** The private dirty pages used by the native heap. */ public int nativePrivateDirty; /** The shared dirty pages used by the native heap. */ @@ -160,6 +164,8 @@ public final class Debug /** The proportional set size that is swappable for everything else. */ /** @hide We may want to expose this, eventually. */ public int otherSwappablePss; + /** @hide The resident set size for everything else. */ + public int otherRss; /** The private dirty pages used by everything else. */ public int otherPrivateDirty; /** The shared dirty pages used by everything else. */ @@ -288,24 +294,26 @@ public final class Debug public static final int NUM_DVK_STATS = 14; /** @hide */ - public static final int NUM_CATEGORIES = 8; + public static final int NUM_CATEGORIES = 9; /** @hide */ - public static final int offsetPss = 0; + public static final int OFFSET_PSS = 0; /** @hide */ - public static final int offsetSwappablePss = 1; + public static final int OFFSET_SWAPPABLE_PSS = 1; /** @hide */ - public static final int offsetPrivateDirty = 2; + public static final int OFFSET_RSS = 2; /** @hide */ - public static final int offsetSharedDirty = 3; + public static final int OFFSET_PRIVATE_DIRTY = 3; /** @hide */ - public static final int offsetPrivateClean = 4; + public static final int OFFSET_SHARED_DIRTY = 4; /** @hide */ - public static final int offsetSharedClean = 5; + public static final int OFFSET_PRIVATE_CLEAN = 5; /** @hide */ - public static final int offsetSwappedOut = 6; + public static final int OFFSET_SHARED_CLEAN = 6; /** @hide */ - public static final int offsetSwappedOutPss = 7; + public static final int OFFSET_SWAPPED_OUT = 7; + /** @hide */ + public static final int OFFSET_SWAPPED_OUT_PSS = 8; private int[] otherStats = new int[(NUM_OTHER_STATS+NUM_DVK_STATS)*NUM_CATEGORIES]; @@ -336,6 +344,13 @@ public final class Debug return dalvikSwappablePss + nativeSwappablePss + otherSwappablePss; } + /** + * @hide Return total RSS memory usage in kB. + */ + public int getTotalRss() { + return dalvikRss + nativeRss + otherRss; + } + /** * Return total private dirty memory usage in kB. */ @@ -382,29 +397,32 @@ public final class Debug /** @hide */ public int getOtherPss(int which) { - return otherStats[which*NUM_CATEGORIES + offsetPss]; + return otherStats[which * NUM_CATEGORIES + OFFSET_PSS]; } - /** @hide */ public int getOtherSwappablePss(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSwappablePss]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPABLE_PSS]; } + /** @hide */ + public int getOtherRss(int which) { + return otherStats[which * NUM_CATEGORIES + OFFSET_RSS]; + } /** @hide */ public int getOtherPrivateDirty(int which) { - return otherStats[which*NUM_CATEGORIES + offsetPrivateDirty]; + return otherStats[which * NUM_CATEGORIES + OFFSET_PRIVATE_DIRTY]; } /** @hide */ public int getOtherSharedDirty(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSharedDirty]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SHARED_DIRTY]; } /** @hide */ public int getOtherPrivateClean(int which) { - return otherStats[which*NUM_CATEGORIES + offsetPrivateClean]; + return otherStats[which * NUM_CATEGORIES + OFFSET_PRIVATE_CLEAN]; } /** @hide */ @@ -414,17 +432,17 @@ public final class Debug /** @hide */ public int getOtherSharedClean(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSharedClean]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SHARED_CLEAN]; } /** @hide */ public int getOtherSwappedOut(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSwappedOut]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPED_OUT]; } /** @hide */ public int getOtherSwappedOutPss(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSwappedOutPss]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPED_OUT_PSS]; } /** @hide */ @@ -741,6 +759,7 @@ public final class Debug public void writeToParcel(Parcel dest, int flags) { dest.writeInt(dalvikPss); dest.writeInt(dalvikSwappablePss); + dest.writeInt(dalvikRss); dest.writeInt(dalvikPrivateDirty); dest.writeInt(dalvikSharedDirty); dest.writeInt(dalvikPrivateClean); @@ -749,6 +768,7 @@ public final class Debug dest.writeInt(dalvikSwappedOutPss); dest.writeInt(nativePss); dest.writeInt(nativeSwappablePss); + dest.writeInt(nativeRss); dest.writeInt(nativePrivateDirty); dest.writeInt(nativeSharedDirty); dest.writeInt(nativePrivateClean); @@ -757,6 +777,7 @@ public final class Debug dest.writeInt(nativeSwappedOutPss); dest.writeInt(otherPss); dest.writeInt(otherSwappablePss); + dest.writeInt(otherRss); dest.writeInt(otherPrivateDirty); dest.writeInt(otherSharedDirty); dest.writeInt(otherPrivateClean); @@ -770,6 +791,7 @@ public final class Debug public void readFromParcel(Parcel source) { dalvikPss = source.readInt(); dalvikSwappablePss = source.readInt(); + dalvikRss = source.readInt(); dalvikPrivateDirty = source.readInt(); dalvikSharedDirty = source.readInt(); dalvikPrivateClean = source.readInt(); @@ -778,6 +800,7 @@ public final class Debug dalvikSwappedOutPss = source.readInt(); nativePss = source.readInt(); nativeSwappablePss = source.readInt(); + nativeRss = source.readInt(); nativePrivateDirty = source.readInt(); nativeSharedDirty = source.readInt(); nativePrivateClean = source.readInt(); @@ -786,6 +809,7 @@ public final class Debug nativeSwappedOutPss = source.readInt(); otherPss = source.readInt(); otherSwappablePss = source.readInt(); + otherRss = source.readInt(); otherPrivateDirty = source.readInt(); otherSharedDirty = source.readInt(); otherPrivateClean = source.readInt(); @@ -1001,8 +1025,8 @@ public final class Debug * under your package-specific directory on primary shared/external storage, * as returned by {@link Context#getExternalFilesDir(String)}. *

    - * See Traceview: - * A Graphical Log Viewer for information about reading trace files. + * See Inspect Trace Logs + * with Traceview for information about reading trace files. *

    * When method tracing is enabled, the VM will run more slowly than usual, * so the timings from the trace files should only be considered in relative @@ -1025,8 +1049,8 @@ public final class Debug * your package-specific directory on primary shared/external storage, as * returned by {@link Context#getExternalFilesDir(String)}. *

    - * See Traceview: - * A Graphical Log Viewer for information about reading trace files. + * See Inspect Trace Logs + * with Traceview for information about reading trace files. *

    * When method tracing is enabled, the VM will run more slowly than usual, * so the timings from the trace files should only be considered in relative @@ -1055,8 +1079,8 @@ public final class Debug * your package-specific directory on primary shared/external storage, as * returned by {@link Context#getExternalFilesDir(String)}. *

    - * See Traceview: - * A Graphical Log Viewer for information about reading trace files. + * See Inspect Trace Logs + * with Traceview for information about reading trace files. *

    * When method tracing is enabled, the VM will run more slowly than usual, * so the timings from the trace files should only be considered in relative @@ -1087,8 +1111,8 @@ public final class Debug * your package-specific directory on primary shared/external storage, as * returned by {@link Context#getExternalFilesDir(String)}. *

    - * See Traceview: - * A Graphical Log Viewer for information about reading trace files. + * See Inspect Trace Logs + * with Traceview for information about reading trace files. *

    * When method tracing is enabled, the VM will run more slowly than usual, * so the timings from the trace files should only be considered in relative @@ -1121,8 +1145,8 @@ public final class Debug * your package-specific directory on primary shared/external storage, as * returned by {@link Context#getExternalFilesDir(String)}. *

    - * See Traceview: - * A Graphical Log Viewer for information about reading trace files. + * See Inspect Trace Logs + * with Traceview for information about reading trace files. * * @param tracePath Path to the trace log file to create. If {@code null}, * this will default to "dmtrace.trace". If the file already @@ -2352,22 +2376,28 @@ public final class Debug } /** - * Attach a library as a jvmti agent to the current runtime. + * Attach a library as a jvmti agent to the current runtime, with the given classloader + * determining the library search path. + *

    + * Note: agents may only be attached to debuggable apps. Otherwise, this function will + * throw a SecurityException. * - * @param library library containing the agent - * @param options options passed to the agent + * @param library the library containing the agent. + * @param options the options passed to the agent. + * @param classLoader the classloader determining the library search path. * - * @throws IOException If the agent could not be attached + * @throws IOException if the agent could not be attached. + * @throws SecurityException if the app is not debuggable. */ - public static void attachJvmtiAgent(@NonNull String library, @Nullable String options) - throws IOException { + public static void attachJvmtiAgent(@NonNull String library, @Nullable String options, + @Nullable ClassLoader classLoader) throws IOException { Preconditions.checkNotNull(library); Preconditions.checkArgument(!library.contains("=")); if (options == null) { - VMDebug.attachAgent(library); + VMDebug.attachAgent(library, classLoader); } else { - VMDebug.attachAgent(library + "=" + options); + VMDebug.attachAgent(library + "=" + options, classLoader); } } } diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index b1794a6dfa6c49c3e62be927c9009339bee7a21d..03203d0502fdd7c51944005ee100eb02891d8f78 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -41,6 +41,7 @@ public class Environment { private static final String ENV_OEM_ROOT = "OEM_ROOT"; private static final String ENV_ODM_ROOT = "ODM_ROOT"; private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT"; + private static final String ENV_PRODUCT_ROOT = "PRODUCT_ROOT"; /** {@hide} */ public static final String DIR_ANDROID = "Android"; @@ -62,6 +63,7 @@ public class Environment { private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem"); private static final File DIR_ODM_ROOT = getDirectory(ENV_ODM_ROOT, "/odm"); private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor"); + private static final File DIR_PRODUCT_ROOT = getDirectory(ENV_PRODUCT_ROOT, "/product"); private static UserEnvironment sCurrentUser; private static boolean sUserRequired; @@ -179,6 +181,16 @@ public class Environment { return DIR_VENDOR_ROOT; } + /** + * Return root directory of the "product" partition holding product-specific + * customizations if any. If present, the partition is mounted read-only. + * + * @hide + */ + public static File getProductDirectory() { + return DIR_PRODUCT_ROOT; + } + /** * Return the system directory for a user. This is for use by system * services to store files relating to the user. This directory will be @@ -292,9 +304,18 @@ public class Environment { } /** {@hide} */ - public static File getProfileSnapshotPath(String packageName, String codePath) { - return buildPath(buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName, - "primary.prof.snapshot")); + public static File getDataVendorCeDirectory(int userId) { + return buildPath(getDataDirectory(), "vendor_ce", String.valueOf(userId)); + } + + /** {@hide} */ + public static File getDataVendorDeDirectory(int userId) { + return buildPath(getDataDirectory(), "vendor_de", String.valueOf(userId)); + } + + /** {@hide} */ + public static File getDataRefProfilesDePackageDirectory(String packageName) { + return buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName); } /** {@hide} */ diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 5c5e351d2eeb9a9f9071f76129cdd61a0e2b563f..fc88e900663919d13b58680b420ba7045fbaecea 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -202,7 +202,8 @@ public class Handler { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( - "Can't create handler inside thread that has not called Looper.prepare()"); + "Can't create handler inside thread " + Thread.currentThread() + + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java index 4d7d9319c94e3cdc3030f89f811557415408ec0e..91b796aba65509cf7a6e65390c3e5e02d024e013 100644 --- a/core/java/android/os/HidlSupport.java +++ b/core/java/android/os/HidlSupport.java @@ -16,6 +16,8 @@ package android.os; +import android.annotation.SystemApi; + import java.util.Arrays; import java.util.Collection; import java.util.Iterator; @@ -25,6 +27,7 @@ import java.util.Objects; import java.util.stream.IntStream; /** @hide */ +@SystemApi public class HidlSupport { /** * Similar to Objects.deepEquals, but also take care of lists. @@ -36,7 +39,9 @@ public class HidlSupport { * 2.3 Both are Lists, elements are checked recursively * 2.4 (If both are collections other than lists or maps, throw an error) * 2.5 lft.equals(rgt) returns true + * @hide */ + @SystemApi public static boolean deepEquals(Object lft, Object rgt) { if (lft == rgt) { return true; @@ -91,6 +96,7 @@ public class HidlSupport { * and should be avoided). * * @param Inner object type. + * @hide */ public static final class Mutable { public E value; @@ -106,7 +112,9 @@ public class HidlSupport { /** * Similar to Arrays.deepHashCode, but also take care of lists. + * @hide */ + @SystemApi public static int deepHashCode(Object o) { if (o == null) { return 0; @@ -133,6 +141,7 @@ public class HidlSupport { return o.hashCode(); } + /** @hide */ private static void throwErrorIfUnsupportedType(Object o) { if (o instanceof Collection && !(o instanceof List)) { throw new UnsupportedOperationException( @@ -146,6 +155,7 @@ public class HidlSupport { } } + /** @hide */ private static int primitiveArrayHashCode(Object o) { Class elementType = o.getClass().getComponentType(); if (elementType == boolean.class) { @@ -185,7 +195,9 @@ public class HidlSupport { * - If both interfaces are stubs, asBinder() returns the object itself. By default, * auto-generated IFoo.Stub does not override equals(), but an implementation can * optionally override it, and {@code interfacesEqual} will use it here. + * @hide */ + @SystemApi public static boolean interfacesEqual(IHwInterface lft, Object rgt) { if (lft == rgt) { return true; @@ -200,7 +212,12 @@ public class HidlSupport { } /** - * Return PID of process if sharable to clients. + * Return PID of process only if on a non-user build. For debugging purposes. + * @hide */ + @SystemApi public static native int getPidIfSharable(); + + /** @hide */ + public HidlSupport() {} } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 5e2a0815b60d39e2cd52157d0a201379f3e81ed4..2a088a659b0fb946a8e7e07553a790161605b6d8 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -16,16 +16,20 @@ package android.os; +import android.annotation.SystemApi; + import libcore.util.NativeAllocationRegistry; import java.util.NoSuchElementException; /** @hide */ +@SystemApi public abstract class HwBinder implements IHwBinder { private static final String TAG = "HwBinder"; private static final NativeAllocationRegistry sNativeRegistry; + /** @hide */ public HwBinder() { native_setup(); @@ -34,33 +38,71 @@ public abstract class HwBinder implements IHwBinder { mNativeContext); } + /** @hide */ @Override public final native void transact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; + /** @hide */ public abstract void onTransact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; + /** @hide */ public native final void registerService(String serviceName) throws RemoteException; + /** + * Returns the specified service from the hwservicemanager. Does not retry. + * + * @param iface fully-qualified interface name for example foo.bar@1.3::IBaz + * @param serviceName the instance name of the service for example default. + * @throws NoSuchElementException when the service is unavailable + * @hide + */ + @SystemApi public static final IHwBinder getService( String iface, String serviceName) throws RemoteException, NoSuchElementException { return getService(iface, serviceName, false /* retry */); } + /** + * Returns the specified service from the hwservicemanager. + * @param iface fully-qualified interface name for example foo.bar@1.3::IBaz + * @param serviceName the instance name of the service for example default. + * @param retry whether to wait for the service to start if it's not already started + * @throws NoSuchElementException when the service is unavailable + * @hide + */ + @SystemApi public static native final IHwBinder getService( String iface, String serviceName, boolean retry) throws RemoteException, NoSuchElementException; + /** + * Configures how many threads the process-wide hwbinder threadpool + * has to process incoming requests. + * + * @hide + */ + @SystemApi public static native final void configureRpcThreadpool( long maxThreads, boolean callerWillJoin); + /** + * Current thread will join hwbinder threadpool and process + * commands in the pool. Should be called after configuring + * a threadpool with callerWillJoin true and then registering + * the provided service if this thread doesn't need to do + * anything else. + * + * @hide + */ + @SystemApi public static native final void joinRpcThreadpool(); // Returns address of the "freeFunction". @@ -81,8 +123,21 @@ public abstract class HwBinder implements IHwBinder { private static native void native_report_sysprop_change(); + /** + * Enable instrumentation if available. + * @hide + */ + @SystemApi + public static void enableInstrumentation() { + native_report_sysprop_change(); + } + /** * Notifies listeners that a system property has changed + * + * TODO(b/72480743): remove this method + * + * @hide */ public static void reportSyspropChanged() { native_report_sysprop_change(); diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java index 5e9b9ae3d49a9e4f783e0559864a40ad5dd77404..405651e992a3b0394911e15ffa4a51bbfb30eec6 100644 --- a/core/java/android/os/HwBlob.java +++ b/core/java/android/os/HwBlob.java @@ -17,10 +17,17 @@ package android.os; import android.annotation.NonNull; +import android.annotation.SystemApi; import libcore.util.NativeAllocationRegistry; -/** @hide */ +/** + * Represents fixed sized allocation of marshalled data used. Helper methods + * allow for access to the unmarshalled data in a variety of ways. + * + * @hide + */ +@SystemApi public class HwBlob { private static final String TAG = "HwBlob"; @@ -34,48 +41,276 @@ public class HwBlob { mNativeContext); } + /** + * @param offset offset to unmarshall a boolean from + * @return the unmarshalled boolean value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final boolean getBool(long offset); + /** + * @param offset offset to unmarshall a byte from + * @return the unmarshalled byte value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final byte getInt8(long offset); + /** + * @param offset offset to unmarshall a short from + * @return the unmarshalled short value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final short getInt16(long offset); + /** + * @param offset offset to unmarshall an int from + * @return the unmarshalled int value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final int getInt32(long offset); + /** + * @param offset offset to unmarshall a long from + * @return the unmarshalled long value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final long getInt64(long offset); + /** + * @param offset offset to unmarshall a float from + * @return the unmarshalled float value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final float getFloat(long offset); + /** + * @param offset offset to unmarshall a double from + * @return the unmarshalled double value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final double getDouble(long offset); + /** + * @param offset offset to unmarshall a string from + * @return the unmarshalled string value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final String getString(long offset); /** - The copyTo... methods copy the blob's data, starting from the given - byte offset, into the array. A total of "size" _elements_ are copied. + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob. */ public native final void copyToBoolArray(long offset, boolean[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob. + */ public native final void copyToInt8Array(long offset, byte[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob. + */ public native final void copyToInt16Array(long offset, short[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob. + */ public native final void copyToInt32Array(long offset, int[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob. + */ public native final void copyToInt64Array(long offset, long[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob. + */ public native final void copyToFloatArray(long offset, float[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob. + */ public native final void copyToDoubleArray(long offset, double[] array, int size); + /** + * Writes a boolean value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jboolean)] is out of range + */ public native final void putBool(long offset, boolean x); + /** + * Writes a byte value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jbyte)] is out of range + */ public native final void putInt8(long offset, byte x); + /** + * Writes a short value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jshort)] is out of range + */ public native final void putInt16(long offset, short x); + /** + * Writes a int value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jint)] is out of range + */ public native final void putInt32(long offset, int x); + /** + * Writes a long value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jlong)] is out of range + */ public native final void putInt64(long offset, long x); + /** + * Writes a float value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jfloat)] is out of range + */ public native final void putFloat(long offset, float x); + /** + * Writes a double value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jdouble)] is out of range + */ public native final void putDouble(long offset, double x); + /** + * Writes a string value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jstring)] is out of range + */ public native final void putString(long offset, String x); + /** + * Put a boolean array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob. + */ public native final void putBoolArray(long offset, boolean[] x); + /** + * Put a byte array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob. + */ public native final void putInt8Array(long offset, byte[] x); + /** + * Put a short array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob. + */ public native final void putInt16Array(long offset, short[] x); + /** + * Put a int array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob. + */ public native final void putInt32Array(long offset, int[] x); + /** + * Put a long array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob. + */ public native final void putInt64Array(long offset, long[] x); + /** + * Put a float array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob. + */ public native final void putFloatArray(long offset, float[] x); + /** + * Put a double array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob. + */ public native final void putDoubleArray(long offset, double[] x); + /** + * Write another HwBlob into this blob at the specified location. + * + * @param offset location to write value + * @param blob data to write + * @throws IndexOutOfBoundsException if [offset, offset + blob's size] outside of the range of + * this blob. + */ public native final void putBlob(long offset, HwBlob blob); + /** + * @return current handle of HwBlob for reference in a parcelled binder transaction + */ public native final long handle(); + /** + * Convert a primitive to a wrapped array for boolean. + * + * @param array from array + * @return transformed array + */ public static Boolean[] wrapArray(@NonNull boolean[] array) { final int n = array.length; Boolean[] wrappedArray = new Boolean[n]; @@ -85,6 +320,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for long. + * + * @param array from array + * @return transformed array + */ public static Long[] wrapArray(@NonNull long[] array) { final int n = array.length; Long[] wrappedArray = new Long[n]; @@ -94,6 +335,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for byte. + * + * @param array from array + * @return transformed array + */ public static Byte[] wrapArray(@NonNull byte[] array) { final int n = array.length; Byte[] wrappedArray = new Byte[n]; @@ -103,6 +350,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for short. + * + * @param array from array + * @return transformed array + */ public static Short[] wrapArray(@NonNull short[] array) { final int n = array.length; Short[] wrappedArray = new Short[n]; @@ -112,6 +365,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for int. + * + * @param array from array + * @return transformed array + */ public static Integer[] wrapArray(@NonNull int[] array) { final int n = array.length; Integer[] wrappedArray = new Integer[n]; @@ -121,6 +380,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for float. + * + * @param array from array + * @return transformed array + */ public static Float[] wrapArray(@NonNull float[] array) { final int n = array.length; Float[] wrappedArray = new Float[n]; @@ -130,6 +395,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for double. + * + * @param array from array + * @return transformed array + */ public static Double[] wrapArray(@NonNull double[] array) { final int n = array.length; Double[] wrappedArray = new Double[n]; diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java index 4ba1144742db01dd1e0e33bacbe442c67899b055..0eb62c95ed715eb9c52adb7a24e3faac7da350ea 100644 --- a/core/java/android/os/HwParcel.java +++ b/core/java/android/os/HwParcel.java @@ -16,17 +16,32 @@ package android.os; -import java.util.ArrayList; -import java.util.Arrays; +import android.annotation.IntDef; +import android.annotation.SystemApi; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; + /** @hide */ +@SystemApi public class HwParcel { private static final String TAG = "HwParcel"; + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_SUCCESS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Status {} + + /** + * Success return error for a transaction. Written to parcels + * using writeStatus. + */ public static final int STATUS_SUCCESS = 0; - public static final int STATUS_ERROR = -1; private static final NativeAllocationRegistry sNativeRegistry; @@ -38,6 +53,9 @@ public class HwParcel { mNativeContext); } + /** + * Creates an initialized and empty parcel. + */ public HwParcel() { native_setup(true /* allocate */); @@ -46,25 +64,106 @@ public class HwParcel { mNativeContext); } + /** + * Writes an interface token into the parcel used to verify that + * a transaction has made it to the write type of interface. + * + * @param interfaceName fully qualified name of interface message + * is being sent to. + */ public native final void writeInterfaceToken(String interfaceName); + /** + * Writes a boolean value to the end of the parcel. + * @param val to write + */ public native final void writeBool(boolean val); + /** + * Writes a byte value to the end of the parcel. + * @param val to write + */ public native final void writeInt8(byte val); + /** + * Writes a short value to the end of the parcel. + * @param val to write + */ public native final void writeInt16(short val); + /** + * Writes a int value to the end of the parcel. + * @param val to write + */ public native final void writeInt32(int val); + /** + * Writes a long value to the end of the parcel. + * @param val to write + */ public native final void writeInt64(long val); + /** + * Writes a float value to the end of the parcel. + * @param val to write + */ public native final void writeFloat(float val); + /** + * Writes a double value to the end of the parcel. + * @param val to write + */ public native final void writeDouble(double val); + /** + * Writes a String value to the end of the parcel. + * + * Note, this will be converted to UTF-8 when it is written. + * + * @param val to write + */ public native final void writeString(String val); + /** + * Writes an array of boolean values to the end of the parcel. + * @param val to write + */ private native final void writeBoolVector(boolean[] val); + /** + * Writes an array of byte values to the end of the parcel. + * @param val to write + */ private native final void writeInt8Vector(byte[] val); + /** + * Writes an array of short values to the end of the parcel. + * @param val to write + */ private native final void writeInt16Vector(short[] val); + /** + * Writes an array of int values to the end of the parcel. + * @param val to write + */ private native final void writeInt32Vector(int[] val); + /** + * Writes an array of long values to the end of the parcel. + * @param val to write + */ private native final void writeInt64Vector(long[] val); + /** + * Writes an array of float values to the end of the parcel. + * @param val to write + */ private native final void writeFloatVector(float[] val); + /** + * Writes an array of double values to the end of the parcel. + * @param val to write + */ private native final void writeDoubleVector(double[] val); + /** + * Writes an array of String values to the end of the parcel. + * + * Note, these will be converted to UTF-8 as they are written. + * + * @param val to write + */ private native final void writeStringVector(String[] val); + /** + * Helper method to write a list of Booleans to val. + * @param val list to write + */ public final void writeBoolVector(ArrayList val) { final int n = val.size(); boolean[] array = new boolean[n]; @@ -75,6 +174,10 @@ public class HwParcel { writeBoolVector(array); } + /** + * Helper method to write a list of Booleans to the end of the parcel. + * @param val list to write + */ public final void writeInt8Vector(ArrayList val) { final int n = val.size(); byte[] array = new byte[n]; @@ -85,6 +188,10 @@ public class HwParcel { writeInt8Vector(array); } + /** + * Helper method to write a list of Shorts to the end of the parcel. + * @param val list to write + */ public final void writeInt16Vector(ArrayList val) { final int n = val.size(); short[] array = new short[n]; @@ -95,6 +202,10 @@ public class HwParcel { writeInt16Vector(array); } + /** + * Helper method to write a list of Integers to the end of the parcel. + * @param val list to write + */ public final void writeInt32Vector(ArrayList val) { final int n = val.size(); int[] array = new int[n]; @@ -105,6 +216,10 @@ public class HwParcel { writeInt32Vector(array); } + /** + * Helper method to write a list of Longs to the end of the parcel. + * @param val list to write + */ public final void writeInt64Vector(ArrayList val) { final int n = val.size(); long[] array = new long[n]; @@ -115,6 +230,10 @@ public class HwParcel { writeInt64Vector(array); } + /** + * Helper method to write a list of Floats to the end of the parcel. + * @param val list to write + */ public final void writeFloatVector(ArrayList val) { final int n = val.size(); float[] array = new float[n]; @@ -125,6 +244,10 @@ public class HwParcel { writeFloatVector(array); } + /** + * Helper method to write a list of Doubles to the end of the parcel. + * @param val list to write + */ public final void writeDoubleVector(ArrayList val) { final int n = val.size(); double[] array = new double[n]; @@ -135,93 +258,272 @@ public class HwParcel { writeDoubleVector(array); } + /** + * Helper method to write a list of Strings to the end of the parcel. + * @param val list to write + */ public final void writeStringVector(ArrayList val) { writeStringVector(val.toArray(new String[val.size()])); } + /** + * Write a hwbinder object to the end of the parcel. + * @param binder value to write + */ public native final void writeStrongBinder(IHwBinder binder); + /** + * Checks to make sure that the interface name matches the name written by the parcel + * sender by writeInterfaceToken + * + * @throws SecurityException interface doesn't match + */ public native final void enforceInterface(String interfaceName); + + /** + * Reads a boolean value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final boolean readBool(); + /** + * Reads a byte value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final byte readInt8(); + /** + * Reads a short value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final short readInt16(); + /** + * Reads a int value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final int readInt32(); + /** + * Reads a long value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final long readInt64(); + /** + * Reads a float value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final float readFloat(); + /** + * Reads a double value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final double readDouble(); + /** + * Reads a String value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final String readString(); + /** + * Reads an array of boolean values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final boolean[] readBoolVectorAsArray(); + /** + * Reads an array of byte values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final byte[] readInt8VectorAsArray(); + /** + * Reads an array of short values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final short[] readInt16VectorAsArray(); + /** + * Reads an array of int values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final int[] readInt32VectorAsArray(); + /** + * Reads an array of long values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final long[] readInt64VectorAsArray(); + /** + * Reads an array of float values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final float[] readFloatVectorAsArray(); + /** + * Reads an array of double values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final double[] readDoubleVectorAsArray(); + /** + * Reads an array of String values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final String[] readStringVectorAsArray(); + /** + * Convenience method to read a Boolean vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readBoolVector() { Boolean[] array = HwBlob.wrapArray(readBoolVectorAsArray()); return new ArrayList(Arrays.asList(array)); } + /** + * Convenience method to read a Byte vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readInt8Vector() { Byte[] array = HwBlob.wrapArray(readInt8VectorAsArray()); return new ArrayList(Arrays.asList(array)); } + /** + * Convenience method to read a Short vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readInt16Vector() { Short[] array = HwBlob.wrapArray(readInt16VectorAsArray()); return new ArrayList(Arrays.asList(array)); } + /** + * Convenience method to read a Integer vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readInt32Vector() { Integer[] array = HwBlob.wrapArray(readInt32VectorAsArray()); return new ArrayList(Arrays.asList(array)); } + /** + * Convenience method to read a Long vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readInt64Vector() { Long[] array = HwBlob.wrapArray(readInt64VectorAsArray()); return new ArrayList(Arrays.asList(array)); } + /** + * Convenience method to read a Float vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readFloatVector() { Float[] array = HwBlob.wrapArray(readFloatVectorAsArray()); return new ArrayList(Arrays.asList(array)); } + /** + * Convenience method to read a Double vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readDoubleVector() { Double[] array = HwBlob.wrapArray(readDoubleVectorAsArray()); return new ArrayList(Arrays.asList(array)); } + /** + * Convenience method to read a String vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList readStringVector() { return new ArrayList(Arrays.asList(readStringVectorAsArray())); } + /** + * Reads a strong binder value from the parcel. + * @return binder object read from parcel or null if no binder can be read + * @throws IllegalArgumentException if the parcel has no more data + */ public native final IHwBinder readStrongBinder(); - // Handle is stored as part of the blob. + /** + * Read opaque segment of data as a blob. + * @return blob of size expectedSize + * @throws IllegalArgumentException if the parcel has no more data + */ public native final HwBlob readBuffer(long expectedSize); + /** + * Read a buffer written using scatter gather. + * + * @param expectedSize size that buffer should be + * @param parentHandle handle from which to read the embedded buffer + * @param offset offset into parent + * @param nullable whether or not to allow for a null return + * @return blob of data with size expectedSize + * @throws NoSuchElementException if an embedded buffer is not available to read + * @throws IllegalArgumentException if expectedSize < 0 + * @throws NullPointerException if the transaction specified the blob to be null + * but nullable is false + */ public native final HwBlob readEmbeddedBuffer( long expectedSize, long parentHandle, long offset, boolean nullable); + /** + * Write a buffer into the transaction. + * @param blob blob to write into the parcel. + */ public native final void writeBuffer(HwBlob blob); - + /** + * Write a status value into the blob. + * @param status value to write + */ public native final void writeStatus(int status); + /** + * @throws IllegalArgumentException if a success vaue cannot be read + * @throws RemoteException if success value indicates a transaction error + */ public native final void verifySuccess(); + /** + * Should be called to reduce memory pressure when this object no longer needs + * to be written to. + */ public native final void releaseTemporaryStorage(); + /** + * Should be called when object is no longer needed to reduce possible memory + * pressure if the Java GC does not get to this object in time. + */ public native final void release(); + /** + * Sends the parcel to the specified destination. + */ public native final void send(); // Returns address of the "freeFunction". diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java index 619f4dc631d5b262e362b59fff863c8594d9308b..0c592e1f04b8a654e759f8ade86c5e7e8eff98a2 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -16,26 +16,57 @@ package android.os; +import android.annotation.SystemApi; + /** @hide */ +@SystemApi public interface IHwBinder { // These MUST match their corresponding libhwbinder/IBinder.h definition !!! + /** @hide */ public static final int FIRST_CALL_TRANSACTION = 1; + /** @hide */ public static final int FLAG_ONEWAY = 1; + /** + * Process a hwbinder transaction. + * + * @hide + */ + @SystemApi public void transact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; + /** + * Return as IHwInterface instance only if this implements descriptor. + * @param descriptor for example foo.bar@1.0::IBaz + * @hide + */ + @SystemApi public IHwInterface queryLocalInterface(String descriptor); /** * Interface for receiving a callback when the process hosting a service * has gone away. */ + @SystemApi public interface DeathRecipient { + /** + * Callback for a registered process dying. + */ + @SystemApi public void serviceDied(long cookie); } + /** + * Notifies the death recipient with the cookie when the process containing + * this binder dies. + */ + @SystemApi public boolean linkToDeath(DeathRecipient recipient, long cookie); + /** + * Unregisters the death recipient from this binder. + */ + @SystemApi public boolean unlinkToDeath(DeathRecipient recipient); } diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java index 7c5ac6f44a492b7051c368d18dc07a66ed67b9fb..a2f59a9abb81464c696ebaa6dc76b9b54c8a1794 100644 --- a/core/java/android/os/IHwInterface.java +++ b/core/java/android/os/IHwInterface.java @@ -16,7 +16,13 @@ package android.os; +import android.annotation.SystemApi; /** @hide */ +@SystemApi public interface IHwInterface { + /** + * Returns the binder object that corresponds to an interface. + */ + @SystemApi public IHwBinder asBinder(); } diff --git a/core/java/android/os/IPermissionController.aidl b/core/java/android/os/IPermissionController.aidl index 5e8590af11f1bbd0a33a4d2d0da337f82831bbb2..3de953a2dfbe7d7489922ca62b65d21c657fbee0 100644 --- a/core/java/android/os/IPermissionController.aidl +++ b/core/java/android/os/IPermissionController.aidl @@ -22,4 +22,5 @@ interface IPermissionController { boolean checkPermission(String permission, int pid, int uid); String[] getPackagesForUid(int uid); boolean isRuntimePermission(String permission); + int getPackageUid(String packageName, int flags); } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 75f7c1f58ab94816a9ba0a19857a597f9db4a83a..1681f11fa526374af847a10c560c1e718e816ae6 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -63,11 +63,6 @@ interface IPowerManager // --- deprecated --- boolean isScreenBrightnessBoosted(); - // temporarily overrides the screen brightness settings to allow the user to - // see the effect of a settings change without applying it immediately - void setTemporaryScreenBrightnessSettingOverride(int brightness); - void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj); - // sets the attention light (used by phone app only) void setAttentionLight(boolean on, int color); } diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl index 1d2a4085045454c87e63fb45de992fcded595d00..8a27700edc15793a02b23a0b4c356053bbf0aefd 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -16,6 +16,7 @@ package android.os; +import android.os.StatsDimensionsValue; import android.os.StatsLogEventWrapper; /** @@ -55,8 +56,17 @@ interface IStatsCompanionService { StatsLogEventWrapper[] pullData(int pullCode); /** Send a broadcast to the specified pkg and class that it should getData now. */ + // TODO: Rename this and use a pending intent instead. oneway void sendBroadcast(String pkg, String cls); + /** + * Requests StatsCompanionService to send a broadcast using the given intentSender + * (which should cast to an IIntentSender), along with the other information specified. + */ + oneway void sendSubscriberBroadcast(in IBinder intentSender, long configUid, long configId, + long subscriptionId, long subscriptionRuleId, + in StatsDimensionsValue dimensionsValue); + /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); } diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 29812e8ab06ebe9a3cfbfa84b89f4f9c1e293167..679b49dfb974ca33d71fcecc38f9c4ff3cfb5301 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -81,7 +81,7 @@ interface IStatsManager { /** * Sets a configuration with the specified config key and subscribes to updates for this * configuration key. Broadcasts will be sent if this configuration needs to be collected. - * The configuration must be a wire-encoded StatsDConfig. The caller specifies the name of the + * The configuration must be a wire-encoded StatsdConfig. The caller specifies the name of the * package and class that should receive these broadcasts. * * Returns if this configuration was correctly registered. @@ -95,4 +95,33 @@ interface IStatsManager { * Returns if this configuration key was removed. */ boolean removeConfiguration(in long configKey); + + /** + * Set the IIntentSender (i.e. PendingIntent) to be used when broadcasting subscriber + * information to the given subscriberId within the given config. + * + * Suppose that the calling uid has added a config with key configKey, and that in this config + * it is specified that when a particular anomaly is detected, a broadcast should be sent to + * a BroadcastSubscriber with id subscriberId. This function links the given intentSender with + * that subscriberId (for that config), so that this intentSender is used to send the broadcast + * when the anomaly is detected. + * + * This function can only be called by the owner (uid) of the config. It must be called each + * time statsd starts. Later calls overwrite previous calls; only one intentSender is stored. + * + * intentSender must be convertible into an IntentSender using IntentSender(IBinder) + * and cannot be null. + * + * Returns true if successful. + */ + boolean setBroadcastSubscriber(long configKey, long subscriberId, in IBinder intentSender); + + /** + * Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair. + * Any broadcasts associated with subscriberId will henceforth not be sent. + * No-op if this (configKey, subsriberId) pair was not associated with an IntentSender. + * + * Returns true if successful. + */ + boolean unsetBroadcastSubscriber(long configKey, long subscriberId); } diff --git a/core/java/android/os/ISystemUpdateManager.aidl b/core/java/android/os/ISystemUpdateManager.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f7f50791f528995fcd0d04a040653046e2fb2316 --- /dev/null +++ b/core/java/android/os/ISystemUpdateManager.aidl @@ -0,0 +1,27 @@ +/* //device/java/android/android/os/ISystemUpdateInfo.aidl +** +** 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.os; + +import android.os.Bundle; +import android.os.PersistableBundle; + +/** @hide */ +interface ISystemUpdateManager { + Bundle retrieveSystemUpdateInfo(); + void updateSystemUpdateInfo(in PersistableBundle data); +} diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 3798a5ec15b5c952e6a691857fb7517e4da0f26e..811cc5ed472c856514c3301649bc17b91fa2592f 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -23,6 +23,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.util.Log; +import android.util.proto.ProtoOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -109,7 +110,7 @@ public final class PowerManager { /* NOTE: Wake lock levels were previously defined as a bit field, except that only a few * combinations were actually supported so the bit field was removed. This explains * why the numbering scheme is so odd. If adding a new wake lock level, any unused - * value can be used. + * value (in frameworks/base/core/proto/android/os/enums.proto) can be used. */ /** @@ -120,7 +121,7 @@ public final class PowerManager { * but the CPU will be kept on until all partial wake locks have been released. *

    */ - public static final int PARTIAL_WAKE_LOCK = 0x00000001; + public static final int PARTIAL_WAKE_LOCK = OsProtoEnums.PARTIAL_WAKE_LOCK; // 0x00000001 /** * Wake lock level: Ensures that the screen is on (but may be dimmed); @@ -137,7 +138,7 @@ public final class PowerManager { * as the user moves between applications and doesn't require a special permission. */ @Deprecated - public static final int SCREEN_DIM_WAKE_LOCK = 0x00000006; + public static final int SCREEN_DIM_WAKE_LOCK = OsProtoEnums.SCREEN_DIM_WAKE_LOCK; // 0x00000006 /** * Wake lock level: Ensures that the screen is on at full brightness; @@ -154,7 +155,8 @@ public final class PowerManager { * as the user moves between applications and doesn't require a special permission. */ @Deprecated - public static final int SCREEN_BRIGHT_WAKE_LOCK = 0x0000000a; + public static final int SCREEN_BRIGHT_WAKE_LOCK = + OsProtoEnums.SCREEN_BRIGHT_WAKE_LOCK; // 0x0000000a /** * Wake lock level: Ensures that the screen and keyboard backlight are on at @@ -171,7 +173,7 @@ public final class PowerManager { * as the user moves between applications and doesn't require a special permission. */ @Deprecated - public static final int FULL_WAKE_LOCK = 0x0000001a; + public static final int FULL_WAKE_LOCK = OsProtoEnums.FULL_WAKE_LOCK; // 0x0000001a /** * Wake lock level: Turns the screen off when the proximity sensor activates. @@ -192,7 +194,8 @@ public final class PowerManager { * Cannot be used with {@link #ACQUIRE_CAUSES_WAKEUP}. *

    */ - public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 0x00000020; + public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = + OsProtoEnums.PROXIMITY_SCREEN_OFF_WAKE_LOCK; // 0x00000020 /** * Wake lock level: Put the screen in a low power state and allow the CPU to suspend @@ -206,7 +209,7 @@ public final class PowerManager { * * {@hide} */ - public static final int DOZE_WAKE_LOCK = 0x00000040; + public static final int DOZE_WAKE_LOCK = OsProtoEnums.DOZE_WAKE_LOCK; // 0x00000040 /** * Wake lock level: Keep the device awake enough to allow drawing to occur. @@ -220,7 +223,7 @@ public final class PowerManager { * * {@hide} */ - public static final int DRAW_WAKE_LOCK = 0x00000080; + public static final int DRAW_WAKE_LOCK = OsProtoEnums.DRAW_WAKE_LOCK; // 0x00000080 /** * Mask for the wake lock level component of a combined wake lock level and flags integer. @@ -1000,24 +1003,6 @@ public final class PowerManager { return false; } - /** - * Sets the brightness of the backlights (screen, keyboard, button). - *

    - * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. - *

    - * - * @param brightness The brightness value from 0 to 255. - * - * @hide Requires signature permission. - */ - public void setBacklightBrightness(int brightness) { - try { - mService.setTemporaryScreenBrightnessSettingOverride(brightness); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Returns true if the specified wake lock level is supported. * @@ -1652,6 +1637,21 @@ public final class PowerManager { } } + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + synchronized (mToken) { + final long token = proto.start(fieldId); + proto.write(PowerManagerProto.WakeLockProto.HEX_STRING, + Integer.toHexString(System.identityHashCode(this))); + proto.write(PowerManagerProto.WakeLockProto.HELD, mHeld); + proto.write(PowerManagerProto.WakeLockProto.INTERNAL_COUNT, mInternalCount); + if (mWorkSource != null) { + mWorkSource.writeToProto(proto, PowerManagerProto.WakeLockProto.WORK_SOURCE); + } + proto.end(token); + } + } + /** * Wraps a Runnable such that this method immediately acquires the wake lock and then * once the Runnable is done the wake lock is released. diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 3ef0961f7633211ee45b7ad8ecd1d4e4f6c7c611..c7d89b0c6e4f641e2a6c24da48a9319c7fb03256 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -70,6 +70,24 @@ public abstract class PowerManagerInternal { } } + /** + * Converts platform constants to proto enums. + */ + public static int wakefulnessToProtoEnum(int wakefulness) { + switch (wakefulness) { + case WAKEFULNESS_ASLEEP: + return PowerManagerInternalProto.WAKEFULNESS_ASLEEP; + case WAKEFULNESS_AWAKE: + return PowerManagerInternalProto.WAKEFULNESS_AWAKE; + case WAKEFULNESS_DREAMING: + return PowerManagerInternalProto.WAKEFULNESS_DREAMING; + case WAKEFULNESS_DOZING: + return PowerManagerInternalProto.WAKEFULNESS_DOZING; + default: + return wakefulness; + } + } + /** * Returns true if the wakefulness state represents an interactive state * as defined by {@link android.os.PowerManager#isInteractive}. diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 0874d93e8262a3d6922e215735daf1732515db89..96e2ae3d5ac102845e5f867d6569332fc15006b5 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -143,7 +143,7 @@ public class Process { * Defines the UID/GID for the WebView zygote process. * @hide */ - public static final int WEBVIEW_ZYGOTE_UID = 1051; + public static final int WEBVIEW_ZYGOTE_UID = 1053; /** * Defines the UID used for resource tracking for OTA updates. @@ -151,6 +151,18 @@ public class Process { */ public static final int OTA_UPDATE_UID = 1061; + /** + * Defines the UID used for incidentd. + * @hide + */ + public static final int INCIDENTD_UID = 1067; + + /** + * Defines the UID/GID for the Secure Element service process. + * @hide + */ + public static final int SE_UID = 1068; + /** {@hide} */ public static final int NOBODY_UID = 9999; @@ -268,6 +280,15 @@ public class Process { */ public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; + /** + * Standard priority of video threads. Applications can not normally + * change to this priority. + * Use with {@link #setThreadPriority(int)} and + * {@link #setThreadPriority(int, int)}, not with the normal + * {@link java.lang.Thread} class. + */ + public static final int THREAD_PRIORITY_VIDEO = -10; + /** * Standard priority of audio threads. Applications can not normally * change to this priority. @@ -558,6 +579,14 @@ public class Process { return UserHandle.of(UserHandle.getUserId(myUid())); } + /** + * Returns whether the given uid belongs to a system core component or not. + * @hide + */ + public static boolean isCoreUid(int uid) { + return UserHandle.isCore(uid); + } + /** * Returns whether the given uid belongs to an application. * @param uid A kernel uid. diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 673a8ba661b67073307b67b2fa0606b85fd14426..3e8e8854634d1190dcd660b56d990ca2b94d675c 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -61,6 +61,7 @@ import java.util.HashSet; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; @@ -101,6 +102,9 @@ public class RecoverySystem { private static final String ACTION_EUICC_FACTORY_RESET = "com.android.internal.action.EUICC_FACTORY_RESET"; + /** used in {@link #wipeEuiccData} as package name of callback intent */ + private static final String PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK = "android"; + /** * The recovery image uses this file to identify the location (i.e. blocks) * of an OTA package on the /data partition. The block map file is @@ -751,7 +755,9 @@ public class RecoverySystem { // Block until the ordered broadcast has completed. condition.block(); - wipeEuiccData(context, wipeEuicc); + if (wipeEuicc) { + wipeEuiccData(context, PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK); + } String shutdownArg = null; if (shutdown) { @@ -767,19 +773,27 @@ public class RecoverySystem { bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg); } - private static void wipeEuiccData(Context context, final boolean isWipeEuicc) { + /** + * Returns whether wipe Euicc data successfully or not. + * + * @param packageName the package name of the caller app. + * + * @hide + */ + public static boolean wipeEuiccData(Context context, final String packageName) { ContentResolver cr = context.getContentResolver(); if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) { // If the eUICC isn't provisioned, there's no reason to either wipe or retain profiles, // as there's nothing to wipe nor retain. Log.d(TAG, "Skipping eUICC wipe/retain as it is not provisioned"); - return; + return true; } EuiccManager euiccManager = (EuiccManager) context.getSystemService( Context.EUICC_SERVICE); if (euiccManager != null && euiccManager.isEnabled()) { CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1); + final AtomicBoolean wipingSucceeded = new AtomicBoolean(false); BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() { @Override @@ -788,19 +802,11 @@ public class RecoverySystem { if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { int detailedCode = intent.getIntExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0); - if (isWipeEuicc) { - Log.e(TAG, "Error wiping euicc data, Detailed code = " - + detailedCode); - } else { - Log.e(TAG, "Error retaining euicc data, Detailed code = " - + detailedCode); - } + Log.e(TAG, "Error wiping euicc data, Detailed code = " + + detailedCode); } else { - if (isWipeEuicc) { - Log.d(TAG, "Successfully wiped euicc data."); - } else { - Log.d(TAG, "Successfully retained euicc data."); - } + Log.d(TAG, "Successfully wiped euicc data."); + wipingSucceeded.set(true /* newValue */); } euiccFactoryResetLatch.countDown(); } @@ -808,7 +814,7 @@ public class RecoverySystem { }; Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET); - intent.setPackage("android"); + intent.setPackage(packageName); PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM); IntentFilter filterConsent = new IntentFilter(); @@ -818,11 +824,7 @@ public class RecoverySystem { Handler euiccHandler = new Handler(euiccHandlerThread.getLooper()); context.getApplicationContext() .registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler); - if (isWipeEuicc) { - euiccManager.eraseSubscriptions(callbackIntent); - } else { - euiccManager.retainSubscriptionsForFactoryReset(callbackIntent); - } + euiccManager.eraseSubscriptions(callbackIntent); try { long waitingTimeMillis = Settings.Global.getLong( context.getContentResolver(), @@ -834,22 +836,19 @@ public class RecoverySystem { waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS; } if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) { - if (isWipeEuicc) { - Log.e(TAG, "Timeout wiping eUICC data."); - } else { - Log.e(TAG, "Timeout retaining eUICC data."); - } + Log.e(TAG, "Timeout wiping eUICC data."); + return false; } - context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - if (isWipeEuicc) { - Log.e(TAG, "Wiping eUICC data interrupted", e); - } else { - Log.e(TAG, "Retaining eUICC data interrupted", e); - } + Log.e(TAG, "Wiping eUICC data interrupted", e); + return false; + } finally { + context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver); } + return wipingSucceeded.get(); } + return false; } /** {@hide} */ diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/core/java/android/os/StatsDimensionsValue.aidl new file mode 100644 index 0000000000000000000000000000000000000000..81a14a4b67d981eb5232b926ababa241836174bd --- /dev/null +++ b/core/java/android/os/StatsDimensionsValue.aidl @@ -0,0 +1,20 @@ +/** + * 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.os; + +/** @hide */ +parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h"; \ No newline at end of file diff --git a/core/java/android/os/StatsDimensionsValue.java b/core/java/android/os/StatsDimensionsValue.java new file mode 100644 index 0000000000000000000000000000000000000000..257cc5250dadb4f3329f75c2ce93605523c948ed --- /dev/null +++ b/core/java/android/os/StatsDimensionsValue.java @@ -0,0 +1,353 @@ +/* + * 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.os; + +import android.annotation.SystemApi; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Container for statsd dimension value information, corresponding to a + * stats_log.proto's DimensionValue. + * + * This consists of a field (an int representing a statsd atom field) + * and a value (which may be one of a number of types). + * + *

    + * Only a single value is held, and it is necessarily one of the following types: + * {@link String}, int, long, boolean, float, + * or tuple (i.e. {@link List} of {@code StatsDimensionsValue}). + * + * The type of value held can be retrieved using {@link #getValueType()}, which returns one of the + * following ints, depending on the type of value: + *

      + *
    • {@link #STRING_VALUE_TYPE}
    • + *
    • {@link #INT_VALUE_TYPE}
    • + *
    • {@link #LONG_VALUE_TYPE}
    • + *
    • {@link #BOOLEAN_VALUE_TYPE}
    • + *
    • {@link #FLOAT_VALUE_TYPE}
    • + *
    • {@link #TUPLE_VALUE_TYPE}
    • + *
    + * Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants + * as a parameter. + * The value itself can be retrieved using the correct get...Value() function for its type. + * + *

    + * The field is always an int, and always exists; it can be obtained using {@link #getField()}. + * + * + * @hide + */ +@SystemApi +public final class StatsDimensionsValue implements Parcelable { + private static final String TAG = "StatsDimensionsValue"; + + // Values of the value type correspond to stats_log.proto's DimensionValue fields. + // Keep constants in sync with services/include/android/os/StatsDimensionsValue.h. + /** Indicates that this holds a String. */ + public static final int STRING_VALUE_TYPE = 2; + /** Indicates that this holds an int. */ + public static final int INT_VALUE_TYPE = 3; + /** Indicates that this holds a long. */ + public static final int LONG_VALUE_TYPE = 4; + /** Indicates that this holds a boolean. */ + public static final int BOOLEAN_VALUE_TYPE = 5; + /** Indicates that this holds a float. */ + public static final int FLOAT_VALUE_TYPE = 6; + /** Indicates that this holds a List of StatsDimensionsValues. */ + public static final int TUPLE_VALUE_TYPE = 7; + + /** Value of a stats_log.proto DimensionsValue.field. */ + private final int mField; + + /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */ + private final int mValueType; + + /** + * Value of a stats_log.proto DimensionsValue.value. + * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[]. + */ + private final Object mValue; // immutable or array of immutables + + /** + * Creates a {@code StatsDimensionValue} from a parcel. + * + * @hide + */ + public StatsDimensionsValue(Parcel in) { + mField = in.readInt(); + mValueType = in.readInt(); + mValue = readValueFromParcel(mValueType, in); + } + + /** + * Return the field, i.e. the tag of a statsd atom. + * + * @return the field + */ + public int getField() { + return mField; + } + + /** + * Retrieve the String held, if any. + * + * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE}, + * null otherwise + */ + public String getStringValue() { + try { + if (mValueType == STRING_VALUE_TYPE) return (String) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return null; + } + + /** + * Retrieve the int held, if any. + * + * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise + */ + public int getIntValue() { + try { + if (mValueType == INT_VALUE_TYPE) return (Integer) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the long held, if any. + * + * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise + */ + public long getLongValue() { + try { + if (mValueType == LONG_VALUE_TYPE) return (Long) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the boolean held, if any. + * + * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE}, + * false otherwise + */ + public boolean getBooleanValue() { + try { + if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return false; + } + + /** + * Retrieve the float held, if any. + * + * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise + */ + public float getFloatValue() { + try { + if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held, + * if any. + * + * @return the {@link List} of {@link StatsDimensionsValue} held + * if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE}, + * null otherwise + */ + public List getTupleValueList() { + if (mValueType != TUPLE_VALUE_TYPE) { + return null; + } + try { + StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue; + List copy = new ArrayList<>(orig.length); + // Shallow copy since StatsDimensionsValue is immutable anyway + for (int i = 0; i < orig.length; i++) { + copy.add(orig[i]); + } + return copy; + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + return null; + } + } + + /** + * Returns the constant representing the type of value stored, namely one of + *

      + *
    • {@link #STRING_VALUE_TYPE}
    • + *
    • {@link #INT_VALUE_TYPE}
    • + *
    • {@link #LONG_VALUE_TYPE}
    • + *
    • {@link #BOOLEAN_VALUE_TYPE}
    • + *
    • {@link #FLOAT_VALUE_TYPE}
    • + *
    • {@link #TUPLE_VALUE_TYPE}
    • + *
    + * + * @return the constant representing the type of value stored + */ + public int getValueType() { + return mValueType; + } + + /** + * Returns whether the type of value stored is equal to the given type. + * + * @param valueType int representing the type of value stored, as used in {@link #getValueType} + * @return true if {@link #getValueType()} is equal to {@code valueType}. + */ + public boolean isValueType(int valueType) { + return mValueType == valueType; + } + + /** + * Returns a String representing the information in this StatsDimensionValue. + * No guarantees are made about the format of this String. + * + * @return String representation + * + * @hide + */ + // Follows the format of statsd's dimension.h toString. + public String toString() { + try { + StringBuilder sb = new StringBuilder(); + sb.append(mField); + sb.append(":"); + if (mValueType == TUPLE_VALUE_TYPE) { + sb.append("{"); + StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue; + for (int i = 0; i < sbvs.length; i++) { + sb.append(sbvs[i].toString()); + sb.append("|"); + } + sb.append("}"); + } else { + sb.append(mValue.toString()); + } + return sb.toString(); + } catch (ClassCastException e) { + Slog.w(TAG, "Failed to successfully get value", e); + } + return ""; + } + + /** + * Parcelable Creator for StatsDimensionsValue. + */ + public static final Parcelable.Creator CREATOR = new + Parcelable.Creator() { + public StatsDimensionsValue createFromParcel(Parcel in) { + return new StatsDimensionsValue(in); + } + + public StatsDimensionsValue[] newArray(int size) { + return new StatsDimensionsValue[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mField); + out.writeInt(mValueType); + writeValueToParcel(mValueType, mValue, out, flags); + } + + /** Writes mValue to a parcel. Returns true if succeeds. */ + private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) { + try { + switch (valueType) { + case STRING_VALUE_TYPE: + out.writeString((String) value); + return true; + case INT_VALUE_TYPE: + out.writeInt((Integer) value); + return true; + case LONG_VALUE_TYPE: + out.writeLong((Long) value); + return true; + case BOOLEAN_VALUE_TYPE: + out.writeBoolean((Boolean) value); + return true; + case FLOAT_VALUE_TYPE: + out.writeFloat((Float) value); + return true; + case TUPLE_VALUE_TYPE: { + StatsDimensionsValue[] values = (StatsDimensionsValue[]) value; + out.writeInt(values.length); + for (int i = 0; i < values.length; i++) { + values[i].writeToParcel(out, flags); + } + return true; + } + default: + Slog.w(TAG, "readValue of an impossible type " + valueType); + return false; + } + } catch (ClassCastException e) { + Slog.w(TAG, "writeValue cast failed", e); + return false; + } + } + + /** Reads mValue from a parcel. */ + private static Object readValueFromParcel(int valueType, Parcel parcel) { + switch (valueType) { + case STRING_VALUE_TYPE: + return parcel.readString(); + case INT_VALUE_TYPE: + return parcel.readInt(); + case LONG_VALUE_TYPE: + return parcel.readLong(); + case BOOLEAN_VALUE_TYPE: + return parcel.readBoolean(); + case FLOAT_VALUE_TYPE: + return parcel.readFloat(); + case TUPLE_VALUE_TYPE: { + final int sz = parcel.readInt(); + StatsDimensionsValue[] values = new StatsDimensionsValue[sz]; + for (int i = 0; i < sz; i++) { + values[i] = new StatsDimensionsValue(parcel); + } + return values; + } + default: + Slog.w(TAG, "readValue of an impossible type " + valueType); + return null; + } + } +} diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 4f6d322ba8710ca62b6266786f99530ae986525b..a9b86752ee5454ca0cb404e2141f2924fef51038 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.util.Log; import android.util.MutableInt; @@ -33,6 +34,7 @@ import java.util.HashMap; * * {@hide} */ +@SystemApi public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; @@ -40,9 +42,11 @@ public class SystemProperties { /** * Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5 * uses reflection to read this whenever text is selected (http://b/36095274). + * @hide */ public static final int PROP_NAME_MAX = Integer.MAX_VALUE; + /** @hide */ public static final int PROP_VALUE_MAX = 91; @GuardedBy("sChangeCallbacks") @@ -86,8 +90,10 @@ public class SystemProperties { * * @param key the key to lookup * @return an empty string if the {@code key} isn't found + * @hide */ @NonNull + @SystemApi public static String get(@NonNull String key) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key); @@ -100,8 +106,10 @@ public class SystemProperties { * @param def the default value in case the property is not set or empty * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty * string otherwise + * @hide */ @NonNull + @SystemApi public static String get(@NonNull String key, @Nullable String def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key, def); @@ -114,7 +122,9 @@ public class SystemProperties { * @param def a default value to return * @return the key parsed as an integer, or def if the key isn't found or * cannot be parsed + * @hide */ + @SystemApi public static int getInt(@NonNull String key, int def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_int(key, def); @@ -127,7 +137,9 @@ public class SystemProperties { * @param def a default value to return * @return the key parsed as a long, or def if the key isn't found or * cannot be parsed + * @hide */ + @SystemApi public static long getLong(@NonNull String key, long def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_long(key, def); @@ -145,7 +157,9 @@ public class SystemProperties { * @param def a default value to return * @return the key parsed as a boolean, or def if the key isn't found or is * not able to be parsed as a boolean. + * @hide */ + @SystemApi public static boolean getBoolean(@NonNull String key, boolean def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_boolean(key, def); @@ -155,6 +169,7 @@ public class SystemProperties { * Set the value for the given {@code key} to {@code val}. * * @throws IllegalArgumentException if the {@code val} exceeds 91 characters + * @hide */ public static void set(@NonNull String key, @Nullable String val) { if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) { @@ -170,6 +185,7 @@ public class SystemProperties { * * @param callback The {@link Runnable} that should be executed when a system property * changes. + * @hide */ public static void addChangeCallback(@NonNull Runnable callback) { synchronized (sChangeCallbacks) { @@ -194,10 +210,14 @@ public class SystemProperties { } } - /* + /** * Notifies listeners that a system property has changed + * @hide */ public static void reportSyspropChanged() { native_report_sysprop_change(); } + + private SystemProperties() { + } } diff --git a/core/java/android/os/SystemUpdateManager.java b/core/java/android/os/SystemUpdateManager.java new file mode 100644 index 0000000000000000000000000000000000000000..ce3e225975f05a52f8e98d85a8f1c531d6aa4631 --- /dev/null +++ b/core/java/android/os/SystemUpdateManager.java @@ -0,0 +1,152 @@ +/* + * 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.os; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; + +/** + * Allows querying and posting system update information. + * + * {@hide} + */ +@SystemApi +@SystemService(Context.SYSTEM_UPDATE_SERVICE) +public class SystemUpdateManager { + private static final String TAG = "SystemUpdateManager"; + + /** The status key of the system update info, expecting an int value. */ + @SystemApi + public static final String KEY_STATUS = "status"; + + /** The title of the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TITLE = "title"; + + /** Whether it is a security update, expecting a boolean value. */ + @SystemApi + public static final String KEY_IS_SECURITY_UPDATE = "is_security_update"; + + /** The build fingerprint after installing the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint"; + + /** The security patch level after installing the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level"; + + /** + * The KEY_STATUS value that indicates there's no update status info available. + */ + @SystemApi + public static final int STATUS_UNKNOWN = 0; + + /** + * The KEY_STATUS value that indicates there's no pending update. + */ + @SystemApi + public static final int STATUS_IDLE = 1; + + /** + * The KEY_STATUS value that indicates an update is available for download, but pending user + * approval to start. + */ + @SystemApi + public static final int STATUS_WAITING_DOWNLOAD = 2; + + /** + * The KEY_STATUS value that indicates an update is in progress (i.e. downloading or installing + * has started). + */ + @SystemApi + public static final int STATUS_IN_PROGRESS = 3; + + /** + * The KEY_STATUS value that indicates an update is available for install. + */ + @SystemApi + public static final int STATUS_WAITING_INSTALL = 4; + + /** + * The KEY_STATUS value that indicates an update will be installed after a reboot. This applies + * to both of A/B and non-A/B OTAs. + */ + @SystemApi + public static final int STATUS_WAITING_REBOOT = 5; + + private final ISystemUpdateManager mService; + + /** @hide */ + public SystemUpdateManager(ISystemUpdateManager service) { + mService = checkNotNull(service, "missing ISystemUpdateManager"); + } + + /** + * Queries the current pending system update info. + * + *

    Requires the {@link android.Manifest.permission#READ_SYSTEM_UPDATE_INFO} or + * {@link android.Manifest.permission#RECOVERY} permission. + * + * @return A {@code Bundle} that contains the pending system update information in key-value + * pairs. + * + * @throws SecurityException if the caller is not allowed to read the info. + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_SYSTEM_UPDATE_INFO, + android.Manifest.permission.RECOVERY, + }) + public Bundle retrieveSystemUpdateInfo() { + try { + return mService.retrieveSystemUpdateInfo(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Allows a system updater to publish the pending update info. + * + *

    The reported info will not persist across reboots. Because only the reporting updater + * understands the criteria to determine a successful/failed update. + * + *

    Requires the {@link android.Manifest.permission#RECOVERY} permission. + * + * @param infoBundle The {@code PersistableBundle} that contains the system update information, + * such as the current update status. {@link #KEY_STATUS} is required in the bundle. + * + * @throws IllegalArgumentException if @link #KEY_STATUS} does not exist. + * @throws SecurityException if the caller is not allowed to update the info. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RECOVERY) + public void updateSystemUpdateInfo(PersistableBundle infoBundle) { + if (infoBundle == null || !infoBundle.containsKey(KEY_STATUS)) { + throw new IllegalArgumentException("Missing status in the bundle"); + } + try { + mService.updateSystemUpdateInfo(infoBundle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 6381b56a65039999bb8e6f35a9547681eeb87cbb..5be72bc5c54bb3adcdc6ce87a922480af61bfdf9 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -126,7 +126,10 @@ public final class UserHandle implements Parcelable { return getAppId(uid1) == getAppId(uid2); } - /** @hide */ + /** + * Whether a UID is an "isolated" UID. + * @hide + */ public static boolean isIsolated(int uid) { if (uid > 0) { final int appId = getAppId(uid); @@ -136,7 +139,11 @@ public final class UserHandle implements Parcelable { } } - /** @hide */ + /** + * Whether a UID belongs to a regular app. *Note* "Not a regular app" does not mean + * "it's system", because of isolated UIDs. Use {@link #isCore} for that. + * @hide + */ public static boolean isApp(int uid) { if (uid > 0) { final int appId = getAppId(uid); @@ -146,6 +153,19 @@ public final class UserHandle implements Parcelable { } } + /** + * Whether a UID belongs to a system core component or not. + * @hide + */ + public static boolean isCore(int uid) { + if (uid > 0) { + final int appId = getAppId(uid); + return appId < Process.FIRST_APPLICATION_UID; + } else { + return false; + } + } + /** * Returns the user for a given uid. * @param uid A uid for an application running in a particular user. diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 60818d14d1ab66b0caee52325f4fa8c8286fd9f1..b2b43cfd6ddc3b23072780589d41899c9a0545fc 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -223,6 +223,34 @@ public class UserManager { */ public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness"; + /** + * Specifies if ambient display is disallowed for the user. + * + *

    The default value is false. + * + *

    This user restriction has no effect on managed profiles. + *

    Key for user restrictions. + *

    Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display"; + + /** + * Specifies if a user is disallowed from changing screen off timeout. + * + *

    The default value is false. + * + *

    This user restriction has no effect on managed profiles. + *

    Key for user restrictions. + *

    Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout"; + /** * Specifies if a user is disallowed from enabling the * "Unknown Sources" setting, that allows installation of apps from unknown sources. @@ -763,6 +791,7 @@ public class UserManager { * @see #getUserRestrictions() * @hide */ + @SystemApi public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background"; /** @@ -2230,12 +2259,6 @@ public class UserManager { } } - /** @removed */ - @Deprecated - public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) { - return requestQuietModeEnabled(enableQuietMode, userHandle); - } - /** * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a * managed profile don't run, generate notifications, or consume data or battery. diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index da0ed54e003e074565a51d37017182a6e34d13e5..b6f16a7b9ff89873b582caf6d7814f9d61ec7ffd 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -16,7 +16,9 @@ package android.os; +import android.hardware.vibrator.V1_0.Constants.EffectStrength; import android.hardware.vibrator.V1_1.Constants.Effect_1_1; +import android.util.MathUtils; import java.util.Arrays; @@ -35,6 +37,12 @@ public abstract class VibrationEffect implements Parcelable { */ public static final int DEFAULT_AMPLITUDE = -1; + /** + * The maximum amplitude value + * @hide + */ + public static final int MAX_AMPLITUDE = 255; + /** * A click effect. * @@ -198,38 +206,75 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ public abstract void validate(); + /** + * Gets the estimated duration of the vibration in milliseconds. + * + * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this + * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where + * the length is device and potentially run-time dependent), this returns -1. + * + * @hide + */ + public abstract long getDuration(); + + /** + * Scale the amplitude with the given constraints. + * + * This assumes that the previous value was in the range [0, MAX_AMPLITUDE] + * @hide + */ + protected static int scale(int amplitude, float gamma, int maxAmplitude) { + float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma); + return (int) (val * maxAmplitude); + } + /** @hide */ public static class OneShot extends VibrationEffect implements Parcelable { - private long mTiming; - private int mAmplitude; + private final long mDuration; + private final int mAmplitude; public OneShot(Parcel in) { - this(in.readLong(), in.readInt()); + mDuration = in.readLong(); + mAmplitude = in.readInt(); } public OneShot(long milliseconds, int amplitude) { - mTiming = milliseconds; + mDuration = milliseconds; mAmplitude = amplitude; } - public long getTiming() { - return mTiming; + @Override + public long getDuration() { + return mDuration; } public int getAmplitude() { return mAmplitude; } + /** + * Scale the amplitude of this effect. + * + * @param gamma the gamma adjustment to apply + * @param maxAmplitude the new maximum amplitude of the effect + * + * @return A {@link OneShot} effect with the same timing but scaled amplitude. + */ + public VibrationEffect scale(float gamma, int maxAmplitude) { + int newAmplitude = scale(mAmplitude, gamma, maxAmplitude); + return new OneShot(mDuration, newAmplitude); + } + @Override public void validate() { if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) { throw new IllegalArgumentException( - "amplitude must either be DEFAULT_AMPLITUDE, " + - "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")"); + "amplitude must either be DEFAULT_AMPLITUDE, " + + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")"); } - if (mTiming <= 0) { + if (mDuration <= 0) { throw new IllegalArgumentException( - "timing must be positive (timing=" + mTiming + ")"); + "duration must be positive (duration=" + mDuration + ")"); } } @@ -239,26 +284,26 @@ public abstract class VibrationEffect implements Parcelable { return false; } VibrationEffect.OneShot other = (VibrationEffect.OneShot) o; - return other.mTiming == mTiming && other.mAmplitude == mAmplitude; + return other.mDuration == mDuration && other.mAmplitude == mAmplitude; } @Override public int hashCode() { int result = 17; - result = 37 * (int) mTiming; - result = 37 * mAmplitude; + result += 37 * (int) mDuration; + result += 37 * mAmplitude; return result; } @Override public String toString() { - return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}"; + return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}"; } @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_ONE_SHOT); - out.writeLong(mTiming); + out.writeLong(mDuration); out.writeInt(mAmplitude); } @@ -279,9 +324,9 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ public static class Waveform extends VibrationEffect implements Parcelable { - private long[] mTimings; - private int[] mAmplitudes; - private int mRepeat; + private final long[] mTimings; + private final int[] mAmplitudes; + private final int mRepeat; public Waveform(Parcel in) { this(in.createLongArray(), in.createIntArray(), in.readInt()); @@ -307,35 +352,69 @@ public abstract class VibrationEffect implements Parcelable { return mRepeat; } + @Override + public long getDuration() { + if (mRepeat >= 0) { + return Long.MAX_VALUE; + } + long duration = 0; + for (long d : mTimings) { + duration += d; + } + return duration; + } + + /** + * Scale the Waveform with the given gamma and new max amplitude. + * + * @param gamma the gamma adjustment to apply + * @param maxAmplitude the new maximum amplitude of the effect + * + * @return A {@link Waveform} effect with the same timings and repeat index + * but scaled amplitude. + */ + public VibrationEffect scale(float gamma, int maxAmplitude) { + if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) { + // Just return a copy of the original if there's no scaling to be done. + return new Waveform(mTimings, mAmplitudes, mRepeat); + } + + int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); + for (int i = 0; i < scaledAmplitudes.length; i++) { + scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude); + } + return new Waveform(mTimings, scaledAmplitudes, mRepeat); + } + @Override public void validate() { if (mTimings.length != mAmplitudes.length) { throw new IllegalArgumentException( - "timing and amplitude arrays must be of equal length" + - " (timings.length=" + mTimings.length + - ", amplitudes.length=" + mAmplitudes.length + ")"); + "timing and amplitude arrays must be of equal length" + + " (timings.length=" + mTimings.length + + ", amplitudes.length=" + mAmplitudes.length + ")"); } if (!hasNonZeroEntry(mTimings)) { - throw new IllegalArgumentException("at least one timing must be non-zero" + - " (timings=" + Arrays.toString(mTimings) + ")"); + throw new IllegalArgumentException("at least one timing must be non-zero" + + " (timings=" + Arrays.toString(mTimings) + ")"); } for (long timing : mTimings) { if (timing < 0) { - throw new IllegalArgumentException("timings must all be >= 0" + - " (timings=" + Arrays.toString(mTimings) + ")"); + throw new IllegalArgumentException("timings must all be >= 0" + + " (timings=" + Arrays.toString(mTimings) + ")"); } } for (int amplitude : mAmplitudes) { if (amplitude < -1 || amplitude > 255) { throw new IllegalArgumentException( - "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" + - " (amplitudes=" + Arrays.toString(mAmplitudes) + ")"); + "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" + + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")"); } } if (mRepeat < -1 || mRepeat >= mTimings.length) { throw new IllegalArgumentException( - "repeat index must be within the bounds of the timings array" + - " (timings.length=" + mTimings.length + ", index=" + mRepeat +")"); + "repeat index must be within the bounds of the timings array" + + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")"); } } @@ -345,26 +424,26 @@ public abstract class VibrationEffect implements Parcelable { return false; } VibrationEffect.Waveform other = (VibrationEffect.Waveform) o; - return Arrays.equals(mTimings, other.mTimings) && - Arrays.equals(mAmplitudes, other.mAmplitudes) && - mRepeat == other.mRepeat; + return Arrays.equals(mTimings, other.mTimings) + && Arrays.equals(mAmplitudes, other.mAmplitudes) + && mRepeat == other.mRepeat; } @Override public int hashCode() { int result = 17; - result = 37 * Arrays.hashCode(mTimings); - result = 37 * Arrays.hashCode(mAmplitudes); - result = 37 * mRepeat; + result += 37 * Arrays.hashCode(mTimings); + result += 37 * Arrays.hashCode(mAmplitudes); + result += 37 * mRepeat; return result; } @Override public String toString() { - return "Waveform{mTimings=" + Arrays.toString(mTimings) + - ", mAmplitudes=" + Arrays.toString(mAmplitudes) + - ", mRepeat=" + mRepeat + - "}"; + return "Waveform{mTimings=" + Arrays.toString(mTimings) + + ", mAmplitudes=" + Arrays.toString(mAmplitudes) + + ", mRepeat=" + mRepeat + + "}"; } @Override @@ -402,16 +481,20 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ public static class Prebaked extends VibrationEffect implements Parcelable { - private int mEffectId; - private boolean mFallback; + private final int mEffectId; + private final boolean mFallback; + + private int mEffectStrength; public Prebaked(Parcel in) { this(in.readInt(), in.readByte() != 0); + mEffectStrength = in.readInt(); } public Prebaked(int effectId, boolean fallback) { mEffectId = effectId; mFallback = fallback; + mEffectStrength = EffectStrength.MEDIUM; } public int getId() { @@ -426,6 +509,39 @@ public abstract class VibrationEffect implements Parcelable { return mFallback; } + @Override + public long getDuration() { + return -1; + } + + /** + * Set the effect strength of the prebaked effect. + */ + public void setEffectStrength(int strength) { + if (!isValidEffectStrength(strength)) { + throw new IllegalArgumentException("Invalid effect strength: " + strength); + } + mEffectStrength = strength; + } + + /** + * Set the effect strength. + */ + public int getEffectStrength() { + return mEffectStrength; + } + + private static boolean isValidEffectStrength(int strength) { + switch (strength) { + case EffectStrength.LIGHT: + case EffectStrength.MEDIUM: + case EffectStrength.STRONG: + return true; + default: + return false; + } + } + @Override public void validate() { switch (mEffectId) { @@ -437,6 +553,10 @@ public abstract class VibrationEffect implements Parcelable { throw new IllegalArgumentException( "Unknown prebaked effect type (value=" + mEffectId + ")"); } + if (!isValidEffectStrength(mEffectStrength)) { + throw new IllegalArgumentException( + "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); + } } @Override @@ -445,17 +565,25 @@ public abstract class VibrationEffect implements Parcelable { return false; } VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; - return mEffectId == other.mEffectId && mFallback == other.mFallback; + return mEffectId == other.mEffectId + && mFallback == other.mFallback + && mEffectStrength == other.mEffectStrength; } @Override public int hashCode() { - return mEffectId; + int result = 17; + result += 37 * mEffectId; + result += 37 * mEffectStrength; + return result; } @Override public String toString() { - return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}"; + return "Prebaked{mEffectId=" + mEffectId + + ", mEffectStrength=" + mEffectStrength + + ", mFallback=" + mFallback + + "}"; } @@ -464,6 +592,7 @@ public abstract class VibrationEffect implements Parcelable { out.writeInt(PARCEL_TOKEN_EFFECT); out.writeInt(mEffectId); out.writeByte((byte) (mFallback ? 1 : 0)); + out.writeInt(mEffectStrength); } public static final Parcelable.Creator CREATOR = diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 8078fb87eb27deb7ec95995232d3abac61554eb0..f1f6f414eba88fb979c00b965d29d4a547abb3b2 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.app.ActivityThread; @@ -23,6 +24,9 @@ import android.content.Context; import android.media.AudioAttributes; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Class that operates the vibrator on the device. *

    @@ -33,6 +37,40 @@ import android.util.Log; public abstract class Vibrator { private static final String TAG = "Vibrator"; + /** + * Vibration intensity: no vibrations. + * @hide + */ + public static final int VIBRATION_INTENSITY_OFF = 0; + + /** + * Vibration intensity: low. + * @hide + */ + public static final int VIBRATION_INTENSITY_LOW = 1; + + /** + * Vibration intensity: medium. + * @hide + */ + public static final int VIBRATION_INTENSITY_MEDIUM = 2; + + /** + * Vibration intensity: high. + * @hide + */ + public static final int VIBRATION_INTENSITY_HIGH = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "VIBRATION_INTENSITY_" }, value = { + VIBRATION_INTENSITY_OFF, + VIBRATION_INTENSITY_LOW, + VIBRATION_INTENSITY_MEDIUM, + VIBRATION_INTENSITY_HIGH + }) + public @interface VibrationIntensity{} + private final String mPackageName; /** @@ -49,6 +87,22 @@ public abstract class Vibrator { mPackageName = context.getOpPackageName(); } + /** + * Get the default vibration intensity for haptic feedback. + * @hide + */ + public int getDefaultHapticFeedbackIntensity() { + return VIBRATION_INTENSITY_MEDIUM; + } + + /** + * Get the default vibration intensity for notifications and ringtones. + * @hide + */ + public int getDefaultNotificationVibrationIntensity() { + return VIBRATION_INTENSITY_HIGH; + } + /** * Check whether the hardware has a vibrator. * diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index d0c2870bfefb143b19d52a89383563cbeff22be5..17d83db685c8099fbd8b7e4dddbb32d6432e965a 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -1,10 +1,16 @@ package android.os; import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; import android.os.WorkSourceProto; +import android.provider.Settings; +import android.provider.Settings.Global; import android.util.Log; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.Arrays; @@ -109,6 +115,17 @@ public class WorkSource implements Parcelable { } } + /** + * Whether system services should create {@code WorkChains} (wherever possible) in the place + * of flat UID lists. + * + * @hide + */ + public static boolean isChainedBatteryAttributionEnabled(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1; + } + /** @hide */ public int size() { return mNum; @@ -450,6 +467,7 @@ public class WorkSource implements Parcelable { * * @hide */ + @SystemApi public WorkChain createWorkChain() { if (mChains == null) { mChains = new ArrayList<>(4); @@ -479,6 +497,29 @@ public class WorkSource implements Parcelable { return mChains; } + /** + * DO NOT USE: Hacky API provided solely for {@code GnssLocationProvider}. See + * {@code setReturningDiffs} as well. + * + * @hide + */ + public void transferWorkChains(WorkSource other) { + if (mChains != null) { + mChains.clear(); + } + + if (other.mChains == null || other.mChains.isEmpty()) { + return; + } + + if (mChains == null) { + mChains = new ArrayList<>(4); + } + + mChains.addAll(other.mChains); + other.mChains.clear(); + } + private boolean removeUids(WorkSource other) { int N1 = mNum; final int[] uids1 = mUids; @@ -817,7 +858,8 @@ public class WorkSource implements Parcelable { * * @hide */ - public static class WorkChain implements Parcelable { + @SystemApi + public static final class WorkChain implements Parcelable { private int mSize; private int[] mUids; private String[] mTags; @@ -829,7 +871,8 @@ public class WorkSource implements Parcelable { mTags = new String[4]; } - // @VisibleForTesting + /** @hide */ + @VisibleForTesting public WorkChain(WorkChain other) { mSize = other.mSize; mUids = other.mUids.clone(); @@ -866,19 +909,32 @@ public class WorkSource implements Parcelable { return mUids[0]; } + /** + * Return the tag associated with the attribution UID. See (@link #getAttributionUid}. + */ + public String getAttributionTag() { + return mTags[0]; + } + // TODO: The following three trivial getters are purely for testing and will be removed // once we have higher level logic in place, e.g for serializing this WorkChain to a proto, // diffing it etc. - // - // @VisibleForTesting + + + /** @hide */ + @VisibleForTesting public int[] getUids() { return mUids; } - // @VisibleForTesting + + /** @hide */ + @VisibleForTesting public String[] getTags() { return mTags; } - // @VisibleForTesting + + /** @hide */ + @VisibleForTesting public int getSize() { return mSize; } diff --git a/core/java/android/os/connectivity/GpsBatteryStats.aidl b/core/java/android/os/connectivity/GpsBatteryStats.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7b96d1a8e06234bb0803e97b754f2a30aaa743f3 --- /dev/null +++ b/core/java/android/os/connectivity/GpsBatteryStats.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.connectivity; + +/** {@hide} */ +parcelable GpsBatteryStats; \ No newline at end of file diff --git a/core/java/android/os/connectivity/GpsBatteryStats.java b/core/java/android/os/connectivity/GpsBatteryStats.java new file mode 100644 index 0000000000000000000000000000000000000000..f2ac5ef6d40b479fd3d783a2347ba49257e05414 --- /dev/null +++ b/core/java/android/os/connectivity/GpsBatteryStats.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os.connectivity; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.location.gnssmetrics.GnssMetrics; + +import java.util.Arrays; + +/** + * API for GPS power stats + * + * @hide + */ +public final class GpsBatteryStats implements Parcelable { + + private long mLoggingDurationMs; + private long mEnergyConsumedMaMs; + private long[] mTimeInGpsSignalQualityLevel; + + public static final Parcelable.Creator CREATOR = new + Parcelable.Creator() { + public GpsBatteryStats createFromParcel(Parcel in) { + return new GpsBatteryStats(in); + } + + public GpsBatteryStats[] newArray(int size) { + return new GpsBatteryStats[size]; + } + }; + + public GpsBatteryStats() { + initialize(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mLoggingDurationMs); + out.writeLong(mEnergyConsumedMaMs); + out.writeLongArray(mTimeInGpsSignalQualityLevel); + } + + public void readFromParcel(Parcel in) { + mLoggingDurationMs = in.readLong(); + mEnergyConsumedMaMs = in.readLong(); + in.readLongArray(mTimeInGpsSignalQualityLevel); + } + + public long getLoggingDurationMs() { + return mLoggingDurationMs; + } + + public long getEnergyConsumedMaMs() { + return mEnergyConsumedMaMs; + } + + public long[] getTimeInGpsSignalQualityLevel() { + return mTimeInGpsSignalQualityLevel; + } + + public void setLoggingDurationMs(long t) { + mLoggingDurationMs = t; + return; + } + + public void setEnergyConsumedMaMs(long e) { + mEnergyConsumedMaMs = e; + return; + } + + public void setTimeInGpsSignalQualityLevel(long[] t) { + mTimeInGpsSignalQualityLevel = Arrays.copyOfRange(t, 0, + Math.min(t.length, GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS)); + return; + } + + @Override + public int describeContents() { + return 0; + } + + private GpsBatteryStats(Parcel in) { + initialize(); + readFromParcel(in); + } + + private void initialize() { + mLoggingDurationMs = 0; + mEnergyConsumedMaMs = 0; + mTimeInGpsSignalQualityLevel = new long[GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS]; + return; + } +} \ No newline at end of file diff --git a/core/java/android/os/connectivity/WifiBatteryStats.aidl b/core/java/android/os/connectivity/WifiBatteryStats.aidl new file mode 100644 index 0000000000000000000000000000000000000000..12ac73828eed61e63bd2bfa051a26029d92f1c5a --- /dev/null +++ b/core/java/android/os/connectivity/WifiBatteryStats.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.connectivity; + +/** {@hide} */ +parcelable WifiBatteryStats; \ No newline at end of file diff --git a/core/java/android/os/connectivity/WifiBatteryStats.java b/core/java/android/os/connectivity/WifiBatteryStats.java new file mode 100644 index 0000000000000000000000000000000000000000..e5341eeeb17be4c85ade81007afffb5b9417ef7c --- /dev/null +++ b/core/java/android/os/connectivity/WifiBatteryStats.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os.connectivity; + +import android.os.BatteryStats; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * API for Wifi power stats + * + * @hide + */ +public final class WifiBatteryStats implements Parcelable { + + private long mLoggingDurationMs; + private long mKernelActiveTimeMs; + private long mNumPacketsTx; + private long mNumBytesTx; + private long mNumPacketsRx; + private long mNumBytesRx; + private long mSleepTimeMs; + private long mScanTimeMs; + private long mIdleTimeMs; + private long mRxTimeMs; + private long mTxTimeMs; + private long mEnergyConsumedMaMs; + private long mNumAppScanRequest; + private long[] mTimeInStateMs; + private long[] mTimeInSupplicantStateMs; + private long[] mTimeInRxSignalStrengthLevelMs; + + public static final Parcelable.Creator CREATOR = new + Parcelable.Creator() { + public WifiBatteryStats createFromParcel(Parcel in) { + return new WifiBatteryStats(in); + } + + public WifiBatteryStats[] newArray(int size) { + return new WifiBatteryStats[size]; + } + }; + + public WifiBatteryStats() { + initialize(); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mLoggingDurationMs); + out.writeLong(mKernelActiveTimeMs); + out.writeLong(mNumPacketsTx); + out.writeLong(mNumBytesTx); + out.writeLong(mNumPacketsRx); + out.writeLong(mNumBytesRx); + out.writeLong(mSleepTimeMs); + out.writeLong(mScanTimeMs); + out.writeLong(mIdleTimeMs); + out.writeLong(mRxTimeMs); + out.writeLong(mTxTimeMs); + out.writeLong(mEnergyConsumedMaMs); + out.writeLong(mNumAppScanRequest); + out.writeLongArray(mTimeInStateMs); + out.writeLongArray(mTimeInRxSignalStrengthLevelMs); + out.writeLongArray(mTimeInSupplicantStateMs); + } + + public void readFromParcel(Parcel in) { + mLoggingDurationMs = in.readLong(); + mKernelActiveTimeMs = in.readLong(); + mNumPacketsTx = in.readLong(); + mNumBytesTx = in.readLong(); + mNumPacketsRx = in.readLong(); + mNumBytesRx = in.readLong(); + mSleepTimeMs = in.readLong(); + mScanTimeMs = in.readLong(); + mIdleTimeMs = in.readLong(); + mRxTimeMs = in.readLong(); + mTxTimeMs = in.readLong(); + mEnergyConsumedMaMs = in.readLong(); + mNumAppScanRequest = in.readLong(); + in.readLongArray(mTimeInStateMs); + in.readLongArray(mTimeInRxSignalStrengthLevelMs); + in.readLongArray(mTimeInSupplicantStateMs); + } + + public long getLoggingDurationMs() { + return mLoggingDurationMs; + } + + public long getKernelActiveTimeMs() { + return mKernelActiveTimeMs; + } + + public long getNumPacketsTx() { + return mNumPacketsTx; + } + + public long getNumBytesTx() { + return mNumBytesTx; + } + + public long getNumPacketsRx() { + return mNumPacketsRx; + } + + public long getNumBytesRx() { + return mNumBytesRx; + } + + public long getSleepTimeMs() { + return mSleepTimeMs; + } + + public long getScanTimeMs() { + return mScanTimeMs; + } + + public long getIdleTimeMs() { + return mIdleTimeMs; + } + + public long getRxTimeMs() { + return mRxTimeMs; + } + + public long getTxTimeMs() { + return mTxTimeMs; + } + + public long getEnergyConsumedMaMs() { + return mEnergyConsumedMaMs; + } + + public long getNumAppScanRequest() { + return mNumAppScanRequest; + } + + public long[] getTimeInStateMs() { + return mTimeInStateMs; + } + + public long[] getTimeInRxSignalStrengthLevelMs() { + return mTimeInRxSignalStrengthLevelMs; + } + + public long[] getTimeInSupplicantStateMs() { + return mTimeInSupplicantStateMs; + } + + public void setLoggingDurationMs(long t) { + mLoggingDurationMs = t; + return; + } + + public void setKernelActiveTimeMs(long t) { + mKernelActiveTimeMs = t; + return; + } + + public void setNumPacketsTx(long n) { + mNumPacketsTx = n; + return; + } + + public void setNumBytesTx(long b) { + mNumBytesTx = b; + return; + } + + public void setNumPacketsRx(long n) { + mNumPacketsRx = n; + return; + } + + public void setNumBytesRx(long b) { + mNumBytesRx = b; + return; + } + + public void setSleepTimeMs(long t) { + mSleepTimeMs = t; + return; + } + + public void setScanTimeMs(long t) { + mScanTimeMs = t; + return; + } + + public void setIdleTimeMs(long t) { + mIdleTimeMs = t; + return; + } + + public void setRxTimeMs(long t) { + mRxTimeMs = t; + return; + } + + public void setTxTimeMs(long t) { + mTxTimeMs = t; + return; + } + + public void setEnergyConsumedMaMs(long e) { + mEnergyConsumedMaMs = e; + return; + } + + public void setNumAppScanRequest(long n) { + mNumAppScanRequest = n; + return; + } + + public void setTimeInStateMs(long[] t) { + mTimeInStateMs = Arrays.copyOfRange(t, 0, + Math.min(t.length, BatteryStats.NUM_WIFI_STATES)); + return; + } + + public void setTimeInRxSignalStrengthLevelMs(long[] t) { + mTimeInRxSignalStrengthLevelMs = Arrays.copyOfRange(t, 0, + Math.min(t.length, BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS)); + return; + } + + public void setTimeInSupplicantStateMs(long[] t) { + mTimeInSupplicantStateMs = Arrays.copyOfRange( + t, 0, Math.min(t.length, BatteryStats.NUM_WIFI_SUPPL_STATES)); + return; + } + + public int describeContents() { + return 0; + } + + private WifiBatteryStats(Parcel in) { + initialize(); + readFromParcel(in); + } + + private void initialize() { + mLoggingDurationMs = 0; + mKernelActiveTimeMs = 0; + mNumPacketsTx = 0; + mNumBytesTx = 0; + mNumPacketsRx = 0; + mNumBytesRx = 0; + mSleepTimeMs = 0; + mScanTimeMs = 0; + mIdleTimeMs = 0; + mRxTimeMs = 0; + mTxTimeMs = 0; + mEnergyConsumedMaMs = 0; + mNumAppScanRequest = 0; + mTimeInStateMs = new long[BatteryStats.NUM_WIFI_STATES]; + Arrays.fill(mTimeInStateMs, 0); + mTimeInRxSignalStrengthLevelMs = new long[BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS]; + Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0); + mTimeInSupplicantStateMs = new long[BatteryStats.NUM_WIFI_SUPPL_STATES]; + Arrays.fill(mTimeInSupplicantStateMs, 0); + return; + } +} \ No newline at end of file diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 070b8c1b0008e35716267fc20eb776733951696a..839a8bf42b1092aa877789e85021da429e245ca4 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -394,4 +394,32 @@ public final class StorageVolume implements Parcelable { parcel.writeString(mFsUuid); parcel.writeString(mState); } + + /** {@hide} */ + public static final class ScopedAccessProviderContract { + + private ScopedAccessProviderContract() { + throw new UnsupportedOperationException("contains constants only"); + } + + public static final String AUTHORITY = "com.android.documentsui.scopedAccess"; + + public static final String TABLE_PACKAGES = "packages"; + public static final String TABLE_PERMISSIONS = "permissions"; + + public static final String COL_PACKAGE = "package_name"; + public static final String COL_VOLUME_UUID = "volume_uuid"; + public static final String COL_DIRECTORY = "directory"; + public static final String COL_GRANTED = "granted"; + + public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE }; + public static final String[] TABLE_PERMISSIONS_COLUMNS = + new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED }; + + public static final int TABLE_PACKAGES_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1; + public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2; + public static final int TABLE_PERMISSIONS_COL_GRANTED = 3; + } } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 3d2e1d1f1d24ab1d5613f978cfac208ad4fc1e11..d77428193749178219e575dbbc6eef3024678f23 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -34,6 +34,7 @@ import android.preference.VolumePreference.VolumeStore; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.System; +import android.service.notification.ZenModeConfig; import android.util.Log; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; @@ -87,10 +88,22 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private static final int MSG_INIT_SAMPLE = 3; private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; + private NotificationManager.Policy mNotificationPolicy; + private boolean mAllowAlarms; + private boolean mAllowMediaSystem; + private boolean mAllowRinger; + public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) { mContext = context; mAudioManager = context.getSystemService(AudioManager.class); mNotificationManager = context.getSystemService(NotificationManager.class); + mNotificationPolicy = mNotificationManager.getNotificationPolicy(); + mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_ALARMS) != 0; + mAllowMediaSystem = (mNotificationPolicy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0; + mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted( + mNotificationPolicy); mStreamType = streamType; mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType); mNotificationOrRing = isNotificationOrRing(mStreamType); @@ -122,6 +135,14 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION; } + private static boolean isAlarmsStream(int stream) { + return stream == AudioManager.STREAM_ALARM; + } + + private static boolean isMediaOrSystemStream(int stream) { + return stream == AudioManager.STREAM_MUSIC || stream == AudioManager.STREAM_SYSTEM; + } + public void setSeekBar(SeekBar seekBar) { if (mSeekBar != null) { mSeekBar.setOnSeekBarChangeListener(null); @@ -135,7 +156,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private boolean isZenMuted() { return mNotificationOrRing && mZenMode == Global.ZEN_MODE_ALARMS - || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; + || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS + || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + && ((!mAllowAlarms && isAlarmsStream(mStreamType)) + || (!mAllowMediaSystem && isMediaOrSystemStream(mStreamType)) + || (!mAllowRinger && isNotificationOrRing(mStreamType)))); } protected void updateSeekBar() { @@ -396,6 +421,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED); + filter.addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED); filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); mContext.registerReceiver(this, filter); } else { @@ -424,6 +450,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) { mZenMode = mNotificationManager.getZenMode(); updateSlider(); + } else if (NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED.equals(action)) { + mNotificationPolicy = mNotificationManager.getNotificationPolicy(); + mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_ALARMS) != 0; + mAllowMediaSystem = (mNotificationPolicy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0; + mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted( + mNotificationPolicy); + updateSlider(); } } diff --git a/core/java/android/privacy/internal/rappor/RapporEncoder.java b/core/java/android/privacy/internal/rappor/RapporEncoder.java index 2eca4c98d235d1fed8cf8fca300fe1a1b5986cba..9ac2b3e1136ed922de0e52878db87fe3e7c47839 100644 --- a/core/java/android/privacy/internal/rappor/RapporEncoder.java +++ b/core/java/android/privacy/internal/rappor/RapporEncoder.java @@ -33,7 +33,6 @@ import java.util.Random; public class RapporEncoder implements DifferentialPrivacyEncoder { // Hard-coded seed and secret for insecure encoder - private static final long INSECURE_RANDOM_SEED = 0x12345678L; private static final byte[] INSECURE_SECRET = new byte[]{ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93, (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54, @@ -66,8 +65,8 @@ public class RapporEncoder implements DifferentialPrivacyEncoder { // Use SecureRandom as random generator. random = sSecureRandom; } else { - // Hard-coded random generator, to have deterministic result. - random = new Random(INSECURE_RANDOM_SEED); + // To have deterministic result by hard coding encoder id as seed. + random = new Random((long) config.mEncoderId.hashCode()); userSecret = INSECURE_SECRET; } mEncoder = new Encoder(random, null, null, diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java index 21694575631aa1376e2db8c6ed935cf7e3d3f9c5..7ad9e013c61718371c65d2eaf7fd30f482b8dd3e 100644 --- a/core/java/android/provider/AlarmClock.java +++ b/core/java/android/provider/AlarmClock.java @@ -154,9 +154,12 @@ public final class AlarmClock { public static final String ACTION_SET_TIMER = "android.intent.action.SET_TIMER"; /** - * Activity Action: Dismiss timers. + * Activity Action: Dismiss a timer. *

    - * Dismiss all currently expired timers. If there are no expired timers, then this is a no-op. + * The timer to dismiss should be specified using the Intent's data URI, which represents a + * deeplink to the timer. + *

    + * If no data URI is provided, dismiss all expired timers. *

    */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 60df467bc20fc0a0063a3871324605609538fbb0..c6c8d9d69bfbc3beddbec865f213969ab8a42a77 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -223,14 +223,13 @@ public class CallLog { /** Call was WIFI call. */ public static final int FEATURES_WIFI = 1 << 3; - /** Call was on RTT at some point */ - public static final int FEATURES_RTT = 1 << 4; - /** * Indicates the call underwent Assisted Dialing. - * @hide */ - public static final Integer FEATURES_ASSISTED_DIALING_USED = 0x10; + public static final int FEATURES_ASSISTED_DIALING_USED = 1 << 4; + + /** Call was on RTT at some point */ + public static final int FEATURES_RTT = 1 << 5; /** * The phone number as the user entered it. diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index e7fd59e45fd07dfd71201e17225df39cf2ccf6a4..d96316a8e2d661e9c700d9c69a523ddbd1e15841 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -738,7 +738,9 @@ public final class DocumentsContract { private static final String PATH_DOCUMENT = "document"; private static final String PATH_CHILDREN = "children"; private static final String PATH_SEARCH = "search"; - private static final String PATH_TREE = "tree"; + // TODO(b/72055774): make private again once ScopedAccessProvider is refactored + /** {@hide} */ + public static final String PATH_TREE = "tree"; private static final String PARAM_QUERY = "query"; private static final String PARAM_MANAGE = "manage"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 850aedd517a91ef7d301350408d85baf652a4daa..2440b489f41621bbfe4efa9587e7c6040585924a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16,6 +16,16 @@ package android.provider; +import static android.provider.SettingsValidators.ANY_INTEGER_VALIDATOR; +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.LOCALE_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; @@ -31,7 +41,6 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; import android.app.NotificationChannel; -import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.SearchManager; import android.app.WallpaperManager; @@ -65,6 +74,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.UserHandle; +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; @@ -1333,18 +1342,6 @@ public final class Settings { public static final String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS"; - /** - * Activity Action: Show notification settings for a single {@link NotificationChannelGroup}. - *

    - * Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel group to display. - * Input: {@link #EXTRA_CHANNEL_GROUP_ID}, the id of the channel group to display. - *

    - * Output: Nothing. - */ - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = - "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS"; - /** * Activity Extra: The package owner of the notification channel settings to display. *

    @@ -1360,15 +1357,6 @@ public final class Settings { */ public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; - /** - * Activity Extra: The {@link NotificationChannelGroup#getId()} of the notification channel - * group settings to display. - *

    - * This must be passed as an extra field to the - * {@link #ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS}. - */ - public static final String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID"; - /** * Activity Action: Show notification redaction settings. * @@ -1491,6 +1479,21 @@ public final class Settings { public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE"; + /** + * Activity Action: Show screen for controlling which apps have access on volume directories. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + *

    + * Applications typically use this action to ask the user to revert the "Do not ask again" + * status of directory access requests made by + * {@link android.os.storage.StorageVolume#createAccessIntent(String)}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = + "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS"; + // End of Intent actions for Settings /** @@ -2119,11 +2122,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 +2226,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 outKeySet) { outKeySet.addAll(MOVED_TO_GLOBAL); @@ -2730,64 +2693,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 +2736,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 +2762,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 +2863,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 +2875,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 +2887,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 +2899,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 +2911,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 +2923,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 +2937,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 +2948,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 +3044,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 +3064,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 +3073,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 +3082,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 +3099,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 +3138,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 +3148,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 +3156,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 +3172,44 @@ 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; + + /** + * The intensity of notification vibrations, if configurable. + * + * Not all devices are capable of changing their vibration intensity; on these devices + * there will likely be no difference between the various vibration intensities except for + * intensity 0 (off) and the rest. + * + * Values:
    + * 0 - Vibration is disabled
    + * 1 - Weak vibrations
    + * 2 - Medium vibrations
    + * 3 - Strong vibrations + * @hide + */ + public static final String NOTIFICATION_VIBRATION_INTENSITY = + "notification_vibration_intensity"; + + /** + * The intensity of haptic feedback vibrations, if configurable. + * + * Not all devices are capable of changing their feedback intensity; on these devices + * there will likely be no difference between the various vibration intensities except for + * intensity 0 (off) and the rest. + * + * Values:
    + * 0 - Vibration is disabled
    + * 1 - Weak vibrations
    + * 2 - Medium vibrations
    + * 3 - Strong vibrations + * @hide + */ + public static final String HAPTIC_FEEDBACK_INTENSITY = + "haptic_feedback_intensity"; + + private static final Validator VIBRATION_INTENSITY_VALIDATOR = + new SettingsValidators.InclusiveIntegerRangeValidator(0, 3); /** * Ringer volume. This is used internally, changing this value will not @@ -3316,7 +3288,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 +3308,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 +3324,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 +3372,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 +3397,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 +3420,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 +3442,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 +3464,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 +3505,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 +3514,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 +3525,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 +3558,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 +3597,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 +3611,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 +3629,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 +3644,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 +3653,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 +3664,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 +3674,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 +3688,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 +3698,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 +3707,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 +3717,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 +3727,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 +3738,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 +3749,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 +3763,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 +3773,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 +3783,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 +3792,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 +3801,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 +3868,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 +3879,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 +3890,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 +3899,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 +3912,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 +3926,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 +3935,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 +3967,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 @@ -4060,13 +4032,18 @@ public final class Settings { LOCK_TO_APP_ENABLED, NOTIFICATION_SOUND, ACCELEROMETER_ROTATION, - SHOW_BATTERY_PERCENT + SHOW_BATTERY_PERCENT, + NOTIFICATION_VIBRATION_INTENSITY, + HAPTIC_FEEDBACK_INTENSITY, }; /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. * + * All settings in {@link LEGACY_RESTORE_SETTINGS} array *must* have a non-null validator, + * otherwise they won't be restored. + * * @hide */ public static final String[] LEGACY_RESTORE_SETTINGS = { @@ -4175,11 +4152,15 @@ public final class Settings { /** * These are all public system 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 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, @@ -4194,6 +4175,8 @@ public final class Settings { VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR); VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR); VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR); + VALIDATORS.put(NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); + VALIDATORS.put(HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR); VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR); VALIDATORS.put(ALARM_ALERT, ALARM_ALERT_VALIDATOR); @@ -4201,6 +4184,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 +4196,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 +4322,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 +4401,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 +4432,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 @@ -4449,6 +4443,9 @@ public final class Settings { public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY; + private static final Validator WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT} * instead @@ -4456,6 +4453,9 @@ public final class Settings { @Deprecated public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Global.WIFI_NUM_OPEN_NETWORKS_KEPT; + private static final Validator WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON} instead */ @@ -5218,6 +5218,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 */ @@ -5235,6 +5237,8 @@ public final class Settings { @Deprecated public static final String ALLOW_MOCK_LOCATION = "mock_location"; + private static final Validator ALLOW_MOCK_LOCATION_VALIDATOR = BOOLEAN_VALIDATOR; + /** * On Android 8.0 (API level 26) and higher versions of the platform, * a 64-bit number (expressed as a hexadecimal string), unique to @@ -5280,6 +5284,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 */ @@ -5327,6 +5333,8 @@ public final class Settings { @TestApi public static final String AUTOFILL_SERVICE = "autofill_service"; + private static final Validator AUTOFILL_SERVICE_VALIDATOR = COMPONENT_NAME_VALIDATOR; + /** * Boolean indicating if Autofill supports field classification. * @@ -5413,9 +5421,38 @@ public final class Settings { * List of input methods that are currently enabled. This is a string * containing the IDs of all enabled input methods, each ID separated * by ':'. + * + * Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0" + * where imeId is ComponentName and subtype is int32. */ public static final String ENABLED_INPUT_METHODS = "enabled_input_methods"; + private static final Validator ENABLED_INPUT_METHODS_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null) { + return false; + } + String[] inputMethods = value.split(":"); + boolean valid = true; + for (String inputMethod : inputMethods) { + if (inputMethod.length() == 0) { + return false; + } + String[] subparts = inputMethod.split(";"); + for (String subpart : subparts) { + // allow either a non negative integer or a ComponentName + valid |= (NON_NEGATIVE_INTEGER_VALIDATOR.validate(subpart) + || COMPONENT_NAME_VALIDATOR.validate(subpart)); + } + if (!valid) { + return false; + } + } + return valid; + } + }; + /** * List of system input methods that are currently disabled. This is a string * containing the IDs of all disabled input methods, each ID separated @@ -5431,6 +5468,8 @@ public final class Settings { */ public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; + private static final Validator SHOW_IME_WITH_HARD_KEYBOARD_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Host name and port for global http proxy. Uses ':' seperator for * between host and port. @@ -5503,37 +5542,75 @@ public final class Settings { * Note: do not rely on this value being present in settings.db or on ContentObserver * notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION} * to receive changes in this value. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final String LOCATION_MODE = "location_mode"; + /** - * Stores the previous location mode when {@link #LOCATION_MODE} is set to - * {@link #LOCATION_MODE_OFF} + * The App or module that changes the location mode. * @hide */ - public static final String LOCATION_PREVIOUS_MODE = "location_previous_mode"; - + public static final String LOCATION_CHANGER = "location_changer"; + /** + * The location changer is unknown or unable to detect. + * @hide + */ + public static final int LOCATION_CHANGER_UNKNOWN = 0; + /** + * Location settings in system settings. + * @hide + */ + public static final int LOCATION_CHANGER_SYSTEM_SETTINGS = 1; /** - * Sets all location providers to the previous states before location was turned off. + * The location icon in drop down notification drawer. * @hide */ - public static final int LOCATION_MODE_PREVIOUS = -1; + public static final int LOCATION_CHANGER_QUICK_SETTINGS = 2; + /** * Location access disabled. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_OFF = 0; + /** * Network Location Provider disabled, but GPS and other sensors enabled. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_SENSORS_ONLY = 1; + /** * Reduced power usage, such as limiting the number of GPS updates per hour. Requests * with {@link android.location.Criteria#POWER_HIGH} may be downgraded to * {@link android.location.Criteria#POWER_MEDIUM}. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_BATTERY_SAVING = 2; + /** * Best-effort location computation allowed. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_HIGH_ACCURACY = 3; /** @@ -5707,6 +5784,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 */ @@ -5718,6 +5797,8 @@ public final class Settings { */ public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled"; + private static final Validator ACCESSIBILITY_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Setting specifying if the accessibility shortcut is enabled. * @hide @@ -5725,6 +5806,8 @@ public final class Settings { public static final String ACCESSIBILITY_SHORTCUT_ENABLED = "accessibility_shortcut_enabled"; + private static final Validator ACCESSIBILITY_SHORTCUT_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Setting specifying if the accessibility shortcut is enabled. * @hide @@ -5732,6 +5815,9 @@ public final class Settings { public static final String ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN = "accessibility_shortcut_on_lock_screen"; + private static final Validator ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Setting specifying if the accessibility shortcut dialog has been shown to this user. * @hide @@ -5739,6 +5825,9 @@ public final class Settings { public static final String ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN = "accessibility_shortcut_dialog_shown"; + private static final Validator ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Setting specifying the accessibility service to be toggled via the accessibility * shortcut. Must be its flattened {@link ComponentName}. @@ -5747,6 +5836,9 @@ public final class Settings { public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; + private static final Validator ACCESSIBILITY_SHORTCUT_TARGET_SERVICE_VALIDATOR = + COMPONENT_NAME_VALIDATOR; + /** * Setting specifying the accessibility service or feature to be toggled via the * accessibility button in the navigation bar. This is either a flattened @@ -5757,17 +5849,32 @@ public final class Settings { public static final String ACCESSIBILITY_BUTTON_TARGET_COMPONENT = "accessibility_button_target_component"; + private static final Validator ACCESSIBILITY_BUTTON_TARGET_COMPONENT_VALIDATOR = + new Validator() { + @Override + public boolean validate(String value) { + // technically either ComponentName or class name, but there's proper value + // validation at callsites, so allow any non-null string + return value != null; + } + }; + /** * If touch exploration is enabled. */ public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled"; + private static final Validator TOUCH_EXPLORATION_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * List of the enabled accessibility providers. */ public static final String ENABLED_ACCESSIBILITY_SERVICES = "enabled_accessibility_services"; + private static final Validator ENABLED_ACCESSIBILITY_SERVICES_VALIDATOR = + new SettingsValidators.ComponentNameListValidator(":"); + /** * List of the accessibility services to which the user has granted * permission to put the device into touch exploration mode. @@ -5777,6 +5884,9 @@ public final class Settings { public static final String TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES = "touch_exploration_granted_accessibility_services"; + private static final Validator TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES_VALIDATOR = + new SettingsValidators.ComponentNameListValidator(":"); + /** * Uri of the slice that's presented on the keyguard. * Defaults to a slice with the date and next alarm. @@ -5795,6 +5905,8 @@ public final class Settings { @Deprecated public static final String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password"; + private static final Validator ACCESSIBILITY_SPEAK_PASSWORD_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether to draw text with high contrast while in accessibility mode. * @@ -5803,6 +5915,9 @@ public final class Settings { public static final String ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED = "high_text_contrast_enabled"; + private static final Validator ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Setting that specifies whether the display magnification is enabled via a system-wide * triple tap gesture. Display magnifications allows the user to zoom in the display content @@ -5815,6 +5930,9 @@ public final class Settings { public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled"; + private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Setting that specifies whether the display magnification is enabled via a shortcut * affordance within the system's navigation area. Display magnifications allows the user to @@ -5826,6 +5944,9 @@ public final class Settings { public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED = "accessibility_display_magnification_navbar_enabled"; + private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED_VALIDATOR + = BOOLEAN_VALIDATOR; + /** * Setting that specifies what the display magnification scale is. * Display magnifications allows the user to zoom in the display @@ -5839,6 +5960,9 @@ public final class Settings { public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE = "accessibility_display_magnification_scale"; + private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE_VALIDATOR = + new SettingsValidators.InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE); + /** * Unused mangnification setting * @@ -5891,6 +6015,9 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_ENABLED = "accessibility_captioning_enabled"; + private static final Validator ACCESSIBILITY_CAPTIONING_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Setting that specifies the language for captions as a locale string, * e.g. en_US. @@ -5901,6 +6028,8 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_LOCALE = "accessibility_captioning_locale"; + private static final Validator ACCESSIBILITY_CAPTIONING_LOCALE_VALIDATOR = LOCALE_VALIDATOR; + /** * Integer property that specifies the preset style for captions, one * of: @@ -5915,6 +6044,10 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_PRESET = "accessibility_captioning_preset"; + private static final Validator ACCESSIBILITY_CAPTIONING_PRESET_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[]{"-1", "0", "1", "2", + "3", "4"}); + /** * Integer property that specifes the background color for captions as a * packed 32-bit color. @@ -5925,6 +6058,9 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR = "accessibility_captioning_background_color"; + private static final Validator ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR_VALIDATOR = + ANY_INTEGER_VALIDATOR; + /** * Integer property that specifes the foreground color for captions as a * packed 32-bit color. @@ -5935,6 +6071,9 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR = "accessibility_captioning_foreground_color"; + private static final Validator ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR_VALIDATOR = + ANY_INTEGER_VALIDATOR; + /** * Integer property that specifes the edge type for captions, one of: *

      @@ -5949,6 +6088,9 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_EDGE_TYPE = "accessibility_captioning_edge_type"; + private static final Validator ACCESSIBILITY_CAPTIONING_EDGE_TYPE_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1", "2"}); + /** * Integer property that specifes the edge color for captions as a * packed 32-bit color. @@ -5960,6 +6102,9 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_EDGE_COLOR = "accessibility_captioning_edge_color"; + private static final Validator ACCESSIBILITY_CAPTIONING_EDGE_COLOR_VALIDATOR = + ANY_INTEGER_VALIDATOR; + /** * Integer property that specifes the window color for captions as a * packed 32-bit color. @@ -5970,6 +6115,9 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_WINDOW_COLOR = "accessibility_captioning_window_color"; + private static final Validator ACCESSIBILITY_CAPTIONING_WINDOW_COLOR_VALIDATOR = + ANY_INTEGER_VALIDATOR; + /** * String property that specifies the typeface for captions, one of: *
        @@ -5985,6 +6133,10 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_TYPEFACE = "accessibility_captioning_typeface"; + private static final Validator ACCESSIBILITY_CAPTIONING_TYPEFACE_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[]{"DEFAULT", + "MONOSPACE", "SANS_SERIF", "SERIF"}); + /** * Floating point property that specifies font scaling for captions. * @@ -5993,12 +6145,18 @@ public final class Settings { public static final String ACCESSIBILITY_CAPTIONING_FONT_SCALE = "accessibility_captioning_font_scale"; + private static final Validator ACCESSIBILITY_CAPTIONING_FONT_SCALE_VALIDATOR = + new SettingsValidators.InclusiveFloatRangeValidator(0.5f, 2.0f); + /** * Setting that specifies whether display color inversion is enabled. */ public static final String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled"; + private static final Validator ACCESSIBILITY_DISPLAY_INVERSION_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Setting that specifies whether display color space adjustment is * enabled. @@ -6008,15 +6166,24 @@ public final class Settings { public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED = "accessibility_display_daltonizer_enabled"; + private static final Validator ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Integer property that specifies the type of color space adjustment to - * perform. Valid values are defined in AccessibilityManager. + * perform. Valid values are defined in AccessibilityManager: + * - AccessibilityManager.DALTONIZER_DISABLED = -1 + * - AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY = 0 + * - AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY = 12 * * @hide */ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER = "accessibility_display_daltonizer"; + private static final Validator ACCESSIBILITY_DISPLAY_DALTONIZER_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[] {"-1", "0", "12"}); + /** * Setting that specifies whether automatic click when the mouse pointer stops moving is * enabled. @@ -6026,6 +6193,9 @@ public final class Settings { public static final String ACCESSIBILITY_AUTOCLICK_ENABLED = "accessibility_autoclick_enabled"; + private static final Validator ACCESSIBILITY_AUTOCLICK_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Integer setting specifying amount of time in ms the mouse pointer has to stay still * before performing click when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set. @@ -6036,6 +6206,9 @@ public final class Settings { public static final String ACCESSIBILITY_AUTOCLICK_DELAY = "accessibility_autoclick_delay"; + private static final Validator ACCESSIBILITY_AUTOCLICK_DELAY_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Whether or not larger size icons are used for the pointer of mouse/trackpad for * accessibility. @@ -6045,12 +6218,18 @@ public final class Settings { public static final String ACCESSIBILITY_LARGE_POINTER_ICON = "accessibility_large_pointer_icon"; + private static final Validator ACCESSIBILITY_LARGE_POINTER_ICON_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * The timeout for considering a press to be a long press in milliseconds. * @hide */ public static final String LONG_PRESS_TIMEOUT = "long_press_timeout"; + private static final Validator LONG_PRESS_TIMEOUT_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * The duration in milliseconds between the first tap's up event and the second tap's * down event for an interaction to be considered part of the same multi-press. @@ -6104,16 +6283,22 @@ public final class Settings { */ public static final String TTS_DEFAULT_RATE = "tts_default_rate"; + private static final Validator TTS_DEFAULT_RATE_VALIDATOR = NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Default text-to-speech engine pitch. 100 = 1x */ public static final String TTS_DEFAULT_PITCH = "tts_default_pitch"; + private static final Validator TTS_DEFAULT_PITCH_VALIDATOR = NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Default text-to-speech engine. */ public static final String TTS_DEFAULT_SYNTH = "tts_default_synth"; + private static final Validator TTS_DEFAULT_SYNTH_VALIDATOR = PACKAGE_NAME_VALIDATOR; + /** * Default text-to-speech language. * @@ -6161,11 +6346,33 @@ public final class Settings { */ public static final String TTS_DEFAULT_LOCALE = "tts_default_locale"; + private static final Validator TTS_DEFAULT_LOCALE_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null || value.length() == 0) { + return false; + } + String[] ttsLocales = value.split(","); + boolean valid = true; + for (String ttsLocale : ttsLocales) { + String[] parts = ttsLocale.split(":"); + valid |= ((parts.length == 2) + && (parts[0].length() > 0) + && ANY_STRING_VALIDATOR.validate(parts[0]) + && LOCALE_VALIDATOR.validate(parts[1])); + } + return valid; + } + }; + /** * Space delimited list of plugin packages that are enabled. */ public static final String TTS_ENABLED_PLUGINS = "tts_enabled_plugins"; + private static final Validator TTS_ENABLED_PLUGINS_VALIDATOR = + new SettingsValidators.PackageNameListValidator(" "); + /** * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} * instead. @@ -6174,6 +6381,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. @@ -6182,6 +6392,9 @@ public final class Settings { public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY; + private static final Validator WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT} * instead. @@ -6190,6 +6403,9 @@ public final class Settings { public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Global.WIFI_NUM_OPEN_NETWORKS_KEPT; + private static final Validator WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON} * instead. @@ -6348,6 +6564,9 @@ public final class Settings { public static final String PREFERRED_TTY_MODE = "preferred_tty_mode"; + private static final Validator PREFERRED_TTY_MODE_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1", "2", "3"}); + /** * Whether the enhanced voice privacy mode is enabled. * 0 = normal voice privacy @@ -6356,6 +6575,8 @@ public final class Settings { */ public static final String ENHANCED_VOICE_PRIVACY_ENABLED = "enhanced_voice_privacy_enabled"; + private static final Validator ENHANCED_VOICE_PRIVACY_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether the TTY mode mode is enabled. * 0 = disabled @@ -6364,6 +6585,8 @@ public final class Settings { */ public static final String TTY_MODE_ENABLED = "tty_mode_enabled"; + private static final Validator TTY_MODE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Controls whether settings backup is enabled. * Type: int ( 0 = disabled, 1 = enabled ) @@ -6534,24 +6757,32 @@ public final class Settings { */ public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd"; + private static final Validator MOUNT_PLAY_NOTIFICATION_SND_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true) * @hide */ public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart"; + private static final Validator MOUNT_UMS_AUTOSTART_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true) * @hide */ public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt"; + private static final Validator MOUNT_UMS_PROMPT_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true) * @hide */ public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled"; + private static final Validator MOUNT_UMS_NOTIFY_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * If nonzero, ANRs in invisible background processes bring up a dialog. * Otherwise, the process will be silently killed. @@ -6561,6 +6792,17 @@ public final class Settings { */ public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; + /** + * If nonzero, crashes in foreground processes will bring up a dialog. + * Otherwise, the process will be silently killed. + * @hide + */ + public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = + "show_first_crash_dialog_dev_option"; + + private static final Validator SHOW_FIRST_CRASH_DIALOG_DEV_OPTION_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * The {@link ComponentName} string of the service to be used as the voice recognition * service. @@ -6586,6 +6828,8 @@ public final class Settings { */ public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker"; + private static final Validator SELECTED_SPELL_CHECKER_VALIDATOR = COMPONENT_NAME_VALIDATOR; + /** * The {@link ComponentName} string of the selected subtype of the selected spell checker * service which is one of the services managed by the text service manager. @@ -6595,13 +6839,18 @@ public final class Settings { public static final String SELECTED_SPELL_CHECKER_SUBTYPE = "selected_spell_checker_subtype"; + private static final Validator SELECTED_SPELL_CHECKER_SUBTYPE_VALIDATOR = + COMPONENT_NAME_VALIDATOR; + /** - * The {@link ComponentName} string whether spell checker is enabled or not. + * Whether spell checker is enabled or not. * * @hide */ public static final String SPELL_CHECKER_ENABLED = "spell_checker_enabled"; + private static final Validator SPELL_CHECKER_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * What happens when the user presses the Power button while in-call * and the screen is on.
        @@ -6613,6 +6862,9 @@ public final class Settings { */ public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior"; + private static final Validator INCALL_POWER_BUTTON_BEHAVIOR_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[]{"1", "2"}); + /** * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen". * @hide @@ -6668,12 +6920,16 @@ public final class Settings { */ public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled"; + private static final Validator WAKE_GESTURE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether the device should doze if configured. * @hide */ public static final String DOZE_ENABLED = "doze_enabled"; + private static final Validator DOZE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether doze should be always on. * @hide @@ -6686,6 +6942,8 @@ public final class Settings { */ public static final String DOZE_PULSE_ON_PICK_UP = "doze_pulse_on_pick_up"; + private static final Validator DOZE_PULSE_ON_PICK_UP_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether the device should pulse on long press gesture. * @hide @@ -6698,6 +6956,8 @@ public final class Settings { */ public static final String DOZE_PULSE_ON_DOUBLE_TAP = "doze_pulse_on_double_tap"; + private static final Validator DOZE_PULSE_ON_DOUBLE_TAP_VALIDATOR = BOOLEAN_VALIDATOR; + /** * The current night mode that has been selected by the user. Owned * and controlled by UiModeManagerService. Constants are as per @@ -6712,6 +6972,8 @@ public final class Settings { */ public static final String SCREENSAVER_ENABLED = "screensaver_enabled"; + private static final Validator SCREENSAVER_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * The user's chosen screensaver components. * @@ -6721,6 +6983,9 @@ public final class Settings { */ public static final String SCREENSAVER_COMPONENTS = "screensaver_components"; + private static final Validator SCREENSAVER_COMPONENTS_VALIDATOR = + new SettingsValidators.ComponentNameListValidator(","); + /** * If screensavers are enabled, whether the screensaver should be automatically launched * when the device is inserted into a (desk) dock. @@ -6728,6 +6993,8 @@ public final class Settings { */ public static final String SCREENSAVER_ACTIVATE_ON_DOCK = "screensaver_activate_on_dock"; + private static final Validator SCREENSAVER_ACTIVATE_ON_DOCK_VALIDATOR = BOOLEAN_VALIDATOR; + /** * If screensavers are enabled, whether the screensaver should be automatically launched * when the screen times out when not on battery. @@ -6735,6 +7002,8 @@ public final class Settings { */ public static final String SCREENSAVER_ACTIVATE_ON_SLEEP = "screensaver_activate_on_sleep"; + private static final Validator SCREENSAVER_ACTIVATE_ON_SLEEP_VALIDATOR = BOOLEAN_VALIDATOR; + /** * If screensavers are enabled, the default screensaver component. * @hide @@ -6747,6 +7016,9 @@ public final class Settings { */ public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; + private static final Validator NFC_PAYMENT_DEFAULT_COMPONENT_VALIDATOR = + COMPONENT_NAME_VALIDATOR; + /** * Whether NFC payment is handled by the foreground application or a default. * @hide @@ -6830,7 +7102,7 @@ public final class Settings { * @hide */ public static final int SHOW_ROTATION_SUGGESTIONS_DEFAULT = - SHOW_ROTATION_SUGGESTIONS_DISABLED; + SHOW_ROTATION_SUGGESTIONS_ENABLED; /** * Read only list of the service components that the current user has explicitly allowed to @@ -6844,6 +7116,9 @@ public final class Settings { public static final String ENABLED_NOTIFICATION_ASSISTANT = "enabled_notification_assistant"; + private static final Validator ENABLED_NOTIFICATION_ASSISTANT_VALIDATOR = + new SettingsValidators.ComponentNameListValidator(":"); + /** * Read only list of the service components that the current user has explicitly allowed to * see all of the user's notifications, separated by ':'. @@ -6855,6 +7130,9 @@ public final class Settings { @Deprecated public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners"; + private static final Validator ENABLED_NOTIFICATION_LISTENERS_VALIDATOR = + new SettingsValidators.ComponentNameListValidator(":"); + /** * Read only list of the packages that the current user has explicitly allowed to * manage do not disturb, separated by ':'. @@ -6867,6 +7145,9 @@ public final class Settings { public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages"; + private static final Validator ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES_VALIDATOR = + new SettingsValidators.PackageNameListValidator(":"); + /** * Defines whether managed profile ringtones should be synced from it's parent profile *

        @@ -6880,6 +7161,8 @@ public final class Settings { @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; + private static final Validator SYNC_PARENT_SOUNDS_VALIDATOR = BOOLEAN_VALIDATOR; + /** @hide */ public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations"; @@ -6966,12 +7249,17 @@ public final class Settings { */ public static final String SLEEP_TIMEOUT = "sleep_timeout"; + private static final Validator SLEEP_TIMEOUT_VALIDATOR = + new SettingsValidators.InclusiveIntegerRangeValidator(-1, Integer.MAX_VALUE); + /** * Controls whether double tap to wake is enabled. * @hide */ public static final String DOUBLE_TAP_TO_WAKE = "double_tap_to_wake"; + private static final Validator DOUBLE_TAP_TO_WAKE_VALIDATOR = BOOLEAN_VALIDATOR; + /** * The current assistant component. It could be a voice interaction service, * or an activity that handles ACTION_ASSIST, or empty which means using the default @@ -6988,6 +7276,8 @@ public final class Settings { */ public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled"; + private static final Validator CAMERA_GESTURE_DISABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Whether the camera launch gesture to double tap the power button when the screen is off * should be disabled. @@ -6997,6 +7287,9 @@ public final class Settings { public static final String CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED = "camera_double_tap_power_gesture_disabled"; + private static final Validator CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Whether the camera double twist gesture to flip between front and back mode should be * enabled. @@ -7006,6 +7299,9 @@ public final class Settings { public static final String CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED = "camera_double_twist_to_flip_enabled"; + private static final Validator CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Whether or not the smart camera lift trigger that launches the camera when the user moves * the phone into a position for taking photos should be enabled. @@ -7028,6 +7324,9 @@ public final class Settings { */ public static final String ASSIST_GESTURE_ENABLED = "assist_gesture_enabled"; + private static final Validator ASSIST_GESTURE_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Sensitivity control for the assist gesture. * @@ -7035,6 +7334,9 @@ public final class Settings { */ public static final String ASSIST_GESTURE_SENSITIVITY = "assist_gesture_sensitivity"; + private static final Validator ASSIST_GESTURE_SENSITIVITY_VALIDATOR = + new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 1.0f); + /** * Whether the assist gesture should silence alerts. * @@ -7043,6 +7345,9 @@ public final class Settings { public static final String ASSIST_GESTURE_SILENCE_ALERTS_ENABLED = "assist_gesture_silence_alerts_enabled"; + private static final Validator ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Whether the assist gesture should wake the phone. * @@ -7051,6 +7356,9 @@ public final class Settings { public static final String ASSIST_GESTURE_WAKE_ENABLED = "assist_gesture_wake_enabled"; + private static final Validator ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * Whether Assist Gesture Deferred Setup has been completed * @@ -7058,6 +7366,8 @@ public final class Settings { */ public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete"; + private static final Validator ASSIST_GESTURE_SETUP_COMPLETE_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Control whether Night display is currently activated. * @hide @@ -7070,6 +7380,8 @@ public final class Settings { */ public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode"; + private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Control the color temperature of Night Display, represented in Kelvin. * @hide @@ -7077,6 +7389,9 @@ public final class Settings { public static final String NIGHT_DISPLAY_COLOR_TEMPERATURE = "night_display_color_temperature"; + private static final Validator NIGHT_DISPLAY_COLOR_TEMPERATURE_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Custom time when Night display is scheduled to activate. * Represented as milliseconds from midnight (e.g. 79200000 == 10pm). @@ -7085,6 +7400,9 @@ public final class Settings { public static final String NIGHT_DISPLAY_CUSTOM_START_TIME = "night_display_custom_start_time"; + private static final Validator NIGHT_DISPLAY_CUSTOM_START_TIME_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Custom time when Night display is scheduled to deactivate. * Represented as milliseconds from midnight (e.g. 21600000 == 6am). @@ -7092,6 +7410,9 @@ public final class Settings { */ public static final String NIGHT_DISPLAY_CUSTOM_END_TIME = "night_display_custom_end_time"; + private static final Validator NIGHT_DISPLAY_CUSTOM_END_TIME_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * A String representing the LocalDateTime when Night display was last activated. Use to * decide whether to apply the current activated state after a reboot or user change. In @@ -7109,6 +7430,9 @@ public final class Settings { */ public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners"; + private static final Validator ENABLED_VR_LISTENERS_VALIDATOR = + new SettingsValidators.ComponentNameListValidator(":"); + /** * Behavior of the display while in VR mode. * @@ -7118,6 +7442,9 @@ public final class Settings { */ public static final String VR_DISPLAY_MODE = "vr_display_mode"; + private static final Validator VR_DISPLAY_MODE_VALIDATOR = + new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1"}); + /** * Lower the display persistence while the system is in VR mode. * @@ -7174,6 +7501,9 @@ public final class Settings { public static final String AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN = "automatic_storage_manager_days_to_retain"; + private static final Validator AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Default number of days of information for the automatic storage manager to retain. * @@ -7215,12 +7545,30 @@ public final class Settings { public static final String SYSTEM_NAVIGATION_KEYS_ENABLED = "system_navigation_keys_enabled"; + private static final Validator SYSTEM_NAVIGATION_KEYS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Holds comma separated list of ordering of QS tiles. * @hide */ public static final String QS_TILES = "sysui_qs_tiles"; + private static final Validator QS_TILES_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null) { + return false; + } + String[] tiles = value.split(","); + boolean valid = true; + for (String tile : tiles) { + // tile can be any non-empty string as specified by OEM + valid |= ((tile.length() > 0) && ANY_STRING_VALIDATOR.validate(tile)); + } + return valid; + } + }; + /** * Specifies whether the web action API is enabled. * @@ -7256,18 +7604,38 @@ public final class Settings { */ public static final String NOTIFICATION_BADGING = "notification_badging"; + private static final Validator NOTIFICATION_BADGING_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Comma separated list of QS tiles that have been auto-added already. * @hide */ public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles"; + private static final Validator QS_AUTO_ADDED_TILES_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null) { + return false; + } + String[] tiles = value.split(","); + boolean valid = true; + for (String tile : tiles) { + // tile can be any non-empty string as specified by OEM + valid |= ((tile.length() > 0) && ANY_STRING_VALIDATOR.validate(tile)); + } + return valid; + } + }; + /** * Whether the Lockdown button should be shown in the power menu. * @hide */ public static final String LOCKDOWN_IN_POWER_MENU = "lockdown_in_power_menu"; + private static final Validator LOCKDOWN_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR; + /** * Backup manager behavioral parameters. * This is encoded as a key=value list, separated by commas. Ex: @@ -7295,6 +7663,13 @@ public final class Settings { */ public static final String BACKUP_MANAGER_CONSTANTS = "backup_manager_constants"; + /** + * Flag to set if the system should predictively attempt to re-enable Bluetooth while + * the user is driving. + * @hide + */ + public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving"; + /** * This are the settings to be backed up. * @@ -7307,8 +7682,6 @@ public final class Settings { public static final String[] SETTINGS_TO_BACKUP = { BUGREPORT_IN_POWER_MENU, // moved to global ALLOW_MOCK_LOCATION, - PARENTAL_CONTROL_ENABLED, - PARENTAL_CONTROL_REDIRECT_URL, USB_MASS_STORAGE_ENABLED, // moved to global ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, ACCESSIBILITY_DISPLAY_DALTONIZER, @@ -7340,12 +7713,9 @@ public final class Settings { ACCESSIBILITY_CAPTIONING_TYPEFACE, ACCESSIBILITY_CAPTIONING_FONT_SCALE, ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, - TTS_USE_DEFAULTS, TTS_DEFAULT_RATE, TTS_DEFAULT_PITCH, TTS_DEFAULT_SYNTH, - TTS_DEFAULT_LANG, - TTS_DEFAULT_COUNTRY, TTS_ENABLED_PLUGINS, TTS_DEFAULT_LOCALE, SHOW_IME_WITH_HARD_KEYBOARD, @@ -7398,9 +7768,161 @@ public final class Settings { SCREENSAVER_ACTIVATE_ON_DOCK, SCREENSAVER_ACTIVATE_ON_SLEEP, LOCKDOWN_IN_POWER_MENU, + SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, }; - /** @hide */ + /** + * 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 VALIDATORS = new ArrayMap<>(); + static { + VALIDATORS.put(BUGREPORT_IN_POWER_MENU, BUGREPORT_IN_POWER_MENU_VALIDATOR); + VALIDATORS.put(ALLOW_MOCK_LOCATION, ALLOW_MOCK_LOCATION_VALIDATOR); + VALIDATORS.put(USB_MASS_STORAGE_ENABLED, USB_MASS_STORAGE_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, + ACCESSIBILITY_DISPLAY_INVERSION_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_DISPLAY_DALTONIZER, + ACCESSIBILITY_DISPLAY_DALTONIZER_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED_VALIDATOR); + VALIDATORS.put(AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE_VALIDATOR); + VALIDATORS.put(ENABLED_ACCESSIBILITY_SERVICES, + ENABLED_ACCESSIBILITY_SERVICES_VALIDATOR); + VALIDATORS.put(ENABLED_VR_LISTENERS, ENABLED_VR_LISTENERS_VALIDATOR); + VALIDATORS.put(ENABLED_INPUT_METHODS, ENABLED_INPUT_METHODS_VALIDATOR); + VALIDATORS.put(TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES_VALIDATOR); + VALIDATORS.put(TOUCH_EXPLORATION_ENABLED, TOUCH_EXPLORATION_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_ENABLED, ACCESSIBILITY_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, + ACCESSIBILITY_SHORTCUT_TARGET_SERVICE_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + ACCESSIBILITY_BUTTON_TARGET_COMPONENT_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_SHORTCUT_ENABLED, + ACCESSIBILITY_SHORTCUT_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, + ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_SPEAK_PASSWORD, ACCESSIBILITY_SPEAK_PASSWORD_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, + ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_PRESET, + ACCESSIBILITY_CAPTIONING_PRESET_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_ENABLED, + ACCESSIBILITY_CAPTIONING_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_LOCALE, + ACCESSIBILITY_CAPTIONING_LOCALE_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, + ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, + ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_EDGE_TYPE, + ACCESSIBILITY_CAPTIONING_EDGE_TYPE_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_EDGE_COLOR, + ACCESSIBILITY_CAPTIONING_EDGE_COLOR_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_TYPEFACE, + ACCESSIBILITY_CAPTIONING_TYPEFACE_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_FONT_SCALE, + ACCESSIBILITY_CAPTIONING_FONT_SCALE_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, + ACCESSIBILITY_CAPTIONING_WINDOW_COLOR_VALIDATOR); + VALIDATORS.put(TTS_DEFAULT_RATE, TTS_DEFAULT_RATE_VALIDATOR); + VALIDATORS.put(TTS_DEFAULT_PITCH, TTS_DEFAULT_PITCH_VALIDATOR); + VALIDATORS.put(TTS_DEFAULT_SYNTH, TTS_DEFAULT_SYNTH_VALIDATOR); + VALIDATORS.put(TTS_ENABLED_PLUGINS, TTS_ENABLED_PLUGINS_VALIDATOR); + VALIDATORS.put(TTS_DEFAULT_LOCALE, TTS_DEFAULT_LOCALE_VALIDATOR); + VALIDATORS.put(SHOW_IME_WITH_HARD_KEYBOARD, SHOW_IME_WITH_HARD_KEYBOARD_VALIDATOR); + VALIDATORS.put(WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR); + VALIDATORS.put(WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, + WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR); + VALIDATORS.put(WIFI_NUM_OPEN_NETWORKS_KEPT, WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR); + VALIDATORS.put(SELECTED_SPELL_CHECKER, SELECTED_SPELL_CHECKER_VALIDATOR); + VALIDATORS.put(SELECTED_SPELL_CHECKER_SUBTYPE, + SELECTED_SPELL_CHECKER_SUBTYPE_VALIDATOR); + VALIDATORS.put(SPELL_CHECKER_ENABLED, SPELL_CHECKER_ENABLED_VALIDATOR); + VALIDATORS.put(MOUNT_PLAY_NOTIFICATION_SND, MOUNT_PLAY_NOTIFICATION_SND_VALIDATOR); + VALIDATORS.put(MOUNT_UMS_AUTOSTART, MOUNT_UMS_AUTOSTART_VALIDATOR); + VALIDATORS.put(MOUNT_UMS_PROMPT, MOUNT_UMS_PROMPT_VALIDATOR); + VALIDATORS.put(MOUNT_UMS_NOTIFY_ENABLED, MOUNT_UMS_NOTIFY_ENABLED_VALIDATOR); + VALIDATORS.put(SLEEP_TIMEOUT, SLEEP_TIMEOUT_VALIDATOR); + VALIDATORS.put(DOUBLE_TAP_TO_WAKE, DOUBLE_TAP_TO_WAKE_VALIDATOR); + VALIDATORS.put(WAKE_GESTURE_ENABLED, WAKE_GESTURE_ENABLED_VALIDATOR); + VALIDATORS.put(LONG_PRESS_TIMEOUT, LONG_PRESS_TIMEOUT_VALIDATOR); + VALIDATORS.put(CAMERA_GESTURE_DISABLED, CAMERA_GESTURE_DISABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_AUTOCLICK_ENABLED, + ACCESSIBILITY_AUTOCLICK_ENABLED_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_AUTOCLICK_DELAY, ACCESSIBILITY_AUTOCLICK_DELAY_VALIDATOR); + VALIDATORS.put(ACCESSIBILITY_LARGE_POINTER_ICON, + ACCESSIBILITY_LARGE_POINTER_ICON_VALIDATOR); + VALIDATORS.put(PREFERRED_TTY_MODE, PREFERRED_TTY_MODE_VALIDATOR); + VALIDATORS.put(ENHANCED_VOICE_PRIVACY_ENABLED, + ENHANCED_VOICE_PRIVACY_ENABLED_VALIDATOR); + VALIDATORS.put(TTY_MODE_ENABLED, TTY_MODE_ENABLED_VALIDATOR); + VALIDATORS.put(INCALL_POWER_BUTTON_BEHAVIOR, INCALL_POWER_BUTTON_BEHAVIOR_VALIDATOR); + VALIDATORS.put(NIGHT_DISPLAY_CUSTOM_START_TIME, + NIGHT_DISPLAY_CUSTOM_START_TIME_VALIDATOR); + VALIDATORS.put(NIGHT_DISPLAY_CUSTOM_END_TIME, NIGHT_DISPLAY_CUSTOM_END_TIME_VALIDATOR); + VALIDATORS.put(NIGHT_DISPLAY_COLOR_TEMPERATURE, + NIGHT_DISPLAY_COLOR_TEMPERATURE_VALIDATOR); + VALIDATORS.put(NIGHT_DISPLAY_AUTO_MODE, NIGHT_DISPLAY_AUTO_MODE_VALIDATOR); + VALIDATORS.put(SYNC_PARENT_SOUNDS, SYNC_PARENT_SOUNDS_VALIDATOR); + VALIDATORS.put(CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED, + CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED_VALIDATOR); + VALIDATORS.put(CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, + CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED_VALIDATOR); + VALIDATORS.put(SYSTEM_NAVIGATION_KEYS_ENABLED, + SYSTEM_NAVIGATION_KEYS_ENABLED_VALIDATOR); + VALIDATORS.put(QS_TILES, QS_TILES_VALIDATOR); + VALIDATORS.put(DOZE_ENABLED, DOZE_ENABLED_VALIDATOR); + VALIDATORS.put(DOZE_PULSE_ON_PICK_UP, DOZE_PULSE_ON_PICK_UP_VALIDATOR); + VALIDATORS.put(DOZE_PULSE_ON_DOUBLE_TAP, DOZE_PULSE_ON_DOUBLE_TAP_VALIDATOR); + VALIDATORS.put(NFC_PAYMENT_DEFAULT_COMPONENT, NFC_PAYMENT_DEFAULT_COMPONENT_VALIDATOR); + VALIDATORS.put(AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, + AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR); + VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR); + VALIDATORS.put(ASSIST_GESTURE_SENSITIVITY, ASSIST_GESTURE_SENSITIVITY_VALIDATOR); + VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, ASSIST_GESTURE_SETUP_COMPLETE_VALIDATOR); + VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, + ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR); + VALIDATORS.put(ASSIST_GESTURE_WAKE_ENABLED, ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR); + VALIDATORS.put(VR_DISPLAY_MODE, VR_DISPLAY_MODE_VALIDATOR); + VALIDATORS.put(NOTIFICATION_BADGING, NOTIFICATION_BADGING_VALIDATOR); + VALIDATORS.put(QS_AUTO_ADDED_TILES, QS_AUTO_ADDED_TILES_VALIDATOR); + VALIDATORS.put(SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_VALIDATOR); + VALIDATORS.put(SCREENSAVER_COMPONENTS, SCREENSAVER_COMPONENTS_VALIDATOR); + VALIDATORS.put(SCREENSAVER_ACTIVATE_ON_DOCK, SCREENSAVER_ACTIVATE_ON_DOCK_VALIDATOR); + VALIDATORS.put(SCREENSAVER_ACTIVATE_ON_SLEEP, SCREENSAVER_ACTIVATE_ON_SLEEP_VALIDATOR); + VALIDATORS.put(LOCKDOWN_IN_POWER_MENU, LOCKDOWN_IN_POWER_MENU_VALIDATOR); + VALIDATORS.put(SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, + SHOW_FIRST_CRASH_DIALOG_DEV_OPTION_VALIDATOR); + VALIDATORS.put(ENABLED_NOTIFICATION_LISTENERS, + ENABLED_NOTIFICATION_LISTENERS_VALIDATOR); //legacy restore setting + VALIDATORS.put(ENABLED_NOTIFICATION_ASSISTANT, + ENABLED_NOTIFICATION_ASSISTANT_VALIDATOR); //legacy restore setting + VALIDATORS.put(ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES, + ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES_VALIDATOR); //legacy restore setting + } + + /** + * Keys we no longer back up under the current schema, but want to continue to + * process when restoring historical backup datasets. + * + * All settings in {@link LEGACY_RESTORE_SETTINGS} array *must* have a non-null validator, + * otherwise they won't be restored. + * + * @hide + */ public static final String[] LEGACY_RESTORE_SETTINGS = { ENABLED_NOTIFICATION_LISTENERS, ENABLED_NOTIFICATION_ASSISTANT, @@ -7421,8 +7943,8 @@ public final class Settings { CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD); CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES); CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS); + CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER); CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE); - CLONE_TO_MANAGED_PROFILE.add(LOCATION_PREVIOUS_MODE); CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED); CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE); } @@ -7473,8 +7995,7 @@ public final class Settings { * @param provider the location provider to query * @return true if the provider is enabled * - * @deprecated use {@link #LOCATION_MODE} or - * {@link LocationManager#isProviderEnabled(String)} + * @deprecated use {@link LocationManager#isProviderEnabled(String)} */ @Deprecated public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { @@ -7487,12 +8008,13 @@ public final class Settings { * @param provider the location provider to query * @param userId the userId to query * @return true if the provider is enabled - * @deprecated use {@link #LOCATION_MODE} or - * {@link LocationManager#isProviderEnabled(String)} + * + * @deprecated use {@link LocationManager#isProviderEnabled(String)} * @hide */ @Deprecated - public static final boolean isLocationProviderEnabledForUser(ContentResolver cr, String provider, int userId) { + public static final boolean isLocationProviderEnabledForUser( + ContentResolver cr, String provider, int userId) { String allowedProviders = Settings.Secure.getStringForUser(cr, LOCATION_PROVIDERS_ALLOWED, userId); return TextUtils.delimitedStringContains(allowedProviders, ',', provider); @@ -7503,7 +8025,8 @@ public final class Settings { * @param cr the content resolver to use * @param provider the location provider to enable or disable * @param enabled true if the provider should be enabled - * @deprecated use {@link #putInt(ContentResolver, String, int)} and {@link #LOCATION_MODE} + * @deprecated This API is deprecated. It requires WRITE_SECURE_SETTINGS permission to + * change location settings. */ @Deprecated public static final void setLocationProviderEnabled(ContentResolver cr, @@ -7519,8 +8042,8 @@ public final class Settings { * @param enabled true if the provider should be enabled * @param userId the userId for which to enable/disable providers * @return true if the value was set, false on database errors - * @deprecated use {@link #putIntForUser(ContentResolver, String, int, int)} and - * {@link #LOCATION_MODE} + * + * @deprecated use {@link LocationManager#setProviderEnabledForUser(String, boolean, int)} * @hide */ @Deprecated @@ -7540,28 +8063,6 @@ public final class Settings { } } - /** - * Saves the current location mode into {@link #LOCATION_PREVIOUS_MODE}. - */ - private static final boolean saveLocationModeForUser(ContentResolver cr, int userId) { - final int mode = getLocationModeForUser(cr, userId); - return putIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, mode, userId); - } - - /** - * Restores the current location mode from {@link #LOCATION_PREVIOUS_MODE}. - */ - private static final boolean restoreLocationModeForUser(ContentResolver cr, int userId) { - int mode = getIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, - LOCATION_MODE_HIGH_ACCURACY, userId); - // Make sure that the previous mode is never "off". Otherwise the user won't be able to - // turn on location any longer. - if (mode == LOCATION_MODE_OFF) { - mode = LOCATION_MODE_HIGH_ACCURACY; - } - return setLocationModeForUser(cr, mode, userId); - } - /** * Thread-safe method for setting the location mode to one of * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, @@ -7575,18 +8076,20 @@ public final class Settings { * @return true if the value was set, false on database errors * * @throws IllegalArgumentException if mode is not one of the supported values + * + * @deprecated To enable/disable location, use + * {@link LocationManager#setLocationEnabledForUser(boolean, int)}. + * To enable/disable a specific location provider, use + * {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}. */ - private static final boolean setLocationModeForUser(ContentResolver cr, int mode, - int userId) { + @Deprecated + private static boolean setLocationModeForUser( + ContentResolver cr, int mode, int userId) { synchronized (mLocationSettingsLock) { boolean gps = false; boolean network = false; switch (mode) { - case LOCATION_MODE_PREVIOUS: - // Retrieve the actual mode and set to that mode. - return restoreLocationModeForUser(cr, userId); case LOCATION_MODE_OFF: - saveLocationModeForUser(cr, userId); break; case LOCATION_MODE_SENSORS_ONLY: gps = true; @@ -7601,15 +8104,7 @@ public final class Settings { default: throw new IllegalArgumentException("Invalid location mode: " + mode); } - // Note it's important that we set the NLP mode first. The Google implementation - // of NLP clears its NLP consent setting any time it receives a - // LocationManager.PROVIDERS_CHANGED_ACTION broadcast and NLP is disabled. Also, - // it shows an NLP consent dialog any time it receives the broadcast, NLP is - // enabled, and the NLP consent is not set. If 1) we were to enable GPS first, - // 2) a setup wizard has its own NLP consent UI that sets the NLP consent setting, - // and 3) the receiver happened to complete before we enabled NLP, then the Google - // NLP would detect the attempt to enable NLP and show a redundant NLP consent - // dialog. Then the people who wrote the setup wizard would be sad. + boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser( cr, LocationManager.NETWORK_PROVIDER, network, userId); boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser( @@ -7796,12 +8291,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 @@ -7832,6 +8331,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 @@ -7869,6 +8370,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 @@ -7882,6 +8385,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: @@ -7895,6 +8400,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. @@ -7902,6 +8431,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. */ @@ -7925,6 +8456,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 @@ -8543,6 +9076,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. @@ -8679,6 +9214,25 @@ 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; + + /** + * Whether to notify the user of carrier networks. + *

        + * If not connected and the scan results have a carrier network, we will + * put this notification up. If we attempt to connect to a network or + * the carrier network(s) disappear, we remove the notification. When we + * show the notification, we will not show it again for + * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time. + * @hide + */ + public static final String WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON = + "wifi_carrier_networks_available_notification_on"; + + private static final Validator WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR = + BOOLEAN_VALIDATOR; + /** * {@hide} */ @@ -8692,6 +9246,9 @@ public final class Settings { public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = "wifi_networks_available_repeat_delay"; + private static final Validator WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * 802.11 country code in ISO 3166 format * @hide @@ -8721,6 +9278,9 @@ public final class Settings { */ public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept"; + private static final Validator WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR = + NON_NEGATIVE_INTEGER_VALIDATOR; + /** * Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this. */ @@ -8741,6 +9301,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. * @@ -8750,6 +9312,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. * @@ -8796,6 +9360,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. @@ -8819,6 +9386,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. @@ -8840,13 +9414,52 @@ public final class Settings { public static final String RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS = "recommended_network_evaluator_cache_expiry_ms"; - /** + /** * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for * connectivity. * @hide */ - public static final String BLE_SCAN_ALWAYS_AVAILABLE = - "ble_scan_always_enabled"; + public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled"; + + /** + * The length in milliseconds of a BLE scan window in a low-power scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms"; + + /** + * The length in milliseconds of a BLE scan window in a balanced scan mode. + * @hide + */ + public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms"; + + /** + * The length in milliseconds of a BLE scan window in a low-latency scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS = + "ble_scan_low_latency_window_ms"; + + /** + * The length in milliseconds of a BLE scan interval in a low-power scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS = + "ble_scan_low_power_interval_ms"; + + /** + * The length in milliseconds of a BLE scan interval in a balanced scan mode. + * @hide + */ + public static final String BLE_SCAN_BALANCED_INTERVAL_MS = + "ble_scan_balanced_interval_ms"; + + /** + * The length in milliseconds of a BLE scan interval in a low-latency scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS = + "ble_scan_low_latency_interval_ms"; /** * Used to save the Wifi_ON state prior to tethering. @@ -8898,6 +9511,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. @@ -8914,6 +9530,14 @@ public final class Settings { public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled"; + /** + * Setting to enable connected MAC randomization in Wi-Fi; disabled by default, and + * setting to 1 will enable it. In the future, additional values may be supported. + * @hide + */ + public static final String WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED = + "wifi_connected_mac_randomization_enabled"; + /** * The maximum number of times we will retry a connection to an access * point for which we have failed in acquiring an IP address from DHCP. @@ -9433,11 +10057,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_"; @@ -9613,7 +10242,8 @@ public final class Settings { * This is encoded as a key=value list, separated by commas. Ex: * * "battery_tip_enabled=true,summary_enabled=true,high_usage_enabled=true," - * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50" + * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50," + * "high_usage_battery_draining=25,high_usage_period_ms=3000" * * The following keys are supported: * @@ -9623,6 +10253,8 @@ public final class Settings { * battery_saver_tip_enabled (boolean) * high_usage_enabled (boolean) * high_usage_app_count (int) + * high_usage_period_ms (long) + * high_usage_battery_draining (int) * app_restriction_enabled (boolean) * reduced_battery_enabled (boolean) * reduced_battery_percent (int) @@ -9663,6 +10295,14 @@ public final class Settings { */ public static final String SYS_VDSO = "sys_vdso"; + /** + * An integer to reduce the FPS by this factor. Only for experiments. Need to reboot the + * device for this setting to take full effect. + * + * @hide + */ + public static final String FPS_DEVISOR = "fps_divisor"; + /** * App standby (app idle) specific settings. * This is encoded as a key=value list, separated by commas. Ex: @@ -9825,6 +10465,8 @@ public final class Settings { * The following keys are supported: *

                  * track_cpu_times_by_proc_state (boolean)
        +         * track_cpu_active_cluster_time (boolean)
        +         * read_binary_cpu_time          (boolean)
                  * 
        * *

        @@ -9851,6 +10493,24 @@ 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 to enable Time Only Mode for watch type devices. + * Type: int (0 for false, 1 for true) + * Default: 0 + * @hide + */ + public static final String TIME_ONLY_MODE_ENABLED + = "time_only_mode_enabled"; + /** * Whether or not Network Watchlist feature is enabled. * Type: int (0 for false, 1 for true) @@ -10002,6 +10662,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 @@ -10010,6 +10673,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). @@ -10079,6 +10744,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 @@ -10094,6 +10762,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. @@ -10140,6 +10810,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 @@ -10635,9 +11308,29 @@ public final class Settings { * * @hide */ + @TestApi public static final String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch"; + /** + * If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored + * and restoring to lower version of platform API will be skipped. + * + * @hide + */ + public static final String OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION = + "override_settings_provider_restore_any_version"; + /** + * Flag to toggle whether system services report attribution chains when they attribute + * battery use via a {@code WorkSource}. + * + * Type: int (0 to disable, 1 to enable) + * + * @hide + */ + public static final String CHAINED_BATTERY_ATTRIBUTION_ENABLED = + "chained_battery_attribution_enabled"; + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. @@ -10666,6 +11359,7 @@ public final class Settings { NETWORK_RECOMMENDATIONS_ENABLED, WIFI_WAKEUP_ENABLED, WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON, USE_OPEN_WIFI_PACKAGE, WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, EMERGENCY_TONE, @@ -10679,6 +11373,43 @@ public final class Settings { SOFT_AP_TIMEOUT_ENABLED }; + /** + * 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 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); + VALIDATORS.put(WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON, + WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR); + } + /** * Global settings that shouldn't be persisted. * @@ -10688,7 +11419,15 @@ public final class Settings { LOCATION_GLOBAL_KILL_SWITCH, }; - /** @hide */ + /** + * Keys we no longer back up under the current schema, but want to continue to + * process when restoring historical backup datasets. + * + * All settings in {@link LEGACY_RESTORE_SETTINGS} array *must* have a non-null validator, + * otherwise they won't be restored. + * + * @hide + */ public static final String[] LEGACY_RESTORE_SETTINGS = { }; @@ -11300,6 +12039,33 @@ public final class Settings { */ public static final String ZRAM_ENABLED = "zram_enabled"; + + /** + * Whether smart replies in notifications are enabled. + * @hide + */ + public static final String ENABLE_SMART_REPLIES_IN_NOTIFICATIONS = + "enable_smart_replies_in_notifications"; + + /** + * If nonzero, crashes in foreground processes will bring up a dialog. + * Otherwise, the process will be silently killed. + * @hide + */ + public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog"; + + /** + * If nonzero, crash dialogs will show an option to restart the app. + * @hide + */ + public static final String SHOW_RESTART_IN_CRASH_DIALOG = "show_restart_in_crash_dialog"; + + /** + * If nonzero, crash dialogs will show an option to mute all future crash dialogs for + * this app. + * @hide + */ + public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog"; } /** diff --git a/core/java/android/provider/SettingsSlicesContract.java b/core/java/android/provider/SettingsSlicesContract.java new file mode 100644 index 0000000000000000000000000000000000000000..f79d852ddefc74de6607bf1e3b0e81098aa4288e --- /dev/null +++ b/core/java/android/provider/SettingsSlicesContract.java @@ -0,0 +1,106 @@ +/* + * 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.ContentResolver; +import android.net.Uri; + +/** + * Provides a contract for platform-supported Settings {@link android.app.slice.Slice Slices}. + *

        + * Contains definitions for the supported {@link android.app.slice.SliceProvider SliceProvider} + * authority, authority {@link Uri}, and key constants. + *

        + * {@link android.app.slice.Slice Slice} presenters interested in learning meta-data about the + * {@link android.app.slice.Slice Slice} should read the {@link android.app.slice.Slice Slice} + * object at runtime. + *

        + * {@link Uri} builder example: + *

        + * Uri wifiActionUri = AUTHORITY_URI
        + *         .buildUpon()
        + *         .appendPath(PATH_SETTING_ACTION)
        + *         .appendPath(KEY_WIFI)
        + *         .build();
        + * Uri bluetoothIntentUri = AUTHORITY_URI
        + *         .buildUpon()
        + *         .appendPath(PATH_SETTING_INTENT)
        + *         .appendPath(KEY_BLUETOOTH)
        + *         .build();
        + * 
        + */ +public class SettingsSlicesContract { + private SettingsSlicesContract() { + } + + /** + * Authority for platform Settings Slices. + */ + public static final String AUTHORITY = "android.settings.slices"; + + /** + * A content:// style uri to the Settings Slices authority, {@link #AUTHORITY}. + */ + public static final Uri BASE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY) + .build(); + + /** + * {@link Uri} path indicating that the requested {@link android.app.slice.Slice Slice} should + * have inline controls for the corresponding setting. + *

        + * This path will only contain Slices defined by keys in this class. + */ + public static final String PATH_SETTING_ACTION = "action"; + + /** + * {@link Uri} path indicating that the requested {@link android.app.slice.Slice Slice} should + * be {@link android.content.Intent Intent}-only. + *

        + * {@link android.app.slice.Slice Slices} with actions should use the {@link + * #PATH_SETTING_ACTION} path. + *

        + * This path will only contain Slices defined by keys in this class + */ + public static final String PATH_SETTING_INTENT = "intent"; + + /** + * {@link Uri} key for the Airplane Mode setting. + */ + public static final String KEY_AIRPLANE_MODE = "airplane_mode"; + + /** + * {@link Uri} key for the Battery Saver setting. + */ + public static final String KEY_BATTERY_SAVER = "battery_saver"; + + /** + * {@link Uri} key for the Bluetooth setting. + */ + public static final String KEY_BLUETOOTH = "bluetooth"; + + /** + * {@link Uri} key for the Location setting. + */ + public static final String KEY_LOCATION = "location"; + + /** + * {@link Uri} key for the Wi-fi setting. + */ + public static final String KEY_WIFI = "wifi"; +} diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java new file mode 100644 index 0000000000000000000000000000000000000000..5885b6b50abde4ab1b7dc58d48970905dbdee3d9 --- /dev/null +++ b/core/java/android/provider/SettingsValidators.java @@ -0,0 +1,249 @@ +/* + * 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; + +import java.util.Locale; + +/** + * 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 ANY_INTEGER_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + try { + Integer.parseInt(value); + return true; + } 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) + if (value == null) { + return false; + } + 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) { + if (value == null) { + return false; + } + return value.length() <= MAX_IPV6_LENGTH; + } + }; + + public static final Validator LOCALE_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null) { + return false; + } + Locale[] validLocales = Locale.getAvailableLocales(); + for (Locale locale : validLocales) { + if (value.equals(locale.toString())) { + return true; + } + } + return false; + } + }; + + 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; + } + } + } + + public static final class ComponentNameListValidator implements Validator { + private final String mSeparator; + + public ComponentNameListValidator(String separator) { + mSeparator = separator; + } + + @Override + public boolean validate(String value) { + if (value == null) { + return false; + } + String[] elements = value.split(mSeparator); + for (String element : elements) { + if (!COMPONENT_NAME_VALIDATOR.validate(element)) { + return false; + } + } + return true; + } + } + + public static final class PackageNameListValidator implements Validator { + private final String mSeparator; + + public PackageNameListValidator(String separator) { + mSeparator = separator; + } + + @Override + public boolean validate(String value) { + if (value == null) { + return false; + } + String[] elements = value.split(mSeparator); + for (String element : elements) { + if (!PACKAGE_NAME_VALIDATOR.validate(element)) { + return false; + } + } + return true; + } + } +} diff --git a/core/java/android/se/omapi/Channel.java b/core/java/android/se/omapi/Channel.java new file mode 100644 index 0000000000000000000000000000000000000000..f0b9fa2de5d27d20b19bbfc1065192423857628c --- /dev/null +++ b/core/java/android/se/omapi/Channel.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright (c) 2015-2017, The Linux Foundation. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import android.util.Log; + +import java.io.IOException; + +/** + * Instances of this class represent an ISO/IEC 7816-4 channel opened to a + * Secure Element. It can be either a logical channel or the basic channel. They + * can be used to send APDUs to the secure element. Channels are opened by + * calling the Session.openBasicChannel(byte[]) or + * Session.openLogicalChannel(byte[]) methods. + * + * @see GlobalPlatform Open Mobile API + */ +public class Channel { + + private static final String TAG = "OMAPI.Channel"; + private Session mSession; + private final ISecureElementChannel mChannel; + private final SEService mService; + private final Object mLock = new Object(); + + Channel(SEService service, Session session, ISecureElementChannel channel) { + if (service == null || session == null || channel == null) { + throw new IllegalArgumentException("Parameters cannot be null"); + } + mService = service; + mSession = session; + mChannel = channel; + } + + /** + * Closes this channel to the Secure Element. If the method is called when + * the channel is already closed, this method will be ignored. The close() + * method shall wait for completion of any pending transmit(byte[] command) + * before closing the channel. + */ + public void close() { + if (!isClosed()) { + synchronized (mLock) { + try { + mChannel.close(); + } catch (Exception e) { + Log.e(TAG, "Error closing channel", e); + } + } + } + } + + /** + * Tells if this channel is closed. + * + * @return true if the channel is closed or in case of an error. + * false otherwise. + */ + public boolean isClosed() { + if (!mService.isConnected()) { + Log.e(TAG, "service not connected to system"); + return true; + } + try { + return mChannel.isClosed(); + } catch (RemoteException e) { + Log.e(TAG, "Exception in isClosed()"); + return true; + } + } + + /** + * Returns a boolean telling if this channel is the basic channel. + * + * @return true if this channel is a basic channel. false if + * this channel is a logical channel. + */ + public boolean isBasicChannel() { + if (!mService.isConnected()) { + throw new IllegalStateException("service not connected to system"); + } + try { + return mChannel.isBasicChannel(); + } catch (RemoteException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + /** + * Transmit an APDU command (as per ISO/IEC 7816-4) to the Secure Element. The + * underlying layers generate as many TPDUs as necessary to transport this APDU. The + * API shall ensure that all available data returned from Secure Element, including + * concatenated responses, are retrieved and made available to the calling application. If a + * warning status code is received the API wont check for further response data but will + * return all data received so far and the warning status code.
        + * The transport part is invisible from the application. The generated response is the + * response of the APDU which means that all protocols related responses are handled + * inside the API or the underlying implementation.
        + * The transmit method shall support extended length APDU commands independently of + * the coding within the ATR.
        + * For status word '61 XX' the API or underlying implementation shall issue a GET + * RESPONSE command as specified by ISO 7816-4 standard with LE=XX; for the status + * word '6C XX', the API or underlying implementation shall reissue the input command + * with LE=XX. For other status words, the API (or underlying implementation) shall return + * the complete response including data and status word to the device application. The API + * (or underlying implementation) shall not handle internally the received status words. The + * channel shall not be closed even if the Secure Element answered with an error code. + * The system ensures the synchronization between all the concurrent calls to this method, + * and that only one APDU will be sent at a time, irrespective of the number of TPDUs that + * might be required to transport it to the SE. The entire APDU communication to this SE is + * locked to the APDU.
        + * The channel information in the class byte in the APDU will be ignored. The system will + * add any required information to ensure the APDU is transported on this channel. + * The only restrictions on the set of commands that can be sent is defined below, the API + * implementation shall be able to send all other commands:
        + *

          + *
        • MANAGE_CHANNEL commands are not allowed.
        • + *
        • SELECT by DF Name (p1=04) are not allowed.
        • + *
        • CLA bytes with channel numbers are de-masked.
        • + *
        + * + * @param command the APDU command to be transmitted, as a byte array. + * + * @return the response received, as a byte array. The returned byte array contains the data + * bytes in the following order: + * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>] + * + * @throws IOException if there is a communication problem to the reader or the Secure Element. + * @throws IllegalStateException if the channel is used after being closed. + * @throws IllegalArgumentException if the command byte array is less than 4 bytes long. + * @throws IllegalArgumentException if Lc byte is inconsistent with length of the byte array. + * @throws IllegalArgumentException if CLA byte is invalid according to [2] (0xff). + * @throws IllegalArgumentException if INS byte is invalid according to [2] (0x6x or 0x9x). + * @throws SecurityException if the command is filtered by the security policy. + * @throws NullPointerException if command is NULL. + */ + public @NonNull byte[] transmit(byte[] command) throws IOException { + if (!mService.isConnected()) { + throw new IllegalStateException("service not connected to system"); + } + synchronized (mLock) { + try { + byte[] response = mChannel.transmit(command); + if (response == null) { + throw new IOException("Error in communicating with Secure Element"); + } + return response; + } catch (RemoteException e) { + throw new IOException(e.getMessage()); + } + } + } + + /** + * Get the session that has opened this channel. + * + * @return the session object this channel is bound to. + */ + public @NonNull Session getSession() { + return mSession; + } + + /** + * Returns the data as received from the application select command inclusively the status word + * received at applet selection. + * The returned byte array contains the data bytes in the following order: + * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>] + * @return The data as returned by the application select command inclusively the status word. + * Only the status word if the application select command has no returned data. + * Returns null if an application select command has not been performed or the selection + * response can not be retrieved by the reader implementation. + */ + public @Nullable byte[] getSelectResponse() { + if (!mService.isConnected()) { + throw new IllegalStateException("service not connected to system"); + } + + byte[] response; + try { + response = mChannel.getSelectResponse(); + } catch (RemoteException e) { + throw new IllegalStateException(e.getMessage()); + } + + if (response != null && response.length == 0) { + response = null; + } + return response; + } + + /** + * Performs a selection of the next Applet on this channel that matches to the partial AID + * specified in the openBasicChannel(byte[] aid) or openLogicalChannel(byte[] aid) method. + * This mechanism can be used by a device application to iterate through all Applets + * matching to the same partial AID. + * If selectNext() returns true a new Applet was successfully selected on this channel. + * If no further Applet exists with matches to the partial AID this method returns false + * and the already selected Applet stays selected.
        + * + * Since the API cannot distinguish between a partial and full AID the API shall rely on the + * response of the Secure Element for the return value of this method.
        + * The implementation of the underlying SELECT command within this method shall use + * the same values as the corresponding openBasicChannel(byte[] aid) or + * openLogicalChannel(byte[] aid) command with the option:
        + * P2='02' (Next occurrence)
        + * The select response stored in the Channel object shall be updated with the APDU + * response of the SELECT command. + + * @return true if new Applet was selected on this channel. + false he already selected Applet stays selected on this channel. + * + * @throws IOException if there is a communication problem to the reader or the Secure Element. + * @throws IllegalStateException if the channel is used after being closed. + * @throws UnsupportedOperationException if this operation is not supported by the card. + */ + public boolean selectNext() throws IOException { + if (!mService.isConnected()) { + throw new IllegalStateException("service not connected to system"); + } + try { + synchronized (mLock) { + return mChannel.selectNext(); + } + } catch (RemoteException e) { + throw new IOException(e.getMessage()); + } + } +} diff --git a/core/java/android/se/omapi/ISecureElementChannel.aidl b/core/java/android/se/omapi/ISecureElementChannel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4ae57ab829cbf3aa9cba613e67871ec55089fdba --- /dev/null +++ b/core/java/android/se/omapi/ISecureElementChannel.aidl @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.se.omapi.ISecureElementSession; + +/** @hide */ +interface ISecureElementChannel { + + /** + * Closes the specified connection and frees internal resources. + * A logical channel will be closed. + */ + void close(); + + /** + * Tells if this channel is closed. + * + * @return true if the channel is closed, + * false otherwise. + */ + boolean isClosed(); + + /** + * Returns a boolean telling if this channel is the basic channel. + * + * @return true if this channel is a basic channel. + * false if this channel is a logical channel. + */ + boolean isBasicChannel(); + + /** + * Returns the data as received from the application select command + * inclusively the status word. The returned byte array contains the data + * bytes in the following order: + * [, ..., , , ] + */ + byte[] getSelectResponse(); + + /** + * Transmits the specified command APDU and returns the response APDU. + * MANAGE channel commands are not supported. + * Selection of applets is not supported in logical channels. + */ + byte[] transmit(in byte[] command); + + /** + * Performs a selection of the next Applet on this channel that matches to + * the partial AID specified in the openBasicChannel(byte[] aid) or + * openLogicalChannel(byte[] aid) method. This mechanism can be used by a + * device application to iterate through all Applets matching to the same + * partial AID. + * If selectNext() returns true a new Applet was successfully selected on + * this channel. + * If no further Applet exists with matches to the partial AID this method + * returns false and the already selected Applet stays selected. + * + * @return true if new Applet was successfully selected. + * false if no further Applet exists which matches the + * partial AID. + */ + boolean selectNext(); +} diff --git a/core/java/android/se/omapi/ISecureElementListener.aidl b/core/java/android/se/omapi/ISecureElementListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3a99d634e4d18a0c1c3bb278c9d7b603ac97166c --- /dev/null +++ b/core/java/android/se/omapi/ISecureElementListener.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +/** + * Interface to receive call-backs when the service is connected. + */ +interface ISecureElementListener { + /** + * Called by the framework when the service is connected. + */ + void serviceConnected(); +} diff --git a/core/java/android/se/omapi/ISecureElementReader.aidl b/core/java/android/se/omapi/ISecureElementReader.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a312c445395a9caabfa033ef3242f8dce6c1be6e --- /dev/null +++ b/core/java/android/se/omapi/ISecureElementReader.aidl @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.se.omapi.ISecureElementSession; + +/** @hide */ +interface ISecureElementReader { + + /** + * Returns true if a card is present in the specified reader. + * Returns false if a card is not present in the specified reader. + */ + boolean isSecureElementPresent(); + + /** + * Connects to a secure element in this reader.
        + * This method prepares (initialises) the Secure Element for communication + * before the Session object is returned (e.g. powers the Secure Element by + * ICC ON if its not already on). There might be multiple sessions opened at + * the same time on the same reader. The system ensures the interleaving of + * APDUs between the respective sessions. + * + * @return a Session object to be used to create Channels. + */ + ISecureElementSession openSession(); + + /** + * Close all the sessions opened on this reader. All the channels opened by + * all these sessions will be closed. + */ + void closeSessions(); + +} diff --git a/core/java/android/se/omapi/ISecureElementService.aidl b/core/java/android/se/omapi/ISecureElementService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4fa799e78757bb44e80dd2c33e5c220805d1096b --- /dev/null +++ b/core/java/android/se/omapi/ISecureElementService.aidl @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright (c) 2015-2017, The Linux Foundation. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.se.omapi.ISecureElementReader; + +/** + * SecureElement service interface. + * @hide + */ +interface ISecureElementService { + + /** + * Returns the friendly names of available Secure Element readers. + */ + String[] getReaders(); + + /** + * Returns SecureElement Service reader object to the given name. + */ + ISecureElementReader getReader(String reader); + + /** + * Checks if the application defined by the package name is allowed to + * receive NFC transaction events for the defined AID. + */ + boolean[] isNFCEventAllowed(String reader, in byte[] aid, + in String[] packageNames); + +} diff --git a/core/java/android/se/omapi/ISecureElementSession.aidl b/core/java/android/se/omapi/ISecureElementSession.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8ea599f2e8666dfea021744f5811a76bb33c2d3b --- /dev/null +++ b/core/java/android/se/omapi/ISecureElementSession.aidl @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright (c) 2015-2017, The Linux Foundation. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.se.omapi.ISecureElementChannel; +import android.se.omapi.ISecureElementReader; +import android.se.omapi.ISecureElementListener; + +/** @hide */ +interface ISecureElementSession { + + /** + * Returns the ATR of the connected card or null if the ATR is not available + */ + byte[] getAtr(); + + /** + * Close the connection with the Secure Element. This will close any + * channels opened by this application with this Secure Element. + */ + void close(); + + /** + * Close any channel opened on this session. + */ + void closeChannels(); + + + /** + * Tells if this session is closed. + * + * @return true if the session is closed, false otherwise. + */ + boolean isClosed(); + + /** + * Opens a connection using the basic channel of the card in the + * specified reader and returns a channel handle. Selects the specified + * applet if aid != null. + * Logical channels cannot be opened with this connection. + * Use interface method openLogicalChannel() to open a logical channel. + */ + ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2, + ISecureElementListener listener); + + /** + * Opens a connection using the next free logical channel of the card in the + * specified reader. Selects the specified applet. + * Selection of other applets with this connection is not supported. + */ + ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2, + ISecureElementListener listener); +} diff --git a/core/java/android/se/omapi/Reader.java b/core/java/android/se/omapi/Reader.java new file mode 100644 index 0000000000000000000000000000000000000000..9f1573973be4e8d4c5dfafb9e3d38ceb8b355d0d --- /dev/null +++ b/core/java/android/se/omapi/Reader.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright (c) 2015-2017, The Linux Foundation. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.annotation.NonNull; +import android.os.RemoteException; +import android.util.Log; + +import java.io.IOException; + +/** + * Instances of this class represent Secure Element Readers supported to this + * device. These Readers can be physical devices or virtual devices. They can be + * removable or not. They can contain Secure Element that can or cannot be + * removed. + * + * @see GlobalPlatform Open Mobile API + */ +public class Reader { + + private static final String TAG = "OMAPI.Reader"; + private final String mName; + private final SEService mService; + private ISecureElementReader mReader; + private final Object mLock = new Object(); + + + Reader(SEService service, String name, ISecureElementReader reader) throws + IOException { + if (reader == null || service == null || name == null) { + throw new IllegalArgumentException("Parameters cannot be null"); + } + mName = name; + mService = service; + mReader = reader; + } + + /** + * Return the name of this reader. + *
          + *
        • If this reader is a SIM reader, then its name must be "SIM[Slot]".
        • + *
        • If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"
        • + *
        • If the reader is a embedded SE reader, then its name must be "eSE[Slot]"
        • + *
        + * Slot is a decimal number without leading zeros. The Numbering must start with 1 + * (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...). + * The slot number “1” for a reader is optional + * (SIM and SIM1 are both valid for the first SIM-reader, + * but if there are two readers then the second reader must be named SIM2). + * This applies also for other SD or SE readers. + * + * @return the reader name, as a String. + */ + public @NonNull String getName() { + return mName; + } + + /** + * Connects to a Secure Element in this reader.
        + * This method prepares (initialises) the Secure Element for communication + * before the Session object is returned (e.g. powers the Secure Element by + * ICC ON if its not already on). There might be multiple sessions opened at + * the same time on the same reader. The system ensures the interleaving of + * APDUs between the respective sessions. + * + * @throws IOException if something went wrong with the communicating to the + * Secure Element or the reader. + * @return a Session object to be used to create Channels. + */ + public @NonNull Session openSession() throws IOException { + if (!mService.isConnected()) { + throw new IllegalStateException("service is not connected"); + } + + synchronized (mLock) { + ISecureElementSession session; + try { + session = mReader.openSession(); + } catch (RemoteException e) { + throw new IOException(e.getMessage()); + } + if (session == null) { + throw new IOException("service session is null."); + } + return new Session(mService, session, this); + } + } + + /** + * Check if a Secure Element is present in this reader. + * + * @throws IllegalStateException if the service is not connected + * @return true if the SE is present, false otherwise. + */ + public boolean isSecureElementPresent() { + if (!mService.isConnected()) { + throw new IllegalStateException("service is not connected"); + } + + try { + return mReader.isSecureElementPresent(); + } catch (RemoteException e) { + throw new IllegalStateException("Error in isSecureElementPresent()"); + } + } + + /** + * Return the Secure Element service this reader is bound to. + * + * @return the SEService object. + */ + public @NonNull SEService getSEService() { + return mService; + } + + /** + * Close all the sessions opened on this reader. + * All the channels opened by all these sessions will be closed. + */ + public void closeSessions() { + if (!mService.isConnected()) { + Log.e(TAG, "service is not connected"); + return; + } + synchronized (mLock) { + try { + mReader.closeSessions(); + } catch (RemoteException ignore) { } + } + } +} diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java new file mode 100644 index 0000000000000000000000000000000000000000..1e37277dcd6d34e96d6b8fa83e57ced564140efc --- /dev/null +++ b/core/java/android/se/omapi/SEService.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.HashMap; + +/** + * The SEService realises the communication to available Secure Elements on the + * device. This is the entry point of this API. It is used to connect to the + * infrastructure and get access to a list of Secure Element Readers. + * + * @see SIMalliance Open Mobile API v3.0 + */ +public class SEService { + + private static final String TAG = "OMAPI.SEService"; + + private final Object mLock = new Object(); + + /** The client context (e.g. activity). */ + private final Context mContext; + + /** The backend system. */ + private volatile ISecureElementService mSecureElementService; + + /** + * Class for interacting with the main interface of the backend. + */ + private ServiceConnection mConnection; + + /** + * Collection of available readers + */ + private final HashMap mReaders = new HashMap(); + + /** + * Listener object that allows the notification of the caller if this + * SEService could be bound to the backend. + */ + private ISecureElementListener mSEListener; + + /** + * Establishes a new connection that can be used to connect to all the + * Secure Elements available in the system. The connection process can be + * quite long, so it happens in an asynchronous way. It is usable only if + * the specified listener is called or if isConnected() returns + * true.
        + * The call-back object passed as a parameter will have its + * serviceConnected() method called when the connection actually happen. + * + * @param context + * the context of the calling application. Cannot be + * null. + * @param listener + * a ISecureElementListener object. Can be null. + */ + public SEService(Context context, ISecureElementListener listener) { + + if (context == null) { + throw new NullPointerException("context must not be null"); + } + + mContext = context; + mSEListener = listener; + + mConnection = new ServiceConnection() { + + public synchronized void onServiceConnected( + ComponentName className, IBinder service) { + + mSecureElementService = ISecureElementService.Stub.asInterface(service); + if (mSEListener != null) { + try { + mSEListener.serviceConnected(); + } catch (RemoteException ignore) { } + } + Log.i(TAG, "Service onServiceConnected"); + } + + public void onServiceDisconnected(ComponentName className) { + mSecureElementService = null; + Log.i(TAG, "Service onServiceDisconnected"); + } + }; + + Intent intent = new Intent(ISecureElementService.class.getName()); + intent.setClassName("com.android.se", + "com.android.se.SecureElementService"); + boolean bindingSuccessful = + mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + if (bindingSuccessful) { + Log.i(TAG, "bindService successful"); + } + } + + /** + * Tells whether or not the service is connected. + * + * @return true if the service is connected. + */ + public boolean isConnected() { + return mSecureElementService != null; + } + + /** + * Returns the list of available Secure Element readers. + * There must be no duplicated objects in the returned list. + * All available readers shall be listed even if no card is inserted. + * + * @return The readers list, as an array of Readers. If there are no + * readers the returned array is of length 0. + */ + public @NonNull Reader[] getReaders() { + if (mSecureElementService == null) { + throw new IllegalStateException("service not connected to system"); + } + String[] readerNames; + try { + readerNames = mSecureElementService.getReaders(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + Reader[] readers = new Reader[readerNames.length]; + int i = 0; + for (String readerName : readerNames) { + if (mReaders.get(readerName) == null) { + try { + mReaders.put(readerName, new Reader(this, readerName, + getReader(readerName))); + readers[i++] = mReaders.get(readerName); + } catch (Exception e) { + Log.e(TAG, "Error adding Reader: " + readerName, e); + } + } else { + readers[i++] = mReaders.get(readerName); + } + } + return readers; + } + + /** + * Releases all Secure Elements resources allocated by this SEService + * (including any binding to an underlying service). + * As a result isConnected() will return false after shutdown() was called. + * After this method call, the SEService object is not connected. + * It is recommended to call this method in the termination method of the calling application + * (or part of this application) which is bound to this SEService. + */ + public void shutdown() { + synchronized (mLock) { + if (mSecureElementService != null) { + for (Reader reader : mReaders.values()) { + try { + reader.closeSessions(); + } catch (Exception ignore) { } + } + } + try { + mContext.unbindService(mConnection); + } catch (IllegalArgumentException e) { + // Do nothing and fail silently since an error here indicates + // that binding never succeeded in the first place. + } + mSecureElementService = null; + } + } + + /** + * Returns the version of the OpenMobile API specification this + * implementation is based on. + * + * @return String containing the OpenMobile API version (e.g. "3.0"). + */ + public String getVersion() { + return "3.2"; + } + + @NonNull ISecureElementListener getListener() { + return mSEListener; + } + + /** + * Obtain a Reader instance from the SecureElementService + */ + private @NonNull ISecureElementReader getReader(String name) { + try { + return mSecureElementService.getReader(name); + } catch (RemoteException e) { + throw new IllegalStateException(e.getMessage()); + } + } +} diff --git a/core/java/android/se/omapi/Session.java b/core/java/android/se/omapi/Session.java new file mode 100644 index 0000000000000000000000000000000000000000..bb2a0327ee0da8c8fb710c2b32de73b5510c46a6 --- /dev/null +++ b/core/java/android/se/omapi/Session.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright (c) 2017, The Linux Foundation. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package android.se.omapi; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import android.util.Log; + +import java.io.IOException; +import java.util.NoSuchElementException; + +/** + * Instances of this class represent a connection session to one of the Secure + * Elements available on the device. These objects can be used to get a + * communication channel with an Applet in the Secure Element. + * This channel can be the basic channel or a logical channel. + * + * @see SIMalliance Open Mobile API v3.0 + */ +public class Session { + + private final Object mLock = new Object(); + private final SEService mService; + private final Reader mReader; + private final ISecureElementSession mSession; + private static final String TAG = "OMAPI.Session"; + + Session(SEService service, ISecureElementSession session, Reader reader) { + if (service == null || reader == null || session == null) { + throw new IllegalArgumentException("Parameters cannot be null"); + } + mService = service; + mReader = reader; + mSession = session; + } + + /** + * Get the reader that provides this session. + * + * @return The Reader object. + */ + public @NonNull Reader getReader() { + return mReader; + } + + /** + * Get the Answer to Reset of this Secure Element.
        + * The returned byte array can be null if the ATR for this Secure Element is + * not available. + * + * @throws IllegalStateException if there was an error connecting to SE or + * if the service was not connected. + * @return the ATR as a byte array or null. + */ + public @Nullable byte[] getATR() { + if (!mService.isConnected()) { + throw new IllegalStateException("service not connected to system"); + } + try { + return mSession.getAtr(); + } catch (RemoteException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + /** + * Close the connection with the Secure Element. This will close any + * channels opened by this application with this Secure Element. + */ + public void close() { + if (!mService.isConnected()) { + Log.e(TAG, "service not connected to system"); + return; + } + synchronized (mLock) { + try { + mSession.close(); + } catch (RemoteException e) { + Log.e(TAG, "Error closing session", e); + } + } + } + + /** + * Tells if this session is closed. + * + * @return true if the session is closed, false otherwise. + */ + public boolean isClosed() { + try { + return mSession.isClosed(); + } catch (RemoteException e) { + // If there was an error here, then the session is considered close + return true; + } + } + + /** + * Close any channel opened on this session. + */ + public void closeChannels() { + if (!mService.isConnected()) { + Log.e(TAG, "service not connected to system"); + return; + } + + synchronized (mLock) { + try { + mSession.closeChannels(); + } catch (RemoteException e) { + Log.e(TAG, "Error closing channels", e); + } + } + } + + /** + * Get an access to the basic channel, as defined in the ISO/IEC 7816-4 specification (the + * one that has number 0). The obtained object is an instance of the Channel class. + * If the AID is null, it means no Applet is to be selected on this channel and the default + * Applet is used. If the AID is defined then the corresponding Applet is selected. + * Once this channel has been opened by a device application, it is considered as "locked" + * by this device application, and other calls to this method will return null, until the + * channel is closed. Some Secure Elements (like the UICC) might always keep the basic channel + * locked (i.e. return null to applications), to prevent access to the basic channel, while + * some other might return a channel object implementing some kind of filtering on the + * commands, restricting the set of accepted command to a smaller set. + * It is recommended for the UICC to reject the opening of the basic channel to a specific + * applet, by always answering null to such a request. + * For other Secure Elements, the recommendation is to accept opening the basic channel + * on the default applet until another applet is selected on the basic channel. As there is no + * other way than a reset to select again the default applet, the implementation of the + * transport API should guarantee that the openBasicChannel(null) command will return + * null until a reset occurs. + * With previous release (V2.05) it was not possible to set P2 value, this value was always + * set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is + * recommended that the device allows all values for P2, however only the following values + * are mandatory: '00', '04', '08', '0C'(as defined in [2]) + * The implementation of the underlying SELECT command within this method shall be + * based on ISO 7816-4 with following options: + *
          + *
        • CLA = '00'
        • + *
        • INS = 'A4'
        • + *
        • P1 = '04' (Select by DF name/application identifier)
        • + *
        + * + * The select response data can be retrieved with byte[] getSelectResponse(). + * The API shall handle received status word as follow. If the status word indicates that the + * Secure Element was able to open a channel (e.g. status word '90 00' or status words + * referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the + * channel opened and the next getSelectResponse() shall return the received status + * word. + * Other received status codes indicating that the Secure Element was able not to open a + * channel shall be considered as an error and the corresponding channel shall not be + * opened. + * The function without P2 as parameter is provided for backwards compatibility and will + * fall back to a select command with P2='00'. + * + * @param aid the AID of the Applet to be selected on this channel, as a + * byte array, or null if no Applet is to be selected. + * @param p2 the P2 parameter of the SELECT APDU executed on this channel. + * @throws IOException if there is a communication problem to the reader or + * the Secure Element. + * @throws IllegalStateException if the Secure Element session is used after + * being closed. + * @throws IllegalArgumentException if the aid's length is not within 5 to + * 16 (inclusive). + * @throws SecurityException if the calling application cannot be granted + * access to this AID or the default Applet on this + * session. + * @throws NoSuchElementException if the AID on the Secure Element is not available or cannot be + * selected. + * @throws UnsupportedOperationException if the given P2 parameter is not + * supported by the device + * @return an instance of Channel if available or null. + */ + public @Nullable Channel openBasicChannel(byte[] aid, byte p2) throws IOException { + if (!mService.isConnected()) { + throw new IllegalStateException("service not connected to system"); + } + + synchronized (mLock) { + try { + ISecureElementChannel channel = mSession.openBasicChannel(aid, p2, + mReader.getSEService().getListener()); + if (channel == null) { + return null; + } + return new Channel(mService, this, channel); + } catch (RemoteException e) { + throw new IOException(e.getMessage()); + } + } + } + + /** + * This method is provided to ease the development of mobile application and for compliancy + * with existing applications. + * This method is equivalent to openBasicChannel(aid, P2=0x00) + * + * @param aid the AID of the Applet to be selected on this channel, as a + * byte array, or null if no Applet is to be selected. + * @throws IOException if there is a communication problem to the reader or + * the Secure Element. + * @throws IllegalStateException if the Secure Element session is used after + * being closed. + * @throws IllegalArgumentException if the aid's length is not within 5 to + * 16 (inclusive). + * @throws SecurityException if the calling application cannot be granted + * access to this AID or the default Applet on this + * session. + * @throws NoSuchElementException if the AID on the Secure Element is not available or cannot be + * selected. + * @throws UnsupportedOperationException if the given P2 parameter is not + * supported by the device + * @return an instance of Channel if available or null. + */ + public @Nullable Channel openBasicChannel(byte[] aid) throws IOException { + return openBasicChannel(aid, (byte) 0x00); + } + + /** + * Open a logical channel with the Secure Element, selecting the Applet represented by + * the given AID. If the AID is null, which means no Applet is to be selected on this + * channel, the default Applet is used. It's up to the Secure Element to choose which + * logical channel will be used. + * With previous release (V2.05) it was not possible to set P2 value, this value was always + * set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is + * recommended that the device allows all values for P2, however only the following values + * are mandatory: '00', '04', '08', '0C'(as defined in [2]) + * The implementation of the underlying SELECT command within this method shall be + * based on ISO 7816-4 with following options: + * + *
          + *
        • CLA = '01' to '03', '40 to 4F'
        • + *
        • INS = 'A4'
        • + *
        • P1 = '04' (Select by DF name/application identifier)
        • + *
        + * + * The select response data can be retrieved with byte[] getSelectResponse(). + * The API shall handle received status word as follow. If the status word indicates that the + * Secure Element was able to open a channel (e.g. status word '90 00' or status words + * referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the + * channel opened and the next getSelectResponse() shall return the received status + * word. + * Other received status codes indicating that the Secure Element was able not to open a + * channel shall be considered as an error and the corresponding channel shall not be + * opened. + * In case of UICC it is recommended for the API to reject the opening of the logical + * channel without a specific AID, by always answering null to such a request. + * The function without P2 as parameter is provided for backwards compatibility and will + * fall back to a select command with P2=00. + * + * @param aid the AID of the Applet to be selected on this channel, as a + * byte array. + * @param p2 the P2 parameter of the SELECT APDU executed on this channel. + * @throws IOException if there is a communication problem to the reader or + * the Secure Element. + * @throws IllegalStateException if the Secure Element is used after being + * closed. + * @throws IllegalArgumentException if the aid's length is not within 5 to + * 16 (inclusive). + * @throws SecurityException if the calling application cannot be granted + * access to this AID or the default Applet on this + * session. + * @throws NoSuchElementException if the AID on the Secure Element is not + * available or cannot be selected or a logical channel is already + * open to a non-multiselectable Applet. + * @throws UnsupportedOperationException if the given P2 parameter is not + * supported by the device. + * @return an instance of Channel. Null if the Secure Element is unable to + * provide a new logical channel. + */ + public @Nullable Channel openLogicalChannel(byte[] aid, byte p2) throws IOException { + + if ((mReader.getName().startsWith("SIM")) && (aid == null)) { + Log.e(TAG, "NULL AID not supported on " + mReader.getName()); + return null; + } + + if (!mService.isConnected()) { + throw new IllegalStateException("service not connected to system"); + } + synchronized (mLock) { + try { + ISecureElementChannel channel = mSession.openLogicalChannel( + aid, + p2, + mReader.getSEService().getListener()); + if (channel == null) { + return null; + } + return new Channel(mService, this, channel); + } catch (RemoteException e) { + throw new IOException(e.getMessage()); + } + } + } + + /** + * This method is provided to ease the development of mobile application and for compliancy + * with existing applications. + * This method is equivalent to openLogicalChannel(aid, P2=0x00) + * + * @param aid the AID of the Applet to be selected on this channel, as a + * byte array. + * @throws IOException if there is a communication problem to the reader or + * the Secure Element. + * @throws IllegalStateException if the Secure Element is used after being + * closed. + * @throws IllegalArgumentException if the aid's length is not within 5 to + * 16 (inclusive). + * @throws SecurityException if the calling application cannot be granted + * access to this AID or the default Applet on this + * session. + * @throws NoSuchElementException if the AID on the Secure Element is not + * available or cannot be selected or a logical channel is already + * open to a non-multiselectable Applet. + * @throws UnsupportedOperationException if the given P2 parameter is not + * supported by the device. + * @return an instance of Channel. Null if the Secure Element is unable to + * provide a new logical channel. + */ + public @Nullable Channel openLogicalChannel(byte[] aid) throws IOException { + return openLogicalChannel(aid, (byte) 0x00); + } +} diff --git a/core/java/android/security/IConfirmationPromptCallback.aidl b/core/java/android/security/IConfirmationPromptCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..96a1a04828b50f0366f1a8b797fa03e30b61f6bc --- /dev/null +++ b/core/java/android/security/IConfirmationPromptCallback.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * This must be kept manually in sync with system/security/keystore until AIDL + * can generate both Java and C++ bindings. + * + * @hide + */ +interface IConfirmationPromptCallback { + oneway void onConfirmationPromptCompleted(in int result, in byte[] dataThatWasConfirmed); +} diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index 57477f580a425d29ae30a0f7d131fedcc5b0bf6c..738eb6865230126bb8c555f43dc32a41cbcf5a91 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -51,7 +51,6 @@ interface IKeystoreService { String grant(String name, int granteeUid); int ungrant(String name, int granteeUid); long getmtime(String name, int uid); - int duplicate(String srcKey, int srcUid, String destKey, int destUid); int is_hardware_backed(String string); int clear_uid(long uid); @@ -82,4 +81,7 @@ interface IKeystoreService { in String wrappingKeyAlias, in byte[] maskingKey, in KeymasterArguments arguments, in long rootSid, in long fingerprintSid, out KeyCharacteristics characteristics); + int presentConfirmationPrompt(IBinder listener, String promptText, in byte[] extraData, + in String locale, in int uiOptionsAsFlags); + int cancelConfirmationPrompt(IBinder listener); } diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index f409e5b74d23fe1aae1c6d6fdaa41d87d4e5188f..346437032845011c60e59d88e3460090e9c7b2ef 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -73,6 +73,7 @@ public final class KeymasterDefs { public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504; public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505; public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506; + public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507; public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600; public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601; @@ -101,6 +102,7 @@ public final class KeymasterDefs { public static final int KM_ALGORITHM_RSA = 1; public static final int KM_ALGORITHM_EC = 3; public static final int KM_ALGORITHM_AES = 32; + public static final int KM_ALGORITHM_3DES = 33; public static final int KM_ALGORITHM_HMAC = 128; // Block modes. @@ -130,6 +132,7 @@ public final class KeymasterDefs { public static final int KM_ORIGIN_GENERATED = 0; public static final int KM_ORIGIN_IMPORTED = 2; public static final int KM_ORIGIN_UNKNOWN = 3; + public static final int KM_ORIGIN_SECURELY_IMPORTED = 4; // Key usability requirements. public static final int KM_BLOB_STANDALONE = 0; @@ -140,6 +143,7 @@ public final class KeymasterDefs { public static final int KM_PURPOSE_DECRYPT = 1; public static final int KM_PURPOSE_SIGN = 2; public static final int KM_PURPOSE_VERIFY = 3; + public static final int KM_PURPOSE_WRAP = 5; // Key formats. public static final int KM_KEY_FORMAT_X509 = 0; diff --git a/core/java/android/security/keystore/BackwardsCompat.java b/core/java/android/security/keystore/BackwardsCompat.java new file mode 100644 index 0000000000000000000000000000000000000000..69558c4d8aafe243be26bb532c14b9bf4abaa4cd --- /dev/null +++ b/core/java/android/security/keystore/BackwardsCompat.java @@ -0,0 +1,127 @@ +/* + * 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.security.keystore; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * Helpers for converting classes between old and new API, so we can preserve backwards + * compatibility while teamfooding. This will be removed soon. + * + * @hide + */ +class BackwardsCompat { + + + static KeychainProtectionParams toLegacyKeychainProtectionParams( + android.security.keystore.recovery.KeyChainProtectionParams keychainProtectionParams + ) { + return new KeychainProtectionParams.Builder() + .setUserSecretType(keychainProtectionParams.getUserSecretType()) + .setSecret(keychainProtectionParams.getSecret()) + .setLockScreenUiFormat(keychainProtectionParams.getLockScreenUiFormat()) + .setKeyDerivationParams( + toLegacyKeyDerivationParams( + keychainProtectionParams.getKeyDerivationParams())) + .build(); + } + + static KeyDerivationParams toLegacyKeyDerivationParams( + android.security.keystore.recovery.KeyDerivationParams keyDerivationParams + ) { + return new KeyDerivationParams( + keyDerivationParams.getAlgorithm(), keyDerivationParams.getSalt()); + } + + static WrappedApplicationKey toLegacyWrappedApplicationKey( + android.security.keystore.recovery.WrappedApplicationKey wrappedApplicationKey + ) { + return new WrappedApplicationKey.Builder() + .setAlias(wrappedApplicationKey.getAlias()) + .setEncryptedKeyMaterial(wrappedApplicationKey.getEncryptedKeyMaterial()) + .build(); + } + + static android.security.keystore.recovery.KeyDerivationParams fromLegacyKeyDerivationParams( + KeyDerivationParams keyDerivationParams + ) { + return new android.security.keystore.recovery.KeyDerivationParams( + keyDerivationParams.getAlgorithm(), keyDerivationParams.getSalt()); + } + + static android.security.keystore.recovery.WrappedApplicationKey fromLegacyWrappedApplicationKey( + WrappedApplicationKey wrappedApplicationKey + ) { + return new android.security.keystore.recovery.WrappedApplicationKey.Builder() + .setAlias(wrappedApplicationKey.getAlias()) + .setEncryptedKeyMaterial(wrappedApplicationKey.getEncryptedKeyMaterial()) + .build(); + } + + static List + fromLegacyWrappedApplicationKeys(List wrappedApplicationKeys + ) { + return map(wrappedApplicationKeys, BackwardsCompat::fromLegacyWrappedApplicationKey); + } + + static List + fromLegacyKeychainProtectionParams( + List keychainProtectionParams) { + return map(keychainProtectionParams, BackwardsCompat::fromLegacyKeychainProtectionParam); + } + + static android.security.keystore.recovery.KeyChainProtectionParams + fromLegacyKeychainProtectionParam(KeychainProtectionParams keychainProtectionParams) { + return new android.security.keystore.recovery.KeyChainProtectionParams.Builder() + .setUserSecretType(keychainProtectionParams.getUserSecretType()) + .setSecret(keychainProtectionParams.getSecret()) + .setLockScreenUiFormat(keychainProtectionParams.getLockScreenUiFormat()) + .setKeyDerivationParams( + fromLegacyKeyDerivationParams( + keychainProtectionParams.getKeyDerivationParams())) + .build(); + } + + static KeychainSnapshot toLegacyKeychainSnapshot( + android.security.keystore.recovery.KeyChainSnapshot keychainSnapshot + ) { + return new KeychainSnapshot.Builder() + .setCounterId(keychainSnapshot.getCounterId()) + .setEncryptedRecoveryKeyBlob(keychainSnapshot.getEncryptedRecoveryKeyBlob()) + .setTrustedHardwarePublicKey(keychainSnapshot.getTrustedHardwarePublicKey()) + .setSnapshotVersion(keychainSnapshot.getSnapshotVersion()) + .setMaxAttempts(keychainSnapshot.getMaxAttempts()) + .setServerParams(keychainSnapshot.getServerParams()) + .setKeychainProtectionParams( + map(keychainSnapshot.getKeyChainProtectionParams(), + BackwardsCompat::toLegacyKeychainProtectionParams)) + .setWrappedApplicationKeys( + map(keychainSnapshot.getWrappedApplicationKeys(), + BackwardsCompat::toLegacyWrappedApplicationKey)) + .build(); + } + + static List map(List as, Function f) { + ArrayList bs = new ArrayList<>(as.size()); + for (A a : as) { + bs.add(f.apply(a)); + } + return bs; + } +} diff --git a/core/java/android/security/keystore/BadCertificateFormatException.java b/core/java/android/security/keystore/BadCertificateFormatException.java new file mode 100644 index 0000000000000000000000000000000000000000..ddc7bd2366acf7b07c5270b5e6ac7b47f2cb512c --- /dev/null +++ b/core/java/android/security/keystore/BadCertificateFormatException.java @@ -0,0 +1,28 @@ +/* + * 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.security.keystore; + +/** + * Error thrown when the recovery agent supplies an invalid X509 certificate. + * + * @hide + */ +public class BadCertificateFormatException extends RecoveryControllerException { + public BadCertificateFormatException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/DecryptionFailedException.java b/core/java/android/security/keystore/DecryptionFailedException.java new file mode 100644 index 0000000000000000000000000000000000000000..945fcf6f88f203b69493376cd00130ead853dbb4 --- /dev/null +++ b/core/java/android/security/keystore/DecryptionFailedException.java @@ -0,0 +1,30 @@ +/* + * 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.security.keystore; + +/** + * Error thrown when decryption failed, due to an agent error. i.e., using the incorrect key, + * trying to decrypt garbage data, trying to decrypt data that has somehow been corrupted, etc. + * + * @hide + */ +public class DecryptionFailedException extends RecoveryControllerException { + + public DecryptionFailedException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/InternalRecoveryServiceException.java b/core/java/android/security/keystore/InternalRecoveryServiceException.java new file mode 100644 index 0000000000000000000000000000000000000000..85829bed919134620c79016ccc02177035a69bf9 --- /dev/null +++ b/core/java/android/security/keystore/InternalRecoveryServiceException.java @@ -0,0 +1,35 @@ +/* + * 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.security.keystore; + +/** + * An error thrown when something went wrong internally in the recovery service. + * + *

        This is an unexpected error, and indicates a problem with the service itself, rather than the + * caller having performed some kind of illegal action. + * + * @hide + */ +public class InternalRecoveryServiceException extends RecoveryControllerException { + public InternalRecoveryServiceException(String msg) { + super(msg); + } + + public InternalRecoveryServiceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/java/android/security/keystore/KeyDerivationParams.java b/core/java/android/security/keystore/KeyDerivationParams.java index b702accffba118f41784462473b36248cf493a2c..b19cee2d31a4b6419d389219ec5848b33ebc5074 100644 --- a/core/java/android/security/keystore/KeyDerivationParams.java +++ b/core/java/android/security/keystore/KeyDerivationParams.java @@ -61,7 +61,7 @@ public final class KeyDerivationParams implements Parcelable { return new KeyDerivationParams(ALGORITHM_SHA256, salt); } - private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) { + KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) { mAlgorithm = algorithm; mSalt = Preconditions.checkNotNull(salt); } diff --git a/core/java/android/security/keystore/RecoveryMetadata.java b/core/java/android/security/keystore/KeychainProtectionParams.java similarity index 79% rename from core/java/android/security/keystore/RecoveryMetadata.java rename to core/java/android/security/keystore/KeychainProtectionParams.java index 3f0945557a5f382ea998cf3e34b3ddae622dd09b..a940fdc778a9307719fcc7c64930c9d4a2b9e400 100644 --- a/core/java/android/security/keystore/RecoveryMetadata.java +++ b/core/java/android/security/keystore/KeychainProtectionParams.java @@ -28,12 +28,26 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** - * Helper class with data necessary to recover Keystore on a new device. - * It defines UI shown to the user and a way to derive a cryptographic key from user output. + * A {@link KeychainSnapshot} is protected with a key derived from the user's lock screen. This + * class wraps all the data necessary to derive the same key on a recovering device: + * + *

          + *
        • UI parameters for the user's lock screen - so that if e.g., the user was using a pattern, + * the recovering device can display the pattern UI to the user when asking them to enter + * the lock screen from their previous device. + *
        • The algorithm used to derive a key from the user's lock screen, e.g. SHA-256 with a salt. + *
        + * + *

        As such, this data is sent along with the {@link KeychainSnapshot} when syncing the current + * version of the keychain. + * + *

        For now, the recoverable keychain only supports a single layer of protection, which is the + * user's lock screen. In the future, the keychain will support multiple layers of protection + * (e.g. an additional keychain password, along with the lock screen). * * @hide */ -public final class RecoveryMetadata implements Parcelable { +public final class KeychainProtectionParams implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD}) @@ -88,7 +102,7 @@ public final class RecoveryMetadata implements Parcelable { * @link {#clearSecret} to overwrite its value in memory. * @hide */ - public RecoveryMetadata(@UserSecretType int userSecretType, + public KeychainProtectionParams(@UserSecretType int userSecretType, @LockScreenUiFormat int lockScreenUiFormat, @NonNull KeyDerivationParams keyDerivationParams, @NonNull byte[] secret) { @@ -98,7 +112,7 @@ public final class RecoveryMetadata implements Parcelable { mSecret = Preconditions.checkNotNull(secret); } - private RecoveryMetadata() { + private KeychainProtectionParams() { } @@ -141,10 +155,10 @@ public final class RecoveryMetadata implements Parcelable { } /** - * Builder for creating {@link RecoveryMetadata}. + * Builder for creating {@link KeychainProtectionParams}. */ public static class Builder { - private RecoveryMetadata mInstance = new RecoveryMetadata(); + private KeychainProtectionParams mInstance = new KeychainProtectionParams(); /** * Sets user secret type. @@ -198,14 +212,14 @@ public final class RecoveryMetadata implements Parcelable { /** - * Creates a new {@link RecoveryMetadata} instance. + * Creates a new {@link KeychainProtectionParams} instance. * The instance will include default values, if {@link setSecret} * or {@link setUserSecretType} were not called. * * @return new instance * @throws NullPointerException if some required fields were not set. */ - public @NonNull RecoveryMetadata build() { + @NonNull public KeychainProtectionParams build() { if (mInstance.mUserSecretType == null) { mInstance.mUserSecretType = TYPE_LOCKSCREEN; } @@ -235,20 +249,17 @@ public final class RecoveryMetadata implements Parcelable { Arrays.fill(mSecret, (byte) 0); } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public RecoveryMetadata createFromParcel(Parcel in) { - return new RecoveryMetadata(in); + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public KeychainProtectionParams createFromParcel(Parcel in) { + return new KeychainProtectionParams(in); } - public RecoveryMetadata[] newArray(int length) { - return new RecoveryMetadata[length]; + public KeychainProtectionParams[] newArray(int length) { + return new KeychainProtectionParams[length]; } }; - /** - * @hide - */ @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mUserSecretType); @@ -260,7 +271,7 @@ public final class RecoveryMetadata implements Parcelable { /** * @hide */ - protected RecoveryMetadata(Parcel in) { + protected KeychainProtectionParams(Parcel in) { mUserSecretType = in.readInt(); mLockScreenUiFormat = in.readInt(); mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR); diff --git a/core/java/android/security/keystore/KeychainSnapshot.java b/core/java/android/security/keystore/KeychainSnapshot.java new file mode 100644 index 0000000000000000000000000000000000000000..23aec25eb12851b63b125b9974b9b24aa6610403 --- /dev/null +++ b/core/java/android/security/keystore/KeychainSnapshot.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.List; + +/** + * A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot: + * + *

          + *
        • The user's lock screen changes. (A key derived from the user's lock screen is used to + * protected the keychain, which is why this forces a new snapshot.) + *
        • A key is added to or removed from the recoverable keychain. + *
        + * + *

        The snapshot data is also encrypted with the remote trusted hardware's public key, so even + * the recovery agent itself should not be able to decipher the data. The recovery agent sends an + * instance of this to the remote trusted hardware whenever a new snapshot is generated. During a + * recovery flow, the recovery agent retrieves a snapshot from the remote trusted hardware. It then + * sends it to the framework, where it is decrypted using the user's lock screen from their previous + * device. + * + * @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 mKeychainProtectionParams; + private List mEntryRecoveryData; + private byte[] mEncryptedRecoveryKeyBlob; + + /** + * @hide + * Deprecated, consider using builder. + */ + public KeychainSnapshot( + int snapshotVersion, + @NonNull List keychainProtectionParams, + @NonNull List wrappedApplicationKeys, + @NonNull byte[] encryptedRecoveryKeyBlob) { + mSnapshotVersion = snapshotVersion; + mKeychainProtectionParams = + Preconditions.checkCollectionElementsNotNull(keychainProtectionParams, + "keychainProtectionParams"); + mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(wrappedApplicationKeys, + "wrappedApplicationKeys"); + mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob); + } + + private KeychainSnapshot() { + + } + + /** + * Snapshot version for given account. It is incremented when user secret or list of application + * keys changes. + */ + public int getSnapshotVersion() { + return mSnapshotVersion; + } + + /** + * 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 getKeychainProtectionParams() { + return mKeychainProtectionParams; + } + + /** + * List of application keys, with key material encrypted by + * the recovery key ({@link #getEncryptedRecoveryKeyBlob}). + */ + public @NonNull List getWrappedApplicationKeys() { + return mEntryRecoveryData; + } + + /** + * Recovery key blob, encrypted by user secret and recovery service public key. + */ + public @NonNull byte[] getEncryptedRecoveryKeyBlob() { + return mEncryptedRecoveryKeyBlob; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public KeychainSnapshot createFromParcel(Parcel in) { + return new KeychainSnapshot(in); + } + + public KeychainSnapshot[] newArray(int length) { + return new KeychainSnapshot[length]; + } + }; + + /** + * Builder for creating {@link KeychainSnapshot}. + * + * @hide + */ + public static class Builder { + private KeychainSnapshot mInstance = new KeychainSnapshot(); + + /** + * Snapshot version for given account. + * + * @param snapshotVersion The snapshot version + * @return This builder. + */ + public Builder setSnapshotVersion(int snapshotVersion) { + mInstance.mSnapshotVersion = snapshotVersion; + return this; + } + + /** + * 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 + * @return This builder. + */ + public Builder setKeychainProtectionParams( + @NonNull List recoveryMetadata) { + mInstance.mKeychainProtectionParams = recoveryMetadata; + return this; + } + + /** + * List of application keys. + * + * @param entryRecoveryData List of application keys + * @return This builder. + */ + public Builder setWrappedApplicationKeys(List entryRecoveryData) { + mInstance.mEntryRecoveryData = entryRecoveryData; + return this; + } + + /** + * Sets recovery key blob + * + * @param encryptedRecoveryKeyBlob The recovery key blob. + * @return This builder. + */ + public Builder setEncryptedRecoveryKeyBlob(@NonNull byte[] encryptedRecoveryKeyBlob) { + mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob; + return this; + } + + + /** + * Creates a new {@link KeychainSnapshot} instance. + * + * @return new instance + * @throws NullPointerException if some required fields were not set. + */ + @NonNull public KeychainSnapshot build() { + Preconditions.checkCollectionElementsNotNull(mInstance.mKeychainProtectionParams, + "recoveryMetadata"); + Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData, + "entryRecoveryData"); + Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob); + Preconditions.checkNotNull(mInstance.mServerParams); + Preconditions.checkNotNull(mInstance.mPublicKey); + return mInstance; + } + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSnapshotVersion); + out.writeTypedList(mKeychainProtectionParams); + out.writeByteArray(mEncryptedRecoveryKeyBlob); + out.writeTypedList(mEntryRecoveryData); + } + + /** + * @hide + */ + protected KeychainSnapshot(Parcel in) { + mSnapshotVersion = in.readInt(); + mKeychainProtectionParams = in.createTypedArrayList(KeychainProtectionParams.CREATOR); + mEncryptedRecoveryKeyBlob = in.createByteArray(); + mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keystore/LockScreenRequiredException.java b/core/java/android/security/keystore/LockScreenRequiredException.java new file mode 100644 index 0000000000000000000000000000000000000000..b07fb9cdd002e4122d04ac925b4558eb46ee5db5 --- /dev/null +++ b/core/java/android/security/keystore/LockScreenRequiredException.java @@ -0,0 +1,30 @@ +/* + * 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.security.keystore; + +/** + * Error thrown when trying to generate keys for a profile that has no lock screen set. + * + *

        A lock screen must be set, as the lock screen is used to encrypt the snapshot. + * + * @hide + */ +public class LockScreenRequiredException extends RecoveryControllerException { + public LockScreenRequiredException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/RecoveryClaim.java b/core/java/android/security/keystore/RecoveryClaim.java new file mode 100644 index 0000000000000000000000000000000000000000..6f566af1dc7df9d10156ab8fa3c5ad52ab54cabe --- /dev/null +++ b/core/java/android/security/keystore/RecoveryClaim.java @@ -0,0 +1,54 @@ +/* + * 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.security.keystore; + +/** + * An attempt to recover a keychain protected by remote secure hardware. + * + * @hide + */ +public class RecoveryClaim { + + private final RecoverySession mRecoverySession; + private final byte[] mClaimBytes; + + RecoveryClaim(RecoverySession recoverySession, byte[] claimBytes) { + mRecoverySession = recoverySession; + mClaimBytes = claimBytes; + } + + /** + * Returns the session associated with the recovery attempt. This is used to match the symmetric + * key, which remains internal to the framework, for decrypting the claim response. + * + * @return The session data. + */ + public RecoverySession getRecoverySession() { + return mRecoverySession; + } + + /** + * Returns the encrypted claim's bytes. + * + *

        This should be sent by the recovery agent to the remote secure hardware, which will use + * it to decrypt the keychain, before sending it re-encrypted with the session's symmetric key + * to the device. + */ + public byte[] getClaimBytes() { + return mClaimBytes; + } +} diff --git a/core/java/android/security/keystore/RecoveryManager.java b/core/java/android/security/keystore/RecoveryController.java similarity index 52% rename from core/java/android/security/keystore/RecoveryManager.java rename to core/java/android/security/keystore/RecoveryController.java index 99bd284e4d805de552a41bfe00635d9d27e91f06..98e6a20990126faa0d5bbd43972938cfdd1b325b 100644 --- a/core/java/android/security/keystore/RecoveryManager.java +++ b/core/java/android/security/keystore/RecoveryController.java @@ -23,6 +23,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.util.Log; import com.android.internal.widget.ILockSettings; @@ -30,12 +31,26 @@ 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. + * + *

        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. + * + *

        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. + * + *

        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. */ public static final int RECOVERY_STATUS_SYNCED = 0; @@ -46,48 +61,97 @@ public class RecoveryManager { /** Key cannot be synced. */ public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3; + /** + * Failed because no snapshot is yet pending to be synced for the user. + * + * @hide + */ + public static final int ERROR_NO_SNAPSHOT_PENDING = 21; + + /** + * Failed due to an error internal to the recovery service. This is unexpected and indicates + * either a problem with the logic in the service, or a problem with a dependency of the + * service (such as AndroidKeyStore). + * + * @hide + */ + public static final int ERROR_SERVICE_INTERNAL_ERROR = 22; + + /** + * Failed because the user does not have a lock screen set. + * + * @hide + */ + public static final int ERROR_INSECURE_USER = 23; + + /** + * Error thrown when attempting to use a recovery session that has since been closed. + * + * @hide + */ + public static final int ERROR_SESSION_EXPIRED = 24; + + /** + * Failed because the provided certificate was not a valid X509 certificate. + * + * @hide + */ + public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25; + + /** + * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong, + * the data has become corrupted, the data has been tampered with, etc. + * + * @hide + */ + public static final int ERROR_DECRYPTION_FAILED = 26; + + 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. * - *

        In addition, RecoveryManager enforces a delay of three months between + *

        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. * * @param rootCertificateAlias alias of a root certificate preinstalled on the device * @param signedPublicKeyList binary blob a list of X509 certificates and signature - * @throws RecoveryManagerException if signature is invalid, or key rotation was rate - * limited. - * @hide + * @throws BadCertificateFormatException if the {@code signedPublicKeyList} is in a bad format. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ public void initRecoveryService( @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) - throws RecoveryManagerException { + throws BadCertificateFormatException, InternalRecoveryServiceException { try { mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) { + throw new BadCertificateFormatException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); } } @@ -97,17 +161,20 @@ public class RecoveryManager { * * @param account specific to Recovery agent. * @return Data necessary to recover keystore. - * @hide + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ - public @NonNull RecoveryData getRecoveryData(@NonNull byte[] account) - throws RecoveryManagerException { + public @NonNull KeychainSnapshot getRecoveryData(@NonNull byte[] account) + throws InternalRecoveryServiceException { try { - RecoveryData recoveryData = mBinder.getRecoveryData(account); - return recoveryData; + return BackwardsCompat.toLegacyKeychainSnapshot(mBinder.getKeyChainSnapshot()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) { + return null; + } + throw wrapUnexpectedServiceSpecificException(e); } } @@ -118,16 +185,17 @@ public class RecoveryManager { * * @param intent triggered when new snapshot is available. Unregisters listener if the value is * {@code null}. - * @hide + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) - throws RecoveryManagerException { + throws InternalRecoveryServiceException { try { mBinder.setSnapshotCreatedPendingIntent(intent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } @@ -136,11 +204,12 @@ public class RecoveryManager { * version. Version zero is used, if no snapshots were created for the account. * * @return Map from recovery agent accounts to snapshot versions. - * @see RecoveryData#getSnapshotVersion - * @hide + * @see KeychainSnapshot#getSnapshotVersion + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ public @NonNull Map getRecoverySnapshotVersions() - throws RecoveryManagerException { + throws InternalRecoveryServiceException { try { // IPC doesn't support generic Maps. @SuppressWarnings("unchecked") @@ -150,27 +219,27 @@ public class RecoveryManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } /** * Server parameters used to generate new recovery key blobs. This value will be included in - * {@code RecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included + * {@code KeychainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included * in vaultParams {@link #startRecoverySession} * * @param serverParams included in recovery key blob. * @see #getRecoveryData - * @throws RecoveryManagerException If parameters rotation is rate limited. - * @hide + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ - public void setServerParams(byte[] serverParams) throws RecoveryManagerException { + public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException { try { mBinder.setServerParams(serverParams); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } @@ -183,16 +252,18 @@ public class RecoveryManager { * @param aliases List of application-specific key aliases. If the array is empty, updates the * status for all existing recoverable keys. * @param status Status specific to recovery agent. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ public void setRecoveryStatus( @NonNull String packageName, @Nullable String[] aliases, int status) - throws NameNotFoundException, RecoveryManagerException { + throws NameNotFoundException, InternalRecoveryServiceException { try { mBinder.setRecoveryStatus(packageName, aliases, status); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } @@ -209,10 +280,10 @@ public class RecoveryManager { * * @return {@code Map} from KeyStore alias to recovery status. * @see #setRecoveryStatus - * @hide + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ - public Map getRecoveryStatus() - throws RecoveryManagerException { + public Map getRecoveryStatus() throws InternalRecoveryServiceException { try { // IPC doesn't support generic Maps. @SuppressWarnings("unchecked") @@ -222,7 +293,7 @@ public class RecoveryManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } @@ -230,36 +301,40 @@ public class RecoveryManager { * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them * is necessary to recover data. * - * @param secretTypes {@link RecoveryMetadata#TYPE_LOCKSCREEN} or {@link - * RecoveryMetadata#TYPE_CUSTOM_PASSWORD} + * @param secretTypes {@link KeychainProtectionParams#TYPE_LOCKSCREEN} or {@link + * KeychainProtectionParams#TYPE_CUSTOM_PASSWORD} + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ public void setRecoverySecretTypes( - @NonNull @RecoveryMetadata.UserSecretType int[] secretTypes) - throws RecoveryManagerException { + @NonNull @KeychainProtectionParams.UserSecretType int[] secretTypes) + throws InternalRecoveryServiceException { try { mBinder.setRecoverySecretTypes(secretTypes); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } /** * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is - * necessary to generate RecoveryData. + * necessary to generate KeychainSnapshot. * * @return list of recovery secret types - * @see RecoveryData + * @see KeychainSnapshot + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ - public @NonNull @RecoveryMetadata.UserSecretType int[] getRecoverySecretTypes() - throws RecoveryManagerException { + public @NonNull @KeychainProtectionParams.UserSecretType int[] getRecoverySecretTypes() + throws InternalRecoveryServiceException { try { return mBinder.getRecoverySecretTypes(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } @@ -269,37 +344,18 @@ public class RecoveryManager { * called. * * @return list of recovery secret types - * @hide + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ - public @NonNull @RecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes() - throws RecoveryManagerException { + @NonNull + public @KeychainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes() + throws InternalRecoveryServiceException { try { return mBinder.getPendingRecoverySecretTypes(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); - } - } - - /** - * Method notifies KeyStore that a user-generated secret is available. This method generates a - * symmetric session key which a trusted remote device can use to return a recovery key. Caller - * should use {@link RecoveryMetadata#clearSecret} to override the secret value in - * memory. - * - * @param recoverySecret user generated secret together with parameters necessary to regenerate - * it on a new device. - * @hide - */ - public void recoverySecretAvailable(@NonNull RecoveryMetadata recoverySecret) - throws RecoveryManagerException { - try { - mBinder.recoverySecretAvailable(recoverySecret); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } @@ -308,7 +364,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. @@ -317,56 +372,90 @@ public class RecoveryManager { * @param vaultChallenge Data passed from server for this recovery session and used to prevent * replay attacks * @param secrets Secrets provided by user, the method only uses type and secret fields. - * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains - * a proof of user secrets, session symmetric key and parameters necessary to identify the - * counter with the number of failed recovery attempts. + * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is + * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric + * key and parameters necessary to identify the counter with the number of failed recovery + * attempts. + * @throws BadCertificateFormatException if the {@code verifierPublicKey} is in an incorrect + * format. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ - public @NonNull byte[] startRecoverySession( - @NonNull String sessionId, + @NonNull public RecoveryClaim startRecoverySession( @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, - @NonNull List secrets) - throws RecoveryManagerException { + @NonNull List secrets) + throws BadCertificateFormatException, InternalRecoveryServiceException { try { + RecoverySession recoverySession = RecoverySession.newInstance(this); byte[] recoveryClaim = mBinder.startRecoverySession( - sessionId, + recoverySession.getSessionId(), verifierPublicKey, vaultParams, vaultChallenge, - secrets); - return recoveryClaim; + BackwardsCompat.fromLegacyKeychainProtectionParams(secrets)); + return new RecoveryClaim(recoverySession, recoveryClaim); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) { + throw new BadCertificateFormatException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); } } /** * Imports keys. * - * @param sessionId Id for recovery session, same as in - * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}. + * @param session Related recovery session, as originally created by invoking + * {@link #startRecoverySession(byte[], byte[], byte[], List)}. * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob * and session. KeyStore only uses package names from the application info in {@link - * EntryRecoveryData}. Caller is responsibility to perform certificates check. + * WrappedApplicationKey}. Caller is responsibility to perform certificates check. * @return Map from alias to raw key material. + * @throws SessionExpiredException if {@code session} has since been closed. + * @throws DecryptionFailedException if unable to decrypt the snapshot. + * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service. */ public Map recoverKeys( - @NonNull String sessionId, + @NonNull RecoverySession session, @NonNull byte[] recoveryKeyBlob, - @NonNull List applicationKeys) - throws RecoveryManagerException { + @NonNull List applicationKeys) + throws SessionExpiredException, DecryptionFailedException, + InternalRecoveryServiceException { try { return (Map) mBinder.recoverKeys( - sessionId, recoveryKeyBlob, applicationKeys); + session.getSessionId(), + recoveryKeyBlob, + BackwardsCompat.fromLegacyWrappedApplicationKeys(applicationKeys)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + if (e.errorCode == ERROR_DECRYPTION_FAILED) { + throw new DecryptionFailedException(e.getMessage()); + } + if (e.errorCode == ERROR_SESSION_EXPIRED) { + throw new SessionExpiredException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Deletes all data associated with {@code session}. Should not be invoked directly but via + * {@link RecoverySession#close()}. + * + * @hide + */ + void closeSession(RecoverySession session) { + try { + mBinder.closeSession(session.getSessionId()); + } catch (RemoteException | ServiceSpecificException e) { + Log.e(TAG, "Unexpected error trying to close session", e); } } @@ -375,17 +464,23 @@ public class RecoveryManager { * raw material of the key. * * @param alias The key alias. - * @throws RecoveryManagerException if an error occurred generating and storing the - * key. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * @throws LockScreenRequiredException if the user has not set a lock screen. This is required + * to generate recoverable keys, as the snapshots are encrypted using a key derived from the + * lock screen. */ public byte[] generateAndStoreKey(@NonNull String alias) - throws RecoveryManagerException { + throws InternalRecoveryServiceException, LockScreenRequiredException { try { return mBinder.generateAndStoreKey(alias); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + if (e.errorCode == ERROR_INSECURE_USER) { + throw new LockScreenRequiredException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); } } @@ -393,14 +488,28 @@ public class RecoveryManager { * Removes a key called {@code alias} from the recoverable key store. * * @param alias The key alias. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. */ - public void removeKey(@NonNull String alias) throws RecoveryManagerException { + public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException { try { mBinder.removeKey(alias); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { - throw RecoveryManagerException.fromServiceSpecificException(e); + throw wrapUnexpectedServiceSpecificException(e); } } + + private InternalRecoveryServiceException wrapUnexpectedServiceSpecificException( + ServiceSpecificException e) { + if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) { + return new InternalRecoveryServiceException(e.getMessage()); + } + + // Should never happen. If it does, it's a bug, and we need to update how the method that + // called this throws its exceptions. + return new InternalRecoveryServiceException("Unexpected error code for method: " + + e.errorCode, e); + } } diff --git a/core/java/android/security/keystore/RecoveryControllerException.java b/core/java/android/security/keystore/RecoveryControllerException.java new file mode 100644 index 0000000000000000000000000000000000000000..5b806b75ebabfb8a522d52ab10315c15bf0c60db --- /dev/null +++ b/core/java/android/security/keystore/RecoveryControllerException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import java.security.GeneralSecurityException; + +/** + * Base exception for errors thrown by {@link RecoveryController}. + * + * @hide + */ +public abstract class RecoveryControllerException extends GeneralSecurityException { + RecoveryControllerException() { } + + RecoveryControllerException(String msg) { + super(msg); + } + + public RecoveryControllerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/java/android/security/keystore/RecoveryData.java b/core/java/android/security/keystore/RecoveryData.java deleted file mode 100644 index 897aa18a0e188a579fe2dcbb6e6c52f9989ca130..0000000000000000000000000000000000000000 --- a/core/java/android/security/keystore/RecoveryData.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore; - -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.internal.util.Preconditions; - -import java.util.List; - -/** - * Helper class which returns data necessary to recover keys. - * Contains - * - *

          - *
        • Snapshot version. - *
        • Recovery metadata with UI and key derivation parameters. - *
        • List of application keys encrypted by recovery key. - *
        • Encrypted recovery key. - *
        - * - * @hide - */ -public final class RecoveryData implements Parcelable { - private int mSnapshotVersion; - private List mRecoveryMetadata; - private List mEntryRecoveryData; - private byte[] mEncryptedRecoveryKeyBlob; - - /** - * @hide - * Deprecated, consider using builder. - */ - public RecoveryData( - int snapshotVersion, - @NonNull List recoveryMetadata, - @NonNull List entryRecoveryData, - @NonNull byte[] encryptedRecoveryKeyBlob) { - mSnapshotVersion = snapshotVersion; - mRecoveryMetadata = - Preconditions.checkCollectionElementsNotNull(recoveryMetadata, "recoveryMetadata"); - mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(entryRecoveryData, - "entryRecoveryData"); - mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob); - } - - private RecoveryData() { - - } - - /** - * Snapshot version for given account. It is incremented when user secret or list of application - * keys changes. - */ - public int getSnapshotVersion() { - return mSnapshotVersion; - } - - /** - * UI and key derivation parameters. Note that combination of secrets may be used. - */ - public @NonNull List getRecoveryMetadata() { - return mRecoveryMetadata; - } - - /** - * List of application keys, with key material encrypted by - * the recovery key ({@link #getEncryptedRecoveryKeyBlob}). - */ - public @NonNull List getEntryRecoveryData() { - return mEntryRecoveryData; - } - - /** - * Recovery key blob, encrypted by user secret and recovery service public key. - */ - public @NonNull byte[] getEncryptedRecoveryKeyBlob() { - return mEncryptedRecoveryKeyBlob; - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public RecoveryData createFromParcel(Parcel in) { - return new RecoveryData(in); - } - - public RecoveryData[] newArray(int length) { - return new RecoveryData[length]; - } - }; - - /** - * Builder for creating {@link RecoveryData}. - */ - public static class Builder { - private RecoveryData mInstance = new RecoveryData(); - - /** - * Snapshot version for given account. - * - * @param snapshotVersion The snapshot version - * @return This builder. - */ - public Builder setSnapshotVersion(int snapshotVersion) { - mInstance.mSnapshotVersion = snapshotVersion; - return this; - } - - /** - * Sets UI and key derivation parameters - * - * @param recoveryMetadata The UI and key derivation parameters - * @return This builder. - */ - public Builder setRecoveryMetadata(@NonNull List recoveryMetadata) { - mInstance.mRecoveryMetadata = recoveryMetadata; - return this; - } - - /** - * List of application keys. - * - * @param entryRecoveryData List of application keys - * @return This builder. - */ - public Builder setEntryRecoveryData(List entryRecoveryData) { - mInstance.mEntryRecoveryData = entryRecoveryData; - return this; - } - - /** - * Sets recovery key blob - * - * @param encryptedRecoveryKeyBlob The recovery key blob. - * @return This builder. - */ - public Builder setEncryptedRecoveryKeyBlob(@NonNull byte[] encryptedRecoveryKeyBlob) { - mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob; - return this; - } - - - /** - * Creates a new {@link RecoveryData} instance. - * - * @return new instance - * @throws NullPointerException if some required fields were not set. - */ - public @NonNull RecoveryData build() { - Preconditions.checkCollectionElementsNotNull(mInstance.mRecoveryMetadata, - "recoveryMetadata"); - Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData, - "entryRecoveryData"); - Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob); - return mInstance; - } - } - - /** - * @hide - */ - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mSnapshotVersion); - out.writeTypedList(mRecoveryMetadata); - out.writeByteArray(mEncryptedRecoveryKeyBlob); - out.writeTypedList(mEntryRecoveryData); - } - - /** - * @hide - */ - protected RecoveryData(Parcel in) { - mSnapshotVersion = in.readInt(); - mRecoveryMetadata = in.createTypedArrayList(RecoveryMetadata.CREATOR); - mEncryptedRecoveryKeyBlob = in.createByteArray(); - mEntryRecoveryData = in.createTypedArrayList(EntryRecoveryData.CREATOR); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/security/keystore/RecoveryManagerException.java b/core/java/android/security/keystore/RecoveryManagerException.java deleted file mode 100644 index 344718aa31d4fd06d83c7fb4b36d51f936eae2c4..0000000000000000000000000000000000000000 --- a/core/java/android/security/keystore/RecoveryManagerException.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore; - -import android.os.ServiceSpecificException; - -/** - * Exception thrown by {@link RecoveryManager} methods. - * - * @hide - */ -public class RecoveryManagerException extends Exception { - /** - * Failed because the loader has not been initialized with a recovery public key yet. - */ - public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; - - /** - * Failed because no snapshot is yet pending to be synced for the user. - */ - public static final int ERROR_NO_SNAPSHOT_PENDING = 21; - - /** - * Failed due to an error internal to AndroidKeyStore. - */ - public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22; - - /** - * Failed because the user does not have a lock screen set. - */ - public static final int ERROR_INSECURE_USER = 24; - - /** - * Failed because of an internal database error. - */ - public static final int ERROR_DATABASE_ERROR = 25; - - /** - * Failed because the provided certificate was not a valid X509 certificate. - */ - public static final int ERROR_BAD_X509_CERTIFICATE = 26; - - /** - * Should never be thrown - some algorithm that all AOSP implementations must support is - * not available. - */ - public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27; - - /** - * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong, - * the data has become corrupted, the data has been tampered with, etc. - */ - public static final int ERROR_DECRYPTION_FAILED = 28; - - /** - * Rate limit is enforced to prevent using too many trusted remote devices, since each device - * can have its own number of user secret guesses allowed. - * - * @hide - */ - public static final int ERROR_RATE_LIMIT_EXCEEDED = 29; - - private int mErrorCode; - - /** - * Creates new {@link #RecoveryManagerException} instance from the error code. - * - * @param errorCode An error code, as listed at the top of this file. - * @param message The associated error message. - * @hide - */ - public static RecoveryManagerException fromErrorCode( - int errorCode, String message) { - return new RecoveryManagerException(errorCode, message); - } - /** - * Creates new {@link #RecoveryManagerException} from {@link - * ServiceSpecificException}. - * - * @param e exception thrown on service side. - * @hide - */ - static RecoveryManagerException fromServiceSpecificException( - ServiceSpecificException e) throws RecoveryManagerException { - throw RecoveryManagerException.fromErrorCode(e.errorCode, e.getMessage()); - } - - private RecoveryManagerException(int errorCode, String message) { - super(message); - mErrorCode = errorCode; - } - - /** Returns errorCode. */ - public int getErrorCode() { - return mErrorCode; - } -} diff --git a/core/java/android/security/keystore/RecoverySession.java b/core/java/android/security/keystore/RecoverySession.java new file mode 100644 index 0000000000000000000000000000000000000000..ae8d91af3230fe4726db0e443ca191f5e3eb5edf --- /dev/null +++ b/core/java/android/security/keystore/RecoverySession.java @@ -0,0 +1,71 @@ +/* + * 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.security.keystore; + +import java.security.SecureRandom; + +/** + * Session to recover a {@link KeychainSnapshot} from the remote trusted hardware, initiated by a + * recovery agent. + * + * @hide + */ +public class RecoverySession implements AutoCloseable { + + private static final int SESSION_ID_LENGTH_BYTES = 16; + + private final String mSessionId; + private final RecoveryController mRecoveryController; + + private RecoverySession(RecoveryController recoveryController, String sessionId) { + mRecoveryController = recoveryController; + mSessionId = sessionId; + } + + /** + * A new session, started by {@code recoveryManager}. + */ + static RecoverySession newInstance(RecoveryController recoveryController) { + return new RecoverySession(recoveryController, newSessionId()); + } + + /** + * Returns a new random session ID. + */ + private static String newSessionId() { + SecureRandom secureRandom = new SecureRandom(); + byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES]; + secureRandom.nextBytes(sessionId); + StringBuilder sb = new StringBuilder(); + for (byte b : sessionId) { + sb.append(Byte.toHexString(b, /*upperCase=*/ false)); + } + return sb.toString(); + } + + /** + * An internal session ID, used by the framework to match recovery claims to snapshot responses. + */ + String getSessionId() { + return mSessionId; + } + + @Override + public void close() { + mRecoveryController.closeSession(this); + } +} diff --git a/core/java/android/security/keystore/SessionExpiredException.java b/core/java/android/security/keystore/SessionExpiredException.java new file mode 100644 index 0000000000000000000000000000000000000000..f13e2060262565ece4528a20516e2aefb781925d --- /dev/null +++ b/core/java/android/security/keystore/SessionExpiredException.java @@ -0,0 +1,28 @@ +/* + * 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.security.keystore; + +/** + * Error thrown when attempting to use a {@link RecoverySession} that has since expired. + * + * @hide + */ +public class SessionExpiredException extends RecoveryControllerException { + public SessionExpiredException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/EntryRecoveryData.java b/core/java/android/security/keystore/WrappedApplicationKey.java similarity index 78% rename from core/java/android/security/keystore/EntryRecoveryData.java rename to core/java/android/security/keystore/WrappedApplicationKey.java index aaca3fe8b2cf2ecdbcda8c53dd5cdaaccdc95617..522bb9557b8d738af444708afde56abde2dc6412 100644 --- a/core/java/android/security/keystore/EntryRecoveryData.java +++ b/core/java/android/security/keystore/WrappedApplicationKey.java @@ -35,16 +35,16 @@ import com.android.internal.util.Preconditions; * * @hide */ -public final class EntryRecoveryData implements Parcelable { +public final class WrappedApplicationKey implements Parcelable { private String mAlias; // The only supported format is AES-256 symmetric key. private byte[] mEncryptedKeyMaterial; /** - * Builder for creating {@link EntryRecoveryData}. + * Builder for creating {@link WrappedApplicationKey}. */ public static class Builder { - private EntryRecoveryData mInstance = new EntryRecoveryData(); + private WrappedApplicationKey mInstance = new WrappedApplicationKey(); /** * Sets Application-specific alias of the key. @@ -70,19 +70,19 @@ public final class EntryRecoveryData implements Parcelable { } /** - * Creates a new {@link EntryRecoveryData} instance. + * Creates a new {@link WrappedApplicationKey} instance. * * @return new instance * @throws NullPointerException if some required fields were not set. */ - public @NonNull EntryRecoveryData build() { + @NonNull public WrappedApplicationKey build() { Preconditions.checkNotNull(mInstance.mAlias); Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial); return mInstance; } } - private EntryRecoveryData() { + private WrappedApplicationKey() { } @@ -90,7 +90,7 @@ public final class EntryRecoveryData implements Parcelable { * Deprecated - consider using Builder. * @hide */ - public EntryRecoveryData(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) { + public WrappedApplicationKey(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) { mAlias = Preconditions.checkNotNull(alias); mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial); } @@ -109,14 +109,14 @@ public final class EntryRecoveryData implements Parcelable { return mEncryptedKeyMaterial; } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public EntryRecoveryData createFromParcel(Parcel in) { - return new EntryRecoveryData(in); + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public WrappedApplicationKey createFromParcel(Parcel in) { + return new WrappedApplicationKey(in); } - public EntryRecoveryData[] newArray(int length) { - return new EntryRecoveryData[length]; + public WrappedApplicationKey[] newArray(int length) { + return new WrappedApplicationKey[length]; } }; @@ -132,7 +132,7 @@ public final class EntryRecoveryData implements Parcelable { /** * @hide */ - protected EntryRecoveryData(Parcel in) { + protected WrappedApplicationKey(Parcel in) { mAlias = in.readString(); mEncryptedKeyMaterial = in.createByteArray(); } diff --git a/core/java/android/security/keystore/recovery/BadCertificateFormatException.java b/core/java/android/security/keystore/recovery/BadCertificateFormatException.java new file mode 100644 index 0000000000000000000000000000000000000000..e0781a520838c68f9017ec95e2df4f1075618811 --- /dev/null +++ b/core/java/android/security/keystore/recovery/BadCertificateFormatException.java @@ -0,0 +1,29 @@ +/* + * 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.security.keystore.recovery; + +/** + * Error thrown when the recovery agent supplies an invalid X509 certificate. + * + * @hide + * Deprecated + */ +public class BadCertificateFormatException extends RecoveryControllerException { + public BadCertificateFormatException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/recovery/DecryptionFailedException.java b/core/java/android/security/keystore/recovery/DecryptionFailedException.java new file mode 100644 index 0000000000000000000000000000000000000000..af00e053ae70df51a0ee7e06de78c7b66f22233e --- /dev/null +++ b/core/java/android/security/keystore/recovery/DecryptionFailedException.java @@ -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.security.keystore.recovery; + +import android.annotation.SystemApi; + +import java.security.GeneralSecurityException; + +/** + * Error thrown when decryption failed, due to an agent error. i.e., using the incorrect key, + * trying to decrypt garbage data, trying to decrypt data that has somehow been corrupted, etc. + * + * @hide + */ +@SystemApi +public class DecryptionFailedException extends GeneralSecurityException { + public DecryptionFailedException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java b/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java new file mode 100644 index 0000000000000000000000000000000000000000..218d26eb565bb6028dc8f66638f53df8c0d9ffe6 --- /dev/null +++ b/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java @@ -0,0 +1,39 @@ +/* + * 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.security.keystore.recovery; + +import android.annotation.SystemApi; + +import java.security.GeneralSecurityException; +/** + * An error thrown when something went wrong internally in the recovery service. + * + *

        This is an unexpected error, and indicates a problem with the service itself, rather than the + * caller having performed some kind of illegal action. + * + * @hide + */ +@SystemApi +public class InternalRecoveryServiceException extends GeneralSecurityException { + public InternalRecoveryServiceException(String msg) { + super(msg); + } + + public InternalRecoveryServiceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/java/android/os/Seccomp.java b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.aidl similarity index 84% rename from core/java/android/os/Seccomp.java rename to core/java/android/security/keystore/recovery/KeyChainProtectionParams.aidl index f14e93fe9403a48a9d5a56bdc12d897c408e94b5..58edc84111fa7ab7db6bd430475e764dfacb225b 100644 --- a/core/java/android/os/Seccomp.java +++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.aidl @@ -14,11 +14,7 @@ * limitations under the License. */ -package android.os; +package android.security.keystore.recovery; -/** - * @hide - */ -public final class Seccomp { - public static final native void setPolicy(); -} +/* @hide */ +parcelable KeyChainProtectionParams; diff --git a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java new file mode 100644 index 0000000000000000000000000000000000000000..a43952a8104871716cdea793c12f5ef118d9faea --- /dev/null +++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore.recovery; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** + * A {@link KeyChainSnapshot} is protected with a key derived from the user's lock screen. This + * class wraps all the data necessary to derive the same key on a recovering device: + * + *

          + *
        • UI parameters for the user's lock screen - so that if e.g., the user was using a pattern, + * the recovering device can display the pattern UI to the user when asking them to enter + * the lock screen from their previous device. + *
        • The algorithm used to derive a key from the user's lock screen, e.g. SHA-256 with a salt. + *
        + * + *

        As such, this data is sent along with the {@link KeyChainSnapshot} when syncing the current + * version of the keychain. + * + *

        For now, the recoverable keychain only supports a single layer of protection, which is the + * user's lock screen. In the future, the keychain will support multiple layers of protection + * (e.g. an additional keychain password, along with the lock screen). + * + * @hide + */ +@SystemApi +public final class KeyChainProtectionParams implements Parcelable { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"TYPE_"}, value = {TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD}) + public @interface UserSecretType { + } + + /** + * Lockscreen secret is required to recover KeyStore. + */ + public static final int TYPE_LOCKSCREEN = 100; + + /** + * Custom passphrase, unrelated to lock screen, is required to recover KeyStore. + */ + public static final int TYPE_CUSTOM_PASSWORD = 101; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"UI_FORMAT_"}, value = {UI_FORMAT_PIN, UI_FORMAT_PASSWORD, UI_FORMAT_PATTERN}) + public @interface LockScreenUiFormat { + } + + /** + * Pin with digits only. + */ + public static final int UI_FORMAT_PIN = 1; + + /** + * Password. String with latin-1 characters only. + */ + public static final int UI_FORMAT_PASSWORD = 2; + + /** + * Pattern with 3 by 3 grid. + */ + public static final int UI_FORMAT_PATTERN = 3; + + @UserSecretType + private Integer mUserSecretType; + + @LockScreenUiFormat + private Integer mLockScreenUiFormat; + + /** + * Parameters of the key derivation function, including algorithm, difficulty, salt. + */ + private KeyDerivationParams mKeyDerivationParams; + private byte[] mSecret; // Derived from user secret. The field must have limited visibility. + + /** + * @param secret Constructor creates a reference to the secret. Caller must use + * @link {#clearSecret} to overwrite its value in memory. + * @hide + */ + public KeyChainProtectionParams(@UserSecretType int userSecretType, + @LockScreenUiFormat int lockScreenUiFormat, + @NonNull KeyDerivationParams keyDerivationParams, + @NonNull byte[] secret) { + mUserSecretType = userSecretType; + mLockScreenUiFormat = lockScreenUiFormat; + mKeyDerivationParams = Preconditions.checkNotNull(keyDerivationParams); + mSecret = Preconditions.checkNotNull(secret); + } + + private KeyChainProtectionParams() { + + } + + /** + * @see TYPE_LOCKSCREEN + * @see TYPE_CUSTOM_PASSWORD + */ + public @UserSecretType int getUserSecretType() { + return mUserSecretType; + } + + /** + * Specifies UX shown to user during recovery. + * Default value is {@code UI_FORMAT_LOCKSCREEN} + * + * @see UI_FORMAT_PIN + * @see UI_FORMAT_PASSWORD + * @see UI_FORMAT_PATTERN + */ + public @LockScreenUiFormat int getLockScreenUiFormat() { + return mLockScreenUiFormat; + } + + /** + * Specifies function used to derive symmetric key from user input + * Format is defined in separate util class. + */ + public @NonNull KeyDerivationParams getKeyDerivationParams() { + return mKeyDerivationParams; + } + + /** + * Secret derived from user input. + * Default value is empty array + * + * @return secret or empty array + */ + public @NonNull byte[] getSecret() { + return mSecret; + } + + /** + * Builder for creating {@link KeyChainProtectionParams}. + */ + public static class Builder { + private KeyChainProtectionParams mInstance = new KeyChainProtectionParams(); + + /** + * Sets user secret type. + * + * @see TYPE_LOCKSCREEN + * @see TYPE_CUSTOM_PASSWORD + * @param userSecretType The secret type + * @return This builder. + */ + public Builder setUserSecretType(@UserSecretType int userSecretType) { + mInstance.mUserSecretType = userSecretType; + return this; + } + + /** + * Sets UI format. + * + * @see UI_FORMAT_PIN + * @see UI_FORMAT_PASSWORD + * @see UI_FORMAT_PATTERN + * @param lockScreenUiFormat The UI format + * @return This builder. + */ + public Builder setLockScreenUiFormat(@LockScreenUiFormat int lockScreenUiFormat) { + mInstance.mLockScreenUiFormat = lockScreenUiFormat; + return this; + } + + /** + * Sets parameters of the key derivation function. + * + * @param keyDerivationParams Key derivation Params + * @return This builder. + */ + public Builder setKeyDerivationParams(@NonNull KeyDerivationParams + keyDerivationParams) { + mInstance.mKeyDerivationParams = keyDerivationParams; + return this; + } + + /** + * Secret derived from user input, or empty array. + * + * @param secret The secret. + * @return This builder. + */ + public Builder setSecret(@NonNull byte[] secret) { + mInstance.mSecret = secret; + return this; + } + + + /** + * Creates a new {@link KeyChainProtectionParams} instance. + * The instance will include default values, if {@link setSecret} + * or {@link setUserSecretType} were not called. + * + * @return new instance + * @throws NullPointerException if some required fields were not set. + */ + @NonNull public KeyChainProtectionParams build() { + if (mInstance.mUserSecretType == null) { + mInstance.mUserSecretType = TYPE_LOCKSCREEN; + } + Preconditions.checkNotNull(mInstance.mLockScreenUiFormat); + Preconditions.checkNotNull(mInstance.mKeyDerivationParams); + if (mInstance.mSecret == null) { + mInstance.mSecret = new byte[]{}; + } + return mInstance; + } + } + + /** + * Removes secret from memory than object is no longer used. + * Since finalizer call is not reliable, please use @link {#clearSecret} directly. + */ + @Override + protected void finalize() throws Throwable { + clearSecret(); + super.finalize(); + } + + /** + * Fills mSecret with zeroes. + */ + public void clearSecret() { + Arrays.fill(mSecret, (byte) 0); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public KeyChainProtectionParams createFromParcel(Parcel in) { + return new KeyChainProtectionParams(in); + } + + public KeyChainProtectionParams[] newArray(int length) { + return new KeyChainProtectionParams[length]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mUserSecretType); + out.writeInt(mLockScreenUiFormat); + out.writeTypedObject(mKeyDerivationParams, flags); + out.writeByteArray(mSecret); + } + + /** + * @hide + */ + protected KeyChainProtectionParams(Parcel in) { + mUserSecretType = in.readInt(); + mLockScreenUiFormat = in.readInt(); + mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR); + mSecret = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keystore/EntryRecoveryData.aidl b/core/java/android/security/keystore/recovery/KeyChainSnapshot.aidl similarity index 89% rename from core/java/android/security/keystore/EntryRecoveryData.aidl rename to core/java/android/security/keystore/recovery/KeyChainSnapshot.aidl index c6c20e337bc89e935d829b6c583cdaffa1bedf40..d02a2eadb8616716b310391eb36544b6a1071536 100644 --- a/core/java/android/security/keystore/EntryRecoveryData.aidl +++ b/core/java/android/security/keystore/recovery/KeyChainSnapshot.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security.keystore; +package android.security.keystore.recovery; /* @hide */ -parcelable EntryRecoveryData; +parcelable KeyChainSnapshot; diff --git a/core/java/android/security/keystore/recovery/KeyChainSnapshot.java b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java new file mode 100644 index 0000000000000000000000000000000000000000..df535ed9d3ef6aa1570ce3938eae1abee50f4a39 --- /dev/null +++ b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore.recovery; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.List; + +/** + * A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot: + * + *

          + *
        • The user's lock screen changes. (A key derived from the user's lock screen is used to + * protected the keychain, which is why this forces a new snapshot.) + *
        • A key is added to or removed from the recoverable keychain. + *
        + * + *

        The snapshot data is also encrypted with the remote trusted hardware's public key, so even + * the recovery agent itself should not be able to decipher the data. The recovery agent sends an + * instance of this to the remote trusted hardware whenever a new snapshot is generated. During a + * recovery flow, the recovery agent retrieves a snapshot from the remote trusted hardware. It then + * sends it to the framework, where it is decrypted using the user's lock screen from their previous + * device. + * + * @hide + */ +@SystemApi +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 mKeyChainProtectionParams; + private List mEntryRecoveryData; + private byte[] mEncryptedRecoveryKeyBlob; + + /** + * @hide + * Deprecated, consider using builder. + */ + public KeyChainSnapshot( + int snapshotVersion, + @NonNull List keyChainProtectionParams, + @NonNull List wrappedApplicationKeys, + @NonNull byte[] encryptedRecoveryKeyBlob) { + mSnapshotVersion = snapshotVersion; + mKeyChainProtectionParams = + Preconditions.checkCollectionElementsNotNull(keyChainProtectionParams, + "KeyChainProtectionParams"); + mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(wrappedApplicationKeys, + "wrappedApplicationKeys"); + mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob); + } + + private KeyChainSnapshot() { + + } + + /** + * Snapshot version for given account. It is incremented when user secret or list of application + * keys changes. + */ + public int getSnapshotVersion() { + return mSnapshotVersion; + } + + /** + * 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 getKeyChainProtectionParams() { + return mKeyChainProtectionParams; + } + + /** + * List of application keys, with key material encrypted by + * the recovery key ({@link #getEncryptedRecoveryKeyBlob}). + */ + public @NonNull List getWrappedApplicationKeys() { + return mEntryRecoveryData; + } + + /** + * Recovery key blob, encrypted by user secret and recovery service public key. + */ + public @NonNull byte[] getEncryptedRecoveryKeyBlob() { + return mEncryptedRecoveryKeyBlob; + } + + public static final Creator CREATOR = + new Creator() { + public KeyChainSnapshot createFromParcel(Parcel in) { + return new KeyChainSnapshot(in); + } + + public KeyChainSnapshot[] newArray(int length) { + return new KeyChainSnapshot[length]; + } + }; + + /** + * Builder for creating {@link KeyChainSnapshot}. + * @hide + */ + public static class Builder { + private KeyChainSnapshot mInstance = new KeyChainSnapshot(); + + /** + * Snapshot version for given account. + * + * @param snapshotVersion The snapshot version + * @return This builder. + */ + public Builder setSnapshotVersion(int snapshotVersion) { + mInstance.mSnapshotVersion = snapshotVersion; + return this; + } + + /** + * 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 + * @return This builder. + */ + public Builder setKeyChainProtectionParams( + @NonNull List recoveryMetadata) { + mInstance.mKeyChainProtectionParams = recoveryMetadata; + return this; + } + + /** + * List of application keys. + * + * @param entryRecoveryData List of application keys + * @return This builder. + */ + public Builder setWrappedApplicationKeys(List entryRecoveryData) { + mInstance.mEntryRecoveryData = entryRecoveryData; + return this; + } + + /** + * Sets recovery key blob + * + * @param encryptedRecoveryKeyBlob The recovery key blob. + * @return This builder. + */ + public Builder setEncryptedRecoveryKeyBlob(@NonNull byte[] encryptedRecoveryKeyBlob) { + mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob; + return this; + } + + + /** + * Creates a new {@link KeyChainSnapshot} instance. + * + * @return new instance + * @throws NullPointerException if some required fields were not set. + */ + @NonNull public KeyChainSnapshot build() { + Preconditions.checkCollectionElementsNotNull(mInstance.mKeyChainProtectionParams, + "recoveryMetadata"); + Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData, + "entryRecoveryData"); + Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob); + Preconditions.checkNotNull(mInstance.mServerParams); + Preconditions.checkNotNull(mInstance.mPublicKey); + return mInstance; + } + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSnapshotVersion); + out.writeTypedList(mKeyChainProtectionParams); + out.writeByteArray(mEncryptedRecoveryKeyBlob); + out.writeTypedList(mEntryRecoveryData); + out.writeInt(mMaxAttempts); + out.writeLong(mCounterId); + out.writeByteArray(mServerParams); + out.writeByteArray(mPublicKey); + } + + /** + * @hide + */ + protected KeyChainSnapshot(Parcel in) { + mSnapshotVersion = in.readInt(); + mKeyChainProtectionParams = in.createTypedArrayList(KeyChainProtectionParams.CREATOR); + mEncryptedRecoveryKeyBlob = in.createByteArray(); + mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR); + mMaxAttempts = in.readInt(); + mCounterId = in.readLong(); + mServerParams = in.createByteArray(); + mPublicKey = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keystore/KeyDerivationParams.aidl b/core/java/android/security/keystore/recovery/KeyDerivationParams.aidl similarity index 93% rename from core/java/android/security/keystore/KeyDerivationParams.aidl rename to core/java/android/security/keystore/recovery/KeyDerivationParams.aidl index f39aa047adeee40e76fbbee1cbacc014796f35d0..2b1bbbe35f1a40521e964a261bd400c1249d4f80 100644 --- a/core/java/android/security/keystore/KeyDerivationParams.aidl +++ b/core/java/android/security/keystore/recovery/KeyDerivationParams.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security.keystore; +package android.security.keystore.recovery; /* @hide */ parcelable KeyDerivationParams; diff --git a/core/java/android/security/keystore/recovery/KeyDerivationParams.java b/core/java/android/security/keystore/recovery/KeyDerivationParams.java new file mode 100644 index 0000000000000000000000000000000000000000..fc909a0aac9e4b95cbb58a9b246299e8fb02f040 --- /dev/null +++ b/core/java/android/security/keystore/recovery/KeyDerivationParams.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore.recovery; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Collection of parameters which define a key derivation function. + * Currently only supports salted SHA-256 + * + * @hide + */ +@SystemApi +public final class KeyDerivationParams implements Parcelable { + private final int mAlgorithm; + private byte[] mSalt; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ALGORITHM_"}, value = {ALGORITHM_SHA256, ALGORITHM_ARGON2ID}) + public @interface KeyDerivationAlgorithm { + } + + /** + * Salted SHA256 + */ + public static final int ALGORITHM_SHA256 = 1; + + /** + * Argon2ID + * @hide + */ + // TODO: add Argon2ID support. + public static final int ALGORITHM_ARGON2ID = 2; + + /** + * Creates instance of the class to to derive key using salted SHA256 hash. + */ + public static KeyDerivationParams createSha256Params(@NonNull byte[] salt) { + return new KeyDerivationParams(ALGORITHM_SHA256, salt); + } + + /** + * @hide + */ + // TODO: Make private once legacy API is removed + public KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) { + mAlgorithm = algorithm; + mSalt = Preconditions.checkNotNull(salt); + } + + /** + * Gets algorithm. + */ + public @KeyDerivationAlgorithm int getAlgorithm() { + return mAlgorithm; + } + + /** + * Gets salt. + */ + public @NonNull byte[] getSalt() { + return mSalt; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public KeyDerivationParams createFromParcel(Parcel in) { + return new KeyDerivationParams(in); + } + + public KeyDerivationParams[] newArray(int length) { + return new KeyDerivationParams[length]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mAlgorithm); + out.writeByteArray(mSalt); + } + + /** + * @hide + */ + protected KeyDerivationParams(Parcel in) { + mAlgorithm = in.readInt(); + mSalt = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keystore/recovery/LockScreenRequiredException.java b/core/java/android/security/keystore/recovery/LockScreenRequiredException.java new file mode 100644 index 0000000000000000000000000000000000000000..0062d290d698dd2552d59b52a8b8dd1aeaf02e9a --- /dev/null +++ b/core/java/android/security/keystore/recovery/LockScreenRequiredException.java @@ -0,0 +1,35 @@ +/* + * 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.security.keystore.recovery; + +import android.annotation.SystemApi; + +import java.security.GeneralSecurityException; + +/** + * Error thrown when trying to generate keys for a profile that has no lock screen set. + * + *

        A lock screen must be set, as the lock screen is used to encrypt the snapshot. + * + * @hide + */ +@SystemApi +public class LockScreenRequiredException extends GeneralSecurityException { + public LockScreenRequiredException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/recovery/RecoveryClaim.java b/core/java/android/security/keystore/recovery/RecoveryClaim.java new file mode 100644 index 0000000000000000000000000000000000000000..45c6b4ff6758c29e305adba401b72430d3d0867f --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoveryClaim.java @@ -0,0 +1,55 @@ +/* + * 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.security.keystore.recovery; + +/** + * An attempt to recover a keychain protected by remote secure hardware. + * + * @hide + * Deprecated + */ +public class RecoveryClaim { + + private final RecoverySession mRecoverySession; + private final byte[] mClaimBytes; + + RecoveryClaim(RecoverySession recoverySession, byte[] claimBytes) { + mRecoverySession = recoverySession; + mClaimBytes = claimBytes; + } + + /** + * Returns the session associated with the recovery attempt. This is used to match the symmetric + * key, which remains internal to the framework, for decrypting the claim response. + * + * @return The session data. + */ + public RecoverySession getRecoverySession() { + return mRecoverySession; + } + + /** + * Returns the encrypted claim's bytes. + * + *

        This should be sent by the recovery agent to the remote secure hardware, which will use + * it to decrypt the keychain, before sending it re-encrypted with the session's symmetric key + * to the device. + */ + public byte[] getClaimBytes() { + return mClaimBytes; + } +} diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java new file mode 100644 index 0000000000000000000000000000000000000000..4e4a0374087e412d17ae7a31f5f4eb45c9029f20 --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore.recovery; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; + +import com.android.internal.widget.ILockSettings; + +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 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. + * + *

        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. + * + *

        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. + * + *

        A recovery agent requires the privileged permission + * {@code android.Manifest.permission#RECOVER_KEYSTORE}. + * + * @hide + */ +@SystemApi +public class RecoveryController { + private static final String TAG = "RecoveryController"; + + /** Key has been successfully synced. */ + public static final int RECOVERY_STATUS_SYNCED = 0; + /** Waiting for recovery agent to sync the key. */ + public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1; + /** Recovery account is not available. */ + public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2; + /** Key cannot be synced. */ + public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3; + + /** + * Failed because no snapshot is yet pending to be synced for the user. + * + * @hide + */ + public static final int ERROR_NO_SNAPSHOT_PENDING = 21; + + /** + * Failed due to an error internal to the recovery service. This is unexpected and indicates + * either a problem with the logic in the service, or a problem with a dependency of the + * service (such as AndroidKeyStore). + * + * @hide + */ + public static final int ERROR_SERVICE_INTERNAL_ERROR = 22; + + /** + * Failed because the user does not have a lock screen set. + * + * @hide + */ + public static final int ERROR_INSECURE_USER = 23; + + /** + * Error thrown when attempting to use a recovery session that has since been closed. + * + * @hide + */ + public static final int ERROR_SESSION_EXPIRED = 24; + + /** + * Failed because the provided certificate was not a valid X509 certificate. + * + * @hide + */ + public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25; + + /** + * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong, + * the data has become corrupted, the data has been tampered with, etc. + * + * @hide + */ + public static final int ERROR_DECRYPTION_FAILED = 26; + + + private final ILockSettings mBinder; + + private RecoveryController(ILockSettings binder) { + mBinder = binder; + } + + /** + * Internal method used by {@code RecoverySession}. + * + * @hide + */ + ILockSettings getBinder() { + return mBinder; + } + + /** + * Gets a new instance of the class. + */ + public static RecoveryController getInstance(Context context) { + ILockSettings lockSettings = + ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")); + return new RecoveryController(lockSettings); + } + + /** + * 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 RecoveryController to select + * which of a set of remote recovery service devices will be used. + * + *

        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. + * + * @param rootCertificateAlias alias of a root certificate preinstalled on the device + * @param signedPublicKeyList binary blob a list of X509 certificates and signature + * @throws CertificateException if the {@code signedPublicKeyList} is in a bad format. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public void initRecoveryService( + @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) + throws CertificateException, InternalRecoveryServiceException { + try { + mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) { + throw new CertificateException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Deprecated - use getKeyChainSnapshot. + * + * Returns data necessary to store all recoverable keys. Key material is + * encrypted with user secret and recovery public key. + * + * @return Data necessary to recover keystore. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public @Nullable KeyChainSnapshot getRecoveryData() + throws InternalRecoveryServiceException { + try { + return mBinder.getKeyChainSnapshot(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) { + return null; + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Returns data necessary to store all recoverable keys. Key material is + * encrypted with user secret and recovery public key. + * + * @return Data necessary to recover keystore or {@code null} if snapshot is not available. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public @Nullable KeyChainSnapshot getKeyChainSnapshot() + throws InternalRecoveryServiceException { + try { + return mBinder.getKeyChainSnapshot(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) { + return null; + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link + * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at + * most one registered listener at any time. + * + * @param intent triggered when new snapshot is available. Unregisters listener if the value is + * {@code null}. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) + throws InternalRecoveryServiceException { + try { + mBinder.setSnapshotCreatedPendingIntent(intent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Server parameters used to generate new recovery key blobs. This value will be included in + * {@code KeyChainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included + * in vaultParams {@link #startRecoverySession} + * + * @param serverParams included in recovery key blob. + * @see #getRecoveryData + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException { + try { + mBinder.setServerParams(serverParams); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Gets aliases of recoverable keys for the application. + * + * @param packageName which recoverable keys' aliases will be returned. + * + * @return {@code List} of all aliases. + */ + public List getAliases(@Nullable String packageName) + throws InternalRecoveryServiceException { + try { + // TODO: update aidl + Map allStatuses = mBinder.getRecoveryStatus(packageName); + return new ArrayList<>(allStatuses.keySet()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Updates recovery status for given key. It is used to notify keystore that key was + * successfully stored on the server or there were an error. Application can check this value + * using {@code getRecoveyStatus}. + * + * @param packageName Application whose recoverable key's status are to be updated. + * @param alias Application-specific key alias. + * @param status Status specific to recovery agent. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public void setRecoveryStatus( + @NonNull String packageName, String alias, int status) + throws NameNotFoundException, InternalRecoveryServiceException { + try { + // TODO: update aidl + String[] aliases = alias == null ? null : new String[]{alias}; + mBinder.setRecoveryStatus(packageName, aliases, status); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Returns recovery status for Application's KeyStore key. + * Negative status values are reserved for recovery agent specific codes. List of common codes: + * + *

          + *
        • {@link #RECOVERY_STATUS_SYNCED} + *
        • {@link #RECOVERY_STATUS_SYNC_IN_PROGRESS} + *
        • {@link #RECOVERY_STATUS_MISSING_ACCOUNT} + *
        • {@link #RECOVERY_STATUS_PERMANENT_FAILURE} + *
        + * + * @param packageName Application whose recoverable key status is returned. + * @param alias Application-specific key alias. + * @return Recovery status. + * @see #setRecoveryStatus + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public int getRecoveryStatus(String packageName, String alias) + throws InternalRecoveryServiceException { + try { + // TODO: update aidl + Map allStatuses = mBinder.getRecoveryStatus(packageName); + Integer status = allStatuses.get(alias); + if (status == null) { + return RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE; + } else { + return status; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them + * is necessary to recover data. + * + * @param secretTypes {@link KeyChainProtectionParams#TYPE_LOCKSCREEN} or {@link + * KeyChainProtectionParams#TYPE_CUSTOM_PASSWORD} + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void setRecoverySecretTypes( + @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes) + throws InternalRecoveryServiceException { + try { + mBinder.setRecoverySecretTypes(secretTypes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is + * necessary to generate KeyChainSnapshot. + * + * @return list of recovery secret types + * @see KeyChainSnapshot + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public @NonNull @KeyChainProtectionParams.UserSecretType int[] getRecoverySecretTypes() + throws InternalRecoveryServiceException { + try { + return mBinder.getRecoverySecretTypes(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Returns a list of recovery secret types, necessary to create a pending recovery snapshot. + * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be + * called. + * + * @return list of recovery secret types + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @NonNull + public @KeyChainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes() + throws InternalRecoveryServiceException { + try { + return mBinder.getPendingRecoverySecretTypes(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Method notifies KeyStore that a user-generated secret is available. This method generates a + * symmetric session key which a trusted remote device can use to return a recovery key. Caller + * should use {@link KeyChainProtectionParams#clearSecret} to override the secret value in + * memory. + * + * @param recoverySecret user generated secret together with parameters necessary to regenerate + * it on a new device. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void recoverySecretAvailable(@NonNull KeyChainProtectionParams recoverySecret) + throws InternalRecoveryServiceException { + try { + mBinder.recoverySecretAvailable(recoverySecret); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable + * key store. Returns the raw material of the key. + * + * @param alias The key alias. + * @param account The account associated with the key + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * @throws LockScreenRequiredException if the user has not set a lock screen. This is required + * to generate recoverable keys, as the snapshots are encrypted using a key derived from the + * lock screen. + */ + public byte[] generateAndStoreKey(@NonNull String alias, byte[] account) + throws InternalRecoveryServiceException, LockScreenRequiredException { + try { + // TODO: add account + return mBinder.generateAndStoreKey(alias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_INSECURE_USER) { + throw new LockScreenRequiredException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Removes a key called {@code alias} from the recoverable key store. + * + * @param alias The key alias. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException { + try { + mBinder.removeKey(alias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + InternalRecoveryServiceException wrapUnexpectedServiceSpecificException( + ServiceSpecificException e) { + if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) { + return new InternalRecoveryServiceException(e.getMessage()); + } + + // Should never happen. If it does, it's a bug, and we need to update how the method that + // called this throws its exceptions. + return new InternalRecoveryServiceException("Unexpected error code for method: " + + e.errorCode, e); + } +} diff --git a/core/java/android/security/keystore/recovery/RecoveryControllerException.java b/core/java/android/security/keystore/recovery/RecoveryControllerException.java new file mode 100644 index 0000000000000000000000000000000000000000..2733acabbeb60ef67a004621143056f65479a141 --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoveryControllerException.java @@ -0,0 +1,37 @@ +/* + * 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.security.keystore.recovery; + +import java.security.GeneralSecurityException; + +/** + * Base exception for errors thrown by {@link RecoveryController}. + * + * @hide + * Deprecated + */ +public abstract class RecoveryControllerException extends GeneralSecurityException { + RecoveryControllerException() { } + + RecoveryControllerException(String msg) { + super(msg); + } + + public RecoveryControllerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/java/android/security/keystore/recovery/RecoverySession.java b/core/java/android/security/keystore/recovery/RecoverySession.java new file mode 100644 index 0000000000000000000000000000000000000000..4db5d6e0ff1547c59b53e9711ce47635b9579fcc --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoverySession.java @@ -0,0 +1,177 @@ +/* + * 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.security.keystore.recovery; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.util.Log; + +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.util.List; +import java.util.Map; + +/** + * Session to recover a {@link KeyChainSnapshot} from the remote trusted hardware, initiated by a + * recovery agent. + * + * @hide + */ +@SystemApi +public class RecoverySession implements AutoCloseable { + private static final String TAG = "RecoverySession"; + + private static final int SESSION_ID_LENGTH_BYTES = 16; + + private final String mSessionId; + private final RecoveryController mRecoveryController; + + private RecoverySession(RecoveryController recoveryController, String sessionId) { + mRecoveryController = recoveryController; + mSessionId = sessionId; + } + + /** + * A new session, started by {@code recoveryManager}. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + static RecoverySession newInstance(RecoveryController recoveryController) { + return new RecoverySession(recoveryController, newSessionId()); + } + + /** + * Returns a new random session ID. + */ + private static String newSessionId() { + SecureRandom secureRandom = new SecureRandom(); + byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES]; + secureRandom.nextBytes(sessionId); + StringBuilder sb = new StringBuilder(); + for (byte b : sessionId) { + sb.append(Byte.toHexString(b, /*upperCase=*/ false)); + } + return sb.toString(); + } + + /** + * Starts a recovery session and returns a blob with proof of recovery secret possession. + * The method generates a symmetric key for a session, which trusted remote device can use to + * return recovery key. + * + * @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. + * @param vaultParams Must match the parameters in the corresponding field in the recovery blob. + * Used to limit number of guesses. + * @param vaultChallenge Data passed from server for this recovery session and used to prevent + * replay attacks + * @param secrets Secrets provided by user, the method only uses type and secret fields. + * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is + * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric + * key and parameters necessary to identify the counter with the number of failed recovery + * attempts. + * @throws CertificateException if the {@code verifierPublicKey} is in an incorrect + * format. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + @NonNull public byte[] start( + @NonNull byte[] verifierPublicKey, + @NonNull byte[] vaultParams, + @NonNull byte[] vaultChallenge, + @NonNull List secrets) + throws CertificateException, InternalRecoveryServiceException { + try { + byte[] recoveryClaim = + mRecoveryController.getBinder().startRecoverySession( + mSessionId, + verifierPublicKey, + vaultParams, + vaultChallenge, + secrets); + return recoveryClaim; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT) { + throw new CertificateException(e.getMessage()); + } + throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Imports keys. + * + * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. + * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob + * and session. KeyStore only uses package names from the application info in {@link + * WrappedApplicationKey}. Caller is responsibility to perform certificates check. + * @return Map from alias to raw key material. + * @throws SessionExpiredException if {@code session} has since been closed. + * @throws DecryptionFailedException if unable to decrypt the snapshot. + * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public Map recoverKeys( + @NonNull byte[] recoveryKeyBlob, + @NonNull List applicationKeys) + throws SessionExpiredException, DecryptionFailedException, + InternalRecoveryServiceException { + try { + return (Map) mRecoveryController.getBinder().recoverKeys( + mSessionId, recoveryKeyBlob, applicationKeys); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) { + throw new DecryptionFailedException(e.getMessage()); + } + if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) { + throw new SessionExpiredException(e.getMessage()); + } + throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * An internal session ID, used by the framework to match recovery claims to snapshot responses. + * + * @hide + */ + String getSessionId() { + return mSessionId; + } + + /** + * Deletes all data associated with {@code session}. Should not be invoked directly but via + * {@link RecoverySession#close()}. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + @Override + public void close() { + try { + mRecoveryController.getBinder().closeSession(mSessionId); + } catch (RemoteException | ServiceSpecificException e) { + Log.e(TAG, "Unexpected error trying to close session", e); + } + } +} diff --git a/core/java/android/security/keystore/recovery/SessionExpiredException.java b/core/java/android/security/keystore/recovery/SessionExpiredException.java new file mode 100644 index 0000000000000000000000000000000000000000..8c18e4191a391625fdadcfdcd43596f700bf0835 --- /dev/null +++ b/core/java/android/security/keystore/recovery/SessionExpiredException.java @@ -0,0 +1,33 @@ +/* + * 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.security.keystore.recovery; + +import android.annotation.SystemApi; + +import java.security.GeneralSecurityException; + +/** + * Error thrown when attempting to use a {@link RecoverySession} that has since expired. + * + * @hide + */ +@SystemApi +public class SessionExpiredException extends GeneralSecurityException { + public SessionExpiredException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/recovery/WrappedApplicationKey.aidl b/core/java/android/security/keystore/recovery/WrappedApplicationKey.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b2d1ae4613462e5270a804887b3d0d5468824aa6 --- /dev/null +++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore.recovery; + +/* @hide */ +parcelable WrappedApplicationKey; diff --git a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java new file mode 100644 index 0000000000000000000000000000000000000000..f360bbe99ba1408375f94d19211769f0bf94403e --- /dev/null +++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore.recovery; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Helper class with data necessary recover a single application key, given a recovery key. + * + *
          + *
        • Alias - Keystore alias of the key. + *
        • Account Recovery Agent specific account associated with the key. + *
        • Encrypted key material. + *
        + * + * Note that Application info is not included. Recovery Agent can only make its own keys + * recoverable. + * + * @hide + */ +@SystemApi +public final class WrappedApplicationKey implements Parcelable { + private String mAlias; + // The only supported format is AES-256 symmetric key. + private byte[] mEncryptedKeyMaterial; + private byte[] mAccount; + + /** + * Builder for creating {@link WrappedApplicationKey}. + */ + public static class Builder { + private WrappedApplicationKey mInstance = new WrappedApplicationKey(); + + /** + * Sets Application-specific alias of the key. + * + * @param alias The alias. + * @return This builder. + */ + public Builder setAlias(@NonNull String alias) { + mInstance.mAlias = alias; + return this; + } + + /** + * Sets Recovery agent specific account. + * + * @param account The account. + * @return This builder. + */ + public Builder setAccount(@NonNull byte[] account) { + mInstance.mAccount = account; + return this; + } + + /** + * Sets key material encrypted by recovery key. + * + * @param encryptedKeyMaterial The key material + * @return This builder + */ + + public Builder setEncryptedKeyMaterial(@NonNull byte[] encryptedKeyMaterial) { + mInstance.mEncryptedKeyMaterial = encryptedKeyMaterial; + return this; + } + + /** + * Creates a new {@link WrappedApplicationKey} instance. + * + * @return new instance + * @throws NullPointerException if some required fields were not set. + */ + @NonNull public WrappedApplicationKey build() { + Preconditions.checkNotNull(mInstance.mAlias); + Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial); + if (mInstance.mAccount == null) { + mInstance.mAccount = new byte[]{}; + } + return mInstance; + } + } + + private WrappedApplicationKey() { + } + + /** + * Deprecated - consider using Builder. + * @hide + */ + public WrappedApplicationKey(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) { + mAlias = Preconditions.checkNotNull(alias); + mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial); + } + + /** + * Application-specific alias of the key. + * + * @see java.security.KeyStore.aliases + */ + public @NonNull String getAlias() { + return mAlias; + } + + /** Key material encrypted by recovery key. */ + public @NonNull byte[] getEncryptedKeyMaterial() { + return mEncryptedKeyMaterial; + } + + /** Account, default value is empty array */ + public @NonNull byte[] getAccount() { + if (mAccount == null) { + return new byte[]{}; + } + return mAccount; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public WrappedApplicationKey createFromParcel(Parcel in) { + return new WrappedApplicationKey(in); + } + + public WrappedApplicationKey[] newArray(int length) { + return new WrappedApplicationKey[length]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mAlias); + out.writeByteArray(mEncryptedKeyMaterial); + out.writeByteArray(mAccount); + } + + /** + * @hide + */ + protected WrappedApplicationKey(Parcel in) { + mAlias = in.readString(); + mEncryptedKeyMaterial = in.createByteArray(); + mAccount = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java new file mode 100644 index 0000000000000000000000000000000000000000..1ef6100fe5a2ac406b5e2902247667c428990e5f --- /dev/null +++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java @@ -0,0 +1,235 @@ +/* + * 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.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. + * + *

        A field classification score is a {@code float} representing how well an + * {@link AutofillValue} filled matches a expected value predicted by an autofill service + * —a full-match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}. + * + *

        The exact score depends on the algorithm used to calculate it— 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_SCORES = 1; + + /** + * 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"; + + /** + * Manifest metadata key for the resource string containing the name of the default field + * classification algorithm. + */ + public static final String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = + "android.autofill.field_classification.default_algorithm"; + /** + * Manifest metadata key for the resource string array containing the names of all field + * classification algorithms provided by the service. + */ + public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = + "android.autofill.field_classification.available_algorithms"; + + + /** {@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_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 actualValues = ((List) args.arg4); + @SuppressWarnings("unchecked") + final String[] userDataValues = (String[]) args.arg5; + final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues, + Arrays.asList(userDataValues)); + if (scores != null) { + data.putParcelable(EXTRA_SCORES, new 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; + } + + /** + * Calculates field classification scores in a batch. + * + *

        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, with the first dimension representing actual values and the + * second dimension values from {@link UserData}. + * + * {@hide} + */ + @Nullable + @SystemApi + public float[][] onGetScores(@Nullable String algorithm, + @Nullable Bundle args, @NonNull List actualValues, + @NonNull List userDataValues) { + Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScore()"); + return null; + } + + private final class AutofillFieldClassificationServiceWrapper + extends IAutofillFieldClassificationService.Stub { + @Override + public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs, + List actualValues, String[] userDataValues) + throws RemoteException { + // TODO(b/70939974): refactor to use PooledLambda + mHandlerCaller.obtainMessageOOOOO(MSG_GET_SCORES, callback, algorithmName, + algorithmArgs, actualValues, userDataValues).sendToTarget(); + } + } + + /** + * Helper class used to encapsulate a float[][] in a Parcelable. + * + * {@hide} + */ + public static final class Scores implements Parcelable { + @NonNull + public final float[][] scores; + + private Scores(Parcel parcel) { + 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(); + } + } + } + + private Scores(@NonNull float[][] scores) { + this.scores = scores; + } + + @Override + public String toString() { + final int size1 = scores.length; + final int size2 = size1 > 0 ? scores[0].length : 0; + final StringBuilder builder = new StringBuilder("Scores [") + .append(size1).append("x").append(size2).append("] "); + for (int i = 0; i < size1; i++) { + builder.append(i).append(": ").append(Arrays.toString(scores[i])).append(' '); + } + return builder.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + 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 CREATOR = new Creator() { + @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 2413e97ba837676ae247b8b503c77aa5cb192e54..f52ac85052898174af42e6cda510930a1f1a5652 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> mFields; + + // Must use LinkedHashMap to preserve insertion order. + @NonNull private final LinkedHashMap> 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 field = mFields.valueAt(i); + for (Entry> entry : mFields.entrySet()) { + final AutofillId id = entry.getKey(); + final Pair 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> mFields = - new ArrayMap<>(); + + // Must use LinkedHashMap to preserve insertion order. + @NonNull private final LinkedHashMap> 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 pair; - for (int i = 0; i < size; i++) { - ids[i] = mFields.keyAt(i); - pair = mFields.valueAt(i); + int i = 0; + for (Entry> 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/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java index 536180335a8376145fdc0304cf709f7afc466164..cd1efd68df6f336fdc7a233c845b6275cdb9f3c2 100644 --- a/core/java/android/service/autofill/FieldClassification.java +++ b/core/java/android/service/autofill/FieldClassification.java @@ -105,21 +105,16 @@ public final class FieldClassification { /** * Represents the score of a {@link UserData} entry for the field. - * - *

        The score is calculated by the given {@link #getAlgorithm() algorithm} and - * the entry is identified by {@link #getRemoteId()}. */ public static final class Match { private final String mRemoteId; private final float mScore; - private final String mAlgorithm; /** @hide */ - public Match(String remoteId, float score, String algorithm) { + public Match(String remoteId, float score) { mRemoteId = Preconditions.checkNotNull(remoteId); mScore = score; - mAlgorithm = algorithm; } /** @@ -150,38 +145,22 @@ public final class FieldClassification { return mScore; } - /** - * Gets the algorithm used to calculate this score. - * - *

        Typically, this is either the algorithm set by - * {@link UserData.Builder#setFieldClassificationAlgorithm(String, android.os.Bundle)}, - * or the - * {@link android.view.autofill.AutofillManager#getDefaultFieldClassificationAlgorithm()}. - */ - @NonNull - public String getAlgorithm() { - return mAlgorithm; - } - @Override public String toString() { if (!sDebug) return super.toString(); final StringBuilder string = new StringBuilder("Match: remoteId="); Helper.appendRedacted(string, mRemoteId); - return string.append(", score=").append(mScore) - .append(", algorithm=").append(mAlgorithm) - .toString(); + return string.append(", score=").append(mScore).toString(); } private void writeToParcel(@NonNull Parcel parcel) { parcel.writeString(mRemoteId); parcel.writeFloat(mScore); - parcel.writeString(mAlgorithm); } private static Match readFromParcel(@NonNull Parcel parcel) { - return new Match(parcel.readString(), parcel.readFloat(), parcel.readString()); + return new Match(parcel.readString(), parcel.readFloat()); } } } diff --git a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..398557d5ad2e626ef073d1a27d79f4018edc47c6 --- /dev/null +++ b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl @@ -0,0 +1,32 @@ +/* + * 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 getScores(in RemoteCallback callback, String algorithmName, in Bundle algorithmArgs, + in List actualValues, in String[] userDataValues); +} diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java index 2f9225acc5201d6eaf0c5f0f421e6381d2cdaed9..9017848055e27ed24c0bc27964272729385df524 100644 --- a/core/java/android/service/autofill/UserData.java +++ b/core/java/android/service/autofill/UserData.java @@ -155,12 +155,9 @@ public final class UserData implements Parcelable { *

        The currently available algorithms can be retrieve through * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}. * - *

        Note: The available algorithms in the Android System can change dinamically, - * so it's not guaranteed that the algorithm set here is the one that will be effectually - * used. If the algorithm set here is not available at runtime, the - * {@link AutofillManager#getDefaultFieldClassificationAlgorithm()} is used instead. - * You can verify which algorithm was used by calling - * {@link FieldClassification.Match#getAlgorithm()}. + *

        If not set, the + * {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is + * used instead. * * @param name name of the algorithm or {@code null} to used default. * @param args optional arguments to the algorithm. diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 2a245d04648632b6ac28a9652a059d9c1fc5e442..99e2c620fa03e5eda48758abbf94151a17ccf089 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -17,6 +17,7 @@ package android.service.dreams; import android.annotation.IdRes; import android.annotation.LayoutRes; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -54,7 +55,6 @@ import com.android.internal.util.DumpUtils.Dump; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.List; /** * Extend this class to implement a custom dream (available to the user as a "Daydream"). @@ -458,14 +458,49 @@ public class DreamService extends Service implements Window.Callback { * was processed in {@link #onCreate}. * *

        Note: Requires a window, do not call before {@link #onAttachedToWindow()}

        + *

        + * Note: In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. * + * @param id the ID to search for * @return The view if found or null otherwise. + * @see View#findViewById(int) + * @see DreamService#requireViewById(int) */ @Nullable public T findViewById(@IdRes int id) { return getWindow().findViewById(id); } + /** + * Finds a view that was identified by the id attribute from the XML that was processed in + * {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid or there is no + * matching view in the hierarchy. + * + *

        Note: Requires a window, do not call before {@link #onAttachedToWindow()}

        + *

        + * Note: In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#requireViewById(int) + * @see DreamService#findViewById(int) + */ + @NonNull + public final T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException( + "ID does not reference a View inside this DreamService"); + } + return view; + } + /** * Marks this dream as interactive to receive input events. * diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 2a352adcee18a98e66c116e0b8f1ffc645e8da1a..b6c6bdc00bbe6d02cfb4faef1f8afe6af84d2dcc 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -18,11 +18,11 @@ package android.service.notification; import android.annotation.IntDef; import android.annotation.SystemApi; -import android.app.AutomaticZenRule; import android.content.Context; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -161,6 +161,22 @@ public final class Condition implements Parcelable { .append(']').toString(); } + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + // id is guarantreed not to be null. + proto.write(ConditionProto.ID, id.toString()); + proto.write(ConditionProto.SUMMARY, summary); + proto.write(ConditionProto.LINE_1, line1); + proto.write(ConditionProto.LINE_2, line2); + proto.write(ConditionProto.ICON, icon); + proto.write(ConditionProto.STATE, state); + proto.write(ConditionProto.FLAGS, flags); + + proto.end(token); + } + @SystemApi public static String stateToString(int state) { if (state == STATE_FALSE) return "STATE_FALSE"; diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 20cd90679195dd46493ef96b299bd959ad2475f1..b7b2b2de35e54f3b1e85804f9a2196a8c2915cb6 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -55,6 +55,7 @@ import android.util.Log; import android.widget.RemoteViews; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import java.lang.annotation.Retention; @@ -1543,7 +1544,11 @@ public abstract class NotificationListenerService extends Service { return mShowBadge; } - private void populate(String key, int rank, boolean matchesInterruptionFilter, + /** + * @hide + */ + @VisibleForTesting + public void populate(String key, int rank, boolean matchesInterruptionFilter, int visibilityOverride, int suppressedVisualEffects, int importance, CharSequence explanation, String overrideGroupKey, NotificationChannel channel, ArrayList overridePeople, diff --git a/core/java/android/service/notification/NotifyingApp.aidl b/core/java/android/service/notification/NotifyingApp.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5358c2fb28b1b237f66c4eb11648038b7ee97e52 --- /dev/null +++ b/core/java/android/service/notification/NotifyingApp.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +parcelable NotifyingApp; \ No newline at end of file diff --git a/core/java/android/service/notification/NotifyingApp.java b/core/java/android/service/notification/NotifyingApp.java new file mode 100644 index 0000000000000000000000000000000000000000..38f18c6f20bfb8a3a558e97d7b2d0d632c513949 --- /dev/null +++ b/core/java/android/service/notification/NotifyingApp.java @@ -0,0 +1,139 @@ +/* + * 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.notification; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * @hide + */ +public final class NotifyingApp implements Parcelable, Comparable { + + private int mUid; + private String mPkg; + private long mLastNotified; + + public NotifyingApp() {} + + protected NotifyingApp(Parcel in) { + mUid = in.readInt(); + mPkg = in.readString(); + mLastNotified = in.readLong(); + } + + public int getUid() { + return mUid; + } + + /** + * Sets the uid of the package that sent the notification. Returns self. + */ + public NotifyingApp setUid(int mUid) { + this.mUid = mUid; + return this; + } + + public String getPackage() { + return mPkg; + } + + /** + * Sets the package that sent the notification. Returns self. + */ + public NotifyingApp setPackage(@NonNull String mPkg) { + this.mPkg = mPkg; + return this; + } + + public long getLastNotified() { + return mLastNotified; + } + + /** + * Sets the time the notification was originally sent. Returns self. + */ + public NotifyingApp setLastNotified(long mLastNotified) { + this.mLastNotified = mLastNotified; + return this; + } + + public static final Creator CREATOR = new Creator() { + @Override + public NotifyingApp createFromParcel(Parcel in) { + return new NotifyingApp(in); + } + + @Override + public NotifyingApp[] newArray(int size) { + return new NotifyingApp[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mUid); + dest.writeString(mPkg); + dest.writeLong(mLastNotified); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NotifyingApp that = (NotifyingApp) o; + return getUid() == that.getUid() + && getLastNotified() == that.getLastNotified() + && Objects.equals(mPkg, that.mPkg); + } + + @Override + public int hashCode() { + return Objects.hash(getUid(), mPkg, getLastNotified()); + } + + /** + * Sorts notifying apps from newest last notified date to oldest. + */ + @Override + public int compareTo(NotifyingApp o) { + if (getLastNotified() == o.getLastNotified()) { + if (getUid() == o.getUid()) { + return getPackage().compareTo(o.getPackage()); + } + return Integer.compare(getUid(), o.getUid()); + } + + return -Long.compare(getLastNotified(), o.getLastNotified()); + } + + @Override + public String toString() { + return "NotifyingApp{" + + "mUid=" + mUid + + ", mPkg='" + mPkg + '\'' + + ", mLastNotified=" + mLastNotified + + '}'; + } +} diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index f658ae03c92750badfa738d91d3ab01426defba1..23ae4b96810f126aeb645e206ab074b5e8fbdb42 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -17,6 +17,7 @@ package android.service.notification; import android.app.ActivityManager; +import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.Context; @@ -33,6 +34,7 @@ import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import com.android.internal.R; @@ -1262,6 +1264,30 @@ public class ZenModeConfig implements Parcelable { .append(']').toString(); } + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(ZenRuleProto.ID, id); + proto.write(ZenRuleProto.NAME, name); + proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime); + proto.write(ZenRuleProto.ENABLED, enabled); + proto.write(ZenRuleProto.ENABLER, enabler); + proto.write(ZenRuleProto.IS_SNOOZING, snoozing); + proto.write(ZenRuleProto.ZEN_MODE, zenMode); + if (conditionId != null) { + proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString()); + } + if (condition != null) { + condition.writeToProto(proto, ZenRuleProto.CONDITION); + } + if (component != null) { + component.writeToProto(proto, ZenRuleProto.COMPONENT); + } + + proto.end(token); + } + private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) { if (d == null) return; if (from == null) { @@ -1385,4 +1411,38 @@ public class ZenModeConfig implements Parcelable { } } + /** + * Determines whether dnd behavior should mute all notification sounds + */ + public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(NotificationManager.Policy + policy) { + boolean allowReminders = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS) != 0; + boolean allowCalls = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) != 0; + boolean allowMessages = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) != 0; + boolean allowEvents = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0; + boolean allowRepeatCallers = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; + return !allowReminders && !allowCalls && !allowMessages && !allowEvents + && !allowRepeatCallers; + } + + /** + * Determines whether dnd behavior should mute all notification sounds + */ + public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(ZenModeConfig config) { + return !config.allowReminders && !config.allowCalls && !config.allowMessages + && !config.allowEvents && !config.allowRepeatCallers; + } + + /** + * Determines whether all dnd mutes all sounds + */ + public static boolean areAllZenBehaviorSoundsMuted(ZenModeConfig config) { + return !config.allowAlarms && !config.allowMediaSystemOther + && areAllPriorityOnlyNotificationZenSoundsMuted(config); + } } diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index ce38ebb9bea7e8d4e04334188c0230f11d02363e..6fa5312be5cce23f678dd7eaf2bcd512c1ae5cbf 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -347,7 +347,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback TextLine line = TextLine.obtain(); line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); - fm.width = (int) Math.ceil(line.metrics(fm)); + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + // Reaching here means there is only one paragraph. + MeasuredParagraph mp = mt.getMeasuredParagraph(0); + fm.width = (int) Math.ceil(mp.getWidth(0, mp.getTextLength())); + } else { + fm.width = (int) Math.ceil(line.metrics(fm)); + } TextLine.recycle(line); return fm; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index bf4b6ac5194f5fcef406e9a116b03465835d8dd3..aa97b2aba749be306628f6343258f7a209ca8615 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1917,10 +1917,10 @@ public abstract class Layout { private static float measurePara(TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - MeasuredText mt = null; + MeasuredParagraph mt = null; TextLine tl = TextLine.obtain(); try { - mt = MeasuredText.buildForBidi(text, start, end, textDir, mt); + mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt); final char[] chars = mt.getChars(); final int len = chars.length; final Directions directions = mt.getDirections(0, len); diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java new file mode 100644 index 0000000000000000000000000000000000000000..aafcf44a73fcf00f9f9195f67830467670bb41f7 --- /dev/null +++ b/core/java/android/text/MeasuredParagraph.java @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Paint; +import android.text.AutoGrowArray.ByteArray; +import android.text.AutoGrowArray.FloatArray; +import android.text.AutoGrowArray.IntArray; +import android.text.Layout.Directions; +import android.text.style.MetricAffectingSpan; +import android.text.style.ReplacementSpan; +import android.util.Pools.SynchronizedPool; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.util.Arrays; + +/** + * MeasuredParagraph provides text information for rendering purpose. + * + * The first motivation of this class is identify the text directions and retrieving individual + * character widths. However retrieving character widths is slower than identifying text directions. + * Thus, this class provides several builder methods for specific purposes. + * + * - buildForBidi: + * Compute only text directions. + * - buildForMeasurement: + * Compute text direction and all character widths. + * - buildForStaticLayout: + * This is bit special. StaticLayout also needs to know text direction and character widths for + * line breaking, but all things are done in native code. Similarly, text measurement is done + * in native code. So instead of storing result to Java array, this keeps the result in native + * code since there is no good reason to move the results to Java layer. + * + * In addition to the character widths, some additional information is computed for each purposes, + * e.g. whole text length for measurement or font metrics for static layout. + * + * MeasuredParagraph is NOT a thread safe object. + * @hide + */ +public class MeasuredParagraph { + private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; + + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024); + + private MeasuredParagraph() {} // Use build static functions instead. + + private static final SynchronizedPool sPool = new SynchronizedPool<>(1); + + private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead. + final MeasuredParagraph mt = sPool.acquire(); + return mt != null ? mt : new MeasuredParagraph(); + } + + /** + * Recycle the MeasuredParagraph. + * + * Do not call any methods after you call this method. + */ + public void recycle() { + release(); + sPool.release(this); + } + + // The casted original text. + // + // This may be null if the passed text is not a Spanned. + private @Nullable Spanned mSpanned; + + // The start offset of the target range in the original text (mSpanned); + private @IntRange(from = 0) int mTextStart; + + // The length of the target range in the original text. + private @IntRange(from = 0) int mTextLength; + + // The copied character buffer for measuring text. + // + // The length of this array is mTextLength. + private @Nullable char[] mCopiedBuffer; + + // The whole paragraph direction. + private @Layout.Direction int mParaDir; + + // True if the text is LTR direction and doesn't contain any bidi characters. + private boolean mLtrWithoutBidi; + + // The bidi level for individual characters. + // + // This is empty if mLtrWithoutBidi is true. + private @NonNull ByteArray mLevels = new ByteArray(); + + // The whole width of the text. + // See getWholeWidth comments. + private @FloatRange(from = 0.0f) float mWholeWidth; + + // Individual characters' widths. + // See getWidths comments. + private @Nullable FloatArray mWidths = new FloatArray(); + + // The span end positions. + // See getSpanEndCache comments. + private @Nullable IntArray mSpanEndCache = new IntArray(4); + + // The font metrics. + // See getFontMetrics comments. + private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); + + // The native MeasuredParagraph. + // See getNativePtr comments. + // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. + private /* Maybe Zero */ long mNativePtr = 0; + private @Nullable Runnable mNativeObjectCleaner; + + // Associate the native object to this Java object. + private void bindNativeObject(/* Non Zero*/ long nativePtr) { + mNativePtr = nativePtr; + mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); + } + + // Decouple the native object from this Java object and release the native object. + private void unbindNativeObject() { + if (mNativePtr != 0) { + mNativeObjectCleaner.run(); + mNativePtr = 0; + } + } + + // Following two objects are for avoiding object allocation. + private @NonNull TextPaint mCachedPaint = new TextPaint(); + private @Nullable Paint.FontMetricsInt mCachedFm; + + /** + * Releases internal buffers. + */ + public void release() { + reset(); + mLevels.clearWithReleasingLargeArray(); + mWidths.clearWithReleasingLargeArray(); + mFontMetrics.clearWithReleasingLargeArray(); + mSpanEndCache.clearWithReleasingLargeArray(); + } + + /** + * Resets the internal state for starting new text. + */ + private void reset() { + mSpanned = null; + mCopiedBuffer = null; + mWholeWidth = 0; + mLevels.clear(); + mWidths.clear(); + mFontMetrics.clear(); + mSpanEndCache.clear(); + unbindNativeObject(); + } + + /** + * Returns the length of the paragraph. + * + * This is always available. + */ + public int getTextLength() { + return mTextLength; + } + + /** + * Returns the characters to be measured. + * + * This is always available. + */ + public @NonNull char[] getChars() { + return mCopiedBuffer; + } + + /** + * Returns the paragraph direction. + * + * This is always available. + */ + public @Layout.Direction int getParagraphDir() { + return mParaDir; + } + + /** + * Returns the directions. + * + * This is always available. + */ + public Directions getDirections(@IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end) { // exclusive + if (mLtrWithoutBidi) { + return Layout.DIRS_ALL_LEFT_TO_RIGHT; + } + + final int length = end - start; + return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, + length); + } + + /** + * Returns the whole text width. + * + * This is available only if the MeasuredParagraph is computed with buildForMeasurement. + * Returns 0 in other cases. + */ + public @FloatRange(from = 0.0f) float getWholeWidth() { + return mWholeWidth; + } + + /** + * Returns the individual character's width. + * + * This is available only if the MeasuredParagraph is computed with buildForMeasurement. + * Returns empty array in other cases. + */ + public @NonNull FloatArray getWidths() { + return mWidths; + } + + /** + * Returns the MetricsAffectingSpan end indices. + * + * If the input text is not a spanned string, this has one value that is the length of the text. + * + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getSpanEndCache() { + return mSpanEndCache; + } + + /** + * Returns the int array which holds FontMetrics. + * + * This array holds the repeat of top, bottom, ascent, descent of font metrics value. + * + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getFontMetrics() { + return mFontMetrics; + } + + /** + * Returns the native ptr of the MeasuredParagraph. + * + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + * Returns 0 in other cases. + */ + public /* Maybe Zero */ long getNativePtr() { + return mNativePtr; + } + + /** + * Returns the width of the given range. + * + * This is not available if the MeasuredParagraph is computed with buildForBidi. + * Returns 0 if the MeasuredParagraph is computed with buildForBidi. + * + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + */ + public float getWidth(int start, int end) { + if (mNativePtr == 0) { + // We have result in Java. + final float[] widths = mWidths.getRawArray(); + float r = 0.0f; + for (int i = start; i < end; ++i) { + r += widths[i]; + } + return r; + } else { + // We have result in native. + return nGetWidth(mNativePtr, start, end); + } + } + + /** + * Generates new MeasuredParagraph for Bidi computation. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + return mt; + } + + /** + * Generates new MeasuredParagraph for measuring texts. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + + mt.mWidths.resize(mt.mTextLength); + if (mt.mTextLength == 0) { + return mt; + } + + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan( + paint, null /* spans */, start, end, 0 /* native static layout ptr */); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan( + paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); + } + } + return mt; + } + + /** + * Generates new MeasuredParagraph for StaticLayout. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForStaticLayout( + @NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + boolean computeHyphenation, + boolean computeLayout, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + if (mt.mTextLength == 0) { + // Need to build empty native measured text for StaticLayout. + // TODO: Stop creating empty measured text for empty lines. + long nativeBuilderPtr = nInitBuilder(); + try { + mt.bindNativeObject( + nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer, + computeHyphenation, computeLayout)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + return mt; + } + + long nativeBuilderPtr = nInitBuilder(); + try { + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); + mt.mSpanEndCache.append(end); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply + // styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, + MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, + nativeBuilderPtr); + mt.mSpanEndCache.append(spanEnd); + } + } + mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer, + computeHyphenation, computeLayout)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + + return mt; + } + + /** + * Reset internal state and analyzes text for bidirectional runs. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + */ + private void resetAndAnalyzeBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end, // exclusive + @NonNull TextDirectionHeuristic textDir) { + reset(); + mSpanned = text instanceof Spanned ? (Spanned) text : null; + mTextStart = start; + mTextLength = end - start; + + if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { + mCopiedBuffer = new char[mTextLength]; + } + TextUtils.getChars(text, start, end, mCopiedBuffer, 0); + + // Replace characters associated with ReplacementSpan to U+FFFC. + if (mSpanned != null) { + ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); + + for (int i = 0; i < spans.length; i++) { + int startInPara = mSpanned.getSpanStart(spans[i]) - start; + int endInPara = mSpanned.getSpanEnd(spans[i]) - start; + // The span interval may be larger and must be restricted to [start, end) + if (startInPara < 0) startInPara = 0; + if (endInPara > mTextLength) endInPara = mTextLength; + Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); + } + } + + if ((textDir == TextDirectionHeuristics.LTR + || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR + || textDir == TextDirectionHeuristics.ANYRTL_LTR) + && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { + mLevels.clear(); + mParaDir = Layout.DIR_LEFT_TO_RIGHT; + mLtrWithoutBidi = true; + } else { + final int bidiRequest; + if (textDir == TextDirectionHeuristics.LTR) { + bidiRequest = Layout.DIR_REQUEST_LTR; + } else if (textDir == TextDirectionHeuristics.RTL) { + bidiRequest = Layout.DIR_REQUEST_RTL; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; + } else { + final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); + bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; + } + mLevels.resize(mTextLength); + mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); + mLtrWithoutBidi = false; + } + } + + private void applyReplacementRun(@NonNull ReplacementSpan replacement, + @IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + // Use original text. Shouldn't matter. + // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for + // backward compatibility? or Should we initialize them for getFontMetricsInt? + final float width = replacement.getSize( + mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); + if (nativeBuilderPtr == 0) { + // Assigns all width to the first character. This is the same behavior as minikin. + mWidths.set(start, width); + if (end > start + 1) { + Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); + } + mWholeWidth += width; + } else { + nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + width); + } + } + + private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + if (nativeBuilderPtr != 0) { + mCachedPaint.getFontMetricsInt(mCachedFm); + } + + if (mLtrWithoutBidi) { + // If the whole text is LTR direction, just apply whole region. + if (nativeBuilderPtr == 0) { + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, + mWidths.getRawArray(), start); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + false /* isRtl */); + } + } else { + // If there is multiple bidi levels, split into individual bidi level and apply style. + byte level = mLevels.get(start); + // Note that the empty text or empty range won't reach this method. + // Safe to search from start + 1. + for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { + if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point + final boolean isRtl = (level & 0x1) != 0; + if (nativeBuilderPtr == 0) { + final int levelLength = levelEnd - levelStart; + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, + isRtl, mWidths.getRawArray(), levelStart); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, + levelEnd, isRtl); + } + if (levelEnd == end) { + break; + } + levelStart = levelEnd; + level = mLevels.get(levelEnd); + } + } + } + } + + private void applyMetricsAffectingSpan( + @NonNull TextPaint paint, + @Nullable MetricAffectingSpan[] spans, + @IntRange(from = 0) int start, // inclusive, in original text buffer + @IntRange(from = 0) int end, // exclusive, in original text buffer + /* Maybe Zero */ long nativeBuilderPtr) { + mCachedPaint.set(paint); + // XXX paint should not have a baseline shift, but... + mCachedPaint.baselineShift = 0; + + final boolean needFontMetrics = nativeBuilderPtr != 0; + + if (needFontMetrics && mCachedFm == null) { + mCachedFm = new Paint.FontMetricsInt(); + } + + ReplacementSpan replacement = null; + if (spans != null) { + for (int i = 0; i < spans.length; i++) { + MetricAffectingSpan span = spans[i]; + if (span instanceof ReplacementSpan) { + // The last ReplacementSpan is effective for backward compatibility reasons. + replacement = (ReplacementSpan) span; + } else { + // TODO: No need to call updateMeasureState for ReplacementSpan as well? + span.updateMeasureState(mCachedPaint); + } + } + } + + final int startInCopiedBuffer = start - mTextStart; + final int endInCopiedBuffer = end - mTextStart; + + if (replacement != null) { + applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, + nativeBuilderPtr); + } else { + applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + } + + if (needFontMetrics) { + if (mCachedPaint.baselineShift < 0) { + mCachedFm.ascent += mCachedPaint.baselineShift; + mCachedFm.top += mCachedPaint.baselineShift; + } else { + mCachedFm.descent += mCachedPaint.baselineShift; + mCachedFm.bottom += mCachedPaint.baselineShift; + } + + mFontMetrics.append(mCachedFm.top); + mFontMetrics.append(mCachedFm.bottom); + mFontMetrics.append(mCachedFm.ascent); + mFontMetrics.append(mCachedFm.descent); + } + } + + /** + * Returns the maximum index that the accumulated width not exceeds the width. + * + * If forward=false is passed, returns the minimum index from the end instead. + * + * This only works if the MeasuredParagraph is computed with buildForMeasurement. + * Undefined behavior in other case. + */ + @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { + float[] w = mWidths.getRawArray(); + if (forwards) { + int i = 0; + while (i < limit) { + width -= w[i]; + if (width < 0.0f) break; + i++; + } + while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; + return i; + } else { + int i = limit - 1; + while (i >= 0) { + width -= w[i]; + if (width < 0.0f) break; + i--; + } + while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { + i++; + } + return limit - i - 1; + } + } + + /** + * Returns the length of the substring. + * + * This only works if the MeasuredParagraph is computed with buildForMeasurement. + * Undefined behavior in other case. + */ + @FloatRange(from = 0.0f) float measure(int start, int limit) { + float width = 0; + float[] w = mWidths.getRawArray(); + for (int i = start; i < limit; ++i) { + width += w[i]; + } + return width; + } + + /** + * This only works if the MeasuredParagraph is computed with buildForStaticLayout. + */ + @IntRange(from = 0) int getMemoryUsage() { + return nGetMemoryUsage(mNativePtr); + } + + private static native /* Non Zero */ long nInitBuilder(); + + /** + * Apply style to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param isRtl True if the text is RTL. + */ + private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + boolean isRtl); + + /** + * Apply ReplacementRun to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param width The width of the replacement. + */ + private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @FloatRange(from = 0) float width); + + private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr, + @NonNull char[] text, + boolean computeHyphenation, + boolean computeLayout); + + private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + + @CriticalNative + private static native float nGetWidth(/* Non Zero */ long nativePtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end); + + @CriticalNative + private static native /* Non Zero */ long nGetReleaseFunc(); + + @CriticalNative + private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr); +} diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index 14d6f9e8a9ef94723838fbb554737480c5562225..bb7a9e0b7906fb873580a13b7937e110720d5989 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,661 +16,412 @@ package android.text; -import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Paint; -import android.text.AutoGrowArray.ByteArray; -import android.text.AutoGrowArray.FloatArray; -import android.text.AutoGrowArray.IntArray; -import android.text.Layout.Directions; -import android.text.style.MetricAffectingSpan; -import android.text.style.ReplacementSpan; -import android.util.Pools.SynchronizedPool; +import android.util.IntArray; -import dalvik.annotation.optimization.CriticalNative; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; -import libcore.util.NativeAllocationRegistry; - -import java.util.Arrays; +import java.util.ArrayList; /** - * MeasuredText provides text information for rendering purpose. - * - * The first motivation of this class is identify the text directions and retrieving individual - * character widths. However retrieving character widths is slower than identifying text directions. - * Thus, this class provides several builder methods for specific purposes. - * - * - buildForBidi: - * Compute only text directions. - * - buildForMeasurement: - * Compute text direction and all character widths. - * - buildForStaticLayout: - * This is bit special. StaticLayout also needs to know text direction and character widths for - * line breaking, but all things are done in native code. Similarly, text measurement is done - * in native code. So instead of storing result to Java array, this keeps the result in native - * code since there is no good reason to move the results to Java layer. - * - * In addition to the character widths, some additional information is computed for each purposes, - * e.g. whole text length for measurement or font metrics for static layout. - * - * MeasuredText is NOT a thread safe object. - * @hide + * A text which has already been measured. */ -public class MeasuredText { - private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; +public class MeasuredText implements Spanned { + private static final char LINE_FEED = '\n'; - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); + // The original text. + private final @NonNull CharSequence mText; - private MeasuredText() {} // Use build static functions instead. + // The inclusive start offset of the measuring target. + private final @IntRange(from = 0) int mStart; - private static final SynchronizedPool sPool = new SynchronizedPool<>(1); + // The exclusive end offset of the measuring target. + private final @IntRange(from = 0) int mEnd; - private static @NonNull MeasuredText obtain() { // Use build static functions instead. - final MeasuredText mt = sPool.acquire(); - return mt != null ? mt : new MeasuredText(); - } + // The TextPaint used for measurement. + private final @NonNull TextPaint mPaint; + + // The requested text direction. + private final @NonNull TextDirectionHeuristic mTextDir; + + // The measured paragraph texts. + private final @NonNull MeasuredParagraph[] mMeasuredParagraphs; + + // The sorted paragraph end offsets. + private final @NonNull int[] mParagraphBreakPoints; + + // 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; /** - * Recycle the MeasuredText. - * - * Do not call any methods after you call this method. + * A Builder for MeasuredText */ - public void recycle() { - release(); - sPool.release(this); - } + 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(); + } - // The casted original text. - // - // This may be null if the passed text is not a Spanned. - private @Nullable Spanned mSpanned; + /** + * 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; + } - // The start offset of the target range in the original text (mSpanned); - private @IntRange(from = 0) int mTextStart; + /** + * 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; + } - // The length of the target range in the original text. - private @IntRange(from = 0) int mTextLength; + /** + * 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; + } - // The copied character buffer for measuring text. - // - // The length of this array is mTextLength. - private @Nullable char[] mCopiedBuffer; + /** + * 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; + } - // The whole paragraph direction. - private @Layout.Direction int mParaDir; + /** + * Build the measured text + * + * @return the measured text. + */ + public @NonNull MeasuredText build() { + return build(true /* build full layout result */); + } - // True if the text is LTR direction and doesn't contain any bidi characters. - private boolean mLtrWithoutBidi; + /** @hide */ + public @NonNull MeasuredText build(boolean computeLayout) { + final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE + && mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE; + + final IntArray paragraphEnds = new IntArray(); + final ArrayList 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. + } - // The bidi level for individual characters. - // - // This is empty if mLtrWithoutBidi is true. - private @NonNull ByteArray mLevels = new ByteArray(); - - // The whole width of the text. - // See getWholeWidth comments. - private @FloatRange(from = 0.0f) float mWholeWidth; - - // Individual characters' widths. - // See getWidths comments. - private @Nullable FloatArray mWidths = new FloatArray(); - - // The span end positions. - // See getSpanEndCache comments. - private @Nullable IntArray mSpanEndCache = new IntArray(4); - - // The font metrics. - // See getFontMetrics comments. - private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); - - // The native MeasuredText. - // See getNativePtr comments. - // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. - private /* Maybe Zero */ long mNativePtr = 0; - private @Nullable Runnable mNativeObjectCleaner; - - // Associate the native object to this Java object. - private void bindNativeObject(/* Non Zero*/ long nativePtr) { - mNativePtr = nativePtr; - mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); - } + paragraphEnds.add(paraEnd); + measuredTexts.add(MeasuredParagraph.buildForStaticLayout( + mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation, + computeLayout, null /* no recycle */)); + } - // Decouple the native object from this Java object and release the native object. - private void unbindNativeObject() { - if (mNativePtr != 0) { - mNativeObjectCleaner.run(); - mNativePtr = 0; + 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; + mStart = start; + mEnd = end; + // Copy the paint so that we can keep the reference of typeface in native layout result. + mPaint = new TextPaint(paint); + mMeasuredParagraphs = measuredTexts; + mParagraphBreakPoints = paragraphBreakPoints; + mTextDir = textDir; + mBreakStrategy = breakStrategy; + mHyphenationFrequency = frequency; } - // Following two objects are for avoiding object allocation. - private @NonNull TextPaint mCachedPaint = new TextPaint(); - private @Nullable Paint.FontMetricsInt mCachedFm; - /** - * Releases internal buffers. + * Return the underlying text. */ - public void release() { - reset(); - mLevels.clearWithReleasingLargeArray(); - mWidths.clearWithReleasingLargeArray(); - mFontMetrics.clearWithReleasingLargeArray(); - mSpanEndCache.clearWithReleasingLargeArray(); + public @NonNull CharSequence getText() { + return mText; } /** - * Resets the internal state for starting new text. + * Returns the inclusive start offset of measured region. */ - private void reset() { - mSpanned = null; - mCopiedBuffer = null; - mWholeWidth = 0; - mLevels.clear(); - mWidths.clear(); - mFontMetrics.clear(); - mSpanEndCache.clear(); - unbindNativeObject(); + public @IntRange(from = 0) int getStart() { + return mStart; } /** - * Returns the characters to be measured. - * - * This is always available. + * Returns the exclusive end offset of measured region. */ - public @NonNull char[] getChars() { - return mCopiedBuffer; + public @IntRange(from = 0) int getEnd() { + return mEnd; } /** - * Returns the paragraph direction. - * - * This is always available. + * Returns the text direction associated with char sequence. */ - public @Layout.Direction int getParagraphDir() { - return mParaDir; + public @NonNull TextDirectionHeuristic getTextDir() { + return mTextDir; } /** - * Returns the directions. - * - * This is always available. + * Returns the paint used to measure this text. */ - public Directions getDirections(@IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end) { // exclusive - if (mLtrWithoutBidi) { - return Layout.DIRS_ALL_LEFT_TO_RIGHT; - } - - final int length = end - start; - return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, - length); + public @NonNull TextPaint getPaint() { + return mPaint; } /** - * Returns the whole text width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns 0 in other cases. + * Returns the length of the paragraph of this text. */ - public @FloatRange(from = 0.0f) float getWholeWidth() { - return mWholeWidth; + public @IntRange(from = 0) int getParagraphCount() { + return mParagraphBreakPoints.length; } /** - * Returns the individual character's width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns empty array in other cases. + * Returns the paragraph start offset of the text. */ - public @NonNull FloatArray getWidths() { - return mWidths; + public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; } /** - * Returns the MetricsAffectingSpan end indices. - * - * If the input text is not a spanned string, this has one value that is the length of the text. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. + * Returns the paragraph end offset of the text. */ - public @NonNull IntArray getSpanEndCache() { - return mSpanEndCache; + public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return mParagraphBreakPoints[paraIndex]; } - /** - * Returns the int array which holds FontMetrics. - * - * This array holds the repeat of top, bottom, ascent, descent of font metrics value. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. - */ - public @NonNull IntArray getFontMetrics() { - return mFontMetrics; + /** @hide */ + public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) { + return mMeasuredParagraphs[paraIndex]; } /** - * Returns the native ptr of the MeasuredText. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns 0 in other cases. + * Returns the break strategy for this text. */ - public /* Maybe Zero */ long getNativePtr() { - return mNativePtr; + public @Layout.BreakStrategy int getBreakStrategy() { + return mBreakStrategy; } /** - * Generates new MeasuredText for Bidi computation. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the hyphenation frequency for this text. */ - public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - return mt; + public @Layout.HyphenationFrequency int getHyphenationFrequency() { + return mHyphenationFrequency; } /** - * Generates new MeasuredText for measuring texts. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns true if the given TextPaint gives the same result of text layout for this text. + * @hide */ - public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - - mt.mWidths.resize(mt.mTextLength); - if (mt.mTextLength == 0) { - return mt; - } - - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, 0 /* native static layout ptr */); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); - } - } - return mt; + public boolean canUseMeasuredResult(@NonNull TextPaint paint) { + return mPaint.getTextSize() == paint.getTextSize() + && mPaint.getTextSkewX() == paint.getTextSkewX() + && mPaint.getTextScaleX() == paint.getTextScaleX() + && mPaint.getLetterSpacing() == paint.getLetterSpacing() + && mPaint.getWordSpacing() == paint.getWordSpacing() + && mPaint.getFlags() == paint.getFlags() // Maybe not all flag affects text layout. + && mPaint.getTextLocales() == paint.getTextLocales() // need to be equals? + && mPaint.getFontVariationSettings() == paint.getFontVariationSettings() + && mPaint.getTypeface() == paint.getTypeface() + && TextUtils.equals(mPaint.getFontFeatureSettings(), paint.getFontFeatureSettings()); } - /** - * Generates new MeasuredText for StaticLayout. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text - */ - public static @NonNull MeasuredText buildForStaticLayout( - @NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - if (mt.mTextLength == 0) { - // Need to build empty native measured text for StaticLayout. - // TODO: Stop creating empty measured text for empty lines. - long nativeBuilderPtr = nInitBuilder(); - try { - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); + /** @hide */ + public int findParaIndex(@IntRange(from = 0) int pos) { + // TODO: Maybe good to remove paragraph concept from MeasuredText and add substring layout + // support to StaticLayout. + for (int i = 0; i < mParagraphBreakPoints.length; ++i) { + if (pos < mParagraphBreakPoints[i]) { + return i; } - return mt; } + throw new IndexOutOfBoundsException( + "pos must be less than " + mParagraphBreakPoints[mParagraphBreakPoints.length - 1] + + ", gave " + pos); + } - long nativeBuilderPtr = nInitBuilder(); - try { - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); - mt.mSpanEndCache.append(end); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply - // styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, - MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, - MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, - nativeBuilderPtr); - mt.mSpanEndCache.append(spanEnd); - } - } - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); + /** @hide */ + public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) { + final int paraIndex = findParaIndex(start); + final int paraStart = getParagraphStart(paraIndex); + final int paraEnd = getParagraphEnd(paraIndex); + if (start < paraStart || paraEnd < end) { + throw new RuntimeException("Cannot measured across the paragraph:" + + "para: (" + paraStart + ", " + paraEnd + "), " + + "request: (" + start + ", " + end + ")"); } - - return mt; + return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart); } /** - * Reset internal state and analyzes text for bidirectional runs. + * Returns the size of native MeasuredText memory usage * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction + * Note that this may not be aculate. Must be used only for testing purposes. + * @hide */ - private void resetAndAnalyzeBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end, // exclusive - @NonNull TextDirectionHeuristic textDir) { - reset(); - mSpanned = text instanceof Spanned ? (Spanned) text : null; - mTextStart = start; - mTextLength = end - start; - - if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { - mCopiedBuffer = new char[mTextLength]; - } - TextUtils.getChars(text, start, end, mCopiedBuffer, 0); - - // Replace characters associated with ReplacementSpan to U+FFFC. - if (mSpanned != null) { - ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); - - for (int i = 0; i < spans.length; i++) { - int startInPara = mSpanned.getSpanStart(spans[i]) - start; - int endInPara = mSpanned.getSpanEnd(spans[i]) - start; - // The span interval may be larger and must be restricted to [start, end) - if (startInPara < 0) startInPara = 0; - if (endInPara > mTextLength) endInPara = mTextLength; - Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); - } - } - - if ((textDir == TextDirectionHeuristics.LTR || - textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || - textDir == TextDirectionHeuristics.ANYRTL_LTR) && - TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { - mLevels.clear(); - mParaDir = Layout.DIR_LEFT_TO_RIGHT; - mLtrWithoutBidi = true; - } else { - final int bidiRequest; - if (textDir == TextDirectionHeuristics.LTR) { - bidiRequest = Layout.DIR_REQUEST_LTR; - } else if (textDir == TextDirectionHeuristics.RTL) { - bidiRequest = Layout.DIR_REQUEST_RTL; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; - } else { - final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); - bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; - } - mLevels.resize(mTextLength); - mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); - mLtrWithoutBidi = false; + public int getMemoryUsage() { + int r = 0; + for (int i = 0; i < getParagraphCount(); ++i) { + r += getMeasuredParagraph(i).getMemoryUsage(); } + return r; } - private void applyReplacementRun(@NonNull ReplacementSpan replacement, - @IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - // Use original text. Shouldn't matter. - // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for - // backward compatibility? or Should we initialize them for getFontMetricsInt? - final float width = replacement.getSize( - mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); - if (nativeBuilderPtr == 0) { - // Assigns all width to the first character. This is the same behavior as minikin. - mWidths.set(start, width); - if (end > start + 1) { - Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); - } - mWholeWidth += width; + /////////////////////////////////////////////////////////////////////////////////////////////// + // Spanned overrides + // + // Just proxy for underlying mText if appropriate. + + @Override + public T[] getSpans(int start, int end, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpans(start, end, type); } else { - nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - width); + return ArrayUtils.emptyArray(type); } } - private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - if (nativeBuilderPtr != 0) { - mCachedPaint.getFontMetricsInt(mCachedFm); - } - - if (mLtrWithoutBidi) { - // If the whole text is LTR direction, just apply whole region. - if (nativeBuilderPtr == 0) { - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, - mWidths.getRawArray(), start); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - false /* isRtl */); - } + @Override + public int getSpanStart(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanStart(tag); } else { - // If there is multiple bidi levels, split into individual bidi level and apply style. - byte level = mLevels.get(start); - // Note that the empty text or empty range won't reach this method. - // Safe to search from start + 1. - for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { - if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point - final boolean isRtl = (level & 0x1) != 0; - if (nativeBuilderPtr == 0) { - final int levelLength = levelEnd - levelStart; - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, - isRtl, mWidths.getRawArray(), levelStart); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, - levelEnd, isRtl); - } - if (levelEnd == end) { - break; - } - levelStart = levelEnd; - level = mLevels.get(levelEnd); - } - } + return -1; } } - private void applyMetricsAffectingSpan( - @NonNull TextPaint paint, - @Nullable MetricAffectingSpan[] spans, - @IntRange(from = 0) int start, // inclusive, in original text buffer - @IntRange(from = 0) int end, // exclusive, in original text buffer - /* Maybe Zero */ long nativeBuilderPtr) { - mCachedPaint.set(paint); - // XXX paint should not have a baseline shift, but... - mCachedPaint.baselineShift = 0; - - final boolean needFontMetrics = nativeBuilderPtr != 0; - - if (needFontMetrics && mCachedFm == null) { - mCachedFm = new Paint.FontMetricsInt(); - } - - ReplacementSpan replacement = null; - if (spans != null) { - for (int i = 0; i < spans.length; i++) { - MetricAffectingSpan span = spans[i]; - if (span instanceof ReplacementSpan) { - // The last ReplacementSpan is effective for backward compatibility reasons. - replacement = (ReplacementSpan) span; - } else { - // TODO: No need to call updateMeasureState for ReplacementSpan as well? - span.updateMeasureState(mCachedPaint); - } - } - } - - final int startInCopiedBuffer = start - mTextStart; - final int endInCopiedBuffer = end - mTextStart; - - if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, - nativeBuilderPtr); + @Override + public int getSpanEnd(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanEnd(tag); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); - } - - if (needFontMetrics) { - if (mCachedPaint.baselineShift < 0) { - mCachedFm.ascent += mCachedPaint.baselineShift; - mCachedFm.top += mCachedPaint.baselineShift; - } else { - mCachedFm.descent += mCachedPaint.baselineShift; - mCachedFm.bottom += mCachedPaint.baselineShift; - } - - mFontMetrics.append(mCachedFm.top); - mFontMetrics.append(mCachedFm.bottom); - mFontMetrics.append(mCachedFm.ascent); - mFontMetrics.append(mCachedFm.descent); + return -1; } } - /** - * Returns the maximum index that the accumulated width not exceeds the width. - * - * If forward=false is passed, returns the minimum index from the end instead. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { - float[] w = mWidths.getRawArray(); - if (forwards) { - int i = 0; - while (i < limit) { - width -= w[i]; - if (width < 0.0f) break; - i++; - } - while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; - return i; + @Override + public int getSpanFlags(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanFlags(tag); } else { - int i = limit - 1; - while (i >= 0) { - width -= w[i]; - if (width < 0.0f) break; - i--; - } - while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { - i++; - } - return limit - i - 1; + return 0; } } - /** - * Returns the length of the substring. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @FloatRange(from = 0.0f) float measure(int start, int limit) { - float width = 0; - float[] w = mWidths.getRawArray(); - for (int i = start; i < limit; ++i) { - width += w[i]; + @Override + public int nextSpanTransition(int start, int limit, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).nextSpanTransition(start, limit, type); + } else { + return mText.length(); } - return width; } - private static native /* Non Zero */ long nInitBuilder(); - - /** - * Apply style to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param isRtl True if the text is RTL. - */ - private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - boolean isRtl); + /////////////////////////////////////////////////////////////////////////////////////////////// + // CharSequence overrides. + // + // Just proxy for underlying mText. - /** - * Apply ReplacementRun to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param width The width of the replacement. - */ - private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @FloatRange(from = 0) float width); + @Override + public int length() { + return mText.length(); + } - private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr, - @NonNull char[] text); + @Override + public char charAt(int index) { + // TODO: Should this be index + mStart ? + return mText.charAt(index); + } - private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + @Override + public CharSequence subSequence(int start, int end) { + // TODO: return MeasuredText. + // TODO: Should this be index + mStart, end + mStart ? + return mText.subSequence(start, end); + } - @CriticalNative - private static native /* Non Zero */ long nGetReleaseFunc(); + @Override + public String toString() { + return mText.toString(); + } } diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java deleted file mode 100644 index 465314dd21ac8fe74fc2b0983a527739fd02aeea..0000000000000000000000000000000000000000 --- a/core/java/android/text/PremeasuredText.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.util.IntArray; - -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; - -import java.util.ArrayList; - -/** - * A text which has already been measured. - * - * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc. - */ -public class PremeasuredText implements Spanned { - private static final char LINE_FEED = '\n'; - - // The original text. - private final @NonNull CharSequence mText; - - // The inclusive start offset of the measuring target. - private final @IntRange(from = 0) int mStart; - - // The exclusive end offset of the measuring target. - private final @IntRange(from = 0) int mEnd; - - // The TextPaint used for measurement. - private final @NonNull TextPaint mPaint; - - // The requested text direction. - private final @NonNull TextDirectionHeuristic mTextDir; - - // The measured paragraph texts. - private final @NonNull MeasuredText[] mMeasuredTexts; - - // The sorted paragraph end offsets. - private final @NonNull int[] mParagraphBreakPoints; - - /** - * Build PremeasuredText 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 PremeasuredText build(@NonNull CharSequence text, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir) { - return PremeasuredText.build(text, paint, textDir, 0, text.length()); - } - - /** - * Build PremeasuredText 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. - */ - public static @NonNull PremeasuredText 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 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. - } - - paragraphEnds.add(paraEnd); - measuredTexts.add(MeasuredText.buildForStaticLayout( - paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); - } - - return new PremeasuredText(text, start, end, paint, textDir, - measuredTexts.toArray(new MeasuredText[measuredTexts.size()]), - paragraphEnds.toArray()); - } - - // Use PremeasuredText.build instead. - private PremeasuredText(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, - @NonNull MeasuredText[] measuredTexts, - @NonNull int[] paragraphBreakPoints) { - mText = text; - mStart = start; - mEnd = end; - mPaint = paint; - mMeasuredTexts = measuredTexts; - mParagraphBreakPoints = paragraphBreakPoints; - mTextDir = textDir; - } - - /** - * Return the underlying text. - */ - public @NonNull CharSequence getText() { - return mText; - } - - /** - * Returns the inclusive start offset of measured region. - */ - public @IntRange(from = 0) int getStart() { - return mStart; - } - - /** - * Returns the exclusive end offset of measured region. - */ - public @IntRange(from = 0) int getEnd() { - return mEnd; - } - - /** - * Returns the text direction associated with char sequence. - */ - public @NonNull TextDirectionHeuristic getTextDir() { - return mTextDir; - } - - /** - * Returns the paint used to measure this text. - */ - public @NonNull TextPaint getPaint() { - return mPaint; - } - - /** - * Returns the length of the paragraph of this text. - */ - public @IntRange(from = 0) int getParagraphCount() { - return mParagraphBreakPoints.length; - } - - /** - * Returns the paragraph start offset of the text. - */ - public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; - } - - /** - * Returns the paragraph end offset of the text. - */ - public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return mParagraphBreakPoints[paraIndex]; - } - - /** @hide */ - public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) { - return mMeasuredTexts[paraIndex]; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Spanned overrides - // - // Just proxy for underlying mText if appropriate. - - @Override - public T[] getSpans(int start, int end, Class type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpans(start, end, type); - } else { - return ArrayUtils.emptyArray(type); - } - } - - @Override - public int getSpanStart(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanStart(tag); - } else { - return -1; - } - } - - @Override - public int getSpanEnd(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanEnd(tag); - } else { - return -1; - } - } - - @Override - public int getSpanFlags(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanFlags(tag); - } else { - return 0; - } - } - - @Override - public int nextSpanTransition(int start, int limit, Class type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).nextSpanTransition(start, limit, type); - } else { - return mText.length(); - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // CharSequence overrides. - // - // Just proxy for underlying mText. - - @Override - public int length() { - return mText.length(); - } - - @Override - public char charAt(int index) { - // TODO: Should this be index + mStart ? - return mText.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - // TODO: return PremeasuredText. - // TODO: Should this be index + mStart, end + mStart ? - return mText.subSequence(start, end); - } - - @Override - public String toString() { - return mText.toString(); - } -} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index d69b1190140fbab73abc62386d350bbbff0bab21..e62f4216f33ac6aeae1cec73af76d2844316749c 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -55,7 +55,8 @@ public class StaticLayout extends Layout { * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * - * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native. + * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in + * native. * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. @@ -650,34 +651,48 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - PremeasuredText premeasured = null; + MeasuredText measured = null; final Spanned spanned; - if (source instanceof PremeasuredText) { - premeasured = (PremeasuredText) source; + final boolean canUseMeasuredText; + if (source instanceof MeasuredText) { + measured = (MeasuredText) source; - final CharSequence original = premeasured.getText(); - spanned = (original instanceof Spanned) ? (Spanned) original : null; - - if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) { + if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) { // The buffer position has changed. Re-measure here. - premeasured = PremeasuredText.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 premeasured information. - - // Overwrite with the one when premeasured. - // TODO: Give an option for developer not to overwrite and measure again here? - textDir = premeasured.getTextDir(); - paint = premeasured.getPaint(); + // We can use measured information. + canUseMeasuredText = true; } } else { - premeasured = PremeasuredText.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(false /* full layout is not necessary for line breaking */); 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 { - for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) { - final int paraStart = premeasured.getParagraphStart(paraIndex); - final int paraEnd = premeasured.getParagraphEnd(paraIndex); + for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) { + final int paraStart = measured.getParagraphStart(paraIndex); + final int paraEnd = measured.getParagraphEnd(paraIndex); int firstWidthLineCount = 1; int firstWidth = outerWidth; @@ -743,10 +758,10 @@ public class StaticLayout extends Layout { } } - final MeasuredText measured = premeasured.getMeasuredText(paraIndex); - final char[] chs = measured.getChars(); - final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); - final int[] fmCache = measured.getFontMetrics().getRawArray(); + final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex); + final char[] chs = measuredPara.getChars(); + final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray(); + final int[] fmCache = measuredPara.getFontMetrics().getRawArray(); // TODO: Stop keeping duplicated width copy in native and Java. widths.resize(chs.length); @@ -759,7 +774,7 @@ public class StaticLayout extends Layout { // Inputs chs, - measured.getNativePtr(), + measuredPara.getNativePtr(), paraEnd - paraStart, firstWidth, firstWidthLineCount, @@ -863,7 +878,7 @@ public class StaticLayout extends Layout { v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, - flags[breakIndex], needMultiply, measured, bufEnd, + flags[breakIndex], needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(), paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); @@ -894,8 +909,8 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { - final MeasuredText measured = - MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null); + final MeasuredParagraph measuredPara = + MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, @@ -903,7 +918,7 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, null, null, fm, 0, - needMultiply, measured, bufEnd, + needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); @@ -913,12 +928,10 @@ public class StaticLayout extends Layout { } } - // The parameters that are not changed in the method are marked as final to make the code - // easier to understand. private int out(final CharSequence text, final int start, final int end, int above, int below, int top, int bottom, int v, final float spacingmult, final float spacingadd, final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, - final int flags, final boolean needMultiply, @NonNull final MeasuredText measured, + final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, @@ -943,21 +956,29 @@ public class StaticLayout extends Layout { mLineDirections = grow; } - lines[off + START] = start; - lines[off + TOP] = v; + if (chooseHt != null) { + fm.ascent = above; + fm.descent = below; + fm.top = top; + fm.bottom = bottom; - // Information about hyphenation, tabs, and directions are needed for determining - // ellipsization, so the values should be assigned before ellipsization. + for (int i = 0; i < chooseHt.length; i++) { + if (chooseHt[i] instanceof LineHeightSpan.WithDensity) { + ((LineHeightSpan.WithDensity) chooseHt[i]) + .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint); + } else { + chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm); + } + } - // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining - // one bit for start field - lines[off + TAB] |= flags & TAB_MASK; - lines[off + HYPHEN] = flags; - lines[off + DIR] |= dir << DIR_SHIFT; - mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart); + above = fm.ascent; + below = fm.descent; + top = fm.top; + bottom = fm.bottom; + } - final boolean firstLine = (j == 0); - final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); + boolean firstLine = (j == 0); + boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); if (ellipsize != null) { // If there is only one line, then do any type of ellipsis except when it is MARQUEE @@ -970,9 +991,9 @@ public class StaticLayout extends Layout { (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) && ellipsize == TextUtils.TruncateAt.END); if (doEllipsis) { - calculateEllipsis(text, start, end, widths, widthStart, - ellipsisWidth - getTotalInsets(j), ellipsize, j, - textWidth, paint, forceEllipsis, dir); + calculateEllipsis(start, end, widths, widthStart, + ellipsisWidth, ellipsize, j, + textWidth, paint, forceEllipsis); } } @@ -991,28 +1012,6 @@ public class StaticLayout extends Layout { } } - if (chooseHt != null) { - fm.ascent = above; - fm.descent = below; - fm.top = top; - fm.bottom = bottom; - - for (int i = 0; i < chooseHt.length; i++) { - if (chooseHt[i] instanceof LineHeightSpan.WithDensity) { - ((LineHeightSpan.WithDensity) chooseHt[i]) - .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint); - - } else { - chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm); - } - } - - above = fm.ascent; - below = fm.descent; - top = fm.top; - bottom = fm.bottom; - } - if (firstLine) { if (trackPad) { mTopPadding = top - above; @@ -1023,6 +1022,8 @@ public class StaticLayout extends Layout { } } + int extra; + if (lastLine) { if (trackPad) { mBottomPadding = bottom - below; @@ -1033,9 +1034,8 @@ public class StaticLayout extends Layout { } } - final int extra; if (needMultiply && (addLastLineLineSpacing || !lastLine)) { - final double ex = (below - above) * (spacingmult - 1) + spacingadd; + double ex = (below - above) * (spacingmult - 1) + spacingadd; if (ex >= 0) { extra = (int)(ex + EXTRA_ROUNDING); } else { @@ -1045,6 +1045,8 @@ public class StaticLayout extends Layout { extra = 0; } + lines[off + START] = start; + lines[off + TOP] = v; lines[off + DESCENT] = below + extra; lines[off + EXTRA] = extra; @@ -1052,7 +1054,7 @@ public class StaticLayout extends Layout { // store the height as if it was ellipsized if (!mEllipsized && currentLineIsTheLastVisibleOne) { // below calculation as if it was the last line - final int maxLineBelow = includePad ? bottom : below; + int maxLineBelow = includePad ? bottom : below; // similar to the calculation of v below, without the extra. mMaxLineHeight = v + (maxLineBelow - above); } @@ -1061,13 +1063,23 @@ public class StaticLayout extends Layout { lines[off + mColumns + START] = end; lines[off + mColumns + TOP] = v; + // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining + // one bit for start field + lines[off + TAB] |= flags & TAB_MASK; + lines[off + HYPHEN] = flags; + lines[off + DIR] |= dir << DIR_SHIFT; + mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart); + mLineCount++; return v; } - private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths, - int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth, - TextPaint paint, boolean forceEllipsis, int dir) { + private void calculateEllipsis(int lineStart, int lineEnd, + float[] widths, int widthStart, + float avail, TextUtils.TruncateAt where, + int line, float textWidth, TextPaint paint, + boolean forceEllipsis) { + avail -= getTotalInsets(line); if (textWidth <= avail && !forceEllipsis) { // Everything fits! mLines[mColumns * line + ELLIPSIS_START] = 0; @@ -1075,53 +1087,11 @@ public class StaticLayout extends Layout { return; } - float tempAvail = avail; - int numberOfTries = 0; - boolean lineFits = false; - mWorkPaint.set(paint); - do { - final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths, - widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir); - if (ellipsizedWidth <= avail) { - lineFits = true; - } else { - numberOfTries++; - if (numberOfTries > 10) { - // If the text still doesn't fit after ten tries, assume it will never fit and - // ellipsize it all. - mLines[mColumns * line + ELLIPSIS_START] = 0; - mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart; - lineFits = true; - } else { - // Some side effect of ellipsization has caused the text to go over the - // available width. Let's make the available width shorter by exactly that - // amount and retry. - tempAvail -= ellipsizedWidth - avail; - } - } - } while (!lineFits); - mEllipsized = true; - } + float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where)); + int ellipsisStart = 0; + int ellipsisCount = 0; + int len = lineEnd - lineStart; - // Returns the width of the ellipsized line which in some rare cases can actually be larger - // than 'avail' (due to kerning or other context-based effect of replacement of text by - // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode, - // returns 0 so the caller can stop iterating. - // - // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it - // should not be accessed while the method is running. - private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths, - int widthStart, float avail, TextUtils.TruncateAt where, int line, - TextPaint paint, boolean forceEllipsis, int dir) { - final int savedHyphenEdit = paint.getHyphenEdit(); - paint.setHyphenEdit(0); - final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where)); - final int ellipsisStart; - final int ellipsisCount; - final int len = lineEnd - lineStart; - final int offset = lineStart - widthStart; - - int hyphen = getHyphen(line); // We only support start ellipsis on a single line if (where == TextUtils.TruncateAt.START) { if (mMaximumVisibleLineCount == 1) { @@ -1129,9 +1099,9 @@ public class StaticLayout extends Layout { int i; for (i = len; i > 0; i--) { - final float w = widths[i - 1 + offset]; + float w = widths[i - 1 + lineStart - widthStart]; if (w + sum + ellipsisWidth > avail) { - while (i < len && widths[i + offset] == 0.0f) { + while (i < len && widths[i + lineStart - widthStart] == 0.0f) { i++; } break; @@ -1142,13 +1112,9 @@ public class StaticLayout extends Layout { ellipsisStart = 0; ellipsisCount = i; - // Strip the potential hyphenation at beginning of line. - hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE; } else { - ellipsisStart = 0; - ellipsisCount = 0; if (Log.isLoggable(TAG, Log.WARN)) { - Log.w(TAG, "Start ellipsis only supported with one line"); + Log.w(TAG, "Start Ellipsis only supported with one line"); } } } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE || @@ -1157,7 +1123,7 @@ public class StaticLayout extends Layout { int i; for (i = 0; i < len; i++) { - final float w = widths[i + offset]; + float w = widths[i + lineStart - widthStart]; if (w + sum + ellipsisWidth > avail) { break; @@ -1166,27 +1132,24 @@ public class StaticLayout extends Layout { sum += w; } - if (forceEllipsis && i == len && len > 0) { + ellipsisStart = i; + ellipsisCount = len - i; + if (forceEllipsis && ellipsisCount == 0 && len > 0) { ellipsisStart = len - 1; ellipsisCount = 1; - } else { - ellipsisStart = i; - ellipsisCount = len - i; } - // Strip the potential hyphenation at end of line. - hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE; - } else { // where = TextUtils.TruncateAt.MIDDLE - // We only support middle ellipsis on a single line. + } else { + // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line if (mMaximumVisibleLineCount == 1) { float lsum = 0, rsum = 0; int left = 0, right = len; - final float ravail = (avail - ellipsisWidth) / 2; + float ravail = (avail - ellipsisWidth) / 2; for (right = len; right > 0; right--) { - final float w = widths[right - 1 + offset]; + float w = widths[right - 1 + lineStart - widthStart]; if (w + rsum > ravail) { - while (right < len && widths[right + offset] == 0.0f) { + while (right < len && widths[right + lineStart - widthStart] == 0.0f) { right++; } break; @@ -1194,9 +1157,9 @@ public class StaticLayout extends Layout { rsum += w; } - final float lavail = avail - ellipsisWidth - rsum; + float lavail = avail - ellipsisWidth - rsum; for (left = 0; left < right; left++) { - final float w = widths[left + offset]; + float w = widths[left + lineStart - widthStart]; if (w + lsum > lavail) { break; @@ -1208,53 +1171,14 @@ public class StaticLayout extends Layout { ellipsisStart = left; ellipsisCount = right - left; } else { - ellipsisStart = 0; - ellipsisCount = 0; if (Log.isLoggable(TAG, Log.WARN)) { - Log.w(TAG, "Middle ellipsis only supported with one line"); + Log.w(TAG, "Middle Ellipsis only supported with one line"); } } } + mEllipsized = true; mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; - - if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) { - // Unsupported ellipsization mode or all text is ellipsized away. Return 0. - return 0.0f; - } - - final boolean isSpanned = text instanceof Spanned; - final Ellipsizer ellipsizedText = isSpanned - ? new SpannedEllipsizer(text) - : new Ellipsizer(text); - ellipsizedText.mLayout = this; - ellipsizedText.mMethod = where; - - final boolean hasTabs = getLineContainsTab(line); - final TabStops tabStops; - if (hasTabs && isSpanned) { - final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart, - lineEnd, TabStopSpan.class); - if (tabs.length == 0) { - tabStops = null; - } else { - tabStops = new TabStops(TAB_INCREMENT, tabs); - } - } else { - tabStops = null; - } - paint.setHyphenEdit(hyphen); - final TextLine textline = TextLine.obtain(); - textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line), - hasTabs, tabStops); - // Since TextLine.metric() returns negative values for RTL text, multiplication by dir - // converts it to an actual width. Note that we don't want to use the absolute value, - // since we may actually have glyphs with negative advances, which by definition always - // fit. - final float ellipsizedWidth = textline.metrics(null) * dir; - TextLine.recycle(textline); - paint.setHyphenEdit(savedHyphenEdit); - return ellipsizedWidth; } private float getTotalInsets(int line) { @@ -1494,8 +1418,6 @@ public class StaticLayout extends Layout { */ private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT; - private TextPaint mWorkPaint = new TextPaint(); - private static final int COLUMNS_NORMAL = 5; private static final int COLUMNS_ELLIPSIZE = 7; private static final int START = 0; diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 86cc0141b0a44dd16dd5f3d89b7ca483f7ce6de9..55367dcce47e24ac2e192352844d066ae2643cb0 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -60,6 +60,7 @@ public class TextLine { private char[] mChars; private boolean mCharsValid; private Spanned mSpanned; + private MeasuredText mMeasured; // Additional width of whitespace for justification. This value is per whitespace, thus // the line width will increase by mAddedWidth x (number of stretchable whitespaces). @@ -118,6 +119,7 @@ public class TextLine { tl.mSpanned = null; tl.mTabs = null; tl.mChars = null; + tl.mMeasured = null; tl.mMetricAffectingSpanSpanSet.recycle(); tl.mCharacterStyleSpanSet.recycle(); @@ -168,6 +170,14 @@ public class TextLine { hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0; } + mMeasured = null; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + if (mt.canUseMeasuredResult(paint)) { + mMeasured = mt; + } + } + mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; if (mCharsValid) { @@ -736,8 +746,13 @@ public class TextLine { return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset); } else { final int delta = mStart; - return wp.getRunAdvance(mText, delta + start, delta + end, - delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); + if (mMeasured == null) { + // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text. + return wp.getRunAdvance(mText, delta + start, delta + end, + delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); + } else { + return mMeasured.getWidth(start + delta, end + delta); + } } } diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 9c9fbf23832fa500b9d9aad9fe83a8182df31184..af0eebfb80d8722e0f40923b98d1df9429427313 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -88,8 +88,8 @@ public class TextUtils { /** {@hide} */ @NonNull - public static String getEllipsisString(@NonNull TruncateAt method) { - return (method == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL; + public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) { + return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL; } @@ -1194,11 +1194,9 @@ public class TextUtils { * or, if it does not fit, a truncated * copy with ellipsis character added at the specified edge or center. */ - @NonNull - public static CharSequence ellipsize(@NonNull CharSequence text, - @NonNull TextPaint p, - @FloatRange(from = 0.0) float avail, - @NonNull TruncateAt where) { + public static CharSequence ellipsize(CharSequence text, + TextPaint p, + float avail, TruncateAt where) { return ellipsize(text, p, avail, where, false, null); } @@ -1214,11 +1212,9 @@ public class TextUtils { * report the start and end of the ellipsized range. TextDirection * is determined by the first strong directional character. */ - @NonNull - public static CharSequence ellipsize(@NonNull CharSequence text, - @NonNull TextPaint paint, - @FloatRange(from = 0.0) float avail, - @NonNull TruncateAt where, + public static CharSequence ellipsize(CharSequence text, + TextPaint paint, + float avail, TruncateAt where, boolean preserveLength, @Nullable EllipsizeCallback callback) { return ellipsize(text, paint, avail, where, preserveLength, callback, @@ -1239,131 +1235,94 @@ public class TextUtils { * * @hide */ - @NonNull - public static CharSequence ellipsize(@NonNull CharSequence text, - @NonNull TextPaint paint, - @FloatRange(from = 0.0) float avail, - @NonNull TruncateAt where, + public static CharSequence ellipsize(CharSequence text, + TextPaint paint, + float avail, TruncateAt where, boolean preserveLength, @Nullable EllipsizeCallback callback, - @NonNull TextDirectionHeuristic textDir, - @NonNull String ellipsis) { + TextDirectionHeuristic textDir, String ellipsis) { + + int len = text.length(); - final int len = text.length(); - MeasuredText mt = null; - MeasuredText resultMt = null; + MeasuredParagraph mt = null; try { - mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); float width = mt.getWholeWidth(); if (width <= avail) { if (callback != null) { callback.ellipsized(0, 0); } + return text; } - // First estimate of effective width of ellipsis. - float ellipsisWidth = paint.measureText(ellipsis); - int numberOfTries = 0; - boolean textFits = false; - int start, end; - CharSequence result; - do { - if (avail < ellipsisWidth) { - // Even the ellipsis can't fit. So it all goes. - start = 0; - end = len; - } else { - final float remainingWidth = avail - ellipsisWidth; - if (where == TruncateAt.START) { - start = 0; - end = len - mt.breakText(len, false /* backwards */, remainingWidth); - } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) { - start = mt.breakText(len, true /* forwards */, remainingWidth); - end = len; - } else { - end = len - mt.breakText(len, false /* backwards */, remainingWidth / 2); - start = mt.breakText(end, true /* forwards */, - remainingWidth - mt.measure(end, len)); - } - } + // XXX assumes ellipsis string does not require shaping and + // is unaffected by style + float ellipsiswid = paint.measureText(ellipsis); + avail -= ellipsiswid; + + int left = 0; + int right = len; + if (avail < 0) { + // it all goes + } else if (where == TruncateAt.START) { + right = len - mt.breakText(len, false, avail); + } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) { + left = mt.breakText(len, true, avail); + } else { + right = len - mt.breakText(len, false, avail / 2); + avail -= mt.measure(right, len); + left = mt.breakText(right, true, avail); + } - final char[] buf = mt.getChars(); - final Spanned sp = text instanceof Spanned ? (Spanned) text : null; - - final int removed = end - start; - final int remaining = len - removed; - if (preserveLength) { - int pos = start; - if (remaining > 0 && removed >= ellipsis.length()) { - ellipsis.getChars(0, ellipsis.length(), buf, start); - pos += ellipsis.length(); - } // else eliminate the ellipsis - while (pos < end) { - buf[pos++] = ELLIPSIS_FILLER; - } - final String s = new String(buf, 0, len); - if (sp == null) { - result = s; - } else { - final SpannableString ss = new SpannableString(s); - copySpansFrom(sp, 0, len, Object.class, ss, 0); - result = ss; - } - } else { - if (remaining == 0) { - result = ""; - } else if (sp == null) { - final StringBuilder sb = new StringBuilder(remaining + ellipsis.length()); - sb.append(buf, 0, start); - sb.append(ellipsis); - sb.append(buf, end, len - end); - result = sb.toString(); - } else { - final SpannableStringBuilder ssb = new SpannableStringBuilder(); - ssb.append(text, 0, start); - ssb.append(ellipsis); - ssb.append(text, end, len); - result = ssb; - } - } + if (callback != null) { + callback.ellipsized(left, right); + } - if (remaining == 0) { // All text is gone. - textFits = true; - } else { - resultMt = MeasuredText.buildForMeasurement( - paint, result, 0, result.length(), textDir, resultMt); - width = resultMt.getWholeWidth(); - if (width <= avail) { - textFits = true; - } else { - numberOfTries++; - if (numberOfTries > 10) { - // If the text still doesn't fit after ten tries, assume it will never - // fit and ellipsize it all. We do this by setting the width of the - // ellipsis to be positive infinity, so we get to empty text in the next - // round. - ellipsisWidth = Float.POSITIVE_INFINITY; - } else { - // Adjust the width of the ellipsis by adding the amount 'width' is - // still over. - ellipsisWidth += width - avail; - } - } + final char[] buf = mt.getChars(); + Spanned sp = text instanceof Spanned ? (Spanned) text : null; + + final int removed = right - left; + final int remaining = len - removed; + if (preserveLength) { + if (remaining > 0 && removed >= ellipsis.length()) { + ellipsis.getChars(0, ellipsis.length(), buf, left); + left += ellipsis.length(); + } // else skip the ellipsis + for (int i = left; i < right; i++) { + buf[i] = ELLIPSIS_FILLER; } - } while (!textFits); - if (callback != null) { - callback.ellipsized(start, end); + String s = new String(buf, 0, len); + if (sp == null) { + return s; + } + SpannableString ss = new SpannableString(s); + copySpansFrom(sp, 0, len, Object.class, ss, 0); + return ss; } - return result; + + if (remaining == 0) { + return ""; + } + + if (sp == null) { + StringBuilder sb = new StringBuilder(remaining + ellipsis.length()); + sb.append(buf, 0, left); + sb.append(ellipsis); + sb.append(buf, right, len - right); + return sb.toString(); + } + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(text, 0, left); + ssb.append(ellipsis); + ssb.append(text, right, len); + return ssb; } finally { if (mt != null) { mt.recycle(); } - if (resultMt != null) { - resultMt.recycle(); - } } } @@ -1394,6 +1353,7 @@ public class TextUtils { * @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"}) * doesn't fit, it will return an empty string. */ + public static CharSequence listEllipsize(@Nullable Context context, @Nullable List elements, @NonNull String separator, @NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail, @@ -1479,11 +1439,11 @@ public class TextUtils { public static CharSequence commaEllipsize(CharSequence text, TextPaint p, float avail, String oneMore, String more, TextDirectionHeuristic textDir) { - MeasuredText mt = null; - MeasuredText tempMt = null; + MeasuredParagraph mt = null; + MeasuredParagraph tempMt = null; try { int len = text.length(); - mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt); final float width = mt.getWholeWidth(); if (width <= avail) { return text; @@ -1523,7 +1483,7 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt = MeasuredText.buildForMeasurement( + tempMt = MeasuredParagraph.buildForMeasurement( p, format, 0, format.length(), textDir, tempMt); float moreWid = tempMt.getWholeWidth(); diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index bbd9c9c0bac02735c2ccad38c03efed26ac48ee7..562ae7ada90ab695d0d368a2dba62b5c87a5a196 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -358,7 +358,7 @@ public class Time { } /** - * Return the current time in YYYYMMDDTHHMMSS format + * Return the current time in YYYYMMDDTHHMMSS<tz> format */ @Override public String toString() { @@ -738,6 +738,7 @@ public class Time { *

        * You should also use toMillis(false) if you want * to read back the same milliseconds that you set with {@link #set(long)} + * or {@link #set(Time)} or after parsing a date string. * *

        * This method can return {@code -1} when the date / time fields have been @@ -745,8 +746,6 @@ public class Time { * For example, when daylight savings transitions cause an hour to be * skipped: times within that hour will return {@code -1} if isDst = * {@code -1}. - * - * or {@link #set(Time)} or after parsing a date string. */ public long toMillis(boolean ignoreDst) { calculator.copyFieldsFromTime(this); diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java index 908ef55a6ae9610659bf3bbff758b258133009f2..3b4eea76b390cc618899810c0bca0551afcb8c8b 100644 --- a/core/java/android/text/style/AbsoluteSizeSpan.java +++ b/core/java/android/text/style/AbsoluteSizeSpan.java @@ -16,71 +16,105 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * A span that changes the size of the text it's attached to. + *

        + * For example, the size of the text can be changed to 55dp like this: + *

        {@code
        + * SpannableString string = new SpannableString("Text with absolute size span");
        + *string.setSpan(new AbsoluteSizeSpan(55, true), 10, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Text with text size updated.
        + */ public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mSize; - private boolean mDip; + private final boolean mDip; /** * Set the text size to size physical pixels. */ public AbsoluteSizeSpan(int size) { - mSize = size; + this(size, false); } /** - * Set the text size to size physical pixels, - * or to size device-independent pixels if - * dip is true. + * Set the text size to size physical pixels, or to size + * device-independent pixels if dip is true. */ public AbsoluteSizeSpan(int size, boolean dip) { mSize = size; mDip = dip; } - public AbsoluteSizeSpan(Parcel src) { + /** + * Creates an {@link AbsoluteSizeSpan} from a parcel. + */ + public AbsoluteSizeSpan(@NonNull Parcel src) { mSize = src.readInt(); mDip = src.readInt() != 0; } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.ABSOLUTE_SIZE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mSize); dest.writeInt(mDip ? 1 : 0); } + /** + * Get the text size. This is in physical pixels if {@link #getDip()} returns false or in + * device-independent pixels if {@link #getDip()} returns true. + * + * @return the text size, either in physical pixels or device-independent pixels. + * @see AbsoluteSizeSpan#AbsoluteSizeSpan(int, boolean) + */ public int getSize() { return mSize; } + /** + * Returns whether the size is in device-independent pixels or not, depending on the + * dip flag passed in {@link #AbsoluteSizeSpan(int, boolean)} + * + * @return true if the size is in device-independent pixels, false + * otherwise + * + * @see #AbsoluteSizeSpan(int, boolean) + */ public boolean getDip() { return mDip; } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { if (mDip) { ds.setTextSize(mSize * ds.density); } else { @@ -89,7 +123,7 @@ public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableS } @Override - public void updateMeasureState(TextPaint ds) { + public void updateMeasureState(@NonNull TextPaint ds) { if (mDip) { ds.setTextSize(mSize * ds.density); } else { diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java index 615830949ed3e936c0721b25690972e81472a407..18c3e16b910ab468a5e3b44f4d8c9db52195f99c 100644 --- a/core/java/android/text/style/AlignmentSpan.java +++ b/core/java/android/text/style/AlignmentSpan.java @@ -16,49 +16,90 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.Layout; import android.text.ParcelableSpan; import android.text.TextUtils; +/** + * Span that allows defining the alignment of text at the paragraph level. + */ public interface AlignmentSpan extends ParagraphStyle { + + /** + * Returns the alignment of the text. + * + * @return the text alignment + */ Layout.Alignment getAlignment(); + /** + * Default implementation of the {@link AlignmentSpan}. + *

        + * For example, a text written in a left to right language, like English, which is by default + * aligned to the left, can be aligned opposite to the layout direction like this: + *

        {@code SpannableString string = new SpannableString("Text with opposite alignment");
        +     *string.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE), 0,
        +     *string.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Align left to right text opposite to the layout direction.
        + *

        + * A text written in a right to left language, like Hebrew, which is by default aligned to the + * right, can be aligned opposite to the layout direction like this: + *

        {@code SpannableString string = new SpannableString("טקסט עם יישור הפוך");
        +     *string.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE), 0,
        +     *string.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Align right to left text opposite to the layout direction.
        + */ class Standard implements AlignmentSpan, ParcelableSpan { - public Standard(Layout.Alignment align) { + private final Layout.Alignment mAlignment; + + /** + * Constructs a {@link Standard} from an alignment. + */ + public Standard(@NonNull Layout.Alignment align) { mAlignment = align; } - public Standard(Parcel src) { + /** + * Constructs a {@link Standard} from a parcel. + */ + public Standard(@NonNull Parcel src) { mAlignment = Layout.Alignment.valueOf(src.readString()); } - + + @Override public int getSpanTypeId() { - return getSpanTypeIdInternal(); - } + return getSpanTypeIdInternal(); + } - /** @hide */ - public int getSpanTypeIdInternal() { + /** @hide */ + @Override + public int getSpanTypeIdInternal() { return TextUtils.ALIGNMENT_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeString(mAlignment.name()); } + @Override public Layout.Alignment getAlignment() { return mAlignment; } - - private final Layout.Alignment mAlignment; } } diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java index de05f50c9860ba019bf57989934f5afffcd3c3e3..44e35615ca55c774edd4eda36fbf8eb55e3cb842 100644 --- a/core/java/android/text/style/BackgroundColorSpan.java +++ b/core/java/android/text/style/BackgroundColorSpan.java @@ -16,52 +16,88 @@ package android.text.style; +import android.annotation.ColorInt; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * Changes the background color of the text to which the span is attached. + *

        + * For example, to set a green background color for a text you would create a {@link + * android.text.SpannableString} based on the text and set the span. + *

        {@code
        + * SpannableString string = new SpannableString("Text with a background color span");
        + *string.setSpan(new BackgroundColorSpan(color), 12, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Set a background color for the text.
        + */ public class BackgroundColorSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { private final int mColor; - public BackgroundColorSpan(int color) { + /** + * Creates a {@link BackgroundColorSpan} from a color integer. + *

        + * + * @param color color integer that defines the background color + * @see android.content.res.Resources#getColor(int, Resources.Theme) + */ + public BackgroundColorSpan(@ColorInt int color) { mColor = color; } - public BackgroundColorSpan(Parcel src) { + /** + * Creates a {@link BackgroundColorSpan} from a parcel. + */ + public BackgroundColorSpan(@NonNull Parcel src) { mColor = src.readInt(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.BACKGROUND_COLOR_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mColor); } + /** + * @return the background color of this span. + * @see BackgroundColorSpan#BackgroundColorSpan(int) + */ + @ColorInt public int getBackgroundColor() { return mColor; } + /** + * Updates the background color of the TextPaint. + */ @Override - public void updateDrawState(TextPaint ds) { - ds.bgColor = mColor; + public void updateDrawState(@NonNull TextPaint textPaint) { + textPaint.bgColor = mColor; } } diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java index 43dd0ff52e49eda59d33239567bd4050d273f003..70175c8611e494d5f83d2e2640f4f3332b67636a 100644 --- a/core/java/android/text/style/BulletSpan.java +++ b/core/java/android/text/style/BulletSpan.java @@ -16,6 +16,11 @@ package android.text.style; +import android.annotation.ColorInt; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Px; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; @@ -26,38 +31,108 @@ import android.text.ParcelableSpan; import android.text.Spanned; import android.text.TextUtils; +/** + * A span which styles paragraphs as bullet points (respecting layout direction). + *

        + * BulletSpans must be attached from the first character to the last character of a single + * paragraph, otherwise the bullet point will not be displayed but the first paragraph encountered + * will have a leading margin. + *

        + * BulletSpans allow configuring the following elements: + *

          + *
        • gap width - the distance, in pixels, between the bullet point and the paragraph. + * Default value is 2px.
        • + *
        • color - the bullet point color. By default, the bullet point color is 0 - no color, + * so it uses the TextView's text color.
        • + *
        • bullet radius - the radius, in pixels, of the bullet point. Default value is + * 4px.
        • + *
        + * For example, a BulletSpan using the default values can be constructed like this: + *
        {@code
        + *  SpannableString string = new SpannableString("Text with\nBullet point");
        + *string.setSpan(new BulletSpan(), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        BulletSpan constructed with default values.
        + *

        + *

        + * To construct a BulletSpan with a gap width of 40px, green bullet point and bullet radius of + * 20px: + *

        {@code
        + *  SpannableString string = new SpannableString("Text with\nBullet point");
        + *string.setSpan(new BulletSpan(40, color, 20), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Customized BulletSpan.
        + */ public class BulletSpan implements LeadingMarginSpan, ParcelableSpan { - private final int mGapWidth; - private final boolean mWantColor; - private final int mColor; - // Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices. - private static final float BULLET_RADIUS = 3 * 1.2f; - private static Path sBulletPath = null; + private static final int STANDARD_BULLET_RADIUS = 4; public static final int STANDARD_GAP_WIDTH = 2; + private static final int STANDARD_COLOR = 0; + @Px + private final int mGapWidth; + @Px + private final int mBulletRadius; + private Path mBulletPath = null; + @ColorInt + private final int mColor; + private final boolean mWantColor; + + /** + * Creates a {@link BulletSpan} with the default values. + */ public BulletSpan() { - mGapWidth = STANDARD_GAP_WIDTH; - mWantColor = false; - mColor = 0; + this(STANDARD_GAP_WIDTH, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS); } + /** + * Creates a {@link BulletSpan} based on a gap width + * + * @param gapWidth the distance, in pixels, between the bullet point and the paragraph. + */ public BulletSpan(int gapWidth) { - mGapWidth = gapWidth; - mWantColor = false; - mColor = 0; + this(gapWidth, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS); } - public BulletSpan(int gapWidth, int color) { + /** + * Creates a {@link BulletSpan} based on a gap width and a color integer. + * + * @param gapWidth the distance, in pixels, between the bullet point and the paragraph. + * @param color the bullet point color, as a color integer + * @see android.content.res.Resources#getColor(int, Resources.Theme) + */ + public BulletSpan(int gapWidth, @ColorInt int color) { + this(gapWidth, color, true, STANDARD_BULLET_RADIUS); + } + + /** + * Creates a {@link BulletSpan} based on a gap width and a color integer. + * + * @param gapWidth the distance, in pixels, between the bullet point and the paragraph. + * @param color the bullet point color, as a color integer. + * @param bulletRadius the radius of the bullet point, in pixels. + * @see android.content.res.Resources#getColor(int, Resources.Theme) + */ + public BulletSpan(int gapWidth, @ColorInt int color, @IntRange(from = 0) int bulletRadius) { + this(gapWidth, color, true, bulletRadius); + } + + private BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor, + @IntRange(from = 0) int bulletRadius) { mGapWidth = gapWidth; - mWantColor = true; + mBulletRadius = bulletRadius; mColor = color; + mWantColor = wantColor; } - public BulletSpan(Parcel src) { + /** + * Creates a {@link BulletSpan} from a parcel. + */ + public BulletSpan(@NonNull Parcel src) { mGapWidth = src.readInt(); mWantColor = src.readInt() != 0; mColor = src.readInt(); + mBulletRadius = src.readInt(); } @Override @@ -77,68 +152,97 @@ public class BulletSpan implements LeadingMarginSpan, ParcelableSpan { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ @Override - public void writeToParcelInternal(Parcel dest, int flags) { + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mGapWidth); dest.writeInt(mWantColor ? 1 : 0); dest.writeInt(mColor); + dest.writeInt(mBulletRadius); } @Override public int getLeadingMargin(boolean first) { - return (int) (2 * BULLET_RADIUS + mGapWidth); + return 2 * mBulletRadius + mGapWidth; + } + + /** + * Get the distance, in pixels, between the bullet point and the paragraph. + * + * @return the distance, in pixels, between the bullet point and the paragraph. + */ + public int getGapWidth() { + return mGapWidth; + } + + /** + * Get the radius, in pixels, of the bullet point. + * + * @return the radius, in pixels, of the bullet point. + */ + public int getBulletRadius() { + return mBulletRadius; + } + + /** + * Get the bullet point color. + * + * @return the bullet point color + */ + public int getColor() { + return mColor; } @Override - public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, - int top, int baseline, int bottom, - CharSequence text, int start, int end, - boolean first, Layout l) { + public void drawLeadingMargin(@NonNull Canvas canvas, @NonNull Paint paint, int x, int dir, + int top, int baseline, int bottom, + @NonNull CharSequence text, int start, int end, + boolean first, @Nullable Layout layout) { if (((Spanned) text).getSpanStart(this) == start) { - Paint.Style style = p.getStyle(); + Paint.Style style = paint.getStyle(); int oldcolor = 0; if (mWantColor) { - oldcolor = p.getColor(); - p.setColor(mColor); + oldcolor = paint.getColor(); + paint.setColor(mColor); } - p.setStyle(Paint.Style.FILL); + paint.setStyle(Paint.Style.FILL); - if (l != null) { + if (layout != null) { // "bottom" position might include extra space as a result of line spacing // configuration. Subtract extra space in order to show bullet in the vertical // center of characters. - final int line = l.getLineForOffset(start); - bottom = bottom - l.getLineExtra(line); + final int line = layout.getLineForOffset(start); + bottom = bottom - layout.getLineExtra(line); } - final float y = (top + bottom) / 2f; + final float yPosition = (top + bottom) / 2f; + final float xPosition = x + dir * mBulletRadius; - if (c.isHardwareAccelerated()) { - if (sBulletPath == null) { - sBulletPath = new Path(); - sBulletPath.addCircle(0.0f, 0.0f, BULLET_RADIUS, Direction.CW); + if (canvas.isHardwareAccelerated()) { + if (mBulletPath == null) { + mBulletPath = new Path(); + mBulletPath.addCircle(0.0f, 0.0f, mBulletRadius, Direction.CW); } - c.save(); - c.translate(x + dir * BULLET_RADIUS, y); - c.drawPath(sBulletPath, p); - c.restore(); + canvas.save(); + canvas.translate(xPosition, yPosition); + canvas.drawPath(mBulletPath, paint); + canvas.restore(); } else { - c.drawCircle(x + dir * BULLET_RADIUS, y, BULLET_RADIUS, p); + canvas.drawCircle(xPosition, yPosition, mBulletRadius, paint); } if (mWantColor) { - p.setColor(oldcolor); + paint.setColor(oldcolor); } - p.setStyle(style); + paint.setStyle(style); } } } diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java index b098f16da1eda1c969af339534a8dd56aad70451..60aed2a782ab3227b9aedb1a9f8fd3322b4198d6 100644 --- a/core/java/android/text/style/ClickableSpan.java +++ b/core/java/android/text/style/ClickableSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.text.TextPaint; import android.view.View; @@ -24,6 +25,16 @@ import android.view.View; * with a movement method of LinkMovementMethod, the affected spans of * text can be selected. If selected and clicked, the {@link #onClick} method will * be called. + *

        + * The text with a ClickableSpan attached will be underlined and the link color will be + * used as a text color. The default link color is the theme's accent color or + * android:textColorLink if this attribute is defined in the theme. + * For example, considering that we have a CustomClickableSpan that extends + * ClickableSpan, it can be used like this: + *

        {@code SpannableString string = new SpannableString("Text with clickable text");
        + *string.setSpan(new CustomClickableSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Text with ClickableSpan.
        */ public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance { private static int sIdCounter = 0; @@ -33,13 +44,13 @@ public abstract class ClickableSpan extends CharacterStyle implements UpdateAppe /** * Performs the click action associated with this span. */ - public abstract void onClick(View widget); + public abstract void onClick(@NonNull View widget); /** * Makes the text underlined and in the link color. */ @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { ds.setColor(ds.linkColor); ds.setUnderlineText(true); } diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java index 7af1c2c8960070dc0ce24115c0e6a7b9dae07fc8..9ee0b074459e0e85bfb97fd609422ab8a35c078a 100644 --- a/core/java/android/text/style/EasyEditSpan.java +++ b/core/java/android/text/style/EasyEditSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.app.PendingIntent; import android.os.Parcel; import android.text.ParcelableSpan; @@ -79,7 +80,7 @@ public class EasyEditSpan implements ParcelableSpan { /** * Constructor called from {@link TextUtils} to restore the span. */ - public EasyEditSpan(Parcel source) { + public EasyEditSpan(@NonNull Parcel source) { mPendingIntent = source.readParcelable(null); mDeleteEnabled = (source.readByte() == 1); } @@ -90,12 +91,12 @@ public class EasyEditSpan implements ParcelableSpan { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeParcelable(mPendingIntent, 0); dest.writeByte((byte) (mDeleteEnabled ? 1 : 0)); } diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java index 2bc6d5406adee3b740c42d63ba99023470313c4a..f770674503f20f9716eecd4a18affb003378d835 100644 --- a/core/java/android/text/style/ForegroundColorSpan.java +++ b/core/java/android/text/style/ForegroundColorSpan.java @@ -17,53 +17,88 @@ package android.text.style; import android.annotation.ColorInt; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * Changes the color of the text to which the span is attached. + *

        + * For example, to set a green text color you would create a {@link + * android.text.SpannableString} based on the text and set the span. + *

        {@code
        + * SpannableString string = new SpannableString("Text with a foreground color span");
        + *string.setSpan(new ForegroundColorSpan(color), 12, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Set a text color.
        + */ public class ForegroundColorSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { private final int mColor; + /** + * Creates a {@link ForegroundColorSpan} from a color integer. + *

        + * To get the color integer associated with a particular color resource ID, use + * {@link android.content.res.Resources#getColor(int, Resources.Theme)} + * + * @param color color integer that defines the text color + */ public ForegroundColorSpan(@ColorInt int color) { mColor = color; } - public ForegroundColorSpan(Parcel src) { + /** + * Creates a {@link ForegroundColorSpan} from a parcel. + */ + public ForegroundColorSpan(@NonNull Parcel src) { mColor = src.readInt(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.FOREGROUND_COLOR_SPAN; } + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mColor); } + /** + * @return the foreground color of this span. + * @see ForegroundColorSpan#ForegroundColorSpan(int) + */ @ColorInt public int getForegroundColor() { return mColor; } + /** + * Updates the color of the TextPaint to the foreground color. + */ @Override - public void updateDrawState(TextPaint ds) { - ds.setColor(mColor); + public void updateDrawState(@NonNull TextPaint textPaint) { + textPaint.setColor(mColor); } } diff --git a/core/java/android/text/style/MetricAffectingSpan.java b/core/java/android/text/style/MetricAffectingSpan.java index 853ecc6e8f4961302ce4004d62c3dfb74bb9212d..61b7947af6389d0d70dc900b140fc19ab6e90310 100644 --- a/core/java/android/text/style/MetricAffectingSpan.java +++ b/core/java/android/text/style/MetricAffectingSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.text.TextPaint; /** @@ -23,13 +24,19 @@ import android.text.TextPaint; * changes the width or height of characters extend this class. */ public abstract class MetricAffectingSpan -extends CharacterStyle -implements UpdateLayout { + extends CharacterStyle + implements UpdateLayout { - public abstract void updateMeasureState(TextPaint p); + /** + * Classes that extend MetricAffectingSpan implement this method to update the text formatting + * in a way that can change the width or height of characters. + * + * @param textPaint the paint used for drawing the text + */ + public abstract void updateMeasureState(@NonNull TextPaint textPaint); /** - * Returns "this" for most MetricAffectingSpans, but for + * Returns "this" for most MetricAffectingSpans, but for * MetricAffectingSpans that were generated by {@link #wrap}, * returns the underlying MetricAffectingSpan. */ @@ -41,18 +48,18 @@ implements UpdateLayout { /** * A Passthrough MetricAffectingSpan is one that * passes {@link #updateDrawState} and {@link #updateMeasureState} - * calls through to the specified MetricAffectingSpan + * calls through to the specified MetricAffectingSpan * while still being a distinct object, * and is therefore able to be attached to the same Spannable * to which the specified MetricAffectingSpan is already attached. */ /* package */ static class Passthrough extends MetricAffectingSpan { private MetricAffectingSpan mStyle; - + /** * Creates a new Passthrough of the specfied MetricAffectingSpan. */ - public Passthrough(MetricAffectingSpan cs) { + Passthrough(@NonNull MetricAffectingSpan cs) { mStyle = cs; } @@ -60,7 +67,7 @@ implements UpdateLayout { * Passes updateDrawState through to the underlying MetricAffectingSpan. */ @Override - public void updateDrawState(TextPaint tp) { + public void updateDrawState(@NonNull TextPaint tp) { mStyle.updateDrawState(tp); } @@ -68,10 +75,10 @@ implements UpdateLayout { * Passes updateMeasureState through to the underlying MetricAffectingSpan. */ @Override - public void updateMeasureState(TextPaint tp) { + public void updateMeasureState(@NonNull TextPaint tp) { mStyle.updateMeasureState(tp); } - + /** * Returns the MetricAffectingSpan underlying this one, or the one * underlying it if it too is a Passthrough. diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java index 7217e1e5efa86ff0a73417fd3da42692c8cd27c3..a1c12c256fa1f02295e17f0df1b35fc07709d569 100644 --- a/core/java/android/text/style/QuoteSpan.java +++ b/core/java/android/text/style/QuoteSpan.java @@ -17,6 +17,9 @@ package android.text.style; import android.annotation.ColorInt; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Px; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Parcel; @@ -24,68 +27,178 @@ import android.text.Layout; import android.text.ParcelableSpan; import android.text.TextUtils; +/** + * A span which styles paragraphs by adding a vertical stripe at the beginning of the text + * (respecting layout direction). + *

        + * A QuoteSpan must be attached from the first character to the last character of a + * single paragraph, otherwise the span will not be displayed. + *

        + * QuoteSpans allow configuring the following elements: + *

          + *
        • color - the vertical stripe color. By default, the stripe color is 0xff0000ff
        • + *
        • gap width - the distance, in pixels, between the stripe and the paragraph. + * Default value is 2px.
        • + *
        • stripe width - the width, in pixels, of the stripe. Default value is + * 2px.
        • + *
        + * For example, a QuoteSpan using the default values can be constructed like this: + *
        {@code SpannableString string = new SpannableString("Text with quote span on a long line");
        + *string.setSpan(new QuoteSpan(), 0, string.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        QuoteSpan constructed with default values.
        + *

        + *

        + * To construct a QuoteSpan with a green stripe, of 20px in width and a gap width of + * 40px: + *

        {@code SpannableString string = new SpannableString("Text with quote span on a long line");
        + *string.setSpan(new QuoteSpan(Color.GREEN, 20, 40), 0, string.length(),
        + *Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Customized QuoteSpan.
        + */ public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan { - private static final int STRIPE_WIDTH = 2; - private static final int GAP_WIDTH = 2; + /** + * Default stripe width in pixels. + */ + public static final int STANDARD_STRIPE_WIDTH_PX = 2; + + /** + * Default gap width in pixels. + */ + public static final int STANDARD_GAP_WIDTH_PX = 2; + + /** + * Default color for the quote stripe. + */ + @ColorInt + public static final int STANDARD_COLOR = 0xff0000ff; + @ColorInt private final int mColor; - + @Px + private final int mStripeWidth; + @Px + private final int mGapWidth; + + /** + * Creates a {@link QuoteSpan} with the default values. + */ public QuoteSpan() { - super(); - mColor = 0xff0000ff; + this(STANDARD_COLOR, STANDARD_STRIPE_WIDTH_PX, STANDARD_GAP_WIDTH_PX); } + /** + * Creates a {@link QuoteSpan} based on a color. + * + * @param color the color of the quote stripe. + */ public QuoteSpan(@ColorInt int color) { - super(); + this(color, STANDARD_STRIPE_WIDTH_PX, STANDARD_GAP_WIDTH_PX); + } + + /** + * Creates a {@link QuoteSpan} based on a color, a stripe width and the width of the gap + * between the stripe and the text. + * + * @param color the color of the quote stripe. + * @param stripeWidth the width of the stripe. + * @param gapWidth the width of the gap between the stripe and the text. + */ + public QuoteSpan(@ColorInt int color, @IntRange(from = 0) int stripeWidth, + @IntRange(from = 0) int gapWidth) { mColor = color; + mStripeWidth = stripeWidth; + mGapWidth = gapWidth; } - public QuoteSpan(Parcel src) { + /** + * Create a {@link QuoteSpan} from a parcel. + */ + public QuoteSpan(@NonNull Parcel src) { mColor = src.readInt(); + mStripeWidth = src.readInt(); + mGapWidth = src.readInt(); } + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } - /** @hide */ + /** + * @hide + */ + @Override public int getSpanTypeIdInternal() { return TextUtils.QUOTE_SPAN; } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { writeToParcelInternal(dest, flags); } - /** @hide */ + /** + * @hide + */ + @Override public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mColor); + dest.writeInt(mStripeWidth); + dest.writeInt(mGapWidth); } + /** + * Get the color of the quote stripe. + * + * @return the color of the quote stripe. + */ @ColorInt public int getColor() { return mColor; } + /** + * Get the width of the quote stripe. + * + * @return the width of the quote stripe. + */ + public int getStripeWidth() { + return mStripeWidth; + } + + /** + * Get the width of the gap between the stripe and the text. + * + * @return the width of the gap between the stripe and the text. + */ + public int getGapWidth() { + return mGapWidth; + } + + @Override public int getLeadingMargin(boolean first) { - return STRIPE_WIDTH + GAP_WIDTH; + return mStripeWidth + mGapWidth; } - public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, - int top, int baseline, int bottom, - CharSequence text, int start, int end, - boolean first, Layout layout) { + @Override + public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, + int top, int baseline, int bottom, + @NonNull CharSequence text, int start, int end, + boolean first, @NonNull Layout layout) { Paint.Style style = p.getStyle(); int color = p.getColor(); p.setStyle(Paint.Style.FILL); p.setColor(mColor); - c.drawRect(x, top, x + dir * STRIPE_WIDTH, bottom, p); + c.drawRect(x, top, x + dir * mStripeWidth, bottom, p); p.setStyle(style); p.setColor(color); diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java index 95f048a2e240313d2c8f61bd80083ac8fcc5d5f3..3094f27ab72d74be0441d68dd13bd3b728e661f1 100644 --- a/core/java/android/text/style/RelativeSizeSpan.java +++ b/core/java/android/text/style/RelativeSizeSpan.java @@ -16,56 +16,85 @@ package android.text.style; +import android.annotation.FloatRange; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * Uniformly scales the size of the text to which it's attached by a certain proportion. + *

        + * For example, a RelativeSizeSpan that increases the text size by 50% can be + * constructed like this: + *

        {@code
        + *  SpannableString string = new SpannableString("Text with relative size span");
        + *string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Text increased by 50% with RelativeSizeSpan.
        + */ public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan { private final float mProportion; - public RelativeSizeSpan(float proportion) { + /** + * Creates a {@link RelativeSizeSpan} based on a proportion. + * + * @param proportion the proportion with which the text is scaled. + */ + public RelativeSizeSpan(@FloatRange(from = 0) float proportion) { mProportion = proportion; } - public RelativeSizeSpan(Parcel src) { + /** + * Creates a {@link RelativeSizeSpan} from a parcel. + */ + public RelativeSizeSpan(@NonNull Parcel src) { mProportion = src.readFloat(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.RELATIVE_SIZE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeFloat(mProportion); } + /** + * @return the proportion with which the text size is changed. + */ public float getSizeChange() { return mProportion; } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { ds.setTextSize(ds.getTextSize() * mProportion); } @Override - public void updateMeasureState(TextPaint ds) { + public void updateMeasureState(@NonNull TextPaint ds) { ds.setTextSize(ds.getTextSize() * mProportion); } } diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java index d085018572eca663a92d01841e3c07b425c28e15..6ef4ceccced18f8698f27c8975dd5014b0ebe55f 100644 --- a/core/java/android/text/style/ScaleXSpan.java +++ b/core/java/android/text/style/ScaleXSpan.java @@ -16,45 +16,79 @@ package android.text.style; +import android.annotation.FloatRange; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * Scales horizontally the size of the text to which it's attached by a certain factor. + *

        + * Values > 1.0 will stretch the text wider. Values < 1.0 will stretch the text narrower. + *

        + * For example, a ScaleXSpan that stretches the text size by 100% can be + * constructed like this: + *

        {@code
        + * SpannableString string = new SpannableString("Text with ScaleX span");
        + *string.setSpan(new ScaleXSpan(2f), 10, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Text scaled by 100% with ScaleXSpan.
        + */ public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan { private final float mProportion; - public ScaleXSpan(float proportion) { + /** + * Creates a {@link ScaleXSpan} based on a proportion. Values > 1.0 will stretch the text wider. + * Values < 1.0 will stretch the text narrower. + * + * @param proportion the horizontal scale factor. + */ + public ScaleXSpan(@FloatRange(from = 0) float proportion) { mProportion = proportion; } - public ScaleXSpan(Parcel src) { + /** + * Creates a {@link ScaleXSpan} from a parcel. + */ + public ScaleXSpan(@NonNull Parcel src) { mProportion = src.readFloat(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.SCALE_X_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeFloat(mProportion); } + /** + * Get the horizontal scale factor for the text. + * + * @return the horizontal scale factor. + */ public float getScaleX() { return mProportion; } diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java index 1389704f6ad87e549195fa541d552a566a7ca7f8..a6305050656a87e8df48da1e8b309272660ec602 100644 --- a/core/java/android/text/style/StrikethroughSpan.java +++ b/core/java/android/text/style/StrikethroughSpan.java @@ -16,42 +16,65 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * A span that strikes through the text it's attached to. + *

        + * The span can be used like this: + *

        {@code
        + * SpannableString string = new SpannableString("Text with strikethrough span");
        + *string.setSpan(new StrikethroughSpan(), 10, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Strikethrough text.
        + */ public class StrikethroughSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { + + /** + * Creates a {@link StrikethroughSpan}. + */ public StrikethroughSpan() { } - - public StrikethroughSpan(Parcel src) { + + /** + * Creates a {@link StrikethroughSpan} from a parcel. + */ + public StrikethroughSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.STRIKETHROUGH_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { ds.setStrikeThruText(true); } } diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java index f1b0d38cf62439ff80910dafe09187c1e3ae768d..3d15aad662b13f8cdc1fa5e404e6d455de1deada 100644 --- a/core/java/android/text/style/SubscriptSpan.java +++ b/core/java/android/text/style/SubscriptSpan.java @@ -16,46 +16,74 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * The span that moves the position of the text baseline lower. + *

        + * The span can be used like this: + *

        {@code
        + *  SpannableString string = new SpannableString("☕- C8H10N4O2\n");
        + *string.setSpan(new SubscriptSpan(), 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        + *string.setSpan(new SubscriptSpan(), 6, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        + *string.setSpan(new SubscriptSpan(), 9, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        + *string.setSpan(new SubscriptSpan(), 11, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Text with SubscriptSpan.
        + * Note: Since the span affects the position of the text, if the text is on the last line of a + * TextView, it may appear cut. + */ public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan { + + /** + * Creates a {@link SubscriptSpan}. + */ public SubscriptSpan() { } - - public SubscriptSpan(Parcel src) { + + /** + * Creates a {@link SubscriptSpan} from a parcel. + */ + public SubscriptSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.SUBSCRIPT_SPAN; } - + + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ + @Override public void writeToParcelInternal(Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint tp) { - tp.baselineShift -= (int) (tp.ascent() / 2); + public void updateDrawState(@NonNull TextPaint textPaint) { + textPaint.baselineShift -= (int) (textPaint.ascent() / 2); } @Override - public void updateMeasureState(TextPaint tp) { - tp.baselineShift -= (int) (tp.ascent() / 2); + public void updateMeasureState(@NonNull TextPaint textPaint) { + textPaint.baselineShift -= (int) (textPaint.ascent() / 2); } } diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java index abcf688f10ff333a9a559ef2c1a065985903c4a9..3dc9d3fdfe0524be4582495258f57f08380d2b36 100644 --- a/core/java/android/text/style/SuperscriptSpan.java +++ b/core/java/android/text/style/SuperscriptSpan.java @@ -16,46 +16,71 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * The span that moves the position of the text baseline higher. + *

        + * The span can be used like this: + *

        {@code
        + *  SpannableString string = new SpannableString("1st example");
        + *string.setSpan(new SuperscriptSpan(), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Text with SuperscriptSpan.
        + * Note: Since the span affects the position of the text, if the text is on the first line of a + * TextView, it may appear cut. This can be avoided by decreasing the text size with an {@link + * AbsoluteSizeSpan} + */ public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSpan { + /** + * Creates a {@link SuperscriptSpan}. + */ public SuperscriptSpan() { } - - public SuperscriptSpan(Parcel src) { + + /** + * Creates a {@link SuperscriptSpan} from a parcel. + */ + public SuperscriptSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.SUPERSCRIPT_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint tp) { - tp.baselineShift += (int) (tp.ascent() / 2); + public void updateDrawState(@NonNull TextPaint textPaint) { + textPaint.baselineShift += (int) (textPaint.ascent() / 2); } @Override - public void updateMeasureState(TextPaint tp) { - tp.baselineShift += (int) (tp.ascent() / 2); + public void updateMeasureState(@NonNull TextPaint textPaint) { + textPaint.baselineShift += (int) (textPaint.ascent() / 2); } } diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java index 9024dcd3925652167cac24388888d59f303d8812..800838ef92e929a3a4b83317a00de48048e8ad5a 100644 --- a/core/java/android/text/style/UnderlineSpan.java +++ b/core/java/android/text/style/UnderlineSpan.java @@ -16,42 +16,65 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * A span that underlines the text it's attached to. + *

        + * The span can be used like this: + *

        {@code
        + * SpannableString string = new SpannableString("Text with underline span");
        + *string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
        + * + *
        Underlined text.
        + */ public class UnderlineSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { + + /** + * Creates an {@link UnderlineSpan}. + */ public UnderlineSpan() { } - - public UnderlineSpan(Parcel src) { + + /** + * Creates an {@link UnderlineSpan} from a parcel. + */ + public UnderlineSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.UNDERLINE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { ds.setUnderlineText(true); } } diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java index 6342c8bcb85e758a580b99c2b57b84796a1be92a..cf7ed9b0566dd22dc14314e206861f4e1f5ed059 100644 --- a/core/java/android/util/AtomicFile.java +++ b/core/java/android/util/AtomicFile.java @@ -17,6 +17,7 @@ package android.util; import android.os.FileUtils; +import android.os.SystemClock; import libcore.io.IoUtils; @@ -47,14 +48,25 @@ import java.util.function.Consumer; public class AtomicFile { private final File mBaseName; private final File mBackupName; + private final String mCommitTag; + private long mStartTime; /** * Create a new AtomicFile for a file located at the given File path. * The secondary backup file will be the same file path with ".bak" appended. */ public AtomicFile(File baseName) { + this(baseName, null); + } + + /** + * @hide Internal constructor that also allows you to have the class + * automatically log commit events. + */ + public AtomicFile(File baseName, String commitTag) { mBaseName = baseName; mBackupName = new File(baseName.getPath() + ".bak"); + mCommitTag = commitTag; } /** @@ -88,6 +100,18 @@ public class AtomicFile { * access to AtomicFile. */ public FileOutputStream startWrite() throws IOException { + return startWrite(mCommitTag != null ? SystemClock.uptimeMillis() : 0); + } + + /** + * @hide Internal version of {@link #startWrite()} that allows you to specify an earlier + * start time of the operation to adjust how the commit is logged. + * @param startTime The effective start time of the operation, in the time + * base of {@link SystemClock#uptimeMillis()}. + */ + public FileOutputStream startWrite(long startTime) throws IOException { + mStartTime = startTime; + // Rename the current file so it may be used as a backup during the next read if (mBaseName.exists()) { if (!mBackupName.exists()) { @@ -135,6 +159,10 @@ public class AtomicFile { } catch (IOException e) { Log.w("AtomicFile", "finishWrite: Got exception:", e); } + if (mCommitTag != null) { + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + mCommitTag, SystemClock.uptimeMillis() - mStartTime); + } } } diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java index 3cc1bd899eeb76c6861c6a511a7601191191d453..ea4266ecb790c9ead13871e70f70b9f7bec42eba 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. + *

        + * 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. *

        * 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/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 62f9717246735b30b04c1450304ed384e023f6ac..8af4b7150a31ebb48723ea0a8da8fde396eb025b 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,13 +38,14 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); - DEFAULT_FLAGS.put("settings_search_v2", "true"); DEFAULT_FLAGS.put("settings_app_info_v2", "true"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); - DEFAULT_FLAGS.put("settings_battery_v2", "false"); + DEFAULT_FLAGS.put("settings_battery_v2", "true"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); - DEFAULT_FLAGS.put("settings_security_settings_v2", "true"); - DEFAULT_FLAGS.put("settings_zone_picker_v2", "false"); + DEFAULT_FLAGS.put("settings_zone_picker_v2", "true"); + DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false"); + DEFAULT_FLAGS.put("settings_about_phone_v2", "false"); + DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false"); } /** diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java index bf335196edef8ec83212e59fc3d349386d0645bd..597089235e6b5f9fa089b3335f2504edfd64422a 100644 --- a/core/java/android/util/MemoryIntArray.java +++ b/core/java/android/util/MemoryIntArray.java @@ -16,13 +16,19 @@ package android.util; +import static android.os.Process.FIRST_APPLICATION_UID; + import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.Process; + +import com.android.internal.annotations.GuardedBy; -import libcore.io.IoUtils; import dalvik.system.CloseGuard; +import libcore.io.IoUtils; + import java.io.Closeable; import java.io.IOException; import java.util.UUID; @@ -49,13 +55,18 @@ import java.util.UUID; */ public final class MemoryIntArray implements Parcelable, Closeable { private static final String TAG = "MemoryIntArray"; + private static final boolean DEBUG = Process.myUid() < FIRST_APPLICATION_UID; private static final int MAX_SIZE = 1024; + private final Object mLock = new Object(); private final CloseGuard mCloseGuard = CloseGuard.get(); private final boolean mIsOwner; private final long mMemoryAddr; + + /** Fd for the shared memory object, -1 when closed */ + @GuardedBy("mLock") private int mFd = -1; /** @@ -74,6 +85,7 @@ public final class MemoryIntArray implements Parcelable, Closeable { mFd = nativeCreate(name, size); mMemoryAddr = nativeOpen(mFd, mIsOwner); mCloseGuard.open("close"); + if (DEBUG) Log.i(TAG, "created " + getString()); } private MemoryIntArray(Parcel parcel) throws IOException { @@ -85,6 +97,8 @@ public final class MemoryIntArray implements Parcelable, Closeable { mFd = pfd.detachFd(); mMemoryAddr = nativeOpen(mFd, mIsOwner); mCloseGuard.open("close"); + + if (DEBUG) Log.i(TAG, "created from parcel " + getString()); } /** @@ -141,13 +155,33 @@ public final class MemoryIntArray implements Parcelable, Closeable { */ @Override public void close() throws IOException { - if (!isClosed()) { - nativeClose(mFd, mMemoryAddr, mIsOwner); - mFd = -1; - mCloseGuard.close(); + synchronized (mLock) { + if (!isClosed()) { + if (DEBUG) { + try { + throw new Exception(); + } catch (Exception here) { + Log.i(TAG, "closing " + getString(), here); + } + } + nativeClose(mFd, mMemoryAddr, mIsOwner); + mFd = -1; + mCloseGuard.close(); + } else { + try { + throw new Exception(); + } catch (Exception here) { + if (DEBUG) Log.i(TAG, getString() + " already closed", here); + } + } } } + private String getString() { + return this.getClass().getSimpleName() + "@" + System.identityHashCode(this) + + " mMemoryAddr=" + mMemoryAddr + " mFd=" + mFd; + } + /** * @return Whether this array is closed and shouldn't be used. */ @@ -162,7 +196,9 @@ public final class MemoryIntArray implements Parcelable, Closeable { mCloseGuard.warnIfOpen(); } - IoUtils.closeQuietly(this); + if (!isClosed()) { + IoUtils.closeQuietly(this); + } } finally { super.finalize(); } @@ -206,7 +242,8 @@ public final class MemoryIntArray implements Parcelable, Closeable { private void enforceNotClosed() { if (isClosed()) { - throw new IllegalStateException("cannot interact with a closed instance"); + throw new IllegalStateException("cannot interact with a closed instance " + + getString()); } } diff --git a/core/java/android/util/MutableBoolean.java b/core/java/android/util/MutableBoolean.java index ed837ab6afd3f3b8b9487c0ff9eff07e187f7aa4..44e73cc38b8f17563eb4e24aa6c86614697be2e1 100644 --- a/core/java/android/util/MutableBoolean.java +++ b/core/java/android/util/MutableBoolean.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableBoolean { public boolean value; diff --git a/core/java/android/util/MutableByte.java b/core/java/android/util/MutableByte.java index cc6b00a8046f759b42c28d4dac6695d9ad1a1f19..b9ec25dab2addfb857f0195365ce1e65d69f3f50 100644 --- a/core/java/android/util/MutableByte.java +++ b/core/java/android/util/MutableByte.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableByte { public byte value; diff --git a/core/java/android/util/MutableChar.java b/core/java/android/util/MutableChar.java index 9a2e2bce5915f9cfb4562203607c7ff0cc616a37..9f7a9ae82c32998044f01f3979bcde24e754677a 100644 --- a/core/java/android/util/MutableChar.java +++ b/core/java/android/util/MutableChar.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableChar { public char value; diff --git a/core/java/android/util/MutableDouble.java b/core/java/android/util/MutableDouble.java index bd7329a380ee2a2be808fef8e56be162ce476625..56e539bc0f6f4b499c43f5c9aafd521ea4554470 100644 --- a/core/java/android/util/MutableDouble.java +++ b/core/java/android/util/MutableDouble.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableDouble { public double value; diff --git a/core/java/android/util/MutableFloat.java b/core/java/android/util/MutableFloat.java index e6f2d7dc7b305015caeb054d1879c7468f8d9b87..6d7ad59d2473a4fd3417307af77a3ecda8a3a4bd 100644 --- a/core/java/android/util/MutableFloat.java +++ b/core/java/android/util/MutableFloat.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableFloat { public float value; diff --git a/core/java/android/util/MutableInt.java b/core/java/android/util/MutableInt.java index a3d8606d916aebc2d7de5d38b96cf79a8e331cc0..bb2456601be34b4e3d0b3347b5431bd173a4b14e 100644 --- a/core/java/android/util/MutableInt.java +++ b/core/java/android/util/MutableInt.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableInt { public int value; diff --git a/core/java/android/util/MutableLong.java b/core/java/android/util/MutableLong.java index 575068ea93643e0ffa79d751f09f7033951c5fcc..86e70e1baebfad02e6f5fc3c7c089002b0364f19 100644 --- a/core/java/android/util/MutableLong.java +++ b/core/java/android/util/MutableLong.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableLong { public long value; diff --git a/core/java/android/util/MutableShort.java b/core/java/android/util/MutableShort.java index 48fb232b5287034a2299c1b303b1ba6c57d4c2af..b94ab0732251abe25e62b927046a9053c2d68a9c 100644 --- a/core/java/android/util/MutableShort.java +++ b/core/java/android/util/MutableShort.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableShort { public short value; diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java index e2e9d53e7e9e618a9ec26932c1f87484939b52ef..a5e38189f39b0b5cc2adbc55e1b6acec957c7b88 100644 --- a/core/java/android/util/PackageUtils.java +++ b/core/java/android/util/PackageUtils.java @@ -105,7 +105,7 @@ public final class PackageUtils { * @param data The data. * @return The digest or null if an error occurs. */ - public static @Nullable String computeSha256Digest(@NonNull byte[] data) { + public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) { MessageDigest messageDigest; try { messageDigest = MessageDigest.getInstance("SHA256"); @@ -116,6 +116,15 @@ public final class PackageUtils { messageDigest.update(data); - return ByteStringUtils.toHexString(messageDigest.digest()); + return messageDigest.digest(); + } + + /** + * Computes the SHA256 digest of some data. + * @param data The data. + * @return The digest or null if an error occurs. + */ + public static @Nullable String computeSha256Digest(@NonNull byte[] data) { + return ByteStringUtils.toHexString(computeSha256DigestBytes(data)); } } diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java new file mode 100644 index 0000000000000000000000000000000000000000..d100f12ed0266d515380ad3ceaa0d8cbb5df815c --- /dev/null +++ b/core/java/android/util/SparseSetArray.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.util; + +/** + * A sparse array of ArraySets, which is suitable to hold userid->packages association. + * + * @hide + */ +public class SparseSetArray { + private final SparseArray> mData = new SparseArray<>(); + + public SparseSetArray() { + } + + /** + * Add a value at index n. + * @return FALSE when the value already existed at the given index, TRUE otherwise. + */ + public boolean add(int n, T value) { + ArraySet set = mData.get(n); + if (set == null) { + set = new ArraySet<>(); + mData.put(n, set); + } + if (set.contains(value)) { + return true; + } + set.add(value); + return false; + } + + /** + * @return whether a value exists at index n. + */ + public boolean contains(int n, T value) { + final ArraySet set = mData.get(n); + if (set == null) { + return false; + } + return set.contains(value); + } + + /** + * Remove a value from index n. + * @return TRUE when the value existed at the given index and removed, FALSE otherwise. + */ + public boolean remove(int n, T value) { + final ArraySet set = mData.get(n); + if (set == null) { + return false; + } + final boolean ret = set.remove(value); + if (set.size() == 0) { + mData.remove(n); + } + return ret; + } + + /** + * Remove all values from index n. + */ + public void remove(int n) { + mData.remove(n); + } + public int size() { + return mData.size(); + } + + public int keyAt(int index) { + return mData.keyAt(index); + } + + public int sizeAt(int index) { + final ArraySet set = mData.valueAt(index); + if (set == null) { + return 0; + } + return set.size(); + } + + public T valueAt(int intIndex, int valueIndex) { + return mData.valueAt(intIndex).valueAt(valueIndex); + } +} diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java index c25b272c11b9407530cb34745934a9ad7600990c..687aa8375e016d9530a636c551013ac279fae891 100644 --- a/core/java/android/util/StatsManager.java +++ b/core/java/android/util/StatsManager.java @@ -17,19 +17,33 @@ package android.util; import android.Manifest; import android.annotation.RequiresPermission; -import android.annotation.SystemApi; import android.os.IBinder; import android.os.IStatsManager; import android.os.RemoteException; import android.os.ServiceManager; + +/* + * + * + * + * + * THIS ENTIRE FILE IS ONLY TEMPORARY TO PREVENT BREAKAGES OF DEPENDENCIES ON OLD APIS. + * The new StatsManager is to be found in android.app.StatsManager. + * TODO: Delete this file! + * + * + * + * + */ + + /** * API for StatsD clients to send configurations and retrieve data. * * @hide */ -@SystemApi -public final class StatsManager { +public class StatsManager { IStatsManager mService; private static final String TAG = "StatsManager"; @@ -41,11 +55,21 @@ public final class StatsManager { public StatsManager() { } + /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) { + // To prevent breakages of dependencies on old API. + + return false; + } + /** * Clients can send a configuration and simultaneously registers the name of a broadcast * receiver that listens for when it should request data. * - * @param configKey An arbitrary string that allows clients to track the configuration. + * @param configKey An arbitrary integer that allows clients to track the configuration. * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all * dependencies eg, conditions and matchers). * @param pkg The package name to receive the broadcast. @@ -69,6 +93,15 @@ public final class StatsManager { } } + /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(String configKey) { + // To prevent breakages of old dependencies. + return false; + } + /** * Remove a configuration from logging. * @@ -92,6 +125,16 @@ public final class StatsManager { } } + /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getData(String configKey) { + // TODO: remove this and all other methods with String-based config keys. + // To prevent build breakages of dependencies. + return null; + } + /** * Clients can request data with a binder call. This getter is destructive and also clears * the retrieved metrics from statsd memory. diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index cc4a0b60dd0af889d72c6d182c6320ed6f39eddf..84ae20b92f3c3179bb1b753b460713fb6bd6679b 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -18,30 +18,18 @@ package android.util; import android.os.SystemClock; +import libcore.util.TimeZoneFinder; +import libcore.util.ZoneInfoDB; + import java.io.PrintWriter; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; import java.util.Date; -import java.util.List; -import libcore.util.TimeZoneFinder; -import libcore.util.ZoneInfoDB; - /** * A class containing utility methods related to time zones. */ public class TimeUtils { /** @hide */ public TimeUtils() {} - private static final boolean DBG = false; - private static final String TAG = "TimeUtils"; - - /** Cached results of getTimeZonesWithUniqueOffsets */ - private static final Object sLastUniqueLockObj = new Object(); - private static List sLastUniqueZoneOffsets = null; - private static String sLastUniqueCountry = null; - /** {@hide} */ private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -75,86 +63,6 @@ public class TimeUtils { .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias); } - /** - * Returns an immutable list of unique time zone IDs for the country. - * - * @param country to find - * @return unmodifiable list of unique time zones, maybe empty but never null. - * @hide - */ - public static List getTimeZoneIdsWithUniqueOffsets(String country) { - synchronized(sLastUniqueLockObj) { - if ((country != null) && country.equals(sLastUniqueCountry)) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets(" + - country + "): return cached version"); - } - return sLastUniqueZoneOffsets; - } - } - - Collection zones = getIcuTimeZones(country); - ArrayList uniqueTimeZones = new ArrayList<>(); - for (android.icu.util.TimeZone zone : zones) { - // See if we already have this offset, - // Using slow but space efficient and these are small. - boolean found = false; - for (int i = 0; i < uniqueTimeZones.size(); i++) { - if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) { - found = true; - break; - } - } - if (!found) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" + - zone.getRawOffset() + " zone.getID=" + zone.getID()); - } - uniqueTimeZones.add(zone); - } - } - - synchronized(sLastUniqueLockObj) { - // Cache the last result - sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones); - sLastUniqueCountry = country; - - return sLastUniqueZoneOffsets; - } - } - - private static List extractZoneIds(List timeZones) { - List ids = new ArrayList<>(timeZones.size()); - for (android.icu.util.TimeZone timeZone : timeZones) { - ids.add(timeZone.getID()); - } - return Collections.unmodifiableList(ids); - } - - /** - * Returns an immutable list of frozen ICU time zones for the country. - * - * @param countryIso is a two character country code. - * @return TimeZone list, maybe empty but never null. - * @hide - */ - private static List getIcuTimeZones(String countryIso) { - if (countryIso == null) { - if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list"); - return Collections.emptyList(); - } - List timeZones = - TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso); - if (timeZones == null) { - if (DBG) { - Log.d(TAG, "getIcuTimeZones(" + countryIso - + "): returned null, converting to empty list"); - } - return Collections.emptyList(); - } - return timeZones; - } - /** * Returns a String indicating the version of the time zone database currently * in use. The format of the string is dependent on the underlying time zone diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index ce8998fcb863e3ce1020a7efaad905a6da29405b..62222b5764e783abc4b44e63819dbefab5e1e1a4 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -16,6 +16,7 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; @@ -42,6 +43,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -105,7 +107,8 @@ public class ApkSignatureSchemeV2Verifier { */ public static X509Certificate[][] verify(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { - return verify(apkFile, true); + VerifiedSigner vSigner = verify(apkFile, true); + return vSigner.certs; } /** @@ -119,10 +122,11 @@ public class ApkSignatureSchemeV2Verifier { */ public static X509Certificate[][] plsCertsNoVerifyOnlyCerts(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { - return verify(apkFile, false); + VerifiedSigner vSigner = verify(apkFile, false); + return vSigner.certs; } - private static X509Certificate[][] verify(String apkFile, boolean verifyIntegrity) + private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk, verifyIntegrity); @@ -138,7 +142,7 @@ public class ApkSignatureSchemeV2Verifier { * verify. * @throws IOException if an I/O error occurs while reading the APK file. */ - private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity) + private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { SignatureInfo signatureInfo = findSignature(apk); return verify(apk, signatureInfo, verifyIntegrity); @@ -163,7 +167,7 @@ public class ApkSignatureSchemeV2Verifier { * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it * against the APK file. */ - private static X509Certificate[][] verify( + private static VerifiedSigner verify( RandomAccessFile apk, SignatureInfo signatureInfo, boolean doVerifyIntegrity) throws SecurityException, IOException { @@ -207,7 +211,14 @@ public class ApkSignatureSchemeV2Verifier { ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); } - return signerCerts.toArray(new X509Certificate[signerCerts.size()][]); + byte[] verityRootHash = null; + if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { + verityRootHash = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + } + + return new VerifiedSigner( + signerCerts.toArray(new X509Certificate[signerCerts.size()][]), + verityRootHash); } private static X509Certificate[] verifySigner( @@ -383,6 +394,38 @@ public class ApkSignatureSchemeV2Verifier { return; } + static byte[] getVerityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, SecurityException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + return vSigner.verityRootHash; + } + } + + static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + return ApkSigningBlockUtils.generateApkVerity(apkPath, bufferFactory, signatureInfo); + } + } + + static byte[] generateFsverityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + if (vSigner.verityRootHash == null) { + return null; + } + return ApkVerityBuilder.generateFsverityRootHash( + apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); + } + } + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: @@ -400,4 +443,20 @@ public class ApkSignatureSchemeV2Verifier { return false; } } + + /** + * Verified APK Signature Scheme v2 signer. + * + * @hide for internal use only. + */ + public static class VerifiedSigner { + public final X509Certificate[][] certs; + public final byte[] verityRootHash; + + public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) { + this.certs = certs; + this.verityRootHash = verityRootHash; + } + + } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index c9e67fe1c9efd82bc1bec0ef4ae7408cd073a69f..a4c590f1b459f9f7005795190743e727320aec68 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -16,6 +16,7 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; @@ -43,6 +44,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -211,6 +213,10 @@ public class ApkSignatureSchemeV3Verifier { ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); } + if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { + result.verityRootHash = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + } + return result; } @@ -471,6 +477,7 @@ public class ApkSignatureSchemeV3Verifier { } } + signedData.rewind(); byte[] encodedCert = readLengthPrefixedByteArray(signedData); int signedSigAlgorithm = signedData.getInt(); if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) { @@ -499,6 +506,38 @@ public class ApkSignatureSchemeV3Verifier { return new VerifiedProofOfRotation(certs, flagsList); } + static byte[] getVerityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, SecurityException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + return vSigner.verityRootHash; + } + } + + static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + return ApkSigningBlockUtils.generateApkVerity(apkPath, bufferFactory, signatureInfo); + } + } + + static byte[] generateFsverityRootHash(String apkPath) + throws NoSuchAlgorithmException, DigestException, IOException, + SignatureNotFoundException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + if (vSigner.verityRootHash == null) { + return null; + } + return ApkVerityBuilder.generateFsverityRootHash( + apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); + } + } + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: @@ -541,6 +580,8 @@ public class ApkSignatureSchemeV3Verifier { public final X509Certificate[] certs; public final VerifiedProofOfRotation por; + public byte[] verityRootHash; + public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) { this.certs = certs; this.por = por; diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 555c4740389a719fb5a351427da5d20379ec6501..de9f55b092004a382bfd6fedc34e59303733f6e8 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -36,7 +36,9 @@ import libcore.io.IoUtils; import java.io.IOException; import java.io.InputStream; +import java.security.DigestException; import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; @@ -78,10 +80,22 @@ public class ApkSignatureVerifier { ApkSignatureSchemeV3Verifier.verify(apkPath); Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; Signature[] signerSigs = convertToSignatures(signerCerts); - return new PackageParser.SigningDetails(signerSigs, - SignatureSchemeVersion.SIGNING_BLOCK_V3); + Signature[] pastSignerSigs = null; + int[] pastSignerSigsFlags = null; + if (vSigner.por != null) { + // populate proof-of-rotation information + pastSignerSigs = new Signature[vSigner.por.certs.size()]; + pastSignerSigsFlags = new int[vSigner.por.flagsList.size()]; + for (int i = 0; i < pastSignerSigs.length; i++) { + pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); + pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i); + } + } + return new PackageParser.SigningDetails( + signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3, + pastSignerSigs, pastSignerSigsFlags); } catch (SignatureNotFoundException e) { - // not signed with v2, try older if allowed + // not signed with v3, try older if allowed if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v3 signature in package " + apkPath, e); @@ -90,7 +104,7 @@ public class ApkSignatureVerifier { // APK Signature Scheme v2 signature found but did not verify throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Failed to collect certificates from " + apkPath - + " using APK Signature Scheme v2", e); + + " using APK Signature Scheme v3", e); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -302,25 +316,37 @@ public class ApkSignatureVerifier { } // first try v3 - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV3"); try { ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath); Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; Signature[] signerSigs = convertToSignatures(signerCerts); - return new PackageParser.SigningDetails(signerSigs, - SignatureSchemeVersion.SIGNING_BLOCK_V3); + Signature[] pastSignerSigs = null; + int[] pastSignerSigsFlags = null; + if (vSigner.por != null) { + // populate proof-of-rotation information + pastSignerSigs = new Signature[vSigner.por.certs.size()]; + pastSignerSigsFlags = new int[vSigner.por.flagsList.size()]; + for (int i = 0; i < pastSignerSigs.length; i++) { + pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); + pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i); + } + } + return new PackageParser.SigningDetails( + signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3, + pastSignerSigs, pastSignerSigsFlags); } catch (SignatureNotFoundException e) { - // not signed with v2, try older if allowed + // not signed with v3, try older if allowed if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v3 signature in package " + apkPath, e); } } catch (Exception e) { - // APK Signature Scheme v2 signature found but did not verify + // APK Signature Scheme v3 signature found but did not verify throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Failed to collect certificates from " + apkPath - + " using APK Signature Scheme v2", e); + + " using APK Signature Scheme v3", e); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -367,4 +393,72 @@ public class ApkSignatureVerifier { // v2 didn't work, try jarsigner return verifyV1Signature(apkPath, false); } + + /** + * @return the verity root hash in the Signing Block. + */ + public static byte[] getVerityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, SecurityException { + // first try v3 + try { + return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath); + } catch (SignatureNotFoundException e) { + // try older version + } + return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath); + } + + /** + * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code + * ByteBufferFactory}. + * + * @return the verity root hash of the generated Merkle tree. + */ + public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + // first try v3 + try { + return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory); + } catch (SignatureNotFoundException e) { + // try older version + } + return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory); + } + + /** + * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash + * in Signing Block. + * + * @return FSverity root hash + */ + public static byte[] generateFsverityRootHash(String apkPath) + throws NoSuchAlgorithmException, DigestException, IOException { + // first try v3 + try { + return ApkSignatureSchemeV3Verifier.generateFsverityRootHash(apkPath); + } catch (SignatureNotFoundException e) { + // try older version + } + try { + return ApkSignatureSchemeV2Verifier.generateFsverityRootHash(apkPath); + } catch (SignatureNotFoundException e) { + return null; + } + } + + /** + * Result of a successful APK verification operation. + */ + public static class Result { + public final Certificate[][] certs; + public final Signature[] sigs; + public final int signatureSchemeVersion; + + public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { + this.certs = certs; + this.sigs = sigs; + this.signatureSchemeVersion = signingVersion; + } + } } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 9d53847f8110e0986cd909869db11bcd0b4e3fee..4146f6fab0110363e26ee1514a280bd8f281be17 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -305,6 +305,26 @@ final class ApkSigningBlockUtils { } } + /** + * Generates the fsverity header and hash tree to be used by kernel for the given apk. This + * method does not check whether the root hash exists in the Signing Block or not. + * + *

        The output is stored in the {@link ByteBuffer} created by the given {@link + * ByteBufferFactory}. + * + * @return the root hash of the generated hash tree. + */ + public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory, + SignatureInfo signatureInfo) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateApkVerity(apk, + signatureInfo, bufferFactory); + return result.rootHash; + } + } + /** * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. * diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java index 7412ef411fb4be63215f4a2cc36cffcc05a855fa..4c6e511ede46f938e96b3b55910fff0960f6fcd4 100644 --- a/core/java/android/util/apk/ApkVerityBuilder.java +++ b/core/java/android/util/apk/ApkVerityBuilder.java @@ -68,31 +68,80 @@ abstract class ApkVerityBuilder { static ApkVerityResult generateApkVerity(RandomAccessFile apk, SignatureInfo signatureInfo, ByteBufferFactory bufferFactory) throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { - assertSigningBlockAlignedAndHasFullPages(signatureInfo); - long signingBlockSize = signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; - long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; + long dataSize = apk.length() - signingBlockSize; int[] levelOffset = calculateVerityLevelOffset(dataSize); + ByteBuffer output = bufferFactory.create( CHUNK_SIZE_BYTES + // fsverity header + extensions + padding - levelOffset[levelOffset.length - 1] + // Merkle tree size - FSVERITY_HEADER_SIZE_BYTES); // second fsverity header (verbatim copy) + levelOffset[levelOffset.length - 1]); // Merkle tree size + output.order(ByteOrder.LITTLE_ENDIAN); - // Start generating the tree from the block boundary as the kernel will expect. - ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES, - output.limit() - FSVERITY_HEADER_SIZE_BYTES); - byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset, - treeOutput); + ByteBuffer header = slice(output, 0, FSVERITY_HEADER_SIZE_BYTES); + ByteBuffer extensions = slice(output, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES); + ByteBuffer tree = slice(output, CHUNK_SIZE_BYTES, output.limit()); + byte[] apkDigestBytes = new byte[DIGEST_SIZE_BYTES]; + ByteBuffer apkDigest = ByteBuffer.wrap(apkDigestBytes); + apkDigest.order(ByteOrder.LITTLE_ENDIAN); - ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT); - output.put(integrityHeader); - output.put(generateFsverityExtensions()); + calculateFsveritySignatureInternal(apk, signatureInfo, tree, apkDigest, header, extensions); - integrityHeader.rewind(); - output.put(integrityHeader); output.rewind(); - return new ApkVerityResult(output, rootHash); + return new ApkVerityResult(output, apkDigestBytes); + } + + /** + * Calculates the fsverity root hash for integrity measurement. This needs to be consistent to + * what kernel returns. + */ + static byte[] generateFsverityRootHash(RandomAccessFile apk, ByteBuffer apkDigest, + SignatureInfo signatureInfo) + throws NoSuchAlgorithmException, DigestException, IOException { + ByteBuffer verityBlock = ByteBuffer.allocate(CHUNK_SIZE_BYTES) + .order(ByteOrder.LITTLE_ENDIAN); + ByteBuffer header = slice(verityBlock, 0, FSVERITY_HEADER_SIZE_BYTES); + ByteBuffer extensions = slice(verityBlock, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES); + + calculateFsveritySignatureInternal(apk, signatureInfo, null, null, header, extensions); + + MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM); + md.update(DEFAULT_SALT); + md.update(verityBlock); + md.update(apkDigest); + return md.digest(); + } + + private static void calculateFsveritySignatureInternal( + RandomAccessFile apk, SignatureInfo signatureInfo, ByteBuffer treeOutput, + ByteBuffer rootHashOutput, ByteBuffer headerOutput, ByteBuffer extensionsOutput) + throws IOException, NoSuchAlgorithmException, DigestException { + assertSigningBlockAlignedAndHasFullPages(signatureInfo); + + long signingBlockSize = + signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset; + long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; + int[] levelOffset = calculateVerityLevelOffset(dataSize); + + if (treeOutput != null) { + byte[] apkRootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, + levelOffset, treeOutput); + if (rootHashOutput != null) { + rootHashOutput.put(apkRootHash); + } + } + + if (headerOutput != null) { + headerOutput.order(ByteOrder.LITTLE_ENDIAN); + generateFsverityHeader(headerOutput, apk.length(), levelOffset.length - 1, + DEFAULT_SALT); + } + + if (extensionsOutput != null) { + extensionsOutput.order(ByteOrder.LITTLE_ENDIAN); + generateFsverityExtensions(extensionsOutput, signatureInfo.apkSigningBlockOffset, + signingBlockSize, signatureInfo.eocdOffset); + } } /** @@ -164,11 +213,11 @@ abstract class ApkVerityBuilder { } private void fillUpLastOutputChunk() { - int extra = (int) (BUFFER_SIZE - mOutput.position() % BUFFER_SIZE); - if (extra == 0) { + int lastBlockSize = (int) (mOutput.position() % BUFFER_SIZE); + if (lastBlockSize == 0) { return; } - mOutput.put(ByteBuffer.allocate(extra)); + mOutput.put(ByteBuffer.allocate(BUFFER_SIZE - lastBlockSize)); } } @@ -211,7 +260,7 @@ abstract class ApkVerityBuilder { eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset), MMAP_REGION_SIZE_BYTES); - // 3. Fill up the rest of buffer with 0s. + // 3. Consume offset of Signing Block as an alternative EoCD. ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate( ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN); alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset)); @@ -259,36 +308,101 @@ abstract class ApkVerityBuilder { return rootHash; } - private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) { + private static ByteBuffer generateFsverityHeader(ByteBuffer buffer, long fileSize, int depth, + byte[] salt) { if (salt.length != 8) { throw new IllegalArgumentException("salt is not 8 bytes long"); } - ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES); - buffer.order(ByteOrder.LITTLE_ENDIAN); - - // TODO(b/30972906): insert a reference when there is a public one. + // TODO(b/30972906): update the reference when there is a better one in public. buffer.put("TrueBrew".getBytes()); // magic - buffer.put((byte) 1); // major version - buffer.put((byte) 0); // minor version - buffer.put((byte) 12); // log2(block-size) == log2(4096) - buffer.put((byte) 7); // log2(leaves-per-node) == log2(block-size / digest-size) - // == log2(4096 / 32) - buffer.putShort((short) 1); // meta algorithm, 1: SHA-256 FIXME finalize constant - buffer.putShort((short) 1); // data algorithm, 1: SHA-256 FIXME finalize constant - buffer.putInt(0x1); // flags, 0x1: has extension, FIXME also hide it - buffer.putInt(0); // reserved - buffer.putLong(fileSize); // original i_size - buffer.put(salt); // salt (8 bytes) - - // TODO(b/30972906): Add extension. + + buffer.put((byte) 1); // major version + buffer.put((byte) 0); // minor version + buffer.put((byte) 12); // log2(block-size): log2(4096) + buffer.put((byte) 7); // log2(leaves-per-node): log2(4096 / 32) + + buffer.putShort((short) 1); // meta algorithm, SHA256_MODE == 1 + buffer.putShort((short) 1); // data algorithm, SHA256_MODE == 1 + + buffer.putInt(0x1); // flags, 0x1: has extension + buffer.putInt(0); // reserved + + buffer.putLong(fileSize); // original file size + + buffer.put((byte) 0); // auth block offset, disabled here + buffer.put((byte) 2); // extension count + buffer.put(salt); // salt (8 bytes) + // skip(buffer, 22); // reserved buffer.rewind(); return buffer; } - private static ByteBuffer generateFsverityExtensions() { - return ByteBuffer.allocate(64); // TODO(b/30972906): implement this. + private static ByteBuffer generateFsverityExtensions(ByteBuffer buffer, long signingBlockOffset, + long signingBlockSize, long eocdOffset) { + // Snapshot of the FSVerity structs (subject to change once upstreamed). + // + // struct fsverity_extension { + // __le16 length; + // u8 type; + // u8 reserved[5]; + // }; + // + // struct fsverity_extension_elide { + // __le64 offset; + // __le64 length; + // } + // + // struct fsverity_extension_patch { + // __le64 offset; + // u8 length; + // u8 reserved[7]; + // u8 databytes[]; + // }; + + final int kSizeOfFsverityExtensionHeader = 8; + + { + // struct fsverity_extension #1 + final int kSizeOfFsverityElidedExtension = 16; + + buffer.putShort((short) // total size of extension, padded to 64-bit alignment + (kSizeOfFsverityExtensionHeader + kSizeOfFsverityElidedExtension)); + buffer.put((byte) 0); // ID of elide extension + skip(buffer, 5); // reserved + + // struct fsverity_extension_elide + buffer.putLong(signingBlockOffset); + buffer.putLong(signingBlockSize); + } + + { + // struct fsverity_extension #2 + final int kSizeOfFsverityPatchExtension = + 8 + // offset size + 1 + // size of length from offset (up to 255) + 7 + // reserved + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; + final int kPadding = (int) divideRoundup(kSizeOfFsverityPatchExtension % 8, 8); + + buffer.putShort((short) // total size of extension, padded to 64-bit alignment + (kSizeOfFsverityExtensionHeader + kSizeOfFsverityPatchExtension + kPadding)); + buffer.put((byte) 1); // ID of patch extension + skip(buffer, 5); // reserved + + // struct fsverity_extension_patch + buffer.putLong(eocdOffset); // offset + buffer.put((byte) ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE); // length + skip(buffer, 7); // reserved + buffer.putInt(Math.toIntExact(signingBlockOffset)); // databytes + + // There are extra kPadding bytes of 0s here, included in the total size field of the + // extension header. The output ByteBuffer is assumed to be initialized to 0. + } + + buffer.rewind(); + return buffer; } /** @@ -344,6 +458,11 @@ abstract class ApkVerityBuilder { return b.slice(); } + /** Skip the {@code ByteBuffer} position by {@code bytes}. */ + private static void skip(ByteBuffer buffer, int bytes) { + buffer.position(buffer.position() + bytes); + } + /** Divides a number and round up to the closest integer. */ private static long divideRoundup(long dividend, long divisor) { return (dividend + divisor - 1) / divisor; diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java index 85b7ec8265d1539e6de7413e1bf7dc9a895ad034..c7bbb9f2a044727e245795b4d9020985d24e385e 100644 --- a/core/java/android/util/proto/ProtoUtils.java +++ b/core/java/android/util/proto/ProtoUtils.java @@ -48,4 +48,26 @@ public class ProtoUtils { proto.write(Duration.END_MS, endMs); proto.end(token); } + + /** + * Helper function to write bit-wise flags to proto as repeated enums + * @hide + */ + public static void writeBitWiseFlagsToProtoEnum(ProtoOutputStream proto, long fieldId, + int flags, int[] origEnums, int[] protoEnums) { + if (protoEnums.length != origEnums.length) { + throw new IllegalArgumentException("The length of origEnums must match protoEnums"); + } + int len = origEnums.length; + for (int i = 0; i < len; i++) { + // handle zero flag case. + if (origEnums[i] == 0 && flags == 0) { + proto.write(fieldId, protoEnums[i]); + return; + } + if ((flags & origEnums[i]) != 0) { + proto.write(fieldId, protoEnums[i]); + } + } + } } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 33fbf73b4371b5a87ca24eed1dfa2acb6a5a6a48..1caea57731609de9129d076fd818d9aa40493bc4 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/Display.java b/core/java/android/view/Display.java index 5bd7446d08b691caed773c3cfe468e0737618c09..31cfebcc3ae31ac3949f06d2066415e23ace8fbb 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -267,21 +267,21 @@ public final class Display { * * @see #getState */ - public static final int STATE_UNKNOWN = 0; + public static final int STATE_UNKNOWN = ViewProtoEnums.DISPLAY_STATE_UNKNOWN; // 0 /** * Display state: The display is off. * * @see #getState */ - public static final int STATE_OFF = 1; + public static final int STATE_OFF = ViewProtoEnums.DISPLAY_STATE_OFF; // 1 /** * Display state: The display is on. * * @see #getState */ - public static final int STATE_ON = 2; + public static final int STATE_ON = ViewProtoEnums.DISPLAY_STATE_ON; // 2 /** * Display state: The display is dozing in a low power state; it is still @@ -291,7 +291,7 @@ public final class Display { * @see #getState * @see android.os.PowerManager#isInteractive */ - public static final int STATE_DOZE = 3; + public static final int STATE_DOZE = ViewProtoEnums.DISPLAY_STATE_DOZE; // 3 /** * Display state: The display is dozing in a suspended low power state; it is still @@ -303,7 +303,7 @@ public final class Display { * @see #getState * @see android.os.PowerManager#isInteractive */ - public static final int STATE_DOZE_SUSPEND = 4; + public static final int STATE_DOZE_SUSPEND = ViewProtoEnums.DISPLAY_STATE_DOZE_SUSPEND; // 4 /** * Display state: The display is on and optimized for VR mode. @@ -311,7 +311,7 @@ public final class Display { * @see #getState * @see android.os.PowerManager#isInteractive */ - public static final int STATE_VR = 5; + public static final int STATE_VR = ViewProtoEnums.DISPLAY_STATE_VR; // 5 /** * Display state: The display is in a suspended full power state; it is still @@ -323,7 +323,7 @@ public final class Display { * @see #getState * @see android.os.PowerManager#isInteractive */ - public static final int STATE_ON_SUSPEND = 6; + public static final int STATE_ON_SUSPEND = ViewProtoEnums.DISPLAY_STATE_ON_SUSPEND; // 6 /* The color mode constants defined below must be kept in sync with the ones in * system/core/include/system/graphics-base.h */ diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 1ef5f0950b16fea3dda8db5717b1ead95e0ec2f5..a61c8c1dba6844bad77fe37bb0e72c3a814bd3e5 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -23,6 +23,8 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.content.res.Resources; +import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; @@ -30,8 +32,12 @@ import android.graphics.RectF; import android.graphics.Region; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.util.PathParser; import android.util.proto.ProtoOutputStream; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.util.List; @@ -43,6 +49,9 @@ import java.util.List; */ public final class DisplayCutout { + private static final String TAG = "DisplayCutout"; + private static final String DP_MARKER = "@dp"; + private static final Rect ZERO_RECT = new Rect(); private static final Region EMPTY_REGION = new Region(); @@ -311,6 +320,40 @@ public final class DisplayCutout { return new DisplayCutout(ZERO_RECT, bounds); } + /** + * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout. + * + * @hide + */ + public static DisplayCutout fromResources(Resources res, int displayWidth) { + String spec = res.getString(R.string.config_mainBuiltInDisplayCutout); + if (TextUtils.isEmpty(spec)) { + return null; + } + spec = spec.trim(); + final boolean inDp = spec.endsWith(DP_MARKER); + if (inDp) { + spec = spec.substring(0, spec.length() - DP_MARKER.length()); + } + + Path p; + try { + p = PathParser.createPathFromPathData(spec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate cutout: ", e); + return null; + } + + final Matrix m = new Matrix(); + if (inDp) { + final float dpToPx = res.getDisplayMetrics().density; + m.postScale(dpToPx, dpToPx); + } + m.postTranslate(displayWidth / 2f, 0); + p.transform(m); + return fromBounds(p); + } + /** * Helper class for passing {@link DisplayCutout} through binder. * diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5607b1134e5b1e1bb9d360bade6a4ed5bd1198ea --- /dev/null +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -0,0 +1,54 @@ +/* + * 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.view; + +import android.app.ActivityManager; +import android.view.IRemoteAnimationFinishedCallback; +import android.graphics.GraphicBuffer; + +/** + * Passed to the {@link IRecentsAnimationRunner} in order for the runner to control to let the + * runner control certain aspects of the recents animation, and to notify window manager when the + * animation has completed. + * + * {@hide} + */ +interface IRecentsAnimationController { + + /** + * Takes a screenshot of the task associated with the given {@param taskId}. Only valid for the + * current set of task ids provided to the handler. + */ + ActivityManager.TaskSnapshot screenshotTask(int taskId); + + /** + * Notifies to the system that the animation into Recents should end, and all leashes associated + * with remote animation targets should be relinquished. If {@param moveHomeToTop} is true, then + * the home activity should be moved to the top. Otherwise, the home activity is hidden and the + * user is returned to the app. + */ + void finish(boolean moveHomeToTop); + + /** + * Called by the handler to indicate that the recents animation input consumer should be + * enabled. This is currently used to work around an issue where registering an input consumer + * mid-animation causes the existing motion event chain to be canceled. Instead, the caller + * may register the recents animation input consumer prior to starting the recents animation + * and then enable it mid-animation to start receiving touch events. + */ + void setInputConsumerEnabled(boolean enabled); +} diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ea6226b3ea69c9e9e34307f7b37c8f559a310d38 --- /dev/null +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.RemoteAnimationTarget; +import android.view.IRecentsAnimationController; + +/** + * Interface that is used to callback from window manager to the process that runs a recents + * animation to start or cancel it. + * + * {@hide} + */ +oneway interface IRecentsAnimationRunner { + + /** + * Called when the system is ready for the handler to start animating all the visible tasks. + */ + void onAnimationStart(in IRecentsAnimationController controller, + in RemoteAnimationTarget[] apps); + + /** + * Called when the system needs to cancel the current animation. This can be due to the + * wallpaper not drawing in time, or the handler not finishing the animation within a predefined + * amount of time. + */ + void onAnimationCanceled(); +} diff --git a/core/java/android/view/IRemoteAnimationFinishedCallback.aidl b/core/java/android/view/IRemoteAnimationFinishedCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ae58b226ec030dded464a59273bc4ca05284fbb5 --- /dev/null +++ b/core/java/android/view/IRemoteAnimationFinishedCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +/** + * Interface to be invoked by the controlling process when a remote animation has finished. + * + * @see IRemoteAnimationRunner + * {@hide} + */ +interface IRemoteAnimationFinishedCallback { + void onAnimationFinished(); +} diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1350ebf10a4f9313b754a57bde891fb96d19353b --- /dev/null +++ b/core/java/android/view/IRemoteAnimationRunner.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.view; + +import android.view.RemoteAnimationTarget; +import android.view.IRemoteAnimationFinishedCallback; + +/** + * Interface that is used to callback from window manager to the process that runs a remote + * animation to start or cancel it. + * + * {@hide} + */ +oneway interface IRemoteAnimationRunner { + + /** + * Called when the process needs to start the remote animation. + * + * @param apps The list of apps to animate. + * @param finishedCallback The callback to invoke when the animation is finished. + */ + void onAnimationStart(in RemoteAnimationTarget[] apps, + in IRemoteAnimationFinishedCallback finishedCallback); + + /** + * Called when the animation was cancelled. From this point on, any updates onto the leashes + * won't have any effect anymore. + */ + void onAnimationCancelled(); +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8c7032207c3477df0414df207e56f619a89bbcda..4adcb8f15be731960eb97d5355cdeb877fce78aa 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -38,6 +38,7 @@ import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDockedStackListener; import android.view.IOnKeyguardExitResult; import android.view.IPinnedStackListener; +import android.view.RemoteAnimationAdapter; import android.view.IRotationWatcher; import android.view.IWallpaperVisibilityListener; import android.view.IWindowSession; @@ -124,6 +125,7 @@ interface IWindowManager void overridePendingAppTransitionMultiThumbFuture( IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback, boolean scaleUp); + void overridePendingAppTransitionRemote(in RemoteAnimationAdapter remoteAnimationAdapter); void executeAppTransition(); /** Used by system ui to report that recents has shown itself. */ @@ -293,6 +295,11 @@ interface IWindowManager */ boolean hasNavigationBar(); + /** + * Get the position of the nav bar + */ + int getNavBarPosition(); + /** * Lock the device immediately with the specified options (can be null). */ diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index ed167c812be18b94d8c14ad507a612887d6c0c2e..d7fd3299b9e34b1dba297beb83eacfaa4f0eeb47 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. @@ -149,26 +150,31 @@ 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 X coordinate of last touch point + * @param touchY Y coordinate of last touch point + * @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 +242,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/KeyEvent.java b/core/java/android/view/KeyEvent.java index ed2b8b61fe014d09574b7642a6ed189434840178..a59740563ceb1bfbdca5087519ed72711b4dc258 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -806,8 +806,10 @@ public class KeyEvent extends InputEvent implements Parcelable { public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283; /** Key code constant: Show all apps */ public static final int KEYCODE_ALL_APPS = 284; + /** Key code constant: Refresh key. */ + public static final int KEYCODE_REFRESH = 285; - private static final int LAST_KEYCODE = KEYCODE_ALL_APPS; + private static final int LAST_KEYCODE = KEYCODE_REFRESH; // NOTE: If you add a new keycode here you must also add it to: // isSystem() diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 04fa637b72f2ac704bfd3cc36acebfcbcad832e5..1d7c1dedc62e29a8341bb4b93995651669cf9fe7 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -26,6 +26,8 @@ import android.util.SparseArray; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import java.util.Objects; + /** * Object used to report movement (mouse, pen, finger, trackball) events. * Motion events may hold either absolute or relative movements and other data, @@ -173,6 +175,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { private static final long NS_PER_MS = 1000000; private static final String LABEL_PREFIX = "AXIS_"; + private static final boolean DEBUG_CONCISE_TOSTRING = false; + /** * An invalid pointer id. * @@ -3236,31 +3240,42 @@ public final class MotionEvent extends InputEvent implements Parcelable { public String toString() { StringBuilder msg = new StringBuilder(); msg.append("MotionEvent { action=").append(actionToString(getAction())); - msg.append(", actionButton=").append(buttonStateToString(getActionButton())); + appendUnless("0", msg, ", actionButton=", buttonStateToString(getActionButton())); final int pointerCount = getPointerCount(); for (int i = 0; i < pointerCount; i++) { - msg.append(", id[").append(i).append("]=").append(getPointerId(i)); - msg.append(", x[").append(i).append("]=").append(getX(i)); - msg.append(", y[").append(i).append("]=").append(getY(i)); - msg.append(", toolType[").append(i).append("]=").append( - toolTypeToString(getToolType(i))); + appendUnless(i, msg, ", id[" + i + "]=", getPointerId(i)); + float x = getX(i); + float y = getY(i); + if (!DEBUG_CONCISE_TOSTRING || x != 0f || y != 0f) { + msg.append(", x[").append(i).append("]=").append(x); + msg.append(", y[").append(i).append("]=").append(y); + } + appendUnless(TOOL_TYPE_SYMBOLIC_NAMES.get(TOOL_TYPE_FINGER), + msg, ", toolType[" + i + "]=", toolTypeToString(getToolType(i))); } - msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState())); - msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState())); - msg.append(", flags=0x").append(Integer.toHexString(getFlags())); - msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags())); - msg.append(", pointerCount=").append(pointerCount); - msg.append(", historySize=").append(getHistorySize()); + appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState())); + appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState())); + appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags())); + appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags())); + appendUnless(1, msg, ", pointerCount=", pointerCount); + appendUnless(0, msg, ", historySize=", getHistorySize()); msg.append(", eventTime=").append(getEventTime()); - msg.append(", downTime=").append(getDownTime()); - msg.append(", deviceId=").append(getDeviceId()); - msg.append(", source=0x").append(Integer.toHexString(getSource())); + if (!DEBUG_CONCISE_TOSTRING) { + msg.append(", downTime=").append(getDownTime()); + msg.append(", deviceId=").append(getDeviceId()); + msg.append(", source=0x").append(Integer.toHexString(getSource())); + } msg.append(" }"); return msg.toString(); } + private static void appendUnless(T defValue, StringBuilder sb, String key, T value) { + if (DEBUG_CONCISE_TOSTRING && Objects.equals(defValue, value)) return; + sb.append(key).append(value); + } + /** * Returns a string that represents the symbolic name of the specified unmasked action * such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java index 5088cdc948ef631bfdfc8d5a5f00e79693a5c570..fbb862be54ef54e74f7850b39bf9e7ce4ca0ccf8 100644 --- a/core/java/android/view/RecordingCanvas.java +++ b/core/java/android/view/RecordingCanvas.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.TemporaryBuffer; import android.text.GraphicsOperations; +import android.text.MeasuredText; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; @@ -473,7 +474,8 @@ public class RecordingCanvas extends Canvas { } nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, - x, y, isRtl, paint.getNativeInstance()); + x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */, + 0 /* measured text offset */); } @Override @@ -503,8 +505,20 @@ public class RecordingCanvas extends Canvas { int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + long measuredTextPtr = 0; + int measuredTextOffset = 0; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + int paraIndex = mt.findParaIndex(start); + if (end <= mt.getParagraphEnd(paraIndex)) { + // Only support if the target is in the same paragraph. + measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr(); + measuredTextOffset = start - mt.getParagraphStart(paraIndex); + } + } nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, - 0, contextLen, x, y, isRtl, paint.getNativeInstance()); + 0, contextLen, x, y, isRtl, paint.getNativeInstance(), + measuredTextPtr, measuredTextOffset); TemporaryBuffer.recycle(buf); } } @@ -626,7 +640,8 @@ public class RecordingCanvas extends Canvas { @FastNative private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count, - int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint); + int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint, + long nativeMeasuredText, int measuredTextOffset); @FastNative private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, diff --git a/core/java/android/view/RemoteAnimationAdapter.aidl b/core/java/android/view/RemoteAnimationAdapter.aidl new file mode 100644 index 0000000000000000000000000000000000000000..855bc740e75ca2d4f8334f6ce6d802f0cd4d5ca2 --- /dev/null +++ b/core/java/android/view/RemoteAnimationAdapter.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +parcelable RemoteAnimationAdapter; diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d597e597b119403ccd6c37c36e42764cade35de3 --- /dev/null +++ b/core/java/android/view/RemoteAnimationAdapter.java @@ -0,0 +1,108 @@ +/* + * 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.view; + +import android.app.ActivityOptions; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Object that describes how to run a remote animation. + *

        + * A remote animation lets another app control the entire app transition. It does so by + *

          + *
        • using {@link ActivityOptions#makeRemoteAnimation}
        • + *
        • using {@link IWindowManager#overridePendingAppTransitionRemote}
        • + *
        + * to register a {@link RemoteAnimationAdapter} that describes how the animation should be run: + * Along some meta-data, this object contains a callback that gets invoked from window manager when + * the transition is ready to be started. + *

        + * Window manager supplies a list of {@link RemoteAnimationTarget}s into the callback. Each target + * contains information about the activity that is animating as well as + * {@link RemoteAnimationTarget#leash}. The controlling app can modify the leash like any other + * {@link SurfaceControl}, including the possibility to synchronize updating the leash's surface + * properties with a frame to be drawn using + * {@link SurfaceControl.Transaction#deferTransactionUntil}. + *

        + * When the animation is done, the controlling app can invoke + * {@link IRemoteAnimationFinishedCallback} that gets supplied into + * {@link IRemoteAnimationRunner#onStartAnimation} + * + * @hide + */ +public class RemoteAnimationAdapter implements Parcelable { + + private final IRemoteAnimationRunner mRunner; + private final long mDuration; + private final long mStatusBarTransitionDelay; + + /** + * @param runner The interface that gets notified when we actually need to start the animation. + * @param duration The duration of the animation. + * @param statusBarTransitionDelay The desired delay for all visual animations in the + * status bar caused by this app animation in millis. + */ + public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, + long statusBarTransitionDelay) { + mRunner = runner; + mDuration = duration; + mStatusBarTransitionDelay = statusBarTransitionDelay; + } + + public RemoteAnimationAdapter(Parcel in) { + mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder()); + mDuration = in.readLong(); + mStatusBarTransitionDelay = in.readLong(); + } + + public IRemoteAnimationRunner getRunner() { + return mRunner; + } + + public long getDuration() { + return mDuration; + } + + public long getStatusBarTransitionDelay() { + return mStatusBarTransitionDelay; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongInterface(mRunner); + dest.writeLong(mDuration); + dest.writeLong(mStatusBarTransitionDelay); + } + + public static final Creator CREATOR + = new Creator() { + public RemoteAnimationAdapter createFromParcel(Parcel in) { + return new RemoteAnimationAdapter(in); + } + + public RemoteAnimationAdapter[] newArray(int size) { + return new RemoteAnimationAdapter[size]; + } + }; +} diff --git a/core/java/android/view/RemoteAnimationDefinition.aidl b/core/java/android/view/RemoteAnimationDefinition.aidl new file mode 100644 index 0000000000000000000000000000000000000000..32ecd01ebf250d7e00d545969f95006b5b034eb5 --- /dev/null +++ b/core/java/android/view/RemoteAnimationDefinition.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +parcelable RemoteAnimationDefinition; diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..381f6926a1e8b122c350d7278a42b749194c023a --- /dev/null +++ b/core/java/android/view/RemoteAnimationDefinition.java @@ -0,0 +1,93 @@ +/* + * 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.view; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; +import android.util.SparseArray; +import android.view.WindowManager.TransitionType; + +/** + * Defines which animation types should be overridden by which remote animation. + * + * @hide + */ +public class RemoteAnimationDefinition implements Parcelable { + + private final SparseArray mTransitionAnimationMap; + + public RemoteAnimationDefinition() { + mTransitionAnimationMap = new SparseArray<>(); + } + + /** + * Registers a remote animation for a specific transition. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @param adapter The adapter that described how to run the remote animation. + */ + public void addRemoteAnimation(@TransitionType int transition, RemoteAnimationAdapter adapter) { + mTransitionAnimationMap.put(transition, adapter); + } + + /** + * Checks whether a remote animation for specific transition is defined. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @return Whether this definition has defined a remote animation for the specified transition. + */ + public boolean hasTransition(@TransitionType int transition) { + return mTransitionAnimationMap.get(transition) != null; + } + + /** + * Retrieves the remote animation for a specific transition. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @return The remote animation adapter for the specified transition. + */ + public @Nullable RemoteAnimationAdapter getAdapter(@TransitionType int transition) { + return mTransitionAnimationMap.get(transition); + } + + public RemoteAnimationDefinition(Parcel in) { + mTransitionAnimationMap = in.readSparseArray(null /* loader */); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeSparseArray((SparseArray) mTransitionAnimationMap); + } + + public static final Creator CREATOR = + new Creator() { + public RemoteAnimationDefinition createFromParcel(Parcel in) { + return new RemoteAnimationDefinition(in); + } + + public RemoteAnimationDefinition[] newArray(int size) { + return new RemoteAnimationDefinition[size]; + } + }; +} diff --git a/core/java/android/view/RemoteAnimationTarget.aidl b/core/java/android/view/RemoteAnimationTarget.aidl new file mode 100644 index 0000000000000000000000000000000000000000..769bf5e166734400770837cd66bade05b0a6bc8c --- /dev/null +++ b/core/java/android/view/RemoteAnimationTarget.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +parcelable RemoteAnimationTarget; diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java new file mode 100644 index 0000000000000000000000000000000000000000..c28c3894482db0b1d6716bc23be25f184c475273 --- /dev/null +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -0,0 +1,161 @@ +/* + * 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.view; + +import android.annotation.IntDef; +import android.app.WindowConfiguration; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Describes an activity to be animated as part of a remote animation. + * + * @hide + */ +public class RemoteAnimationTarget implements Parcelable { + + /** + * The app is in the set of opening apps of this transition. + */ + public static final int MODE_OPENING = 0; + + /** + * The app is in the set of closing apps of this transition. + */ + public static final int MODE_CLOSING = 1; + + @IntDef(prefix = { "MODE_" }, value = { + MODE_OPENING, + MODE_CLOSING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Mode {} + + /** + * The {@link Mode} to describe whether this app is opening or closing. + */ + public final @Mode int mode; + + /** + * The id of the task this app belongs to. + */ + public final int taskId; + + /** + * The {@link SurfaceControl} object to actually control the transform of the app. + */ + public final SurfaceControl leash; + + /** + * Whether the app is translucent and may reveal apps behind. + */ + public final boolean isTranslucent; + + /** + * The clip rect window manager applies when clipping the app's main surface in screen space + * coordinates. This is just a hint to the animation runner: If running a clip-rect animation, + * anything that extends beyond these bounds will not have any effect. This implies that any + * clip-rect animation should likely stop at these bounds. + */ + public final Rect clipRect; + + /** + * The index of the element in the tree in prefix order. This should be used for z-layering + * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to + * happen. + */ + public final int prefixOrderIndex; + + /** + * The source position of the app, in screen spaces coordinates. If the position of the leash + * is modified from the controlling app, any animation transform needs to be offset by this + * amount. + */ + public final Point position; + + /** + * The bounds of the source container the app lives in, in screen space coordinates. If the crop + * of the leash is modified from the controlling app, it needs to take the source container + * bounds into account when calculating the crop. + */ + public final Rect sourceContainerBounds; + + /** + * The window configuration for the target. + */ + public final WindowConfiguration windowConfiguration; + + public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, + Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds, + WindowConfiguration windowConfig) { + this.mode = mode; + this.taskId = taskId; + this.leash = leash; + this.isTranslucent = isTranslucent; + this.clipRect = new Rect(clipRect); + this.prefixOrderIndex = prefixOrderIndex; + this.position = new Point(position); + this.sourceContainerBounds = new Rect(sourceContainerBounds); + this.windowConfiguration = windowConfig; + } + + public RemoteAnimationTarget(Parcel in) { + taskId = in.readInt(); + mode = in.readInt(); + leash = in.readParcelable(null); + isTranslucent = in.readBoolean(); + clipRect = in.readParcelable(null); + prefixOrderIndex = in.readInt(); + position = in.readParcelable(null); + sourceContainerBounds = in.readParcelable(null); + windowConfiguration = in.readParcelable(null); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(taskId); + dest.writeInt(mode); + dest.writeParcelable(leash, 0 /* flags */); + dest.writeBoolean(isTranslucent); + dest.writeParcelable(clipRect, 0 /* flags */); + dest.writeInt(prefixOrderIndex); + dest.writeParcelable(position, 0 /* flags */); + dest.writeParcelable(sourceContainerBounds, 0 /* flags */); + dest.writeParcelable(windowConfiguration, 0 /* flags */); + } + + public static final Creator CREATOR + = new Creator() { + public RemoteAnimationTarget createFromParcel(Parcel in) { + return new RemoteAnimationTarget(in); + } + + public RemoteAnimationTarget[] newArray(int size) { + return new RemoteAnimationTarget[size]; + } + }; +} diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 1f7f8b9a0c3208970097f20561a15d19e3e194b8..8830c90addacd054faaa5945b35e6dd0443eb837 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -283,6 +283,7 @@ public class Surface implements Parcelable { */ public long getNextFrameNumber() { synchronized (mLock) { + checkNotReleasedLocked(); return nativeGetNextFrameNumber(mNativeObject); } } diff --git a/core/java/android/security/keystore/RecoveryData.aidl b/core/java/android/view/SurfaceControl.aidl similarity index 89% rename from core/java/android/security/keystore/RecoveryData.aidl rename to core/java/android/view/SurfaceControl.aidl index 4200de1637a87798ee45054412511c4c8a5a5675..744ead2be6432bcbed51e5e463aa7f9ddc5d4c5e 100644 --- a/core/java/android/security/keystore/RecoveryData.aidl +++ b/core/java/android/view/SurfaceControl.aidl @@ -14,7 +14,6 @@ * limitations under the License. */ -package android.security.keystore; +package android.view; -/* @hide */ -parcelable RecoveryData; +parcelable SurfaceControl; diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 578679b12b9a9b3b5668b150479ea91ae7cdf70a..4a9da4a6672f0fb4d85ca9c412d5e844184841c4 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.res.CompatibilityInfo.Translator; import android.content.res.Configuration; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; @@ -114,7 +115,7 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb final Rect mScreenRect = new Rect(); SurfaceSession mSurfaceSession; - SurfaceControl mSurfaceControl; + SurfaceControlWithBackground mSurfaceControl; // In the case of format changes we switch out the surface in-place // we need to preserve the old one until the new one has drawn. SurfaceControl mDeferredDestroySurfaceControl; @@ -925,6 +926,17 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb return mSubLayer >= 0; } + /** + * Set an opaque background color to use with this {@link SurfaceView} when it's being resized + * and size of the content hasn't updated yet. This color will fill the expanded area when the + * view becomes larger. + * @param bgColor An opaque color to fill the background. Alpha component will be ignored. + * @hide + */ + public void setResizeBackgroundColor(int bgColor) { + mSurfaceControl.setBackgroundColor(bgColor); + } + private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { private static final String LOG_TAG = "SurfaceHolder"; @@ -1219,6 +1231,19 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb mBackgroundControl.deferTransactionUntil(barrier, frame); } + /** Set the color to fill the background with. */ + private void setBackgroundColor(int bgColor) { + final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f, + Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f }; + + SurfaceControl.openTransaction(); + try { + mBackgroundControl.setColor(colorComponents); + } finally { + SurfaceControl.closeTransaction(); + } + } + void updateBackgroundVisibility() { if (mOpaque && mVisible) { mBackgroundControl.show(); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 8b730f282b1be694e2afdcde362cd03d5689b30c..370c97e37262655900f014c20011283363394f8b 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -165,18 +165,6 @@ public final class ThreadedRenderer { */ public static final String OVERDRAW_PROPERTY_SHOW = "show"; - /** - * Defines the rendering pipeline to be used by the ThreadedRenderer. - * - * Possible values: - * "opengl", will use the existing OpenGL renderer - * "skiagl", will use Skia's OpenGL renderer - * "skiavk", will use Skia's Vulkan renderer - * - * @hide - */ - public static final String DEBUG_RENDERER_PROPERTY = "debug.hwui.renderer"; - /** * Turn on to debug non-rectangular clip operations. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ad71b58514e31e11d4b1f23fd42e9ab3168fb164..5150c1fec8a3126c15fb9f072a7fd6f55c16a4d3 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; + import static java.lang.Math.max; import android.animation.AnimatorInflater; @@ -3226,6 +3228,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000; + /** + * The last aggregated visibility. Used to detect when it truly changes. + */ + private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000; + /* End of masks for mPrivateFlags3 */ /** @@ -3387,6 +3394,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * decorations when they are shown. You can perform layout of your inner * UI elements to account for non-fullscreen system UI through the * {@link #fitSystemWindows(Rect)} method. + * + *

        Note: on displays that have a {@link DisplayCutout}, the window may still be placed + * differently than if {@link #SYSTEM_UI_FLAG_FULLSCREEN} was set, if the + * window's {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode + * layoutInDisplayCutoutMode} is + * {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. To avoid this, use either of the other modes. + * + * @see WindowManager.LayoutParams#layoutInDisplayCutoutMode + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER */ public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400; @@ -4193,6 +4212,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static boolean sCanFocusZeroSized; + /** + * Always assign focus if a focusable View is available. + */ + private static boolean sAlwaysAssignFocus; + private String mTransitionName; static class TintInfo { @@ -4418,6 +4442,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; + private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; private UnsetPressedState mUnsetPressedState; @@ -4810,6 +4835,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P; + sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P; + sCompatibilityDone = true; } } @@ -7000,8 +7027,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Called when this view wants to give up focus. If focus is cleared * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called. *

        - * Note: When a View clears focus the framework is trying - * to give focus to the first focusable View from the top. Hence, if this + * Note: When not in touch-mode, the framework will try to give focus + * to the first focusable View from the top after focus is cleared. Hence, if this * View is the first from the top that can take focus, then all callbacks * related to clearing focus will be invoked after which the framework will * give focus to this view. @@ -7012,7 +7039,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, System.out.println(this + " clearFocus()"); } - clearFocusInternal(null, true, true); + final boolean refocus = sAlwaysAssignFocus || !isInTouchMode(); + clearFocusInternal(null, true, refocus); } /** @@ -7174,7 +7202,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -7207,20 +7235,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifyEnterOrExitForAutoFillIfNeeded(gainFocus); } - private void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) { - if (isAutofillable() && isAttachedToWindow()) { + /** @hide */ + public void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) { + if (canNotifyAutofillEnterExitEvent()) { AutofillManager afm = getAutofillManager(); if (afm != null) { - if (enter && hasWindowFocus() && isFocused()) { + if (enter && isFocused()) { // We have not been laid out yet, hence cannot evaluate // whether this view is visible to the user, we will do // the evaluation once layout is complete. if (!isLaidOut()) { mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; } else if (isVisibleToUser()) { + // TODO This is a potential problem that View gets focus before it's visible + // to User. Ideally View should handle the event when isVisibleToUser() + // becomes true where it should issue notifyViewEntered(). afm.notifyViewEntered(this); } - } else if (!hasWindowFocus() || !isFocused()) { + } else if (!isFocused()) { afm.notifyViewExited(this); } } @@ -7240,7 +7272,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void setAccessibilityPaneTitle(CharSequence accessibilityPaneTitle) { if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) { mAccessibilityPaneTitle = accessibilityPaneTitle; - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE); } } @@ -7354,7 +7387,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) { - if (!isShown()) { + // Panes disappearing are relevant even if though the view is no longer visible. + boolean isWindowStateChanged = + (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0); + if (!isShown() && !isWindowDisappearedEvent) { return; } onInitializeAccessibilityEvent(event); @@ -7462,6 +7500,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) + && !TextUtils.isEmpty(getAccessibilityPaneTitle())) { + event.getText().add(getAccessibilityPaneTitle()); + } } /** @@ -8283,6 +8325,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && getAutofillViewId() > LAST_APP_AUTOFILL_ID; } + /** @hide */ + public boolean canNotifyAutofillEnterExitEvent() { + return isAutofillable() && isAttachedToWindow(); + } + private void populateVirtualStructure(ViewStructure structure, AccessibilityNodeProvider provider, AccessibilityNodeInfo info) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), @@ -8505,6 +8552,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setLongClickable(isLongClickable()); info.setContextClickable(isContextClickable()); info.setLiveRegion(getAccessibilityLiveRegion()); + if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipText != null)) { + info.setTooltipText(mTooltipInfo.mTooltipText); + info.addAction((mTooltipInfo.mTooltipPopup == null) + ? AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP + : AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP); + } // TODO: These make sense only if we are in an AdapterView but all // views can be selected. Maybe from accessibility perspective @@ -8873,9 +8926,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0; if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); } } @@ -8908,7 +8961,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalBeforeId = beforeId; - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -8952,7 +9005,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalAfterId = afterId; - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -8995,7 +9048,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && mID == View.NO_ID) { mID = generateViewId(); } - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -10496,7 +10549,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (pflags3 != mPrivateFlags3) { mPrivateFlags3 = pflags3; - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11210,6 +11264,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!isLayoutValid()) { mPrivateFlags |= PFLAG_WANTS_FOCUS; + } else { + clearParentsWantFocus(); } handleFocusGainInternal(direction, previouslyFocusedRect); @@ -11324,7 +11380,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT) & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11382,9 +11438,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11561,8 +11617,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifyAccessibilityStateChanged(int changeType) { - notifyAccessibilityStateChanged(this, changeType); + public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) { + if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { + return; + } + + // Changes to views with a pane title count as window state changes, as the pane title + // marks them as significant parts of the UI. + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + event.setContentChangeTypes(changeType); + onPopulateAccessibilityEvent(event); + if (mParent != null) { + try { + mParent.requestSendAccessibilityEvent(this, event); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + + // If this is a live region, we should send a subtree change event + // from this view immediately. Otherwise, we can let it propagate up. + if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(changeType); + sendAccessibilityEventUnchecked(event); + } else if (mParent != null) { + try { + mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } } /** @@ -11576,23 +11667,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifyAccessibilitySubtreeChanged() { - if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { - mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); - } - } - - void notifyAccessibilityStateChanged(View source, int changeType) { + public void notifySubtreeAccessibilityStateChangedIfNeeded() { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } - if (mParent != null) { - try { - mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); - } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() - + " does not fully implement ViewParent", e); + + if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { + mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; + if (mParent != null) { + try { + mParent.notifySubtreeAccessibilityStateChanged( + this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } } } } @@ -11614,10 +11703,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Reset the flag indicating the accessibility state of the subtree rooted * at this view changed. - * - * @hide */ - public void resetSubtreeAccessibilityStateChanged() { + void resetSubtreeAccessibilityStateChanged() { mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } @@ -11778,7 +11865,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || getAccessibilitySelectionEnd() != end) && (start == end)) { setAccessibilitySelection(start, end); - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); return true; } @@ -11796,6 +11883,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } } break; + case R.id.accessibilityActionShowTooltip: { + if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipPopup != null)) { + // Tooltip already showing + return false; + } + return showLongClickTooltip(0, 0); + } + case R.id.accessibilityActionHideTooltip: { + if ((mTooltipInfo == null) || (mTooltipInfo.mTooltipPopup == null)) { + // No tooltip showing + return false; + } + hideTooltip(); + return true; + } } return false; } @@ -12400,8 +12502,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, imm.focusIn(this); } - notifyEnterOrExitForAutoFillIfNeeded(hasWindowFocus); - refreshDrawableState(); } @@ -12520,6 +12620,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper public void onVisibilityAggregated(boolean isVisible) { + // Update our internal visibility tracking so we can detect changes + boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0; + mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE) + : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE); if (isVisible && mAttachInfo != null) { initialAwakenScrollBars(); } @@ -12560,6 +12664,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + if (isVisible != oldVisible) { + notifyViewAccessibilityStateChangedIfNeeded(isVisible + ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED + : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); + } + } } /** @@ -13772,7 +13883,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) { dispatchVisibilityAggregated(newVisibility == VISIBLE); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -13818,13 +13929,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } else if ((changed & ENABLED_MASK) != 0) { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -13859,13 +13970,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param oldt Previous vertical scroll origin. */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); - ViewRootImpl root = getViewRootImpl(); - if (root != null) { - root.getAccessibilityState() - .getSendViewScrolledAccessibilityEvent() - .post(this, /* dx */ l - oldl, /* dy */ t - oldt); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); } mBackgroundSizeChanged = true; @@ -14261,7 +14369,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14305,7 +14413,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14349,7 +14457,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14386,7 +14494,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14423,7 +14531,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14626,7 +14734,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mTransformationInfo.mAlpha != alpha) { // Report visibility changes, which can affect children, to accessibility if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } mTransformationInfo.mAlpha = alpha; if (onSetAlpha((int) (alpha * 255))) { @@ -15128,7 +15236,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15162,7 +15270,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15332,7 +15440,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void invalidateOutline() { rebuildOutline(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); invalidateViewProperty(false, false); } @@ -15527,7 +15635,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15575,7 +15683,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -16452,6 +16560,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. + * This event is sent at most once every + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. + */ + private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) { + if (mSendViewScrolledAccessibilityEvent == null) { + mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + } + mSendViewScrolledAccessibilityEvent.post(dx, dy); + } + /** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is @@ -17707,13 +17827,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); - if (mAttachInfo != null - && mAttachInfo.mViewRootImpl.mAccessibilityState != null - && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) { - mAttachInfo.mViewRootImpl.mAccessibilityState - .getSendViewScrolledAccessibilityEvent() - .cancelIfPendingFor(this); - } + cancel(mSendViewScrolledAccessibilityEvent); stopNestedScroll(); // Anything that started animating right before detach should already @@ -17816,6 +17930,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mAttachInfo != null ? mAttachInfo.mSession : null; } + /** + * 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. */ @@ -20332,7 +20455,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mForegroundInfo.mBoundsChanged = true; } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; } @@ -21776,7 +21899,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (selected) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -22131,7 +22254,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise - * @see View#findViewById(int) + * @see View#requireViewById(int) */ @Nullable public final T findViewById(@IdRes int id) { @@ -22141,6 +22264,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return findViewTraversal(id); } + /** + * Finds the first descendant view with the given ID, the view itself if the ID matches + * {@link #getId()}, or throws an IllegalArgumentException if the ID is invalid or there is no + * matching view in the hierarchy. + *

        + * Note: In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#findViewById(int) + */ + @NonNull + public final T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this View"); + } + return view; + } + /** * Finds a view by its unuque and stable accessibility id. * @@ -23484,15 +23630,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) { @@ -23503,40 +23647,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; } /** @@ -26332,6 +26486,53 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Resuable callback for sending + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + */ + private class SendViewScrolledAccessibilityEvent implements Runnable { + public volatile boolean mIsPending; + public int mDeltaX; + public int mDeltaY; + + public void post(int dx, int dy) { + mDeltaX += dx; + mDeltaY += dy; + if (!mIsPending) { + mIsPending = true; + postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); + } + } + + @Override + public void run() { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_SCROLLED); + event.setScrollDeltaX(mDeltaX); + event.setScrollDeltaY(mDeltaY); + sendAccessibilityEventUnchecked(event); + } + reset(); + } + + private void reset() { + mIsPending = false; + mDeltaX = 0; + mDeltaY = 0; + } + } + + /** + * Remove the pending callback for sending a + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + */ + private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) { + if (callback == null || !callback.mIsPending) return; + removeCallbacks(callback); + callback.reset(); + } + /** *

        * This class represents a delegate that can be registered in a {@link View} @@ -26948,6 +27149,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean fromTouch = (mPrivateFlags3 & PFLAG3_FINGER_DOWN) == PFLAG3_FINGER_DOWN; mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText); mAttachInfo.mTooltipHost = this; + // The available accessibility actions have changed + notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED); return true; } @@ -26966,6 +27169,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo != null) { mAttachInfo.mTooltipHost = null; } + // The available accessibility actions have changed + notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED); } private boolean showLongClickTooltip(int x, int y) { @@ -26974,8 +27179,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return showTooltip(x, y, true); } - private void showHoverTooltip() { - showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false); + private boolean showHoverTooltip() { + return showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false); } boolean dispatchTooltipHoverEvent(MotionEvent event) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e0864bd5d72ac1c0abbd13ffca7390d5992448ff..a8bdb85660d05240b7d681fcfc714406826334f4 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -57,6 +57,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.LayoutAnimationController; import android.view.animation.Transformation; +import android.view.autofill.Helper; import com.android.internal.R; @@ -3474,8 +3475,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (!isLaidOut()) { - Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring " - + childrenCount + " children of " + getAccessibilityViewId()); + if (Helper.sVerbose) { + Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring " + + childrenCount + " children of " + getAccessibilityViewId()); + } return; } @@ -3646,34 +3649,44 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return ViewGroup.class.getName(); } + @Override + public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { + // If this is a live region, we should send a subtree change event + // from this view. Otherwise, we can let it propagate up. + if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + } else if (mParent != null) { + try { + mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + /** @hide */ @Override - public void notifyAccessibilitySubtreeChanged() { + public void notifySubtreeAccessibilityStateChangedIfNeeded() { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } // If something important for a11y is happening in this subtree, make sure it's dispatched // from a view that is important for a11y so it doesn't get lost. - if (getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - && !isImportantForAccessibility() - && getChildCount() > 0) { + if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) + && !isImportantForAccessibility() && (getChildCount() > 0)) { ViewParent a11yParent = getParentForAccessibility(); if (a11yParent instanceof View) { - ((View) a11yParent).notifyAccessibilitySubtreeChanged(); + ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded(); return; } } - super.notifyAccessibilitySubtreeChanged(); - } - - @Override - public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { - notifyAccessibilityStateChanged(source, changeType); + super.notifySubtreeAccessibilityStateChangedIfNeeded(); } - /** @hide */ @Override - public void resetSubtreeAccessibilityStateChanged() { + void resetSubtreeAccessibilityStateChanged() { super.resetSubtreeAccessibilityStateChanged(); View[] children = mChildren; final int childCount = mChildrenCount; @@ -5085,7 +5098,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (child.getVisibility() != View.GONE) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } if (mTransientIndices != null) { @@ -5355,7 +5368,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager dispatchViewRemoved(view); if (view.getVisibility() != View.GONE) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); @@ -6074,7 +6087,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (invalidate) { invalidateViewProperty(false, false); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f81a4c33271a3e66f2c4a8dbf7b2da0c3623e34a..0af12544294c4de1602db9c35e4e70f34ef05df2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -20,7 +20,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; @@ -89,11 +89,9 @@ import android.view.accessibility.AccessibilityManager.HighTextContrastChangeLis import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; -import android.view.accessibility.AccessibilityViewHierarchyState; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; -import android.view.accessibility.ThrottlingAccessibilityEventSender; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputMethodManager; @@ -115,6 +113,7 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashSet; import java.util.concurrent.CountDownLatch; /** @@ -461,6 +460,10 @@ public final class ViewRootImpl implements ViewParent, new AccessibilityInteractionConnectionManager(); final HighContrastTextManager mHighContrastTextManager; + SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent; + + HashSet mTempHashSet; + private final int mDensity; private final int mNoncompatDensity; @@ -475,8 +478,6 @@ public final class ViewRootImpl implements ViewParent, private boolean mNeedsRendererSetup; - protected AccessibilityViewHierarchyState mAccessibilityState; - /** * Consistency verifier for debugging purposes. */ @@ -530,7 +531,7 @@ public final class ViewRootImpl implements ViewParent, mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); if (!sCompatibilityDone) { - sAlwaysAssignFocus = true; + sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P; sCompatibilityDone = true; } @@ -895,6 +896,26 @@ public final class ViewRootImpl implements ViewParent, return mWindowAttributes.getTitle(); } + /** + * @return the width of the root view. Note that this will return {@code -1} until the first + * layout traversal, when the width is set. + * + * @hide + */ + public int getWidth() { + return mWidth; + } + + /** + * @return the height of the root view. Note that this will return {@code -1} until the first + * layout traversal, when the height is set. + * + * @hide + */ + public int getHeight() { + return mHeight; + } + void destroyHardwareResources() { if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.destroyHardwareResources(mView); @@ -1596,9 +1617,9 @@ public final class ViewRootImpl implements ViewParent, void dispatchApplyInsets(View host) { WindowInsets insets = getWindowInsets(true /* forceConstruct */); - final boolean layoutInCutout = - (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; - if (!layoutInCutout) { + final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode + == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); + if (!dispatchCutout) { // Window is either not laid out in cutout or the status bar inset takes care of // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy. insets = insets.consumeDisplayCutout(); @@ -2337,7 +2358,7 @@ public final class ViewRootImpl implements ViewParent, } if (mFirst) { - if (sAlwaysAssignFocus) { + if (sAlwaysAssignFocus || !isInTouchMode()) { // handle first focus request if (DEBUG_INPUT_RESIZE) { Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); @@ -3608,7 +3629,7 @@ public final class ViewRootImpl implements ViewParent, checkThread(); if (mView != null) { if (!mView.hasFocus()) { - if (sAlwaysAssignFocus) { + if (sAlwaysAssignFocus || !isInTouchMode()) { v.requestFocus(); } } else { @@ -4211,10 +4232,7 @@ public final class ViewRootImpl implements ViewParent, // find the best view to give focus to in this brave new non-touch-mode // world - final View focused = focusSearch(null, View.FOCUS_DOWN); - if (focused != null) { - return focused.requestFocus(View.FOCUS_DOWN); - } + return mView.restoreDefaultFocus(); } return false; } @@ -7261,9 +7279,11 @@ public final class ViewRootImpl implements ViewParent, * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ private void postSendWindowContentChangedCallback(View source, int changeType) { - getAccessibilityState() - .getSendWindowContentChangedAccessibilityEvent() - .runOrPost(source, changeType); + if (mSendWindowContentChangedAccessibilityEvent == null) { + mSendWindowContentChangedAccessibilityEvent = + new SendWindowContentChangedAccessibilityEvent(); + } + mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType); } /** @@ -7271,20 +7291,11 @@ public final class ViewRootImpl implements ViewParent, * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. */ private void removeSendWindowContentChangedCallback() { - if (mAccessibilityState != null - && mAccessibilityState.isWindowContentChangedEventSenderInitialized()) { - ThrottlingAccessibilityEventSender.cancelIfPending( - mAccessibilityState.getSendWindowContentChangedAccessibilityEvent()); + if (mSendWindowContentChangedAccessibilityEvent != null) { + mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent); } } - AccessibilityViewHierarchyState getAccessibilityState() { - if (mAccessibilityState == null) { - mAccessibilityState = new AccessibilityViewHierarchyState(); - } - return mAccessibilityState; - } - @Override public boolean showContextMenuForChild(View originalView) { return false; @@ -7320,8 +7331,12 @@ public final class ViewRootImpl implements ViewParent, return false; } - // Send any pending event to prevent reordering - flushPendingAccessibilityEvents(); + // Immediately flush pending content changed event (if any) to preserve event order + if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + && mSendWindowContentChangedAccessibilityEvent != null + && mSendWindowContentChangedAccessibilityEvent.mSource != null) { + mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun(); + } // Intercept accessibility focus events fired by virtual nodes to keep // track of accessibility focus position in such nodes. @@ -7365,19 +7380,6 @@ public final class ViewRootImpl implements ViewParent, return true; } - /** @hide */ - public void flushPendingAccessibilityEvents() { - if (mAccessibilityState != null) { - if (mAccessibilityState.isScrollEventSenderInitialized()) { - mAccessibilityState.getSendViewScrolledAccessibilityEvent().sendNowIfPending(); - } - if (mAccessibilityState.isWindowContentChangedEventSenderInitialized()) { - mAccessibilityState.getSendWindowContentChangedAccessibilityEvent() - .sendNowIfPending(); - } - } - } - /** * Updates the focused virtual view, when necessary, in response to a * content changed event. @@ -7512,6 +7514,39 @@ public final class ViewRootImpl implements ViewParent, return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT; } + private View getCommonPredecessor(View first, View second) { + if (mTempHashSet == null) { + mTempHashSet = new HashSet(); + } + HashSet seen = mTempHashSet; + seen.clear(); + View firstCurrent = first; + while (firstCurrent != null) { + seen.add(firstCurrent); + ViewParent firstCurrentParent = firstCurrent.mParent; + if (firstCurrentParent instanceof View) { + firstCurrent = (View) firstCurrentParent; + } else { + firstCurrent = null; + } + } + View secondCurrent = second; + while (secondCurrent != null) { + if (seen.contains(secondCurrent)) { + seen.clear(); + return secondCurrent; + } + ViewParent secondCurrentParent = secondCurrent.mParent; + if (secondCurrentParent instanceof View) { + secondCurrent = (View) secondCurrentParent; + } else { + secondCurrent = null; + } + } + seen.clear(); + return null; + } + void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( @@ -8122,6 +8157,80 @@ public final class ViewRootImpl implements ViewParent, } } + private class SendWindowContentChangedAccessibilityEvent implements Runnable { + private int mChangeTypes = 0; + + public View mSource; + public long mLastEventTimeMillis; + + @Override + public void run() { + // Protect against re-entrant code and attempt to do the right thing in the case that + // we're multithreaded. + View source = mSource; + mSource = null; + if (source == null) { + Log.e(TAG, "Accessibility content change has no source"); + return; + } + // The accessibility may be turned off while we were waiting so check again. + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + mLastEventTimeMillis = SystemClock.uptimeMillis(); + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(mChangeTypes); + source.sendAccessibilityEventUnchecked(event); + } else { + mLastEventTimeMillis = 0; + } + // In any case reset to initial state. + source.resetSubtreeAccessibilityStateChanged(); + mChangeTypes = 0; + } + + public void runOrPost(View source, int changeType) { + if (mHandler.getLooper() != Looper.myLooper()) { + CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the " + + "original thread that created a view hierarchy can touch its views."); + // TODO: Throw the exception + Log.e(TAG, "Accessibility content change on non-UI thread. Future Android " + + "versions will throw an exception.", e); + // Attempt to recover. This code does not eliminate the thread safety issue, but + // it should force any issues to happen near the above log. + mHandler.removeCallbacks(this); + if (mSource != null) { + // Dispatch whatever was pending. It's still possible that the runnable started + // just before we removed the callbacks, and bad things will happen, but at + // least they should happen very close to the logged error. + run(); + } + } + if (mSource != null) { + // If there is no common predecessor, then mSource points to + // a removed view, hence in this case always prefer the source. + View predecessor = getCommonPredecessor(mSource, source); + mSource = (predecessor != null) ? predecessor : source; + mChangeTypes |= changeType; + return; + } + mSource = source; + mChangeTypes = changeType; + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; + final long minEventIntevalMillis = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastMillis >= minEventIntevalMillis) { + removeCallbacksAndRun(); + } else { + mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); + } + } + + public void removeCallbacksAndRun() { + mHandler.removeCallbacks(this); + run(); + } + } + private static class KeyFallbackManager { // This is used to ensure that key-fallback events are only dispatched once. We attempt diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 176927fef0eb47f46717678e5098fdb8c094ad6d..93b3fc25509a5494bfdf84f734395bd2862fc5bf 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -25,7 +25,6 @@ import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StyleRes; -import android.annotation.SystemApi; import android.app.WindowConfiguration; import android.content.Context; import android.content.pm.ActivityInfo; @@ -1254,14 +1253,6 @@ public abstract class Window { } } - /** @hide */ - @SystemApi - public void setDisableWallpaperTouchEvents(boolean disable) { - setPrivateFlags(disable - ? WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS : 0, - WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS); - } - /** @hide */ public abstract void alwaysReadCloseOnTouchAttr(); @@ -1339,9 +1330,9 @@ public abstract class Window { /** * Finds a view that was identified by the {@code android:id} XML attribute - * that was processed in {@link android.app.Activity#onCreate}. This will - * implicitly call {@link #getDecorView} with all of the associated - * side-effects. + * that was processed in {@link android.app.Activity#onCreate}. + *

        + * This will implicitly call {@link #getDecorView} with all of the associated side-effects. *

        * Note: In most cases -- depending on compiler support -- * the resulting view is automatically cast to the target class type. If @@ -1351,11 +1342,35 @@ public abstract class Window { * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise * @see View#findViewById(int) + * @see Window#requireViewById(int) */ @Nullable public T findViewById(@IdRes int id) { return getDecorView().findViewById(id); } + /** + * Finds a view that was identified by the {@code android:id} XML attribute + * that was processed in {@link android.app.Activity#onCreate}, or throws an + * IllegalArgumentException if the ID is invalid, or there is no matching view in the hierarchy. + *

        + * Note: In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#requireViewById(int) + * @see Window#findViewById(int) + */ + @NonNull + public final T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Window"); + } + return view; + } /** * Convenience for @@ -2244,9 +2259,36 @@ public abstract class Window { *

        * The transitionName for the view background will be "android:navigation:background". *

        + * @attr ref android.R.styleable#Window_navigationBarColor */ public abstract void setNavigationBarColor(@ColorInt int color); + /** + * Shows a thin line of the specified color between the navigation bar and the app + * content. + *

        + * For this to take effect, + * the window must be drawing the system bar backgrounds with + * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and + * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set. + * + * @param dividerColor The color of the thin line. + * @attr ref android.R.styleable#Window_navigationBarDividerColor + */ + public void setNavigationBarDividerColor(@ColorInt int dividerColor) { + } + + /** + * Retrieves the color of the navigation bar divider. + * + * @return The color of the navigation bar divider color. + * @see #setNavigationBarColor(int) + * @attr ref android.R.styleable#Window_navigationBarDividerColor + */ + public @ColorInt int getNavigationBarDividerColor() { + return 0; + } + /** @hide */ public void setTheme(int resId) { } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a65aba152c8344a751aba85c4b76fd82e3d038d9..1c5e8719775004683fa81867786e15b25c978e1d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -21,7 +21,6 @@ import static android.view.WindowLayoutParamsProto.ALPHA; import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS; import static android.view.WindowLayoutParamsProto.COLOR_MODE; import static android.view.WindowLayoutParamsProto.FLAGS; -import static android.view.WindowLayoutParamsProto.FLAGS_EXTRA; import static android.view.WindowLayoutParamsProto.FORMAT; import static android.view.WindowLayoutParamsProto.GRAVITY; import static android.view.WindowLayoutParamsProto.HAS_SYSTEM_UI_LISTENERS; @@ -46,7 +45,6 @@ import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.IntDef; -import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -100,11 +98,198 @@ public interface WindowManager extends ViewManager { int DOCKED_BOTTOM = 4; /** @hide */ - final static String INPUT_CONSUMER_PIP = "pip_input_consumer"; + String INPUT_CONSUMER_PIP = "pip_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; + String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + /** @hide */ + String INPUT_CONSUMER_RECENTS_ANIMATION = "recents_animation_input_consumer"; + + /** + * Not set up for a transition. + * @hide + */ + int TRANSIT_UNSET = -1; + + /** + * No animation for transition. + * @hide + */ + int TRANSIT_NONE = 0; + + /** + * A window in a new activity is being opened on top of an existing one in the same task. + * @hide + */ + int TRANSIT_ACTIVITY_OPEN = 6; + + /** + * The window in the top-most activity is being closed to reveal the previous activity in the + * same task. + * @hide + */ + int TRANSIT_ACTIVITY_CLOSE = 7; + + /** + * A window in a new task is being opened on top of an existing one in another activity's task. + * @hide + */ + int TRANSIT_TASK_OPEN = 8; + + /** + * A window in the top-most activity is being closed to reveal the previous activity in a + * different task. + * @hide + */ + int TRANSIT_TASK_CLOSE = 9; + + /** + * A window in an existing task is being displayed on top of an existing one in another + * activity's task. + * @hide + */ + int TRANSIT_TASK_TO_FRONT = 10; + + /** + * A window in an existing task is being put below all other tasks. + * @hide + */ + int TRANSIT_TASK_TO_BACK = 11; + + /** + * A window in a new activity that doesn't have a wallpaper is being opened on top of one that + * does, effectively closing the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_CLOSE = 12; + + /** + * A window in a new activity that does have a wallpaper is being opened on one that didn't, + * effectively opening the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_OPEN = 13; + + /** + * A window in a new activity is being opened on top of an existing one, and both are on top + * of the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_INTRA_OPEN = 14; + + /** + * The window in the top-most activity is being closed to reveal the previous activity, and + * both are on top of the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_INTRA_CLOSE = 15; + + /** + * A window in a new task is being opened behind an existing one in another activity's task. + * The new window will show briefly and then be gone. + * @hide + */ + int TRANSIT_TASK_OPEN_BEHIND = 16; + + /** + * A window in a task is being animated in-place. + * @hide + */ + int TRANSIT_TASK_IN_PLACE = 17; + + /** + * An activity is being relaunched (e.g. due to configuration change). + * @hide + */ + int TRANSIT_ACTIVITY_RELAUNCH = 18; + + /** + * A task is being docked from recents. + * @hide + */ + int TRANSIT_DOCK_TASK_FROM_RECENTS = 19; + + /** + * Keyguard is going away. + * @hide + */ + int TRANSIT_KEYGUARD_GOING_AWAY = 20; + + /** + * Keyguard is going away with showing an activity behind that requests wallpaper. + * @hide + */ + int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = 21; + + /** + * Keyguard is being occluded. + * @hide + */ + int TRANSIT_KEYGUARD_OCCLUDE = 22; + + /** + * Keyguard is being unoccluded. + * @hide + */ + int TRANSIT_KEYGUARD_UNOCCLUDE = 23; + + /** + * @hide + */ + @IntDef(prefix = { "TRANSIT_" }, value = { + TRANSIT_UNSET, + TRANSIT_NONE, + TRANSIT_ACTIVITY_OPEN, + TRANSIT_ACTIVITY_CLOSE, + TRANSIT_TASK_OPEN, + TRANSIT_TASK_CLOSE, + TRANSIT_TASK_TO_FRONT, + TRANSIT_TASK_TO_BACK, + TRANSIT_WALLPAPER_CLOSE, + TRANSIT_WALLPAPER_OPEN, + TRANSIT_WALLPAPER_INTRA_OPEN, + TRANSIT_WALLPAPER_INTRA_CLOSE, + TRANSIT_TASK_OPEN_BEHIND, + TRANSIT_TASK_IN_PLACE, + TRANSIT_ACTIVITY_RELAUNCH, + TRANSIT_DOCK_TASK_FROM_RECENTS, + TRANSIT_KEYGUARD_GOING_AWAY, + TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + TRANSIT_KEYGUARD_OCCLUDE, + TRANSIT_KEYGUARD_UNOCCLUDE + }) + @Retention(RetentionPolicy.SOURCE) + @interface TransitionType {} + + /** + * Transition flag: Keyguard is going away, but keeping the notification shade open + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = 0x1; + + /** + * Transition flag: Keyguard is going away, but doesn't want an animation for it + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = 0x2; + + /** + * Transition flag: Keyguard is going away while it was showing the system wallpaper. + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = 0x4; + + /** + * @hide + */ + @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = { + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER, + }) + @Retention(RetentionPolicy.SOURCE) + @interface TransitionFlags {} /** * Exception that is thrown when trying to add view whose @@ -889,7 +1074,12 @@ public interface WindowManager extends ViewManager { * decorations around the border (such as the status bar). The * window must correctly position its contents to take the screen * decoration into account. This flag is normally set for you - * by Window as described in {@link Window#setFlags}. */ + * by Window as described in {@link Window#setFlags}. + * + *

        Note: on displays that have a {@link DisplayCutout}, the window may be placed + * such that it avoids the {@link DisplayCutout} area if necessary according to the + * {@link #layoutInDisplayCutoutMode}. + */ public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100; /** Window flag: allow window to extend outside of the screen. */ @@ -1295,33 +1485,6 @@ public interface WindowManager extends ViewManager { }, formatToHexString = true) public int flags; - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @LongDef( - flag = true, - value = { - LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA, - }) - @interface Flags2 {} - - /** - * Window flag: allow placing the window within the area that overlaps with the - * display cutout. - * - *

        - * The window must correctly position its contents to take the display cutout into account. - * - * @see DisplayCutout - */ - public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001; - - /** - * Various behavioral options/flags. Default is none. - * - * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA - */ - @Flags2 public long flags2; - /** * If the window has requested hardware acceleration, but this is not * allowed in the process it is in, then still render it as if it is @@ -2050,6 +2213,77 @@ public interface WindowManager extends ViewManager { */ public boolean hasSystemUiListeners; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER}) + @interface LayoutInDisplayCutoutMode {} + + /** + * Controls how the window is laid out if there is a {@link DisplayCutout}. + * + *

        + * Defaults to {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. + * + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + * @see DisplayCutout + */ + @LayoutInDisplayCutoutMode + public int layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; + + /** + * The window is allowed to extend into the {@link DisplayCutout} area, only if the + * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is + * laid out such that it does not overlap with the {@link DisplayCutout} area. + * + *

        + * In practice, this means that if the window did not set FLAG_FULLSCREEN or + * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait. + * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the + * cutout area. + * + *

        + * The usual precautions for not overlapping with the status bar are sufficient for ensuring + * that no important content overlaps with the DisplayCutout. + * + * @see DisplayCutout + * @see WindowInsets + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; + + /** + * The window is always allowed to extend into the {@link DisplayCutout} area, + * even if fullscreen or in landscape. + * + *

        + * The window must make sure that no important content overlaps with the + * {@link DisplayCutout}. + * + * @see DisplayCutout + * @see WindowInsets#getDisplayCutout() + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; + + /** + * The window is never allowed to overlap with the DisplayCutout area. + * + *

        + * This should be used with windows that transiently set SYSTEM_UI_FLAG_FULLSCREEN to + * avoid a relayout of the window when the flag is set or cleared. + * + * @see DisplayCutout + * @see View#SYSTEM_UI_FLAG_FULLSCREEN SYSTEM_UI_FLAG_FULLSCREEN + * @see View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; + + /** * When this window has focus, disable touch pad pointer gesture processing. * The window will receive raw position updates from the touch pad instead @@ -2273,9 +2507,9 @@ public interface WindowManager extends ViewManager { out.writeInt(y); out.writeInt(type); out.writeInt(flags); - out.writeLong(flags2); out.writeInt(privateFlags); out.writeInt(softInputMode); + out.writeInt(layoutInDisplayCutoutMode); out.writeInt(gravity); out.writeFloat(horizontalMargin); out.writeFloat(verticalMargin); @@ -2329,9 +2563,9 @@ public interface WindowManager extends ViewManager { y = in.readInt(); type = in.readInt(); flags = in.readInt(); - flags2 = in.readLong(); privateFlags = in.readInt(); softInputMode = in.readInt(); + layoutInDisplayCutoutMode = in.readInt(); gravity = in.readInt(); horizontalMargin = in.readFloat(); verticalMargin = in.readFloat(); @@ -2462,10 +2696,6 @@ public interface WindowManager extends ViewManager { flags = o.flags; changes |= FLAGS_CHANGED; } - if (flags2 != o.flags2) { - flags2 = o.flags2; - changes |= FLAGS_CHANGED; - } if (privateFlags != o.privateFlags) { privateFlags = o.privateFlags; changes |= PRIVATE_FLAGS_CHANGED; @@ -2474,6 +2704,10 @@ public interface WindowManager extends ViewManager { softInputMode = o.softInputMode; changes |= SOFT_INPUT_MODE_CHANGED; } + if (layoutInDisplayCutoutMode != o.layoutInDisplayCutoutMode) { + layoutInDisplayCutoutMode = o.layoutInDisplayCutoutMode; + changes |= LAYOUT_CHANGED; + } if (gravity != o.gravity) { gravity = o.gravity; changes |= LAYOUT_CHANGED; @@ -2651,6 +2885,10 @@ public interface WindowManager extends ViewManager { sb.append(softInputModeToString(softInputMode)); sb.append('}'); } + if (layoutInDisplayCutoutMode != 0) { + sb.append(" layoutInDisplayCutoutMode="); + sb.append(layoutInDisplayCutoutModeToString(layoutInDisplayCutoutMode)); + } sb.append(" ty="); sb.append(ViewDebug.intToString(LayoutParams.class, "type", type)); if (format != PixelFormat.OPAQUE) { @@ -2719,11 +2957,6 @@ public interface WindowManager extends ViewManager { sb.append(System.lineSeparator()); sb.append(prefix).append(" fl=").append( ViewDebug.flagsToString(LayoutParams.class, "flags", flags)); - if (flags2 != 0) { - sb.append(System.lineSeparator()); - // TODO(roosa): add a long overload for ViewDebug.flagsToString. - sb.append(prefix).append(" fl2=0x").append(Long.toHexString(flags2)); - } if (privateFlags != 0) { sb.append(System.lineSeparator()); sb.append(prefix).append(" pfl=").append(ViewDebug.flagsToString( @@ -2771,7 +3004,6 @@ public interface WindowManager extends ViewManager { proto.write(NEEDS_MENU_KEY, needsMenuKey); proto.write(COLOR_MODE, mColorMode); proto.write(FLAGS, flags); - proto.write(FLAGS_EXTRA, flags2); proto.write(PRIVATE_FLAGS, privateFlags); proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility); proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility); @@ -2849,6 +3081,20 @@ public interface WindowManager extends ViewManager { && height == WindowManager.LayoutParams.MATCH_PARENT; } + private static String layoutInDisplayCutoutModeToString( + @LayoutInDisplayCutoutMode int mode) { + switch (mode) { + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT: + return "default"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS: + return "always"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER: + return "never"; + default: + return "unknown(" + mode + ")"; + } + } + private static String softInputModeToString(@SoftInputModeFlags int softInputMode) { final StringBuilder result = new StringBuilder(); final int state = softInputMode & SOFT_INPUT_MASK_STATE; diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 9c1c9e34cfb4275b271892701383c89b3a359e15..a6f36bbf4ef4c819e555863800ecf0a5e70d7a80 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -45,6 +45,11 @@ public interface WindowManagerPolicyConstants { int PRESENCE_INTERNAL = 1 << 0; int PRESENCE_EXTERNAL = 1 << 1; + // Navigation bar position values + int NAV_BAR_LEFT = 1 << 0; + int NAV_BAR_RIGHT = 1 << 1; + int NAV_BAR_BOTTOM = 1 << 2; + /** * Sticky broadcast of the current HDMI plugged state. */ diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index aa61926dcedca76860ac00aa541b7fd76770af0e..e0f74a7d6dd6eeab6dd4d9c1b3ba8917eae46b57 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -192,9 +192,11 @@ import java.util.List; * TRANSITION TYPES
        *

        *

        - * Window state changed - represents the event of opening a - * {@link android.widget.PopupWindow}, {@link android.view.Menu}, - * {@link android.app.Dialog}, etc.
        + * Window state changed - represents the event of a change to a section of + * the user interface that is visually distinct. Should be sent from either the + * root view of a window or from a view that is marked as a pane + * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes + * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.
        * Type: {@link #TYPE_WINDOW_STATE_CHANGED}
        * Properties:
        *

          @@ -203,7 +205,7 @@ import java.util.List; *
        • {@link #getClassName()} - The class name of the source.
        • *
        • {@link #getPackageName()} - The package name of the source.
        • *
        • {@link #getEventTime()} - The event time.
        • - *
        • {@link #getText()} - The text of the source's sub-tree.
        • + *
        • {@link #getText()} - The text of the source's sub-tree, including the pane titles.
        • *
        *

        *

        @@ -436,8 +438,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010; /** - * Represents the event of opening a {@link android.widget.PopupWindow}, - * {@link android.view.Menu}, {@link android.app.Dialog}, etc. + * Represents the event of a change to a visually distinct section of the user interface. + * These events should only be dispatched from {@link android.view.View}s that have + * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those + * sources. Details about the change are available from {@link #getContentChangeTypes()}. */ public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020; @@ -565,11 +569,29 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; /** - * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: * The node's pane title changed. */ public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008; + /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * The node has a pane title, and either just appeared or just was assigned a title when it + * had none before. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010; + + /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * Can mean one of two slightly different things. The primary meaning is that the node has + * a pane title, and was removed from the node hierarchy. It will also be sent if the pane + * title is set to {@code null} after it contained a title. + * No source will be returned if the node is no longer on the screen. To make the change more + * clear for the user, the first entry in {@link #getText()} will return the value that would + * have been returned by {@code getSource().getPaneTitle()}. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020; + /** * Change type for {@link #TYPE_WINDOWS_CHANGED} event: * The window was added. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 311dd4b8b294cef47333b85f59d43dbfb546a1a5..23e7d61912765426a27f41fbd13a5406e9246a9d 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. */ @@ -725,6 +727,7 @@ public class AccessibilityNodeInfo implements Parcelable { private CharSequence mError; private CharSequence mPaneTitle; private CharSequence mContentDescription; + private CharSequence mTooltipText; private String mViewIdResourceName; private ArrayList mExtraDataKeys; @@ -2408,6 +2411,30 @@ public class AccessibilityNodeInfo implements Parcelable { setBooleanProperty(BOOLEAN_PROPERTY_IS_SHOWING_HINT, showingHintText); } + /** + * 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. + * + *

        + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

        + * + * @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. * @@ -2628,6 +2655,34 @@ public class AccessibilityNodeInfo implements Parcelable { : contentDescription.subSequence(0, contentDescription.length()); } + /** + * Gets the tooltip text of this node. + * + * @return The tooltip text. + */ + @Nullable + public CharSequence getTooltipText() { + return mTooltipText; + } + + /** + * Sets the tooltip text of this node. + *

        + * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

        + * + * @param tooltipText The tooltip text. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setTooltipText(@Nullable CharSequence tooltipText) { + enforceNotSealed(); + mTooltipText = (tooltipText == null) ? null + : tooltipText.subSequence(0, tooltipText.length()); + } + /** * Sets the view for which the view represented by this info serves as a * label for accessibility purposes. @@ -3183,6 +3238,10 @@ public class AccessibilityNodeInfo implements Parcelable { nonDefaultFields |= bitAt(fieldIndex); } fieldIndex++; + if (!Objects.equals(mTooltipText, DEFAULT.mTooltipText)) { + nonDefaultFields |= bitAt(fieldIndex); + } + fieldIndex++; if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) { nonDefaultFields |= bitAt(fieldIndex); } @@ -3303,6 +3362,8 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeCharSequence(mContentDescription); } if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle); + if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText); + if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName); if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart); @@ -3375,6 +3436,7 @@ public class AccessibilityNodeInfo implements Parcelable { mError = other.mError; mContentDescription = other.mContentDescription; mPaneTitle = other.mPaneTitle; + mTooltipText = other.mTooltipText; mViewIdResourceName = other.mViewIdResourceName; if (mActions != null) mActions.clear(); @@ -3496,6 +3558,7 @@ public class AccessibilityNodeInfo implements Parcelable { mContentDescription = parcel.readCharSequence(); } if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString(); + if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence(); if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString(); if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt(); @@ -3658,6 +3721,10 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_SET_PROGRESS"; case R.id.accessibilityActionContextClick: return "ACTION_CONTEXT_CLICK"; + case R.id.accessibilityActionShowTooltip: + return "ACTION_SHOW_TOOLTIP"; + case R.id.accessibilityActionHideTooltip: + return "ACTION_HIDE_TOOLTIP"; default: return "ACTION_UNKNOWN"; } @@ -3771,6 +3838,7 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; error: ").append(mError); builder.append("; maxTextLength: ").append(mMaxTextLength); builder.append("; contentDescription: ").append(mContentDescription); + builder.append("; tooltipText: ").append(mTooltipText); builder.append("; viewIdResName: ").append(mViewIdResourceName); builder.append("; checkable: ").append(isCheckable()); @@ -4185,6 +4253,20 @@ public class AccessibilityNodeInfo implements Parcelable { public static final AccessibilityAction ACTION_MOVE_WINDOW = new AccessibilityAction(R.id.accessibilityActionMoveWindow); + /** + * Action to show a tooltip. A node should expose this action only for views with tooltip + * text that but are not currently showing a tooltip. + */ + public static final AccessibilityAction ACTION_SHOW_TOOLTIP = + new AccessibilityAction(R.id.accessibilityActionShowTooltip); + + /** + * Action to hide a tooltip. A node should expose this action only for views that are + * currently showing a tooltip. + */ + public static final AccessibilityAction ACTION_HIDE_TOOLTIP = + new AccessibilityAction(R.id.accessibilityActionHideTooltip); + private final int mActionId; private final CharSequence mLabel; @@ -4597,7 +4679,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 +4694,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 +4782,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/accessibility/AccessibilityViewHierarchyState.java b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java deleted file mode 100644 index 447fafaae171c36fe35e52ed8aee9c5d350289a5..0000000000000000000000000000000000000000 --- a/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.accessibility; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -/** - * Accessibility-related state of a {@link android.view.ViewRootImpl} - * - * @hide - */ -public class AccessibilityViewHierarchyState { - private @Nullable SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; - private @Nullable SendWindowContentChangedAccessibilityEvent - mSendWindowContentChangedAccessibilityEvent; - - /** - * @return a {@link SendViewScrolledAccessibilityEvent}, creating one if needed - */ - public @NonNull SendViewScrolledAccessibilityEvent getSendViewScrolledAccessibilityEvent() { - if (mSendViewScrolledAccessibilityEvent == null) { - mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); - } - return mSendViewScrolledAccessibilityEvent; - } - - public boolean isScrollEventSenderInitialized() { - return mSendViewScrolledAccessibilityEvent != null; - } - - /** - * @return a {@link SendWindowContentChangedAccessibilityEvent}, creating one if needed - */ - public @NonNull SendWindowContentChangedAccessibilityEvent - getSendWindowContentChangedAccessibilityEvent() { - if (mSendWindowContentChangedAccessibilityEvent == null) { - mSendWindowContentChangedAccessibilityEvent = - new SendWindowContentChangedAccessibilityEvent(); - } - return mSendWindowContentChangedAccessibilityEvent; - } - - public boolean isWindowContentChangedEventSenderInitialized() { - return mSendWindowContentChangedAccessibilityEvent != null; - } -} diff --git a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java deleted file mode 100644 index 40a1b6a28b98609f31459c86d6ca90d11fa8bb3e..0000000000000000000000000000000000000000 --- a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.accessibility; - - -import android.annotation.NonNull; -import android.view.View; - -/** - * Sender for {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. - * - * @hide - */ -public class SendViewScrolledAccessibilityEvent extends ThrottlingAccessibilityEventSender { - - public int mDeltaX; - public int mDeltaY; - - /** - * Post a scroll event to be sent for the given view - */ - public void post(View source, int dx, int dy) { - if (!isPendingFor(source)) sendNowIfPending(); - - mDeltaX += dx; - mDeltaY += dy; - - if (!isPendingFor(source)) scheduleFor(source); - } - - @Override - protected void performSendEvent(@NonNull View source) { - AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); - event.setScrollDeltaX(mDeltaX); - event.setScrollDeltaY(mDeltaY); - source.sendAccessibilityEventUnchecked(event); - } - - @Override - protected void resetState(@NonNull View source) { - mDeltaX = 0; - mDeltaY = 0; - } -} diff --git a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java deleted file mode 100644 index df38fba5ecfb9c07003786cc39e8d805993b59c6..0000000000000000000000000000000000000000 --- a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.accessibility; - - -import static com.android.internal.util.ObjectUtils.firstNotNull; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.view.View; -import android.view.ViewParent; - -import java.util.HashSet; - -/** - * @hide - */ -public class SendWindowContentChangedAccessibilityEvent - extends ThrottlingAccessibilityEventSender { - - private int mChangeTypes = 0; - - private HashSet mTempHashSet; - - @Override - protected void performSendEvent(@NonNull View source) { - AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - event.setContentChangeTypes(mChangeTypes); - source.sendAccessibilityEventUnchecked(event); - } - - @Override - protected void resetState(@Nullable View source) { - if (source != null) { - source.resetSubtreeAccessibilityStateChanged(); - } - mChangeTypes = 0; - } - - /** - * Post the {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with the given - * {@link AccessibilityEvent#getContentChangeTypes change type} for the given view - */ - public void runOrPost(View source, int changeType) { - if (source.getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) { - sendNowIfPending(); - mChangeTypes = changeType; - sendNow(source); - } else { - mChangeTypes |= changeType; - scheduleFor(source); - } - } - - @Override - protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) { - // If there is no common predecessor, then oldSource points to - // a removed view, hence in this case always prefer the newSource. - return firstNotNull( - getCommonPredecessor(oldSource, newSource), - newSource); - } - - private View getCommonPredecessor(View first, View second) { - if (mTempHashSet == null) { - mTempHashSet = new HashSet<>(); - } - HashSet seen = mTempHashSet; - seen.clear(); - View firstCurrent = first; - while (firstCurrent != null) { - seen.add(firstCurrent); - ViewParent firstCurrentParent = firstCurrent.getParent(); - if (firstCurrentParent instanceof View) { - firstCurrent = (View) firstCurrentParent; - } else { - firstCurrent = null; - } - } - View secondCurrent = second; - while (secondCurrent != null) { - if (seen.contains(secondCurrent)) { - seen.clear(); - return secondCurrent; - } - ViewParent secondCurrentParent = secondCurrent.getParent(); - if (secondCurrentParent instanceof View) { - secondCurrent = (View) secondCurrentParent; - } else { - secondCurrent = null; - } - } - seen.clear(); - return null; - } -} diff --git a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java deleted file mode 100644 index 66fa30107d2d330180f4e387118864312f71c209..0000000000000000000000000000000000000000 --- a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.accessibility; - - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.util.Log; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewRootImpl; -import android.view.ViewRootImpl.CalledFromWrongThreadException; - -/** - * A throttling {@link AccessibilityEvent} sender that relies on its currently associated - * 'source' view's {@link View#postDelayed delayed execution} to delay and possibly - * {@link #tryMerge merge} together any events that come in less than - * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval - * the configured amount of milliseconds} apart. - * - * The suggested usage is to create a singleton extending this class, holding any state specific to - * the particular event type that the subclass represents, and have an 'entrypoint' method that - * delegates to {@link #scheduleFor(View)}. - * For example: - * - * {@code - * public void post(View view, String text, int resId) { - * mText = text; - * mId = resId; - * scheduleFor(view); - * } - * } - * - * @see #scheduleFor(View) - * @see #tryMerge(View, View) - * @see #performSendEvent(View) - * @hide - */ -public abstract class ThrottlingAccessibilityEventSender { - - private static final boolean DEBUG = false; - private static final String LOG_TAG = "ThrottlingA11ySender"; - - View mSource; - private long mLastSendTimeMillis = Long.MIN_VALUE; - private boolean mIsPending = false; - - private final Runnable mWorker = () -> { - View source = mSource; - if (DEBUG) Log.d(LOG_TAG, thisClass() + ".run(mSource = " + source + ")"); - - if (!checkAndResetIsPending() || source == null) { - resetStateInternal(); - return; - } - - // Accessibility may be turned off while we were waiting - if (isAccessibilityEnabled(source)) { - mLastSendTimeMillis = SystemClock.uptimeMillis(); - performSendEvent(source); - } - resetStateInternal(); - }; - - /** - * Populate and send an {@link AccessibilityEvent} using the given {@code source} view, as well - * as any extra data from this instance's state. - * - * Send the event via {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)} or - * {@link View#sendAccessibilityEvent(int)} on the provided {@code source} view to allow for - * overrides of those methods on {@link View} subclasses to take effect, and/or make sure that - * an {@link View#getAccessibilityDelegate() accessibility delegate} is not ignored if any. - */ - protected abstract void performSendEvent(@NonNull View source); - - /** - * Perform optional cleanup after {@link #performSendEvent} - * - * @param source the view this event was associated with - */ - protected abstract void resetState(@Nullable View source); - - /** - * Attempt to merge the pending events for source views {@code oldSource} and {@code newSource} - * into one, with source set to the resulting {@link View} - * - * A result of {@code null} means merger is not possible, resulting in the currently pending - * event being flushed before proceeding. - */ - protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) { - return null; - } - - /** - * Schedules a {@link #performSendEvent} with the source {@link View} set to given - * {@code source} - * - * If an event is already scheduled a {@link #tryMerge merge} will be attempted. - * If merging is not possible (as indicated by the null result from {@link #tryMerge}), - * the currently scheduled event will be {@link #sendNow sent immediately} and the new one - * will be scheduled afterwards. - */ - protected final void scheduleFor(@NonNull View source) { - if (DEBUG) Log.d(LOG_TAG, thisClass() + ".scheduleFor(source = " + source + ")"); - - Handler uiHandler = source.getHandler(); - if (uiHandler == null || uiHandler.getLooper() != Looper.myLooper()) { - CalledFromWrongThreadException e = new CalledFromWrongThreadException( - "Expected to be called from main thread but was called from " - + Thread.currentThread()); - // TODO: Throw the exception - Log.e(LOG_TAG, "Accessibility content change on non-UI thread. Future Android " - + "versions will throw an exception.", e); - } - - if (!isAccessibilityEnabled(source)) return; - - if (mIsPending) { - View merged = tryMerge(mSource, source); - if (merged != null) { - setSource(merged); - return; - } else { - sendNow(); - } - } - - setSource(source); - - final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastSendTimeMillis; - final long minEventIntervalMillis = - ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); - if (timeSinceLastMillis >= minEventIntervalMillis) { - sendNow(); - } else { - mSource.postDelayed(mWorker, minEventIntervalMillis - timeSinceLastMillis); - } - } - - static boolean isAccessibilityEnabled(@NonNull View contextProvider) { - return AccessibilityManager.getInstance(contextProvider.getContext()).isEnabled(); - } - - protected final void sendNow(View source) { - setSource(source); - sendNow(); - } - - private void sendNow() { - mSource.removeCallbacks(mWorker); - mWorker.run(); - } - - /** - * Flush the event if one is pending - */ - public void sendNowIfPending() { - if (mIsPending) sendNow(); - } - - /** - * Cancel the event if one is pending and is for the given view - */ - public final void cancelIfPendingFor(@NonNull View source) { - if (isPendingFor(source)) cancelIfPending(this); - } - - /** - * @return whether an event is currently pending for the given source view - */ - protected final boolean isPendingFor(@Nullable View source) { - return mIsPending && mSource == source; - } - - /** - * Cancel the event if one is not null and pending - */ - public static void cancelIfPending(@Nullable ThrottlingAccessibilityEventSender sender) { - if (sender == null || !sender.checkAndResetIsPending()) return; - sender.mSource.removeCallbacks(sender.mWorker); - sender.resetStateInternal(); - } - - void resetStateInternal() { - if (DEBUG) Log.d(LOG_TAG, thisClass() + ".resetStateInternal()"); - - resetState(mSource); - setSource(null); - } - - boolean checkAndResetIsPending() { - if (mIsPending) { - mIsPending = false; - return true; - } else { - return false; - } - } - - private void setSource(@Nullable View source) { - if (DEBUG) Log.d(LOG_TAG, thisClass() + ".setSource(" + source + ")"); - - if (source == null && mIsPending) { - Log.e(LOG_TAG, "mSource nullified while callback still pending: " + this); - return; - } - - if (source != null && !mIsPending) { - // At most one can be pending at any given time - View oldSource = mSource; - if (oldSource != null) { - ViewRootImpl viewRootImpl = oldSource.getViewRootImpl(); - if (viewRootImpl != null) { - viewRootImpl.flushPendingAccessibilityEvents(); - } - } - mIsPending = true; - } - mSource = source; - } - - String thisClass() { - return getClass().getSimpleName(); - } - - @Override - public String toString() { - return thisClass() + "(" + mSource + ")"; - } - -} diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index f5c36139df0f55980c6ae30aa592a4bc8549b73b..990fbdb019d6f8bb5c8b6e7bbf248712d2bfb451 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -156,6 +156,8 @@ public class AnimationUtils { anim = new RotateAnimation(c, attrs); } else if (name.equals("translate")) { anim = new TranslateAnimation(c, attrs); + } else if (name.equals("cliprect")) { + anim = new ClipRectAnimation(c, attrs); } else { throw new RuntimeException("Unknown animation name: " + parser.getName()); } diff --git a/core/java/android/view/animation/ClipRectAnimation.java b/core/java/android/view/animation/ClipRectAnimation.java index e194927e8301cf804906f2319911b5a001bd4c21..21509d3a1159fe8f43407e0a8f4adfe40f28a653 100644 --- a/core/java/android/view/animation/ClipRectAnimation.java +++ b/core/java/android/view/animation/ClipRectAnimation.java @@ -16,7 +16,11 @@ package android.view.animation; +import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.DisplayMetrics; /** * An animation that controls the clip of an object. See the @@ -26,8 +30,84 @@ import android.graphics.Rect; * @hide */ public class ClipRectAnimation extends Animation { - protected Rect mFromRect = new Rect(); - protected Rect mToRect = new Rect(); + protected final Rect mFromRect = new Rect(); + protected final Rect mToRect = new Rect(); + + private int mFromLeftType = ABSOLUTE; + private int mFromTopType = ABSOLUTE; + private int mFromRightType = ABSOLUTE; + private int mFromBottomType = ABSOLUTE; + + private int mToLeftType = ABSOLUTE; + private int mToTopType = ABSOLUTE; + private int mToRightType = ABSOLUTE; + private int mToBottomType = ABSOLUTE; + + private float mFromLeftValue; + private float mFromTopValue; + private float mFromRightValue; + private float mFromBottomValue; + + private float mToLeftValue; + private float mToTopValue; + private float mToRightValue; + private float mToBottomValue; + + /** + * Constructor used when a ClipRectAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public ClipRectAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ClipRectAnimation); + + Description d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromLeft)); + mFromLeftType = d.type; + mFromLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromTop)); + mFromTopType = d.type; + mFromTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromRight)); + mFromRightType = d.type; + mFromRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromBottom)); + mFromBottomType = d.type; + mFromBottomValue = d.value; + + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toLeft)); + mToLeftType = d.type; + mToLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toTop)); + mToTopType = d.type; + mToTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toRight)); + mToRightType = d.type; + mToRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toBottom)); + mToBottomType = d.type; + mToBottomValue = d.value; + + a.recycle(); + } /** * Constructor to use when building a ClipRectAnimation from code @@ -39,8 +119,15 @@ public class ClipRectAnimation extends Animation { if (fromClip == null || toClip == null) { throw new RuntimeException("Expected non-null animation clip rects"); } - mFromRect.set(fromClip); - mToRect.set(toClip); + mFromLeftValue = fromClip.left; + mFromTopValue = fromClip.top; + mFromRightValue= fromClip.right; + mFromBottomValue = fromClip.bottom; + + mToLeftValue = toClip.left; + mToTopValue = toClip.top; + mToRightValue= toClip.right; + mToBottomValue = toClip.bottom; } /** @@ -48,8 +135,7 @@ public class ClipRectAnimation extends Animation { */ public ClipRectAnimation(int fromL, int fromT, int fromR, int fromB, int toL, int toT, int toR, int toB) { - mFromRect.set(fromL, fromT, fromR, fromB); - mToRect.set(toL, toT, toR, toB); + this(new Rect(fromL, fromT, fromR, fromB), new Rect(toL, toT, toR, toB)); } @Override @@ -65,4 +151,17 @@ public class ClipRectAnimation extends Animation { public boolean willChangeTransformationMatrix() { return false; } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + mFromRect.set((int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth), + (int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight), + (int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth), + (int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight)); + mToRect.set((int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth), + (int) resolveSize(mToTopType, mToTopValue, height, parentHeight), + (int) resolveSize(mToRightType, mToRightValue, width, parentWidth), + (int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight)); + } } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 78b41c6f4c7bed463530e7022fd2af88cd60a2ca..4b24a71c8bfb5760a1c5e5caee75d8b13d295309 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -53,6 +53,7 @@ import java.lang.annotation.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; @@ -169,7 +170,6 @@ 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"; @@ -258,6 +258,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. * @@ -341,6 +347,10 @@ public final class AutofillManager { @GuardedBy("mLock") @Nullable private AutofillId mSaveTriggerId; + /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */ + @GuardedBy("mLock") + private boolean mOnInvisibleCalled; + /** If set, session is commited when the activity is finished; otherwise session is canceled. */ @GuardedBy("mLock") private boolean mSaveOnFinish; @@ -396,6 +406,11 @@ public final class AutofillManager { */ boolean isVisibleForAutofill(); + /** + * Client might disable enter/exit event e.g. when activity is paused. + */ + boolean isDisablingEnterExitEventForAutofill(); + /** * Finds views by traversing the hierarchies of the client. * @@ -498,6 +513,19 @@ public final class AutofillManager { } } + /** + * Called once the client becomes invisible. + * + * @see AutofillClient#isVisibleForAutofill() + * + * {@hide} + */ + public void onInvisibleForAutofill() { + synchronized (mLock) { + mOnInvisibleCalled = true; + } + } + /** * Save state before activity lifecycle * @@ -623,21 +651,45 @@ public final class AutofillManager { return false; } + private boolean isClientVisibleForAutofillLocked() { + final AutofillClient client = getClient(); + return client != null && client.isVisibleForAutofill(); + } + + private boolean isClientDisablingEnterExitEvent() { + final AutofillClient client = getClient(); + return client != null && client.isDisablingEnterExitEventForAutofill(); + } + private void notifyViewEntered(@NonNull View view, int flags) { if (!hasAutofillFeature()) { return; } - AutofillCallback callback = null; + AutofillCallback callback; synchronized (mLock) { - if (shouldIgnoreViewEnteredLocked(view, flags)) return; + callback = notifyViewEnteredLocked(view, flags); + } - ensureServiceClientAddedIfNeededLocked(); + if (callback != null) { + mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + } - if (!mEnabled) { - if (mCallback != null) { - callback = mCallback; - } - } else { + /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ + private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) { + if (shouldIgnoreViewEnteredLocked(view, flags)) return null; + + AutofillCallback callback = null; + + ensureServiceClientAddedIfNeededLocked(); + + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + // don't notify entered when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view); final AutofillValue value = view.getAutofillValue(); @@ -650,10 +702,7 @@ public final class AutofillManager { } } } - - if (callback != null) { - mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); - } + return callback; } /** @@ -666,9 +715,16 @@ public final class AutofillManager { return; } synchronized (mLock) { - ensureServiceClientAddedIfNeededLocked(); + notifyViewExitedLocked(view); + } + } - if (mEnabled && isActiveLocked()) { + void notifyViewExitedLocked(@NonNull View view) { + ensureServiceClientAddedIfNeededLocked(); + + if (mEnabled && isActiveLocked()) { + // dont notify exited when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view); // Update focus on existing session. @@ -719,7 +775,7 @@ public final class AutofillManager { } } if (mTrackedViews != null) { - mTrackedViews.notifyViewVisibilityChanged(id, isVisible); + mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible); } } } @@ -752,17 +808,32 @@ public final class AutofillManager { if (!hasAutofillFeature()) { return; } - AutofillCallback callback = null; + AutofillCallback callback; synchronized (mLock) { - if (shouldIgnoreViewEnteredLocked(view, flags)) return; + callback = notifyViewEnteredLocked(view, virtualId, bounds, flags); + } - ensureServiceClientAddedIfNeededLocked(); + if (callback != null) { + callback.onAutofillEvent(view, virtualId, + AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + } - if (!mEnabled) { - if (mCallback != null) { - callback = mCallback; - } - } else { + /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ + private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds, + int flags) { + AutofillCallback callback = null; + if (shouldIgnoreViewEnteredLocked(view, flags)) return callback; + + ensureServiceClientAddedIfNeededLocked(); + + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + // don't notify entered when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); if (!isActiveLocked()) { @@ -774,11 +845,7 @@ public final class AutofillManager { } } } - - if (callback != null) { - callback.onAutofillEvent(view, virtualId, - AutofillCallback.EVENT_INPUT_UNAVAILABLE); - } + return callback; } /** @@ -792,9 +859,16 @@ public final class AutofillManager { return; } synchronized (mLock) { - ensureServiceClientAddedIfNeededLocked(); + notifyViewExitedLocked(view, virtualId); + } + } - if (mEnabled && isActiveLocked()) { + private void notifyViewExitedLocked(@NonNull View view, int virtualId) { + ensureServiceClientAddedIfNeededLocked(); + + if (mEnabled && isActiveLocked()) { + // don't notify exited when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); // Update focus on existing session. @@ -1107,17 +1181,15 @@ public final class AutofillManager { *
        field classification. * *

        Note: 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. */ @NonNull public List getAvailableFieldClassificationAlgorithms() { + final String[] algorithms; try { - final List names = mService.getAvailableFieldClassificationAlgorithms(); - return names != null ? names : Collections.emptyList(); + algorithms = mService.getAvailableFieldClassificationAlgorithms(); + return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; @@ -1155,7 +1227,7 @@ public final class AutofillManager { } /** @hide */ - public void onAuthenticationResult(int authenticationId, Intent data) { + public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { if (!hasAutofillFeature()) { return; } @@ -1167,9 +1239,24 @@ public final class AutofillManager { if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data); synchronized (mLock) { - if (!isActiveLocked() || data == null) { + if (!isActiveLocked()) { return; } + // If authenticate activity closes itself during onCreate(), there is no onStop/onStart + // of app activity. We enforce enter event to re-show fill ui in such case. + // CTS example: + // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt + // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt + if (!mOnInvisibleCalled && focusView != null + && focusView.canNotifyAutofillEnterExitEvent()) { + notifyViewExitedLocked(focusView); + notifyViewEnteredLocked(focusView, 0); + } + if (data == null) { + // data is set to null when result is not RESULT_OK + return; + } + final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); final Bundle responseData = new Bundle(); responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); @@ -1402,6 +1489,9 @@ public final class AutofillManager { if (sessionId == mSessionId) { final AutofillClient client = getClient(); if (client != null) { + // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() + // before onAuthenticationResult() + mOnInvisibleCalled = false; client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent); } } @@ -1767,6 +1857,7 @@ public final class AutofillManager { pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); + pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); pw.print(pfx); pw.print("tracked views: "); if (mTrackedViews == null) { @@ -1937,15 +2028,13 @@ public final class AutofillManager { * @param id the id of the view/virtual view whose visibility changed. * @param isVisible visible if the view is visible in the view hierarchy. */ - void notifyViewVisibilityChanged(@NonNull AutofillId id, boolean isVisible) { - AutofillClient client = getClient(); - + void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { if (sDebug) { Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible=" + isVisible); } - if (client != null && client.isVisibleForAutofill()) { + if (isClientVisibleForAutofillLocked()) { if (isVisible) { if (isInSet(mInvisibleTrackedIds, id)) { mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java index 5cba21e3cc078acc9f86e468596d8d8365a589bd..e80fdd93542cc06ef889040f95f23335b4792ec1 100644 --- a/core/java/android/view/autofill/AutofillPopupWindow.java +++ b/core/java/android/view/autofill/AutofillPopupWindow.java @@ -78,8 +78,10 @@ public class AutofillPopupWindow extends PopupWindow { public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) { mWindowPresenter = new WindowPresenter(presenter); + setTouchModal(false); setOutsideTouchable(true); - setInputMethodMode(INPUT_METHOD_NEEDED); + setInputMethodMode(INPUT_METHOD_NOT_NEEDED); + setFocusable(true); } @Override diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 1afa35e80a26b379485df172c659740b554803f6..1a11fbba00116b08474a5be1061397a7605f53ae 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 getAvailableFieldClassificationAlgorithms(); + String[] getAvailableFieldClassificationAlgorithms(); String getDefaultFieldClassificationAlgorithm(); } diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java index 003f221d08b21f0d188ab679356f8433e51f8571..1eb300eafb661197ed45ee8335a93e72a7d30ebe 100644 --- a/core/java/android/view/inputmethod/ExtractedText.java +++ b/core/java/android/view/inputmethod/ExtractedText.java @@ -29,6 +29,8 @@ import android.text.TextUtils; public class ExtractedText implements Parcelable { /** * The text that has been extracted. + * + * @see android.widget.TextView#getText() */ public CharSequence text; @@ -88,6 +90,8 @@ public class ExtractedText implements Parcelable { /** * The hint that has been extracted. + * + * @see android.widget.TextView#getHint() */ public CharSequence hint; diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 57f9895f45fa90e09aad6ce667debc3cdc94c1f9..eba91763acf1a9ad64e628881c14d17878590600 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project + * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.inputmethod; @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -131,13 +132,13 @@ public interface InputConnection { * spans. Editor authors: you should strive to * send text with styles if possible, but it is not required. */ - static final int GET_TEXT_WITH_STYLES = 0x0001; + int GET_TEXT_WITH_STYLES = 0x0001; /** * Flag for use with {@link #getExtractedText} to indicate you * would like to receive updates when the extracted text changes. */ - public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001; + int GET_EXTRACTED_TEXT_MONITOR = 0x0001; /** * Get n characters of text before the current cursor @@ -176,7 +177,7 @@ public interface InputConnection { * @return the text before the cursor position; the length of the * returned text might be less than n. */ - public CharSequence getTextBeforeCursor(int n, int flags); + CharSequence getTextBeforeCursor(int n, int flags); /** * Get n characters of text after the current cursor @@ -215,7 +216,7 @@ public interface InputConnection { * @return the text after the cursor position; the length of the * returned text might be less than n. */ - public CharSequence getTextAfterCursor(int n, int flags); + CharSequence getTextAfterCursor(int n, int flags); /** * Gets the selected text, if any. @@ -249,7 +250,7 @@ public interface InputConnection { * later, returns false when the target application does not implement * this method. */ - public CharSequence getSelectedText(int flags); + CharSequence getSelectedText(int flags); /** * Retrieve the current capitalization mode in effect at the @@ -279,7 +280,7 @@ public interface InputConnection { * @return the caps mode flags that are in effect at the current * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}. */ - public int getCursorCapsMode(int reqModes); + int getCursorCapsMode(int reqModes); /** * Retrieve the current text in the input connection's editor, and @@ -314,8 +315,7 @@ public interface InputConnection { * longer valid of the editor can't comply with the request for * some reason. */ - public ExtractedText getExtractedText(ExtractedTextRequest request, - int flags); + ExtractedText getExtractedText(ExtractedTextRequest request, int flags); /** * Delete beforeLength characters of text before the @@ -342,8 +342,8 @@ public interface InputConnection { * delete more characters than are in the editor, as that may have * ill effects on the application. Calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on your service after the batch input is over.

        + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on your service after the batch input is over.

        * *

        Editor authors: please be careful of race * conditions in implementing this call. An IME can make a change @@ -369,7 +369,7 @@ public interface InputConnection { * that range. * @return true on success, false if the input connection is no longer valid. */ - public boolean deleteSurroundingText(int beforeLength, int afterLength); + boolean deleteSurroundingText(int beforeLength, int afterLength); /** * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are: @@ -397,7 +397,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer valid. Returns * {@code false} when the target application does not implement this method. */ - public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); + boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); /** * Replace the currently composing text with the given text, and @@ -416,8 +416,8 @@ public interface InputConnection { *

        This is usually called by IMEs to add or remove or change * characters in the composing span. Calling this method will * cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over.

        + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over.

        * *

        Editor authors: please keep in mind the * text may be very similar or completely different than what was @@ -455,7 +455,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setComposingText(CharSequence text, int newCursorPosition); + boolean setComposingText(CharSequence text, int newCursorPosition); /** * Mark a certain region of text as composing text. If there was a @@ -474,8 +474,8 @@ public interface InputConnection { *

        Since this does not change the contents of the text, editors should not call * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and * IMEs should not receive - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}. - *

        + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)}.

        * *

        This has no impact on the cursor/selection position. It may * result in the cursor being anywhere inside or outside the @@ -488,7 +488,7 @@ public interface InputConnection { * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the * target application does not implement this method. */ - public boolean setComposingRegion(int start, int end); + boolean setComposingRegion(int start, int end); /** * Have the text editor finish whatever composing text is @@ -507,7 +507,7 @@ public interface InputConnection { * @return true on success, false if the input connection * is no longer valid. */ - public boolean finishComposingText(); + boolean finishComposingText(); /** * Commit text to the text box and set the new cursor position. @@ -522,8 +522,8 @@ public interface InputConnection { * then {@link #finishComposingText()}.

        * *

        Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * Editor authors, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -543,7 +543,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitText(CharSequence text, int newCursorPosition); + boolean commitText(CharSequence text, int newCursorPosition); /** * Commit a completion the user has selected from the possible ones @@ -569,8 +569,8 @@ public interface InputConnection { * *

        Calling this method (with a valid {@link CompletionInfo} object) * will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * Editor authors, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -581,15 +581,15 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitCompletion(CompletionInfo text); + boolean commitCompletion(CompletionInfo text); /** * Commit a correction automatically performed on the raw user's input. A * typical example would be to correct typos using a dictionary. * *

        Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * Editor authors, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -601,7 +601,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns false * when the target application does not implement this method. */ - public boolean commitCorrection(CorrectionInfo correctionInfo); + boolean commitCorrection(CorrectionInfo correctionInfo); /** * Set the selection of the text editor. To set the cursor @@ -609,8 +609,8 @@ public interface InputConnection { * *

        Since this moves the cursor, calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * Editor authors, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -628,7 +628,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setSelection(int start, int end); + boolean setSelection(int start, int end); /** * Have the editor perform an action it has said it can do. @@ -642,7 +642,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean performEditorAction(int editorAction); + boolean performEditorAction(int editorAction); /** * Perform a context menu action on the field. The given id may be one of: @@ -652,7 +652,7 @@ public interface InputConnection { * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, * or {@link android.R.id#switchInputMethod} */ - public boolean performContextMenuAction(int id); + boolean performContextMenuAction(int id); /** * Tell the editor that you are starting a batch of editor @@ -662,8 +662,8 @@ public interface InputConnection { * *

        IME authors: use this to avoid getting * calls to - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * corresponding to intermediate state. Also, use this to avoid + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} corresponding to intermediate state. Also, use this to avoid * flickers that may arise from displaying intermediate state. Be * sure to call {@link #endBatchEdit} for each call to this, or * you may block updates in the editor.

        @@ -678,7 +678,7 @@ public interface InputConnection { * this method starts a batch edit, that means it will always return true * unless the input connection is no longer valid. */ - public boolean beginBatchEdit(); + boolean beginBatchEdit(); /** * Tell the editor that you are done with a batch edit previously @@ -696,7 +696,7 @@ public interface InputConnection { * the latest one (in other words, if the nesting count is > 0), false * otherwise or if the input connection is no longer valid. */ - public boolean endBatchEdit(); + boolean endBatchEdit(); /** * Send a key event to the process that is currently attached @@ -734,7 +734,7 @@ public interface InputConnection { * @see KeyCharacterMap#PREDICTIVE * @see KeyCharacterMap#ALPHA */ - public boolean sendKeyEvent(KeyEvent event); + boolean sendKeyEvent(KeyEvent event); /** * Clear the given meta key pressed states in the given input @@ -749,7 +749,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean clearMetaKeyStates(int states); + boolean clearMetaKeyStates(int states); /** * Called back when the connected IME switches between fullscreen and normal modes. @@ -766,7 +766,7 @@ public interface InputConnection { * devices. * @see InputMethodManager#isFullscreenMode() */ - public boolean reportFullscreenMode(boolean enabled); + boolean reportFullscreenMode(boolean enabled); /** * API to send private commands from an input method to its @@ -786,7 +786,7 @@ public interface InputConnection { * associated editor understood it), false if the input connection is no longer * valid. */ - public boolean performPrivateCommand(String action, Bundle data); + boolean performPrivateCommand(String action, Bundle data); /** * The editor is requested to call @@ -794,7 +794,7 @@ public interface InputConnection { * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be * used together with {@link #CURSOR_UPDATE_MONITOR}. */ - public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0; + int CURSOR_UPDATE_IMMEDIATE = 1 << 0; /** * The editor is requested to call @@ -805,7 +805,7 @@ public interface InputConnection { * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}. *

        */ - public static final int CURSOR_UPDATE_MONITOR = 1 << 1; + int CURSOR_UPDATE_MONITOR = 1 << 1; /** * Called by the input method to ask the editor for calling back @@ -821,7 +821,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the * target application does not implement this method. */ - public boolean requestCursorUpdates(int cursorUpdateMode); + boolean requestCursorUpdates(int cursorUpdateMode); /** * Called by the {@link InputMethodManager} to enable application developers to specify a @@ -832,7 +832,7 @@ public interface InputConnection { * * @return {@code null} to use the default {@link Handler}. */ - public Handler getHandler(); + Handler getHandler(); /** * Called by the system up to only once to notify that the system is about to invalidate @@ -846,7 +846,7 @@ public interface InputConnection { * *

        Note: This does nothing when called from input methods.

        */ - public void closeConnection(); + void closeConnection(); /** * When this flag is used, the editor will be able to request read access to the content URI @@ -863,7 +863,7 @@ public interface InputConnection { * client is able to request a temporary read-only access even after the current IME is switched * to any other IME as long as the client keeps {@link InputContentInfo} object.

        **/ - public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = + int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001 /** @@ -897,6 +897,39 @@ public interface InputConnection { * @return {@code true} if this request is accepted by the application, whether the request * is already handled or still being handled in background, {@code false} otherwise. */ - public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, + boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts); + + /** + * Called by the input method to tell a hint about the locales of text to be committed. + * + *

        This is just a hint for editor authors (and the system) to choose better options when + * they have to disambiguate languages, like editor authors can do for input methods with + * {@link EditorInfo#hintLocales}.

        + * + *

        The language hint provided by this callback should have higher priority than + * {@link InputMethodSubtype#getLanguageTag()}, which cannot be updated dynamically.

        + * + *

        Note that in general it is discouraged for input method to specify + * {@link android.text.style.LocaleSpan} when inputting text, mainly because of application + * compatibility concerns.

        + *
          + *
        • When an existing text that already has {@link android.text.style.LocaleSpan} is being + * modified by both the input method and application, there is no reliable and easy way to + * keep track of who modified {@link android.text.style.LocaleSpan}. For instance, if the + * text was updated by JavaScript, it it highly likely that span information is completely + * removed, while some input method attempts to preserve spans if possible.
        • + *
        • There is no clear semantics regarding whether {@link android.text.style.LocaleSpan} + * means a weak (ignorable) hint or a strong hint. This becomes more problematic when + * multiple {@link android.text.style.LocaleSpan} instances are specified to the same + * text region, especially when those spans are conflicting.
        • + *
        + * @param languageHint list of languages sorted by the priority and/or probability + */ + default void reportLanguageHint(@NonNull LocaleList languageHint) { + // Intentionally empty. + // + // We need to have *some* default implementation for the source compatibility. + // See Bug 72127682 for details. + } } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 317730ca092c6752bef971b15e2f97d52b0cd4b8..cbe6856bba410be8f96cbdbf3473f8642563dcf7 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -1,23 +1,25 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.inputmethod; +import android.annotation.NonNull; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.view.KeyEvent; /** @@ -74,6 +76,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextBeforeCursor(int n, int flags) { return mTarget.getTextBeforeCursor(n, flags); } @@ -82,6 +85,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextAfterCursor(int n, int flags) { return mTarget.getTextAfterCursor(n, flags); } @@ -90,6 +94,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getSelectedText(int flags) { return mTarget.getSelectedText(flags); } @@ -98,6 +103,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public int getCursorCapsMode(int reqModes) { return mTarget.getCursorCapsMode(reqModes); } @@ -106,6 +112,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { return mTarget.getExtractedText(request, flags); } @@ -114,6 +121,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength); } @@ -122,6 +130,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { return mTarget.deleteSurroundingText(beforeLength, afterLength); } @@ -130,6 +139,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { return mTarget.setComposingText(text, newCursorPosition); } @@ -138,6 +148,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingRegion(int start, int end) { return mTarget.setComposingRegion(start, end); } @@ -146,6 +157,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean finishComposingText() { return mTarget.finishComposingText(); } @@ -154,6 +166,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitText(CharSequence text, int newCursorPosition) { return mTarget.commitText(text, newCursorPosition); } @@ -162,6 +175,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCompletion(CompletionInfo text) { return mTarget.commitCompletion(text); } @@ -170,6 +184,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCorrection(CorrectionInfo correctionInfo) { return mTarget.commitCorrection(correctionInfo); } @@ -178,6 +193,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setSelection(int start, int end) { return mTarget.setSelection(start, end); } @@ -186,6 +202,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performEditorAction(int editorAction) { return mTarget.performEditorAction(editorAction); } @@ -194,6 +211,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performContextMenuAction(int id) { return mTarget.performContextMenuAction(id); } @@ -202,6 +220,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean beginBatchEdit() { return mTarget.beginBatchEdit(); } @@ -210,6 +229,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean endBatchEdit() { return mTarget.endBatchEdit(); } @@ -218,6 +238,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean sendKeyEvent(KeyEvent event) { return mTarget.sendKeyEvent(event); } @@ -226,6 +247,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean clearMetaKeyStates(int states) { return mTarget.clearMetaKeyStates(states); } @@ -234,6 +256,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean reportFullscreenMode(boolean enabled) { return mTarget.reportFullscreenMode(enabled); } @@ -242,6 +265,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performPrivateCommand(String action, Bundle data) { return mTarget.performPrivateCommand(action, data); } @@ -250,6 +274,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return mTarget.requestCursorUpdates(cursorUpdateMode); } @@ -258,6 +283,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public Handler getHandler() { return mTarget.getHandler(); } @@ -266,6 +292,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public void closeConnection() { mTarget.closeConnection(); } @@ -274,7 +301,17 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return mTarget.commitContent(inputContentInfo, flags, opts); } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override + public void reportLanguageHint(@NonNull LocaleList languageHint) { + mTarget.reportLanguageHint(languageHint); + } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 80d7b6b7b7b2e76cfd0b4dea5d05ea606579105c..7db5c32072967ad4c6569a6d67b3ea25c5d37e80 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -337,20 +337,23 @@ public final class InputMethodManager { int mCursorCandEnd; /** - * Represents an invalid action notification sequence number. {@link InputMethodManagerService} - * always issues a positive integer for action notification sequence numbers. Thus -1 is - * guaranteed to be different from any valid sequence number. + * Represents an invalid action notification sequence number. + * {@link com.android.server.InputMethodManagerService} always issues a positive integer for + * action notification sequence numbers. Thus {@code -1} is guaranteed to be different from any + * valid sequence number. */ private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1; /** - * The next sequence number that is to be sent to {@link InputMethodManagerService} via + * The next sequence number that is to be sent to + * {@link com.android.server.InputMethodManagerService} via * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed. */ private int mNextUserActionNotificationSequenceNumber = NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; /** - * The last sequence number that is already sent to {@link InputMethodManagerService}. + * The last sequence number that is already sent to + * {@link com.android.server.InputMethodManagerService}. */ private int mLastSentUserActionNotificationSequenceNumber = NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; @@ -1079,15 +1082,15 @@ public final class InputMethodManager { } /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should only be hidden if it was not explicitly shown + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)} + * to indicate that the soft input window should only be hidden if it was not explicitly shown * by the user. */ public static final int HIDE_IMPLICIT_ONLY = 0x0001; /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should normally be hidden, unless it was originally + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)} + * to indicate that the soft input window should normally be hidden, unless it was originally * shown with {@link #SHOW_FORCED}. */ public static final int HIDE_NOT_ALWAYS = 0x0002; @@ -1255,12 +1258,7 @@ public final class InputMethodManager { // The view is running on a different thread than our own, so // we need to reschedule our work for over there. if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); - vh.post(new Runnable() { - @Override - public void run() { - startInputInner(startInputReason, null, 0, 0, 0); - } - }); + vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0)); return false; } @@ -1871,9 +1869,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. - * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, int flags) { @@ -1903,9 +1901,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} or * {@link #SHOW_FORCED} bit set. - * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, int flags) { @@ -2429,8 +2427,8 @@ public final class InputMethodManager { * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access * permission to the content. * - *

        See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, EditorInfo)} - * for details.

        + *

        See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, + * InputConnection)} for details.

        * * @param token Supplies the identifying token given to an input method when it was started, * which allows it to perform this operation on itself. diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java index 19660d95e9272c7b9a650a6ac510c750313f6dd6..69a59a5b4b3686ca0e4ffdf901fa6b20f22c6bab 100644 --- a/core/java/android/view/textclassifier/EntityConfidence.java +++ b/core/java/android/view/textclassifier/EntityConfidence.java @@ -18,6 +18,8 @@ package android.view.textclassifier; import android.annotation.FloatRange; import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import com.android.internal.util.Preconditions; @@ -30,17 +32,16 @@ import java.util.Map; /** * Helper object for setting and getting entity scores for classified text. * - * @param the entity type. * @hide */ -final class EntityConfidence { +final class EntityConfidence implements Parcelable { - private final ArrayMap mEntityConfidence = new ArrayMap<>(); - private final ArrayList mSortedEntities = new ArrayList<>(); + private final ArrayMap mEntityConfidence = new ArrayMap<>(); + private final ArrayList mSortedEntities = new ArrayList<>(); EntityConfidence() {} - EntityConfidence(@NonNull EntityConfidence source) { + EntityConfidence(@NonNull EntityConfidence source) { Preconditions.checkNotNull(source); mEntityConfidence.putAll(source.mEntityConfidence); mSortedEntities.addAll(source.mSortedEntities); @@ -54,24 +55,16 @@ final class EntityConfidence { * @param source a map from entity to a confidence value in the range 0 (low confidence) to * 1 (high confidence). */ - EntityConfidence(@NonNull Map source) { + EntityConfidence(@NonNull Map source) { Preconditions.checkNotNull(source); // Prune non-existent entities and clamp to 1. mEntityConfidence.ensureCapacity(source.size()); - for (Map.Entry it : source.entrySet()) { + for (Map.Entry it : source.entrySet()) { if (it.getValue() <= 0) continue; mEntityConfidence.put(it.getKey(), Math.min(1, it.getValue())); } - - // Create a list of entities sorted by decreasing confidence for getEntities(). - mSortedEntities.ensureCapacity(mEntityConfidence.size()); - mSortedEntities.addAll(mEntityConfidence.keySet()); - mSortedEntities.sort((e1, e2) -> { - float score1 = mEntityConfidence.get(e1); - float score2 = mEntityConfidence.get(e2); - return Float.compare(score2, score1); - }); + resetSortedEntitiesFromMap(); } /** @@ -79,7 +72,7 @@ final class EntityConfidence { * high confidence to low confidence. */ @NonNull - public List getEntities() { + public List getEntities() { return Collections.unmodifiableList(mSortedEntities); } @@ -89,7 +82,7 @@ final class EntityConfidence { * classified text. */ @FloatRange(from = 0.0, to = 1.0) - public float getConfidenceScore(T entity) { + public float getConfidenceScore(String entity) { if (mEntityConfidence.containsKey(entity)) { return mEntityConfidence.get(entity); } @@ -100,4 +93,51 @@ final class EntityConfidence { public String toString() { return mEntityConfidence.toString(); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEntityConfidence.size()); + for (Map.Entry entry : mEntityConfidence.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeFloat(entry.getValue()); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public EntityConfidence createFromParcel(Parcel in) { + return new EntityConfidence(in); + } + + @Override + public EntityConfidence[] newArray(int size) { + return new EntityConfidence[size]; + } + }; + + private EntityConfidence(Parcel in) { + final int numEntities = in.readInt(); + mEntityConfidence.ensureCapacity(numEntities); + for (int i = 0; i < numEntities; ++i) { + mEntityConfidence.put(in.readString(), in.readFloat()); + } + resetSortedEntitiesFromMap(); + } + + private void resetSortedEntitiesFromMap() { + mSortedEntities.clear(); + mSortedEntities.ensureCapacity(mEntityConfidence.size()); + mSortedEntities.addAll(mEntityConfidence.keySet()); + mSortedEntities.sort((e1, e2) -> { + float score1 = mEntityConfidence.get(e1); + float score2 = mEntityConfidence.get(e2); + return Float.compare(score2, score1); + }); + } } diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java index 2c93a19bbe0e8c58801511d271611f7010405fab..8edf97ea033685b7dcf9e635c911f8da644ba273 100644 --- a/core/java/android/view/textclassifier/SmartSelection.java +++ b/core/java/android/view/textclassifier/SmartSelection.java @@ -16,6 +16,7 @@ package android.view.textclassifier; +import android.annotation.Nullable; import android.content.res.AssetFileDescriptor; /** @@ -146,11 +147,24 @@ final class SmartSelection { final String mCollection; /** float range: 0 - 1 */ final float mScore; + @Nullable final DatetimeParseResult mDatetime; ClassificationResult(String collection, float score) { mCollection = collection; mScore = score; + mDatetime = null; } + + ClassificationResult(String collection, float score, DatetimeParseResult datetime) { + mCollection = collection; + mScore = score; + mDatetime = datetime; + } + } + + /** Parsed date information for the classification result. */ + static final class DatetimeParseResult { + long mMsSinceEpoch; } /** Represents a result of Annotate call. */ diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 7ffbf6357f452401cafaf3b604fdafbdb55e6d8c..54e93d5afd880e273a8d65e2881b0f8181b5719a 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -22,8 +22,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import android.view.View.OnClickListener; import android.view.textclassifier.TextClassifier.EntityType; @@ -31,6 +36,7 @@ import android.view.textclassifier.TextClassifier.EntityType; import com.android.internal.util.Preconditions; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.Map; @@ -52,7 +58,7 @@ import java.util.Map; * Button button = new Button(context); * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); * button.setText(classification.getLabel()); - * button.setOnClickListener(classification.getOnClickListener()); + * button.setOnClickListener(v -> context.startActivity(classification.getIntent())); * } * *

        e.g. starting an action mode with menu items that can handle the classified text: @@ -90,7 +96,6 @@ import java.util.Map; * ... * }); * } - * */ public final class TextClassification { @@ -99,6 +104,10 @@ public final class TextClassification { */ static final TextClassification EMPTY = new TextClassification.Builder().build(); + // TODO(toki): investigate a way to derive this based on device properties. + private static final int MAX_PRIMARY_ICON_SIZE = 192; + private static final int MAX_SECONDARY_ICON_SIZE = 144; + @NonNull private final String mText; @Nullable private final Drawable mPrimaryIcon; @Nullable private final String mPrimaryLabel; @@ -107,8 +116,7 @@ public final class TextClassification { @NonNull private final List mSecondaryIcons; @NonNull private final List mSecondaryLabels; @NonNull private final List mSecondaryIntents; - @NonNull private final List mSecondaryOnClickListeners; - @NonNull private final EntityConfidence mEntityConfidence; + @NonNull private final EntityConfidence mEntityConfidence; @NonNull private final String mSignature; private TextClassification( @@ -120,12 +128,10 @@ public final class TextClassification { @NonNull List secondaryIcons, @NonNull List secondaryLabels, @NonNull List secondaryIntents, - @NonNull List secondaryOnClickListeners, @NonNull Map entityConfidence, @NonNull String signature) { Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size()); Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size()); - Preconditions.checkArgument(secondaryOnClickListeners.size() == secondaryIntents.size()); mText = text; mPrimaryIcon = primaryIcon; mPrimaryLabel = primaryLabel; @@ -134,8 +140,7 @@ public final class TextClassification { mSecondaryIcons = secondaryIcons; mSecondaryLabels = secondaryLabels; mSecondaryIntents = secondaryIntents; - mSecondaryOnClickListeners = secondaryOnClickListeners; - mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntityConfidence = new EntityConfidence(entityConfidence); mSignature = signature; } @@ -186,7 +191,6 @@ public final class TextClassification { * @see #getSecondaryIntent(int) * @see #getSecondaryLabel(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) */ @IntRange(from = 0) public int getSecondaryActionsCount() { @@ -198,13 +202,10 @@ public final class TextClassification { * classified text. * * @param index Index of the action to get the icon for. - * * @throws IndexOutOfBoundsException if the specified index is out of range. - * * @see #getSecondaryActionsCount() for the number of actions available. * @see #getSecondaryIntent(int) * @see #getSecondaryLabel(int) - * @see #getSecondaryOnClickListener(int) * @see #getIcon() */ @Nullable @@ -228,13 +229,10 @@ public final class TextClassification { * the classified text. * * @param index Index of the action to get the label for. - * * @throws IndexOutOfBoundsException if the specified index is out of range. - * * @see #getSecondaryActionsCount() * @see #getSecondaryIntent(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) * @see #getLabel() */ @Nullable @@ -257,13 +255,10 @@ public final class TextClassification { * Returns one of the secondary intents that may be fired to act on the classified text. * * @param index Index of the action to get the intent for. - * * @throws IndexOutOfBoundsException if the specified index is out of range. - * * @see #getSecondaryActionsCount() * @see #getSecondaryLabel(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) * @see #getIntent() */ @Nullable @@ -281,30 +276,11 @@ public final class TextClassification { return mPrimaryIntent; } - /** - * Returns one of the secondary OnClickListeners that may be triggered to act on the - * classified text. - * - * @param index Index of the action to get the click listener for. - * - * @throws IndexOutOfBoundsException if the specified index is out of range. - * - * @see #getSecondaryActionsCount() - * @see #getSecondaryIntent(int) - * @see #getSecondaryLabel(int) - * @see #getSecondaryIcon(int) - * @see #getOnClickListener() - */ - @Nullable - public OnClickListener getSecondaryOnClickListener(int index) { - return mSecondaryOnClickListeners.get(index); - } - /** * Returns the primary OnClickListener that may be triggered to act on the classified - * text. - * - * @see #getSecondaryOnClickListener(int) + * text. This field is not parcelable and will be null for all objects read from a parcel. + * Instead, call Context#startActivity(Intent) with the result of #getSecondaryIntent(int). + * Note that this may fail if the activity doesn't have permission to send the intent. */ @Nullable public OnClickListener getOnClickListener() { @@ -334,6 +310,42 @@ public final class TextClassification { mSignature); } + /** Helper for parceling via #ParcelableWrapper. */ + private void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE); + dest.writeInt(primaryIconBitmap != null ? 1 : 0); + if (primaryIconBitmap != null) { + primaryIconBitmap.writeToParcel(dest, flags); + } + dest.writeString(mPrimaryLabel); + dest.writeInt(mPrimaryIntent != null ? 1 : 0); + if (mPrimaryIntent != null) { + mPrimaryIntent.writeToParcel(dest, flags); + } + // mPrimaryOnClickListener is not parcelable. + dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE)); + dest.writeStringList(mSecondaryLabels); + dest.writeTypedList(mSecondaryIntents); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } + + /** Helper for unparceling via #ParcelableWrapper. */ + private TextClassification(Parcel in) { + mText = in.readString(); + mPrimaryIcon = in.readInt() == 0 + ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in)); + mPrimaryLabel = in.readString(); + mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in); + mPrimaryOnClickListener = null; // not parcelable + mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR)); + mSecondaryLabels = in.createStringArrayList(); + mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); + } + /** * Creates an OnClickListener that starts an activity with the specified intent. * @@ -348,6 +360,68 @@ public final class TextClassification { return v -> context.startActivity(intent); } + /** + * Returns a Bitmap representation of the Drawable + * + * @param drawable The drawable to convert. + * @param maxDims The maximum edge length of the resulting bitmap (in pixels). + */ + @Nullable + private static Bitmap drawableToBitmap(@Nullable Drawable drawable, int maxDims) { + if (drawable == null) { + return null; + } + final int actualWidth = Math.max(1, drawable.getIntrinsicWidth()); + final int actualHeight = Math.max(1, drawable.getIntrinsicHeight()); + final double scaleWidth = ((double) maxDims) / actualWidth; + final double scaleHeight = ((double) maxDims) / actualHeight; + final double scale = Math.min(1.0, Math.min(scaleWidth, scaleHeight)); + final int width = (int) (actualWidth * scale); + final int height = (int) (actualHeight * scale); + if (drawable instanceof BitmapDrawable) { + final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (actualWidth != width || actualHeight != height) { + return Bitmap.createScaledBitmap( + bitmapDrawable.getBitmap(), width, height, /*filter=*/false); + } else { + return bitmapDrawable.getBitmap(); + } + } else { + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + } + + /** + * Returns a list of drawables converted to Bitmaps + * + * @param drawables The drawables to convert. + * @param maxDims The maximum edge length of the resulting bitmaps (in pixels). + */ + private static List drawablesToBitmaps(List drawables, int maxDims) { + final List bitmaps = new ArrayList<>(drawables.size()); + for (Drawable drawable : drawables) { + bitmaps.add(drawableToBitmap(drawable, maxDims)); + } + return bitmaps; + } + + /** Returns a list of drawable wrappers for a list of bitmaps. */ + private static List bitmapsToDrawables(List bitmaps) { + final List drawables = new ArrayList<>(bitmaps.size()); + for (Bitmap bitmap : bitmaps) { + if (bitmap != null) { + drawables.add(new BitmapDrawable(null, bitmap)); + } else { + drawables.add(null); + } + } + return drawables; + } + /** * Builder for building {@link TextClassification} objects. * @@ -358,9 +432,9 @@ public final class TextClassification { * .setText(classifiedText) * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9) * .setEntityType(TextClassifier.TYPE_OTHER, 0.1) - * .setPrimaryAction(intent, label, icon, onClickListener) - * .addSecondaryAction(intent1, label1, icon1, onClickListener1) - * .addSecondaryAction(intent2, label2, icon2, onClickListener2) + * .setPrimaryAction(intent, label, icon) + * .addSecondaryAction(intent1, label1, icon1) + * .addSecondaryAction(intent2, label2, icon2) * .build(); * } */ @@ -370,7 +444,6 @@ public final class TextClassification { @NonNull private final List mSecondaryIcons = new ArrayList<>(); @NonNull private final List mSecondaryLabels = new ArrayList<>(); @NonNull private final List mSecondaryIntents = new ArrayList<>(); - @NonNull private final List mSecondaryOnClickListeners = new ArrayList<>(); @NonNull private final Map mEntityConfidence = new ArrayMap<>(); @Nullable Drawable mPrimaryIcon; @Nullable String mPrimaryLabel; @@ -413,16 +486,14 @@ public final class TextClassification { *

        Note: If all input parameters are set to null, this method will be a * no-op. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder addSecondaryAction( - @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, - @Nullable OnClickListener onClickListener) { - if (intent != null || label != null || icon != null || onClickListener != null) { + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) { + if (intent != null || label != null || icon != null) { mSecondaryIntents.add(intent); mSecondaryLabels.add(label); mSecondaryIcons.add(icon); - mSecondaryOnClickListeners.add(onClickListener); } return this; } @@ -432,7 +503,6 @@ public final class TextClassification { */ public Builder clearSecondaryActions() { mSecondaryIntents.clear(); - mSecondaryOnClickListeners.clear(); mSecondaryLabels.clear(); mSecondaryIcons.clear(); return this; @@ -440,26 +510,23 @@ public final class TextClassification { /** * Sets the primary action that may be performed on the classified text. This is - * equivalent to calling {@code - * setIntent(intent).setLabel(label).setIcon(icon).setOnClickListener(onClickListener)}. + * equivalent to calling {@code setIntent(intent).setLabel(label).setIcon(icon)}. * *

        Note: If all input parameters are null, there will be no * primary action but there may still be secondary actions. * - * @see #addSecondaryAction(Intent, String, Drawable, OnClickListener) + * @see #addSecondaryAction(Intent, String, Drawable) */ public Builder setPrimaryAction( - @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, - @Nullable OnClickListener onClickListener) { - return setIntent(intent).setLabel(label).setIcon(icon) - .setOnClickListener(onClickListener); + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) { + return setIntent(intent).setLabel(label).setIcon(icon); } /** * Sets the icon for the primary action that may be rendered on a widget used to act * on the classified text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setIcon(@Nullable Drawable icon) { mPrimaryIcon = icon; @@ -470,7 +537,7 @@ public final class TextClassification { * Sets the label for the primary action that may be rendered on a widget used to * act on the classified text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setLabel(@Nullable String label) { mPrimaryLabel = label; @@ -481,7 +548,7 @@ public final class TextClassification { * Sets the intent for the primary action that may be fired to act on the classified * text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setIntent(@Nullable Intent intent) { mPrimaryIntent = intent; @@ -490,9 +557,8 @@ public final class TextClassification { /** * Sets the OnClickListener for the primary action that may be triggered to act on - * the classified text. - * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * the classified text. This field is not parcelable and will always be null when the + * object is read from a parcel. */ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) { mPrimaryOnClickListener = onClickListener; @@ -515,10 +581,8 @@ public final class TextClassification { public TextClassification build() { return new TextClassification( mText, - mPrimaryIcon, mPrimaryLabel, - mPrimaryIntent, mPrimaryOnClickListener, - mSecondaryIcons, mSecondaryLabels, - mSecondaryIntents, mSecondaryOnClickListeners, + mPrimaryIcon, mPrimaryLabel, mPrimaryIntent, mPrimaryOnClickListener, + mSecondaryIcons, mSecondaryLabels, mSecondaryIntents, mEntityConfidence, mSignature); } } @@ -526,9 +590,12 @@ public final class TextClassification { /** * Optional input parameters for generating TextClassification. */ - public static final class Options { + public static final class Options implements Parcelable { - private LocaleList mDefaultLocales; + private @Nullable LocaleList mDefaultLocales; + private @Nullable Calendar mReferenceTime; + + public Options() {} /** * @param defaultLocales ordered list of locale preferences that may be used to disambiguate @@ -540,6 +607,16 @@ public final class TextClassification { return this; } + /** + * @param referenceTime reference time based on which relative dates (e.g. "tomorrow" should + * be interpreted. This should usually be the time when the text was originally + * composed. If no reference time is set, now is used. + */ + public Options setReferenceTime(Calendar referenceTime) { + mReferenceTime = referenceTime; + return this; + } + /** * @return ordered list of locale preferences that can be used to disambiguate * the provided text. @@ -548,5 +625,96 @@ public final class TextClassification { public LocaleList getDefaultLocales() { return mDefaultLocales; } + + /** + * @return reference time based on which relative dates (e.g. "tomorrow") should be + * interpreted. + */ + @Nullable + public Calendar getReferenceTime() { + return mReferenceTime; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + dest.writeInt(mReferenceTime != null ? 1 : 0); + if (mReferenceTime != null) { + dest.writeSerializable(mReferenceTime); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + if (in.readInt() > 0) { + mReferenceTime = (Calendar) in.readSerializable(); + } + } + } + + /** + * Parcelable wrapper for TextClassification objects. + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + @NonNull private TextClassification mTextClassification; + + public ParcelableWrapper(@NonNull TextClassification textClassification) { + Preconditions.checkNotNull(textClassification); + mTextClassification = textClassification; + } + + @NonNull + public TextClassification getTextClassification() { + return mTextClassification; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mTextClassification.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(new TextClassification(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + } } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index ed6043038600af464a38118e8453f02b05693607..04ab4474a40cc629219ee26eaeb8de2ff76faea3 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -23,6 +23,8 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArraySet; import com.android.internal.util.Preconditions; @@ -45,12 +47,26 @@ public interface TextClassifier { /** @hide */ String DEFAULT_LOG_TAG = "androidtc"; + /** The TextClassifier failed to run. */ String TYPE_UNKNOWN = ""; + /** The classifier ran, but didn't recognize a known entity. */ String TYPE_OTHER = "other"; + /** E-mail address (e.g. "noreply@android.com"). */ String TYPE_EMAIL = "email"; + /** Phone number (e.g. "555-123 456"). */ String TYPE_PHONE = "phone"; + /** Physical address. */ String TYPE_ADDRESS = "address"; + /** Web URL. */ String TYPE_URL = "url"; + /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or + * relative like "tomorrow". **/ + String TYPE_DATE = "date"; + /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or + * relative like "tomorrow at 5:30pm". **/ + String TYPE_DATE_TIME = "datetime"; + /** Flight number in IATA format. */ + String TYPE_FLIGHT_NUMBER = "flight"; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -61,6 +77,9 @@ public interface TextClassifier { TYPE_PHONE, TYPE_ADDRESS, TYPE_URL, + TYPE_DATE, + TYPE_DATE_TIME, + TYPE_FLIGHT_NUMBER, }) @interface EntityType {} @@ -275,8 +294,8 @@ public interface TextClassifier { /** * Returns a {@link Collection} of the entity types in the specified preset. * - * @see #ENTITIES_ALL - * @see #ENTITIES_NONE + * @see #ENTITY_PRESET_ALL + * @see #ENTITY_PRESET_NONE */ default Collection getEntitiesForPreset(@EntityPreset int entityPreset) { return Collections.EMPTY_LIST; @@ -305,7 +324,7 @@ public interface TextClassifier { * * Configs are initially based on a predefined preset, and can be modified from there. */ - final class EntityConfig { + final class EntityConfig implements Parcelable { private final @TextClassifier.EntityPreset int mEntityPreset; private final Collection mExcludedEntityTypes; private final Collection mIncludedEntityTypes; @@ -355,6 +374,37 @@ public interface TextClassifier { } return Collections.unmodifiableList(entities); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEntityPreset); + dest.writeStringList(new ArrayList<>(mExcludedEntityTypes)); + dest.writeStringList(new ArrayList<>(mIncludedEntityTypes)); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public EntityConfig createFromParcel(Parcel in) { + return new EntityConfig(in); + } + + @Override + public EntityConfig[] newArray(int size) { + return new EntityConfig[size]; + } + }; + + private EntityConfig(Parcel in) { + mEntityPreset = in.readInt(); + mExcludedEntityTypes = new ArraySet<>(in.createStringArrayList()); + mIncludedEntityTypes = new ArraySet<>(in.createStringArrayList()); + } } /** diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java index 51e6168e9aa58ebeec7dd87cfefcb570745a50b9..00695b797cb3d726a33f6e2b86d545ee055bf674 100644 --- a/core/java/android/view/textclassifier/TextClassifierConstants.java +++ b/core/java/android/view/textclassifier/TextClassifierConstants.java @@ -45,19 +45,24 @@ public final class TextClassifierConstants { "smart_selection_dark_launch"; private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = "smart_selection_enabled_for_edit_text"; + private static final String SMART_LINKIFY_ENABLED = + "smart_linkify_enabled"; private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; + private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true; /** Default settings. */ static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); private final boolean mDarkLaunch; private final boolean mSuggestSelectionEnabledForEditableText; + private final boolean mSmartLinkifyEnabled; private TextClassifierConstants() { mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; + mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT; } private TextClassifierConstants(@Nullable String settings) { @@ -74,6 +79,9 @@ public final class TextClassifierConstants { mSuggestSelectionEnabledForEditableText = parser.getBoolean( SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); + mSmartLinkifyEnabled = parser.getBoolean( + SMART_LINKIFY_ENABLED, + SMART_LINKIFY_ENABLED_DEFAULT); } static TextClassifierConstants loadFromString(String settings) { @@ -87,4 +95,8 @@ public final class TextClassifierConstants { public boolean isSuggestSelectionEnabledForEditableText() { return mSuggestSelectionEnabledForEditableText; } + + public boolean isSmartLinkifyEnabled() { + return mSmartLinkifyEnabled; + } } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index aea3cb0603f9da4a39a1f8b66ed56b31dccc4ef5..6a44fb38ee13cbca937fba35b1c56f1a26f0274b 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -18,7 +18,9 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.SearchManager; import android.content.ComponentName; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -28,11 +30,11 @@ import android.net.Uri; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.provider.Browser; +import android.provider.CalendarContract; import android.provider.ContactsContract; import android.provider.Settings; import android.text.util.Linkify; import android.util.Patterns; -import android.view.View.OnClickListener; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -43,6 +45,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -50,6 +53,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -74,7 +78,10 @@ final class TextClassifierImpl implements TextClassifier { TextClassifier.TYPE_ADDRESS, TextClassifier.TYPE_EMAIL, TextClassifier.TYPE_PHONE, - TextClassifier.TYPE_URL)); + TextClassifier.TYPE_URL, + TextClassifier.TYPE_DATE, + TextClassifier.TYPE_DATE_TIME, + TextClassifier.TYPE_FLIGHT_NUMBER)); private static final List ENTITY_TYPES_BASE = Collections.unmodifiableList(Arrays.asList( TextClassifier.TYPE_ADDRESS, @@ -105,16 +112,17 @@ final class TextClassifierImpl implements TextClassifier { @Override public TextSelection suggestSelection( @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, - @NonNull TextSelection.Options options) { + @Nullable TextSelection.Options options) { Utils.validateInput(text, selectionStartIndex, selectionEndIndex); try { if (text.length() > 0) { final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); + final boolean darkLaunchAllowed = options != null && options.isDarkLaunchAllowed(); final SmartSelection smartSelection = getSmartSelection(locales); final String string = text.toString(); final int start; final int end; - if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) { + if (getSettings().isDarkLaunch() && !darkLaunchAllowed) { start = selectionStartIndex; end = selectionEndIndex; } else { @@ -123,7 +131,7 @@ final class TextClassifierImpl implements TextClassifier { start = startEnd[0]; end = startEnd[1]; } - if (start <= end + if (start < end && start >= 0 && end <= string.length() && start <= selectionStartIndex && end >= selectionEndIndex) { final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end); @@ -158,19 +166,19 @@ final class TextClassifierImpl implements TextClassifier { @Override public TextClassification classifyText( @NonNull CharSequence text, int startIndex, int endIndex, - @NonNull TextClassification.Options options) { + @Nullable TextClassification.Options options) { Utils.validateInput(text, startIndex, endIndex); try { if (text.length() > 0) { final String string = text.toString(); final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); + final Calendar refTime = (options == null) ? null : options.getReferenceTime(); final SmartSelection.ClassificationResult[] results = getSmartSelection(locales) .classifyText(string, startIndex, endIndex, getHintFlags(string, startIndex, endIndex)); if (results.length > 0) { - final TextClassification classificationResult = - createClassificationResult(results, string, startIndex, endIndex); - return classificationResult; + return createClassificationResult( + results, string, startIndex, endIndex, refTime); } } } catch (Throwable t) { @@ -187,6 +195,11 @@ final class TextClassifierImpl implements TextClassifier { Utils.validateInput(text); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); + + if (!getSettings().isSmartLinkifyEnabled()) { + return builder.build(); + } + try { final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; final Collection entitiesToIdentify = @@ -406,18 +419,24 @@ final class TextClassifierImpl implements TextClassifier { private TextClassification createClassificationResult( SmartSelection.ClassificationResult[] classifications, - String text, int start, int end) { + String text, int start, int end, @Nullable Calendar referenceTime) { final String classifiedText = text.substring(start, end); final TextClassification.Builder builder = new TextClassification.Builder() .setText(classifiedText); final int size = classifications.length; + SmartSelection.ClassificationResult highestScoringResult = null; + float highestScore = Float.MIN_VALUE; for (int i = 0; i < size; i++) { builder.setEntityType(classifications[i].mCollection, classifications[i].mScore); + if (classifications[i].mScore > highestScore) { + highestScoringResult = classifications[i]; + highestScore = classifications[i].mScore; + } } - final String type = getHighestScoringType(classifications); - addActions(builder, IntentFactory.create(mContext, type, classifiedText)); + addActions(builder, IntentFactory.create( + mContext, referenceTime, highestScoringResult, classifiedText)); return builder.setSignature(getSignature(text, start, end)).build(); } @@ -437,11 +456,10 @@ final class TextClassifierImpl implements TextClassifier { } if (resolveInfo != null && resolveInfo.activityInfo != null) { final String packageName = resolveInfo.activityInfo.packageName; - CharSequence label; + final String label = IntentFactory.getLabel(mContext, intent); Drawable icon; if ("android".equals(packageName)) { // Requires the chooser to find an activity to handle the intent. - label = IntentFactory.getLabel(mContext, intent); icon = null; } else { // A default activity will handle the intent. @@ -451,18 +469,11 @@ final class TextClassifierImpl implements TextClassifier { if (icon == null) { icon = resolveInfo.loadIcon(pm); } - label = resolveInfo.activityInfo.loadLabel(pm); - if (label == null) { - label = resolveInfo.loadLabel(pm); - } } - final String labelString = (label != null) ? label.toString() : null; - final OnClickListener onClickListener = - TextClassification.createStartActivityOnClickListener(mContext, intent); if (i == 0) { - builder.setPrimaryAction(intent, labelString, icon, onClickListener); + builder.setPrimaryAction(intent, label, icon); } else { - builder.addSecondaryAction(intent, labelString, icon, onClickListener); + builder.addSecondaryAction(intent, label, icon); } } } @@ -481,23 +492,6 @@ final class TextClassifierImpl implements TextClassifier { return flag; } - private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) { - if (types.length < 1) { - return ""; - } - - String type = types[0].mCollection; - float highestScore = types[0].mScore; - final int size = types.length; - for (int i = 1; i < size; i++) { - if (types[i].mScore > highestScore) { - type = types[i].mCollection; - highestScore = types[i].mScore; - } - } - return type; - } - /** * Closes the ParcelFileDescriptor and logs any errors that occur. */ @@ -512,87 +506,175 @@ final class TextClassifierImpl implements TextClassifier { /** * Creates intents based on the classification type. */ - private static final class IntentFactory { + static final class IntentFactory { + + private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5); + private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1); private IntentFactory() {} @NonNull - public static List create(Context context, String type, String text) { - final List intents = new ArrayList<>(); - type = type.trim().toLowerCase(Locale.ENGLISH); + public static List create( + Context context, + @Nullable Calendar referenceTime, + SmartSelection.ClassificationResult classification, + String text) { + final String type = classification.mCollection.trim().toLowerCase(Locale.ENGLISH); text = text.trim(); switch (type) { case TextClassifier.TYPE_EMAIL: - intents.add(new Intent(Intent.ACTION_SENDTO) - .setData(Uri.parse(String.format("mailto:%s", text)))); - intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT) - .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) - .putExtra(ContactsContract.Intents.Insert.EMAIL, text)); - break; + return createForEmail(text); case TextClassifier.TYPE_PHONE: - intents.add(new Intent(Intent.ACTION_DIAL) - .setData(Uri.parse(String.format("tel:%s", text)))); - intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT) - .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) - .putExtra(ContactsContract.Intents.Insert.PHONE, text)); - intents.add(new Intent(Intent.ACTION_SENDTO) - .setData(Uri.parse(String.format("smsto:%s", text)))); - break; + return createForPhone(text); case TextClassifier.TYPE_ADDRESS: - intents.add(new Intent(Intent.ACTION_VIEW) - .setData(Uri.parse(String.format("geo:0,0?q=%s", text)))); - break; + return createForAddress(text); case TextClassifier.TYPE_URL: - final String httpPrefix = "http://"; - final String httpsPrefix = "https://"; - if (text.toLowerCase().startsWith(httpPrefix)) { - text = httpPrefix + text.substring(httpPrefix.length()); - } else if (text.toLowerCase().startsWith(httpsPrefix)) { - text = httpsPrefix + text.substring(httpsPrefix.length()); + return createForUrl(context, text); + case TextClassifier.TYPE_DATE: + case TextClassifier.TYPE_DATE_TIME: + if (classification.mDatetime != null) { + Calendar eventTime = Calendar.getInstance(); + eventTime.setTimeInMillis(classification.mDatetime.mMsSinceEpoch); + return createForDatetime(type, referenceTime, eventTime); } else { - text = httpPrefix + text; + return new ArrayList<>(); } - intents.add(new Intent(Intent.ACTION_VIEW, Uri.parse(text)) - .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName())); - break; + case TextClassifier.TYPE_FLIGHT_NUMBER: + return createForFlight(text); + default: + return new ArrayList<>(); + } + } + + @NonNull + private static List createForEmail(String text) { + return Arrays.asList( + new Intent(Intent.ACTION_SENDTO) + .setData(Uri.parse(String.format("mailto:%s", text))), + new Intent(Intent.ACTION_INSERT_OR_EDIT) + .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) + .putExtra(ContactsContract.Intents.Insert.EMAIL, text)); + } + + @NonNull + private static List createForPhone(String text) { + return Arrays.asList( + new Intent(Intent.ACTION_DIAL) + .setData(Uri.parse(String.format("tel:%s", text))), + new Intent(Intent.ACTION_INSERT_OR_EDIT) + .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) + .putExtra(ContactsContract.Intents.Insert.PHONE, text), + new Intent(Intent.ACTION_SENDTO) + .setData(Uri.parse(String.format("smsto:%s", text)))); + } + + @NonNull + private static List createForAddress(String text) { + return Arrays.asList(new Intent(Intent.ACTION_VIEW) + .setData(Uri.parse(String.format("geo:0,0?q=%s", text)))); + } + + @NonNull + private static List createForUrl(Context context, String text) { + final String httpPrefix = "http://"; + final String httpsPrefix = "https://"; + if (text.toLowerCase().startsWith(httpPrefix)) { + text = httpPrefix + text.substring(httpPrefix.length()); + } else if (text.toLowerCase().startsWith(httpsPrefix)) { + text = httpsPrefix + text.substring(httpsPrefix.length()); + } else { + text = httpPrefix + text; + } + return Arrays.asList(new Intent(Intent.ACTION_VIEW, Uri.parse(text)) + .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName())); + } + + @NonNull + private static List createForDatetime( + String type, @Nullable Calendar referenceTime, Calendar eventTime) { + if (referenceTime == null) { + // If no reference time was given, use now. + referenceTime = Calendar.getInstance(); + } + List intents = new ArrayList<>(); + intents.add(createCalendarViewIntent(eventTime)); + final long millisSinceReference = + eventTime.getTimeInMillis() - referenceTime.getTimeInMillis(); + if (millisSinceReference > MIN_EVENT_FUTURE_MILLIS) { + intents.add(createCalendarCreateEventIntent(eventTime, type)); } return intents; } + @NonNull + private static List createForFlight(String text) { + return Arrays.asList(new Intent(Intent.ACTION_WEB_SEARCH) + .putExtra(SearchManager.QUERY, text)); + } + + @NonNull + private static Intent createCalendarViewIntent(Calendar eventTime) { + Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); + builder.appendPath("time"); + ContentUris.appendId(builder, eventTime.getTimeInMillis()); + return new Intent(Intent.ACTION_VIEW).setData(builder.build()); + } + + @NonNull + private static Intent createCalendarCreateEventIntent( + Calendar eventTime, @EntityType String type) { + final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type); + return new Intent(Intent.ACTION_INSERT) + .setData(CalendarContract.Events.CONTENT_URI) + .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay) + .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, eventTime.getTimeInMillis()) + .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, + eventTime.getTimeInMillis() + DEFAULT_EVENT_DURATION); + } + @Nullable public static String getLabel(Context context, @Nullable Intent intent) { if (intent == null || intent.getAction() == null) { return null; } + final String authority = + intent.getData() == null ? null : intent.getData().getAuthority(); switch (intent.getAction()) { case Intent.ACTION_DIAL: return context.getString(com.android.internal.R.string.dial); case Intent.ACTION_SENDTO: - switch (intent.getScheme()) { - case "mailto": - return context.getString(com.android.internal.R.string.email); - case "smsto": - return context.getString(com.android.internal.R.string.sms); - default: - return null; + if ("mailto".equals(intent.getScheme())) { + return context.getString(com.android.internal.R.string.email); + } else if ("smsto".equals(intent.getScheme())) { + return context.getString(com.android.internal.R.string.sms); + } else { + return null; + } + case Intent.ACTION_INSERT: + if (CalendarContract.AUTHORITY.equals(authority)) { + return context.getString(com.android.internal.R.string.add_calendar_event); } + return null; case Intent.ACTION_INSERT_OR_EDIT: - switch (intent.getDataString()) { - case ContactsContract.Contacts.CONTENT_ITEM_TYPE: - return context.getString(com.android.internal.R.string.add_contact); - default: - return null; + if (ContactsContract.Contacts.CONTENT_ITEM_TYPE.equals( + intent.getType())) { + return context.getString(com.android.internal.R.string.add_contact); + } else { + return null; } case Intent.ACTION_VIEW: - switch (intent.getScheme()) { - case "geo": - return context.getString(com.android.internal.R.string.map); - case "http": // fall through - case "https": - return context.getString(com.android.internal.R.string.browse); - default: - return null; + if (CalendarContract.AUTHORITY.equals(authority)) { + return context.getString(com.android.internal.R.string.view_calendar); + } else if ("geo".equals(intent.getScheme())) { + return context.getString(com.android.internal.R.string.map); + } else if ("http".equals(intent.getScheme()) + || "https".equals(intent.getScheme())) { + return context.getString(com.android.internal.R.string.browse); + } else { + return null; } + case Intent.ACTION_WEB_SEARCH: + return context.getString(com.android.internal.R.string.view_flight); default: return null; } diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 6c587cf9d47540b80967d48ce50144f7a8566725..ba854e040460facfd46b08b647bc7308c72b8bff 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -20,6 +20,8 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.text.SpannableString; import android.text.style.ClickableSpan; import android.view.View; @@ -38,7 +40,7 @@ import java.util.function.Function; * A collection of links, representing subsequences of text and the entity types (phone number, * address, url, etc) they may be. */ -public final class TextLinks { +public final class TextLinks implements Parcelable { private final String mFullText; private final List mLinks; @@ -83,11 +85,40 @@ public final class TextLinks { return true; } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mFullText); + dest.writeTypedList(mLinks); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public TextLinks createFromParcel(Parcel in) { + return new TextLinks(in); + } + + @Override + public TextLinks[] newArray(int size) { + return new TextLinks[size]; + } + }; + + private TextLinks(Parcel in) { + mFullText = in.readString(); + mLinks = in.createTypedArrayList(TextLink.CREATOR); + } + /** * A link, identifying a substring of text and possible entity types for it. */ - public static final class TextLink { - private final EntityConfidence mEntityScores; + public static final class TextLink implements Parcelable { + private final EntityConfidence mEntityScores; private final String mOriginalText; private final int mStart; private final int mEnd; @@ -105,7 +136,7 @@ public final class TextLinks { mOriginalText = originalText; mStart = start; mEnd = end; - mEntityScores = new EntityConfidence<>(entityScores); + mEntityScores = new EntityConfidence(entityScores); } /** @@ -153,16 +184,51 @@ public final class TextLinks { @TextClassifier.EntityType String entityType) { return mEntityScores.getConfidenceScore(entityType); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mEntityScores.writeToParcel(dest, flags); + dest.writeString(mOriginalText); + dest.writeInt(mStart); + dest.writeInt(mEnd); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public TextLink createFromParcel(Parcel in) { + return new TextLink(in); + } + + @Override + public TextLink[] newArray(int size) { + return new TextLink[size]; + } + }; + + private TextLink(Parcel in) { + mEntityScores = EntityConfidence.CREATOR.createFromParcel(in); + mOriginalText = in.readString(); + mStart = in.readInt(); + mEnd = in.readInt(); + } } /** * Optional input parameters for generating TextLinks. */ - public static final class Options { + public static final class Options implements Parcelable { private LocaleList mDefaultLocales; private TextClassifier.EntityConfig mEntityConfig; + public Options() {} + /** * @param defaultLocales ordered list of locale preferences that may be used to * disambiguate the provided text. If no locale preferences exist, @@ -201,6 +267,45 @@ public final class TextLinks { public TextClassifier.EntityConfig getEntityConfig() { return mEntityConfig; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + dest.writeInt(mEntityConfig != null ? 1 : 0); + if (mEntityConfig != null) { + mEntityConfig.writeToParcel(dest, flags); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + if (in.readInt() > 0) { + mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in); + } + } } /** diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 25e9e7ecf0e7c742240679d1543ec06cf0009437..774d42db67a08c0a051a29027c56786eceb862e4 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -21,6 +21,8 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import android.view.textclassifier.TextClassifier.EntityType; @@ -36,7 +38,7 @@ public final class TextSelection { private final int mStartIndex; private final int mEndIndex; - @NonNull private final EntityConfidence mEntityConfidence; + @NonNull private final EntityConfidence mEntityConfidence; @NonNull private final String mSignature; private TextSelection( @@ -44,7 +46,7 @@ public final class TextSelection { @NonNull String signature) { mStartIndex = startIndex; mEndIndex = endIndex; - mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntityConfidence = new EntityConfidence(entityConfidence); mSignature = signature; } @@ -110,6 +112,22 @@ public final class TextSelection { mStartIndex, mEndIndex, mEntityConfidence, mSignature); } + /** Helper for parceling via #ParcelableWrapper. */ + private void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStartIndex); + dest.writeInt(mEndIndex); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } + + /** Helper for unparceling via #ParcelableWrapper. */ + private TextSelection(Parcel in) { + mStartIndex = in.readInt(); + mEndIndex = in.readInt(); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); + } + /** * Builder used to build {@link TextSelection} objects. */ @@ -170,11 +188,13 @@ public final class TextSelection { /** * Optional input parameters for generating TextSelection. */ - public static final class Options { + public static final class Options implements Parcelable { - private LocaleList mDefaultLocales; + private @Nullable LocaleList mDefaultLocales; private boolean mDarkLaunchAllowed; + public Options() {} + /** * @param defaultLocales ordered list of locale preferences that may be used to disambiguate * the provided text. If no locale preferences exist, set this to null or an empty @@ -216,5 +236,82 @@ public final class TextSelection { public boolean isDarkLaunchAllowed() { return mDarkLaunchAllowed; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + dest.writeInt(mDarkLaunchAllowed ? 1 : 0); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + mDarkLaunchAllowed = in.readInt() != 0; + } + } + + /** + * Parcelable wrapper for TextSelection objects. + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + @NonNull private TextSelection mTextSelection; + + public ParcelableWrapper(@NonNull TextSelection textSelection) { + Preconditions.checkNotNull(textSelection); + mTextSelection = textSelection; + } + + @NonNull + public TextSelection getTextSelection() { + return mTextSelection; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mTextSelection.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(new TextSelection(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 90cc481483edeb0a6725914308dbb4c1255c2d5d..cba11a84e9eb1697c11afc62b07c5871e4b89a4a 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -931,12 +931,12 @@ public abstract class WebSettings { * Note that this setting affects only JavaScript access to file scheme * resources. Other access to such resources, for example, from image HTML * elements, is unaffected. To prevent possible violation of same domain policy - * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier - * devices, you should explicitly set this value to {@code false}. + * when targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and earlier, + * you should explicitly set this value to {@code false}. *

        - * The default value is {@code true} for API level + * The default value is {@code true} for apps targeting * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, - * and {@code false} for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + * and {@code false} when targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} * and above. * * @param flag whether JavaScript running in the context of a file scheme @@ -947,18 +947,18 @@ public abstract class WebSettings { /** * Sets whether JavaScript running in the context of a file scheme URL * should be allowed to access content from other file scheme URLs. To - * enable the most restrictive, and therefore secure policy, this setting + * enable the most restrictive, and therefore secure, policy this setting * should be disabled. Note that the value of this setting is ignored if * the value of {@link #getAllowUniversalAccessFromFileURLs} is {@code true}. * Note too, that this setting affects only JavaScript access to file scheme * resources. Other access to such resources, for example, from image HTML * elements, is unaffected. To prevent possible violation of same domain policy - * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier - * devices, you should explicitly set this value to {@code false}. + * when targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and earlier, + * you should explicitly set this value to {@code false}. *

        - * The default value is {@code true} for API level + * The default value is {@code true} for apps targeting * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, - * and {@code false} for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + * and {@code false} when targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} * and above. * * @param flag whether JavaScript running in the context of a file scheme @@ -1394,26 +1394,26 @@ public abstract class WebSettings { /** - * Sets whether Safe Browsing is enabled. Safe browsing allows WebView to + * Sets whether Safe Browsing is enabled. Safe Browsing allows WebView to * protect against malware and phishing attacks by verifying the links. * *

        - * Safe browsing is disabled by default. The recommended way to enable Safe browsing is using a - * manifest tag to change the default value to enabled for all WebViews (read general Safe Browsing info). + * Safe Browsing can be disabled for all WebViews using a manifest tag (read general Safe Browsing info). The + * manifest tag has a lower precedence than this API. * *

        - * This API overrides the manifest tag value for this WebView. + * Safe Browsing is enabled by default for devices which support it. * - * @param enabled Whether Safe browsing is enabled. + * @param enabled Whether Safe Browsing is enabled. */ public abstract void setSafeBrowsingEnabled(boolean enabled); /** - * Gets whether Safe browsing is enabled. + * Gets whether Safe Browsing is enabled. * See {@link #setSafeBrowsingEnabled}. * - * @return {@code true} if Safe browsing is enabled and {@code false} otherwise. + * @return {@code true} if Safe Browsing is enabled and {@code false} otherwise. */ public abstract boolean getSafeBrowsingEnabled(); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index deb94e816a69d5f7f5886bc6a8003fe0bd1ca7ff..d2cb70e027c1fc920dc16a2c220cc42648b31cf0 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -327,22 +327,25 @@ import java.util.Map; *

        Safe Browsing

        * *

        - * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the - * user to allow them to navigate back safely or proceed to the malicious page. + * With Safe Browsing, WebView will block malicious URLs and present a warning UI to the user to + * allow them to navigate back safely or proceed to the malicious page. *

        - * The recommended way for apps to enable the feature is putting the following tag in the manifest's - * {@code } element: + * Safe Browsing is enabled by default on devices which support it. If your app needs to disable + * Safe Browsing for all WebViews, it can do so in the manifest's {@code } element: *

        *

          * <manifest>
          *     <application>
          *         ...
          *         <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
        - *             android:value="true" />
        + *             android:value="false" />
          *     </application>
          * </manifest>
          * 
        * + *

        + * Otherwise, see {@link WebSettings#setSafeBrowsingEnabled}. + * */ // Implementation notes. // The WebView is a thin API class that delegates its public API to a backend WebViewProvider @@ -663,6 +666,10 @@ public class WebView extends AbsoluteLayout if (context == null) { throw new IllegalArgumentException("Invalid context argument"); } + if (mWebViewThread == null) { + throw new RuntimeException( + "WebView cannot be initialized on a thread that has no Looper."); + } sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2; checkThread(); @@ -1666,9 +1673,8 @@ public class WebView extends AbsoluteLayout * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those * devices {@code callback} will receive {@code false}. *

        - * This does not enable the Safe Browsing feature itself, and should only be called if Safe - * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This - * prepares resources used for Safe Browsing. + * This should not be called if Safe Browsing has been disabled by manifest tag or {@link + * WebSettings#setSafeBrowsingEnabled}. This prepares resources used for Safe Browsing. *

        * This should be called with the Application Context (and will always use the Application * context to do its work regardless). @@ -2412,6 +2418,24 @@ public class WebView extends AbsoluteLayout return mProvider.getTextClassifier(); } + /** + * Returns the {@link ClassLoader} used to load internal WebView classes. + * This method is meant for use by the WebView Support Library, there is no reason to use this + * method otherwise. + */ + @NonNull + public static ClassLoader getWebViewClassLoader() { + return getFactory().getWebViewClassLoader(); + } + + /** + * Returns the {@link Looper} corresponding to the thread on which WebView calls must be made. + */ + @NonNull + public Looper getLooper() { + return mWebViewThread; + } + //------------------------------------------------------------------------- // Interface for WebView providers //------------------------------------------------------------------------- diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index b3522ec94c0c26dfb4c0d9a3763703b4f079a145..e9fe481112a20f6736ebdf7435c2814926078b6c 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -27,7 +27,6 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StrictMode; import android.os.Trace; import android.util.AndroidRuntimeException; import android.util.ArraySet; @@ -251,7 +250,6 @@ public final class WebViewFactory { "WebView.disableWebView() was called: WebView is disabled"); } - StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { Class providerClass = getProviderClass(); @@ -279,7 +277,6 @@ public final class WebViewFactory { } } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); - StrictMode.setThreadPolicy(oldPolicy); } } } diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 3ced6a5f77690525b2c0fa847852882e0e517595..4f7cdabd47a5a5d88da06cb0f852b2190b01decb 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -172,4 +172,10 @@ public interface WebViewFactoryProvider { * @return the singleton WebViewDatabase instance */ WebViewDatabase getWebViewDatabase(Context context); + + /** + * Gets the classloader used to load internal WebView implementation classes. This interface + * should only be used by the WebView Support Library. + */ + ClassLoader getWebViewClassLoader(); } diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java index 5d091c91abfbb3c5e08c8197927f78b4b6c3d327..b0e9f0194ed4728ab8a9baece9b362ab7f305abf 100644 --- a/core/java/android/webkit/WebViewProviderInfo.java +++ b/core/java/android/webkit/WebViewProviderInfo.java @@ -17,10 +17,10 @@ package android.webkit; import android.annotation.SystemApi; +import android.content.pm.Signature; import android.os.Parcel; import android.os.Parcelable; - -import java.util.Arrays; +import android.util.Base64; /** * @hide @@ -34,7 +34,14 @@ public final class WebViewProviderInfo implements Parcelable { this.description = description; this.availableByDefault = availableByDefault; this.isFallback = isFallback; - this.signatures = signatures; + if (signatures == null) { + this.signatures = new Signature[0]; + } else { + this.signatures = new Signature[signatures.length]; + for (int n = 0; n < signatures.length; n++) { + this.signatures[n] = new Signature(Base64.decode(signatures[n], Base64.DEFAULT)); + } + } } // aidl stuff @@ -54,7 +61,7 @@ public final class WebViewProviderInfo implements Parcelable { description = in.readString(); availableByDefault = (in.readInt() > 0); isFallback = (in.readInt() > 0); - signatures = in.createStringArray(); + signatures = in.createTypedArray(Signature.CREATOR); } @Override @@ -68,7 +75,7 @@ public final class WebViewProviderInfo implements Parcelable { out.writeString(description); out.writeInt(availableByDefault ? 1 : 0); out.writeInt(isFallback ? 1 : 0); - out.writeStringArray(signatures); + out.writeTypedArray(signatures, 0); } // fields read from framework resource @@ -76,5 +83,5 @@ public final class WebViewProviderInfo implements Parcelable { public final String description; public final boolean availableByDefault; public final boolean isFallback; - public final String[] signatures; + public final Signature[] signatures; } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 6bee58f96f8a6f32dba00e1e792b356ac7d005d2..a13de75edee8682cff361cc48bcbe01417943efb 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -19,6 +19,7 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.annotation.NonNull; +import android.annotation.TestApi; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -30,6 +31,7 @@ import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; import android.os.Debug; import android.os.Handler; +import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.os.StrictMode; @@ -2744,13 +2746,21 @@ public abstract class AbsListView extends AdapterView implements Te } private void drawSelector(Canvas canvas) { - if (!mSelectorRect.isEmpty()) { + if (shouldDrawSelector()) { final Drawable selector = mSelector; selector.setBounds(mSelectorRect); selector.draw(canvas); } } + /** + * @hide + */ + @TestApi + public final boolean shouldDrawSelector() { + return !mSelectorRect.isEmpty(); + } + /** * Controls whether the selection highlight drawable should be drawn on top of the item or * behind it. @@ -6026,6 +6036,11 @@ public abstract class AbsListView extends AdapterView implements Te public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return getTarget().commitContent(inputContentInfo, flags, opts); } + + @Override + public void reportLanguageHint(@NonNull LocaleList languageHint) { + getTarget().reportLanguageHint(languageHint); + } } /** @@ -6849,7 +6864,7 @@ public abstract class AbsListView extends AdapterView implements Te // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 08374cb11981e7685d5499c54a8c25befc80e9e8..6c192563658ec5345e0af81b313c7cc3e9b6c266 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -1093,7 +1093,7 @@ public abstract class AdapterView extends ViewGroup { checkSelectionChanged(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } /** diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index af01a3eb22bdc34fe0c5895c3d8a3dcbe3840773..92bfd56d8988ab3d651c49e40aa510b8d10f5fbb 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -132,7 +132,7 @@ public class CheckedTextView extends TextView implements Checkable { if (mChecked != checked) { mChecked = checked; refreshDrawableState(); - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index e57f15365c2618e130ae655bde1885817d54a96e..0762b15626f7f0f5b0f44402d5525fb0f4c626d6 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -158,7 +158,7 @@ public abstract class CompoundButton extends Button implements Checkable { mCheckedFromResource = false; mChecked = checked; refreshDrawableState(); - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); // Avoid infinite recursions if setChecked() is called from a listener diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 336c20cdcdc0f0cd0590bb2c93b9390630741ef2..728824c2f0dc7b34bfe2cc806d00c6bd61d814d0 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/Editor.java b/core/java/android/widget/Editor.java index bddba07766a9bf9e1030c198cae08e1aa576e64e..7bb0db1cf9109f610f81ad9792714a24b37c404e 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3673,6 +3673,8 @@ public class Editor { mIsShowingUp = true; super.show(); } + + mSuggestionListView.setVisibility(mNumberOfSuggestions == 0 ? View.GONE : View.VISIBLE); } @Override @@ -4006,7 +4008,6 @@ public class Editor { if (isValidAssistMenuItem( textClassification.getIcon(), textClassification.getLabel(), - textClassification.getOnClickListener(), textClassification.getIntent())) { final MenuItem item = menu.add( TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, @@ -4014,14 +4015,15 @@ public class Editor { .setIcon(textClassification.getIcon()) .setIntent(textClassification.getIntent()); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - mAssistClickHandlers.put(item, textClassification.getOnClickListener()); + mAssistClickHandlers.put( + item, TextClassification.createStartActivityOnClickListener( + mTextView.getContext(), textClassification.getIntent())); } final int count = textClassification.getSecondaryActionsCount(); for (int i = 0; i < count; i++) { if (!isValidAssistMenuItem( textClassification.getSecondaryIcon(i), textClassification.getSecondaryLabel(i), - textClassification.getSecondaryOnClickListener(i), textClassification.getSecondaryIntent(i))) { continue; } @@ -4032,7 +4034,9 @@ public class Editor { .setIcon(textClassification.getSecondaryIcon(i)) .setIntent(textClassification.getSecondaryIntent(i)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - mAssistClickHandlers.put(item, textClassification.getSecondaryOnClickListener(i)); + mAssistClickHandlers.put(item, + TextClassification.createStartActivityOnClickListener( + mTextView.getContext(), textClassification.getSecondaryIntent(i))); } } @@ -4048,10 +4052,9 @@ public class Editor { } } - private boolean isValidAssistMenuItem( - Drawable icon, CharSequence label, OnClickListener onClick, Intent intent) { + private boolean isValidAssistMenuItem(Drawable icon, CharSequence label, Intent intent) { final boolean hasUi = icon != null || !TextUtils.isEmpty(label); - final boolean hasAction = onClick != null || isSupportedIntent(intent); + final boolean hasAction = isSupportedIntent(intent); return hasUi && hasAction; } @@ -4626,7 +4629,7 @@ public class Editor { return 0; } - protected final void showMagnifier() { + protected final void showMagnifier(@NonNull final MotionEvent event) { if (mMagnifier == null) { return; } @@ -4652,9 +4655,10 @@ public class Editor { final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); - // Horizontally snap to character offset. - final float xPosInView = getHorizontal(mTextView.getLayout(), offset) - + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + // Horizontally move the magnifier smoothly. + final int[] textViewLocationOnScreen = new int[2]; + mTextView.getLocationOnScreen(textViewLocationOnScreen); + final float xPosInView = event.getRawX() - textViewLocationOnScreen[0]; // Vertically snap to middle of current line. final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f @@ -4849,11 +4853,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_UP: @@ -5207,11 +5211,11 @@ public class Editor { // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 26dfcc2d668a06c5e57c52c3270d84aa9a510742..e42217f710c4126a6818870d73535303a9d9af04 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -18,7 +18,6 @@ package android.widget; import android.annotation.FloatRange; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UiThread; import android.content.Context; import android.graphics.Bitmap; @@ -30,8 +29,11 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.PixelCopy; import android.view.Surface; +import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; +import android.view.ViewParent; +import android.view.ViewRootImpl; import com.android.internal.util.Preconditions; @@ -44,6 +46,8 @@ public final class Magnifier { private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1; // The view to which this magnifier is attached. private final View mView; + // The coordinates of the view in the surface. + private final int[] mViewCoordinatesInSurface; // The window containing the magnifier. private final PopupWindow mWindow; // The center coordinates of the window containing the magnifier. @@ -87,6 +91,8 @@ public final class Magnifier { com.android.internal.R.dimen.magnifier_height); mZoomScale = context.getResources().getFloat( com.android.internal.R.dimen.magnifier_zoom_scale); + // The view's surface coordinates will not be updated until the magnifier is first shown. + mViewCoordinatesInSurface = new int[2]; mWindow = new PopupWindow(context); mWindow.setContentView(content); @@ -119,10 +125,36 @@ public final class Magnifier { configureCoordinates(xPosInView, yPosInView); - // Clamp startX value to avoid distorting the rendering of the magnifier content. - final int startX = Math.max(0, Math.min( + // Clamp the startX value to avoid magnifying content which does not belong to the magnified + // view. This will not take into account overlapping views. + // For this, we compute: + // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a + // potential scrolling container. For example, if mView is a + // TextView contained in a HorizontalScrollView, + // mViewCoordinatesInSurface will reflect the surface position of + // the first text character, rather than the position of the first + // visible one. Therefore, we need to add back the amount of + // scrolling from the parent containers. + // - actualWidth: similarly, the width of a View will be larger than its actually visible + // width when it is contained in a scrolling container. We need to use + // the minimum width of a scrolling container which contains this view. + int zeroScrollXInSurface = mViewCoordinatesInSurface[0]; + int actualWidth = mView.getWidth(); + ViewParent viewParent = mView.getParent(); + while (viewParent instanceof View) { + final View container = (View) viewParent; + if (container.canScrollHorizontally(-1 /* left scroll */) + || container.canScrollHorizontally(1 /* right scroll */)) { + zeroScrollXInSurface += container.getScrollX(); + actualWidth = Math.min(actualWidth, container.getWidth() + - container.getPaddingLeft() - container.getPaddingRight()); + } + viewParent = viewParent.getParent(); + } + + final int startX = Math.max(zeroScrollXInSurface, Math.min( mCenterZoomCoords.x - mBitmap.getWidth() / 2, - mView.getWidth() - mBitmap.getWidth())); + zeroScrollXInSurface + actualWidth - mBitmap.getWidth())); final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) { @@ -160,58 +192,79 @@ public final class Magnifier { } } - private void configureCoordinates(float xPosInView, float yPosInView) { - final float posX; - final float posY; - + private void configureCoordinates(final float xPosInView, final float yPosInView) { + // Compute the coordinates of the center of the content going to be displayed in the + // magnifier. These are relative to the surface the content is copied from. + final float contentPosX; + final float contentPosY; if (mView instanceof SurfaceView) { // No offset required if the backing Surface matches the size of the SurfaceView. - posX = xPosInView; - posY = yPosInView; + contentPosX = xPosInView; + contentPosY = yPosInView; } else { - final int[] coordinatesInSurface = new int[2]; - mView.getLocationInSurface(coordinatesInSurface); - posX = xPosInView + coordinatesInSurface[0]; - posY = yPosInView + coordinatesInSurface[1]; + mView.getLocationInSurface(mViewCoordinatesInSurface); + contentPosX = xPosInView + mViewCoordinatesInSurface[0]; + contentPosY = yPosInView + mViewCoordinatesInSurface[1]; } + mCenterZoomCoords.x = Math.round(contentPosX); + mCenterZoomCoords.y = Math.round(contentPosY); - mCenterZoomCoords.x = Math.round(posX); - mCenterZoomCoords.y = Math.round(posY); - - final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize( + // Compute the position of the magnifier window. These have to be relative to the window + // of the view the magnifier is attached to, as the magnifier popup is a panel window + // attached to that window. + final int[] viewCoordinatesInWindow = new int[2]; + mView.getLocationInWindow(viewCoordinatesInWindow); + final int verticalOffset = mView.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.magnifier_offset); - mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2; - mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset; + final float magnifierPosX = xPosInView + viewCoordinatesInWindow[0]; + final float magnifierPosY = yPosInView + viewCoordinatesInWindow[1] - verticalOffset; + mWindowCoords.x = Math.round(magnifierPosX - mWindowWidth / 2); + mWindowCoords.y = Math.round(magnifierPosY - mWindowHeight / 2); } private void performPixelCopy(final int startXInSurface, final int startYInSurface) { - final Surface surface = getValidViewSurface(); - if (surface != null) { - mPixelCopyRequestRect.set(startXInSurface, startYInSurface, - startXInSurface + mBitmap.getWidth(), startYInSurface + mBitmap.getHeight()); - - PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap, - result -> { - getImageView().invalidate(); - mPrevStartCoordsInSurface.x = startXInSurface; - mPrevStartCoordsInSurface.y = startYInSurface; - }, - mPixelCopyHandler); - } - } - - @Nullable - private Surface getValidViewSurface() { + // Get the view surface where the content will be copied from. final Surface surface; + final int surfaceWidth; + final int surfaceHeight; if (mView instanceof SurfaceView) { - surface = ((SurfaceView) mView).getHolder().getSurface(); + final SurfaceHolder surfaceHolder = ((SurfaceView) mView).getHolder(); + surface = surfaceHolder.getSurface(); + surfaceWidth = surfaceHolder.getSurfaceFrame().right; + surfaceHeight = surfaceHolder.getSurfaceFrame().bottom; } else if (mView.getViewRootImpl() != null) { - surface = mView.getViewRootImpl().mSurface; + final ViewRootImpl viewRootImpl = mView.getViewRootImpl(); + surface = viewRootImpl.mSurface; + surfaceWidth = viewRootImpl.getWidth(); + surfaceHeight = viewRootImpl.getHeight(); } else { surface = null; + surfaceWidth = NONEXISTENT_PREVIOUS_CONFIG_VALUE; + surfaceHeight = NONEXISTENT_PREVIOUS_CONFIG_VALUE; } - return (surface != null && surface.isValid()) ? surface : null; + if (surface == null || !surface.isValid()) { + return; + } + + // Clamp copy coordinates inside the surface to avoid displaying distorted content. + final int clampedStartXInSurface = Math.max(0, + Math.min(startXInSurface, surfaceWidth - mWindowWidth)); + final int clampedStartYInSurface = Math.max(0, + Math.min(startYInSurface, surfaceHeight - mWindowHeight)); + + // Perform the pixel copy. + mPixelCopyRequestRect.set(clampedStartXInSurface, + clampedStartYInSurface, + clampedStartXInSurface + mBitmap.getWidth(), + clampedStartYInSurface + mBitmap.getHeight()); + PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap, + result -> { + getImageView().invalidate(); + mPrevStartCoordsInSurface.x = startXInSurface; + mPrevStartCoordsInSurface.y = startYInSurface; + }, + mPixelCopyHandler); } private ImageView getImageView() { diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java new file mode 100644 index 0000000000000000000000000000000000000000..a4da05f8b982837b03f1459d1f11de4099ddb633 --- /dev/null +++ b/core/java/android/widget/MediaControlView2.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.session.MediaController; +import android.media.update.ApiLoader; +import android.media.update.MediaControlView2Provider; +import android.media.update.ViewProvider; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/** + * A View that contains the controls for MediaPlayer2. + * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward", + * "Subtitle", "Full Screen", and it is also possible to add multiple custom buttons. + * + *

        + * MediaControlView2 can be initialized in two different ways: + * 1) When VideoView2 is initialized, it automatically initializes a MediaControlView2 instance and + * adds it to the view. + * 2) Initialize MediaControlView2 programmatically and add it to a ViewGroup instance. + * + * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController, + * which is necessary to communicate with MediaSession2. In the second option, however, the + * developer needs to manually retrieve a MediaController instance and set it to MediaControlView2 + * by calling setController(MediaController controller). + * + * TODO PUBLIC API + * @hide + */ +public class MediaControlView2 extends FrameLayout { + /** @hide */ + @IntDef({ + BUTTON_PLAY_PAUSE, + BUTTON_FFWD, + BUTTON_REW, + BUTTON_NEXT, + BUTTON_PREV, + BUTTON_SUBTITLE, + BUTTON_FULL_SCREEN, + BUTTON_OVERFLOW, + BUTTON_MUTE, + BUTTON_ASPECT_RATIO, + BUTTON_SETTINGS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Button {} + + /** + * MediaControlView2 button value for playing and pausing media. + */ + public static final int BUTTON_PLAY_PAUSE = 1; + /** + * MediaControlView2 button value for jumping 30 seconds forward. + */ + public static final int BUTTON_FFWD = 2; + /** + * MediaControlView2 button value for jumping 10 seconds backward. + */ + public static final int BUTTON_REW = 3; + /** + * MediaControlView2 button value for jumping to next media. + */ + public static final int BUTTON_NEXT = 4; + /** + * MediaControlView2 button value for jumping to previous media. + */ + public static final int BUTTON_PREV = 5; + /** + * MediaControlView2 button value for showing/hiding subtitle track. + */ + public static final int BUTTON_SUBTITLE = 6; + /** + * MediaControlView2 button value for toggling full screen. + */ + public static final int BUTTON_FULL_SCREEN = 7; + /** + * MediaControlView2 button value for showing/hiding overflow buttons. + */ + public static final int BUTTON_OVERFLOW = 8; + /** + * MediaControlView2 button value for muting audio. + */ + public static final int BUTTON_MUTE = 9; + /** + * MediaControlView2 button value for adjusting aspect ratio of view. + */ + public static final int BUTTON_ASPECT_RATIO = 10; + /** + * MediaControlView2 button value for showing/hiding settings page. + */ + public static final int BUTTON_SETTINGS = 11; + + /** + * String for receiving command to show subtitle from MediaSession. Can be checked by + * implementing {@link android.media.session.MediaSession.Callback#onCommand} + */ + public static final String COMMAND_SHOW_SUBTITLE = "showSubtitle"; + /** + * String for receiving command to hide subtitle from MediaSession. Can be checked by + * implementing {@link android.media.session.MediaSession.Callback#onCommand} + */ + public static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle"; + /** + * String for receiving command to set fullscreen from MediaSession. Can be checked by + * implementing {@link android.media.session.MediaSession.Callback#onCommand} + */ + public static final String COMMAND_SET_FULLSCREEN = "setFullscreen"; + + private final MediaControlView2Provider mProvider; + + public MediaControlView2(@NonNull Context context) { + this(context, null); + } + + public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mProvider = ApiLoader.getProvider(context) + .createMediaControlView2(this, new SuperProvider()); + } + + /** + * @hide + */ + public MediaControlView2Provider getProvider() { + return mProvider; + } + + /** + * Sets MediaController instance to control corresponding MediaSession. + */ + public void setController(MediaController controller) { + mProvider.setController_impl(controller); + } + + /** + * Shows the control view on screen. It will disappear automatically after 3 seconds of + * inactivity. + */ + public void show() { + mProvider.show_impl(); + } + + /** + * Shows the control view on screen. It will disappear automatically after {@code timeout} + * milliseconds of inactivity. + */ + public void show(long timeout) { + mProvider.show_impl(timeout); + } + + /** + * Returns whether the control view is currently shown or hidden. + */ + public boolean isShowing() { + return mProvider.isShowing_impl(); + } + + /** + * Hide the control view from the screen. + */ + public void hide() { + mProvider.hide_impl(); + } + + /** + * Changes the visibility state of an individual button. Default value is View.Visible. + * + * @param button the {@code Button} assigned to individual buttons + *

          + *
        • {@link #BUTTON_PLAY_PAUSE} + *
        • {@link #BUTTON_FFWD} + *
        • {@link #BUTTON_REW} + *
        • {@link #BUTTON_NEXT} + *
        • {@link #BUTTON_PREV} + *
        • {@link #BUTTON_SUBTITLE} + *
        • {@link #BUTTON_FULL_SCREEN} + *
        • {@link #BUTTON_MUTE} + *
        • {@link #BUTTON_OVERFLOW} + *
        • {@link #BUTTON_ASPECT_RATIO} + *
        • {@link #BUTTON_SETTINGS} + *
        + * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + */ + public void setButtonVisibility(@Button int button, @Visibility int visibility) { + mProvider.setButtonVisibility_impl(button, visibility); + } + + @Override + protected void onAttachedToWindow() { + mProvider.onAttachedToWindow_impl(); + } + + @Override + protected void onDetachedFromWindow() { + mProvider.onDetachedFromWindow_impl(); + } + + @Override + public CharSequence getAccessibilityClassName() { + return mProvider.getAccessibilityClassName_impl(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mProvider.onTouchEvent_impl(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + return mProvider.onTrackballEvent_impl(ev); + } + + @Override + public void onFinishInflate() { + mProvider.onFinishInflate_impl(); + } + + @Override + public void setEnabled(boolean enabled) { + mProvider.setEnabled_impl(enabled); + } + + private class SuperProvider implements ViewProvider { + @Override + public void onAttachedToWindow_impl() { + MediaControlView2.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + MediaControlView2.super.onDetachedFromWindow(); + } + + @Override + public CharSequence getAccessibilityClassName_impl() { + return MediaControlView2.super.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return MediaControlView2.super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return MediaControlView2.super.onTrackballEvent(ev); + } + + @Override + public void onFinishInflate_impl() { + MediaControlView2.super.onFinishInflate(); + } + + @Override + public void setEnabled_impl(boolean enabled) { + MediaControlView2.super.setEnabled(enabled); + } + } +} diff --git a/core/java/android/widget/MediaController2.java b/core/java/android/widget/MediaController2.java deleted file mode 100644 index 9035137d5362b0e6dc743fd337413e049512eb71..0000000000000000000000000000000000000000 --- a/core/java/android/widget/MediaController2.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.widget; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Canvas; -import android.media.session.MediaController; -import android.media.update.ApiLoader; -import android.media.update.MediaController2Provider; -import android.media.update.ViewProvider; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; - -/** - * TODO PUBLIC API - * @hide - */ -public class MediaController2 extends FrameLayout { - private final MediaController2Provider mProvider; - - public MediaController2(@NonNull Context context) { - this(context, null); - } - - public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public MediaController2(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mProvider = ApiLoader.getProvider(context) - .createMediaController2(this, new SuperProvider()); - } - - public void setController(MediaController controller) { - mProvider.setController_impl(controller); - } - - public void setAnchorView(View view) { - mProvider.setAnchorView_impl(view); - } - - public void show() { - mProvider.show_impl(); - } - - public void show(int timeout) { - mProvider.show_impl(timeout); - } - - public boolean isShowing() { - return mProvider.isShowing_impl(); - } - - public void hide() { - mProvider.hide_impl(); - } - - public void setPrevNextListeners(OnClickListener next, OnClickListener prev) { - mProvider.setPrevNextListeners_impl(next, prev); - } - - public void showCCButton() { - mProvider.showCCButton_impl(); - } - - public boolean isPlaying() { - return mProvider.isPlaying_impl(); - } - - public int getCurrentPosition() { - return mProvider.getCurrentPosition_impl(); - } - - public int getBufferPercentage() { - return mProvider.getBufferPercentage_impl(); - } - - public boolean canPause() { - return mProvider.canPause_impl(); - } - - public boolean canSeekBackward() { - return mProvider.canSeekBackward_impl(); - } - - public boolean canSeekForward() { - return mProvider.canSeekForward_impl(); - } - - public void showSubtitle() { - mProvider.showSubtitle_impl(); - } - - public void hideSubtitle() { - mProvider.hideSubtitle_impl(); - } - - @Override - protected void onAttachedToWindow() { - mProvider.onAttachedToWindow_impl(); - } - - @Override - protected void onDetachedFromWindow() { - mProvider.onDetachedFromWindow_impl(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - mProvider.onLayout_impl(changed, left, top, right, bottom); - } - - @Override - public void draw(Canvas canvas) { - mProvider.draw_impl(canvas); - } - - @Override - public CharSequence getAccessibilityClassName() { - return mProvider.getAccessibilityClassName_impl(); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mProvider.onTouchEvent_impl(ev); - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - return mProvider.onTrackballEvent_impl(ev); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return mProvider.onKeyDown_impl(keyCode, event); - } - - @Override - public void onFinishInflate() { - mProvider.onFinishInflate_impl(); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - return mProvider.dispatchKeyEvent_impl(event); - } - - @Override - public void setEnabled(boolean enabled) { - mProvider.setEnabled_impl(enabled); - } - - private class SuperProvider implements ViewProvider { - @Override - public void onAttachedToWindow_impl() { - MediaController2.super.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow_impl() { - MediaController2.super.onDetachedFromWindow(); - } - - @Override - public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) { - MediaController2.super.onLayout(changed, left, top, right, bottom); - } - - @Override - public void draw_impl(Canvas canvas) { - MediaController2.super.draw(canvas); - } - - @Override - public CharSequence getAccessibilityClassName_impl() { - return MediaController2.super.getAccessibilityClassName(); - } - - @Override - public boolean onTouchEvent_impl(MotionEvent ev) { - return MediaController2.super.onTouchEvent(ev); - } - - @Override - public boolean onTrackballEvent_impl(MotionEvent ev) { - return MediaController2.super.onTrackballEvent(ev); - } - - @Override - public boolean onKeyDown_impl(int keyCode, KeyEvent event) { - return MediaController2.super.onKeyDown(keyCode, event); - } - - @Override - public void onFinishInflate_impl() { - MediaController2.super.onFinishInflate(); - } - - @Override - public boolean dispatchKeyEvent_impl(KeyEvent event) { - return MediaController2.super.dispatchKeyEvent(event); - } - - @Override - public void setEnabled_impl(boolean enabled) { - MediaController2.super.setEnabled(enabled); - } - } -} diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 716b205380c20aefb70d3eecfc04b071f9edd6cb..5710db3ce8e0e4e9b7c3c14a60e2d0d85263ad01 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -27,8 +27,10 @@ import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.annotation.FloatRange; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; import android.annotation.Size; import android.annotation.StringRes; import android.annotation.StyleRes; @@ -52,6 +54,7 @@ import android.graphics.BaseCanvas; import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Paint; +import android.graphics.Paint.FontMetricsInt; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.Rect; @@ -77,8 +80,8 @@ import android.text.GraphicsOperations; import android.text.InputFilter; import android.text.InputType; import android.text.Layout; +import android.text.MeasuredText; import android.text.ParcelableSpan; -import android.text.PremeasuredText; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; @@ -306,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 { @@ -400,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(); @@ -924,6 +929,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int inputType = EditorInfo.TYPE_NULL; a = theme.obtainStyledAttributes( attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); + int firstBaselineToTopHeight = -1; + int lastBaselineToBottomHeight = -1; + int lineHeight = -1; readTextAppearance(context, a, attributes, true /* styleArray */); @@ -1249,6 +1257,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_justificationMode: mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); break; + + case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: + firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); + break; + + case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: + lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); + break; + + 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); } } @@ -1563,6 +1585,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; } + + if (firstBaselineToTopHeight >= 0) { + setFirstBaselineToTopHeight(firstBaselineToTopHeight); + } + if (lastBaselineToBottomHeight >= 0) { + setLastBaselineToBottomHeight(lastBaselineToBottomHeight); + } + if (lineHeight >= 0) { + setLineHeight(lineHeight); + } } /** @@ -2365,7 +2397,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(mText); if (hasPasswordTransformationMethod()) { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -3165,6 +3197,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** + * @inheritDoc + * + * @see #setFirstBaselineToTopHeight(int) + * @see #setLastBaselineToBottomHeight(int) + */ @Override public void setPadding(int left, int top, int right, int bottom) { if (left != mPaddingLeft @@ -3179,6 +3217,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener invalidate(); } + /** + * @inheritDoc + * + * @see #setFirstBaselineToTopHeight(int) + * @see #setLastBaselineToBottomHeight(int) + */ @Override public void setPaddingRelative(int start, int top, int end, int bottom) { if (start != getPaddingStart() @@ -3193,6 +3237,97 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener invalidate(); } + /** + * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is + * equal to the distance between the firt text baseline and the top of this TextView. + * Note that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was + * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. + * + * @param firstBaselineToTopHeight distance between first baseline to top of the container + * in pixels + * + * @see #getFirstBaselineToTopHeight() + * @see #setPadding(int, int, int, int) + * @see #setPaddingRelative(int, int, int, int) + * + * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight + */ + public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { + Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); + + final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); + final int fontMetricsTop; + if (getIncludeFontPadding()) { + fontMetricsTop = fontMetrics.top; + } else { + fontMetricsTop = fontMetrics.ascent; + } + + // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size + // in settings). At the moment, we don't. + + if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { + final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); + setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); + } + } + + /** + * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is + * equal to the distance between the last text baseline and the bottom of this TextView. + * Note that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was + * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. + * + * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container + * in pixels + * + * @see #getLastBaselineToBottomHeight() + * @see #setPadding(int, int, int, int) + * @see #setPaddingRelative(int, int, int, int) + * + * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight + */ + public void setLastBaselineToBottomHeight( + @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { + Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); + + final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); + final int fontMetricsBottom; + if (getIncludeFontPadding()) { + fontMetricsBottom = fontMetrics.bottom; + } else { + fontMetricsBottom = fontMetrics.descent; + } + + // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size + // in settings). At the moment, we don't. + + if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { + final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); + } + } + + /** + * Returns the distance between the first text baseline and the top of this TextView. + * + * @see #setFirstBaselineToTopHeight(int) + * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight + */ + public int getFirstBaselineToTopHeight() { + return getPaddingTop() - getPaint().getFontMetricsInt().top; + } + + /** + * Returns the distance between the last text baseline and the bottom of this TextView. + * + * @see #setLastBaselineToBottomHeight(int) + * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight + */ + public int getLastBaselineToBottomHeight() { + return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; + } + /** * Gets the autolink mask of the text. See {@link * android.text.util.Linkify#ALL Linkify.ALL} and peers for @@ -3756,6 +3891,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @param elegant set the paint's elegant metrics flag. * + * @see #isElegantTextHeight() * @see Paint#isElegantTextHeight() * * @attr ref android.R.styleable#TextView_elegantTextHeight @@ -4973,6 +5109,54 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mSpacingAdd; } + /** + * Sets an explicit line height for this TextView. This is equivalent to the vertical distance + * between subsequent baselines in the TextView. + * + * @param lineHeight the line height in pixels + * + * @see #setLineSpacing(float, float) + * @see #getLineSpacing() + * + * @attr ref android.R.styleable#TextView_lineHeight + */ + public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { + Preconditions.checkArgumentNonnegative(lineHeight); + + final int fontHeight = getPaint().getFontMetricsInt(null); + // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. + if (lineHeight != fontHeight) { + // Set lineSpacingExtra by the difference of lineSpacing with lineHeight + setLineSpacing(lineHeight - fontHeight, 1f); + } + } + + /** + * 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; + notifyViewAccessibilityStateChangedIfNeeded( + 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} @@ -5393,7 +5577,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); - } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) { + } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } @@ -5476,7 +5660,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(text, 0, oldlen, textLength); onTextChanged(text, 0, oldlen, textLength); - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); + notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); if (needEditableForNotification) { sendAfterTextChanged((Editable) text); @@ -6210,7 +6394,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setError(CharSequence error, Drawable icon) { createEditorIfNeeded(); mEditor.setError(error, icon); - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -9125,8 +9309,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * - * Checks whether the transformation method applied to this TextView is set to ALL CAPS. This - * settings is internally ignored if this field is editable or selectable. + * Checks whether the transformation method applied to this TextView is set to ALL CAPS. * @return Whether the current transformation method is for ALL CAPS. * * @see #setAllCaps(boolean) @@ -10524,6 +10707,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 new file mode 100644 index 0000000000000000000000000000000000000000..ac83313bd835779354d0340261a8154cbf999669 --- /dev/null +++ b/core/java/android/widget/VideoView2.java @@ -0,0 +1,578 @@ +/* + * 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.widget; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.MediaPlayerInterface; +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.media.update.ApiLoader; +import android.media.update.VideoView2Provider; +import android.media.update.ViewProvider; +import android.net.Uri; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +// TODO: Replace MediaSession wtih MediaSession2 once MediaSession2 is submitted. +/** + * Displays a video file. VideoView2 class is a View class which is wrapping MediaPlayer2 so that + * developers can easily implement a video rendering application. + * + *

        + * Data sources that VideoView2 supports : + * VideoView2 can play video files and audio-only files as + * well. It can load from various sources such as resources or content providers. The supported + * media file formats are the same as MediaPlayer2. + * + *

        + * View type can be selected : + * VideoView2 can render videos on top of TextureView as well as + * SurfaceView selectively. The default is SurfaceView and it can be changed using + * {@link #setViewType(int)} method. Using SurfaceView is recommended in most cases for saving + * battery. TextureView might be preferred for supporting various UIs such as animation and + * translucency. + * + *

        + * Differences between {@link VideoView} class : + * VideoView2 covers and inherits the most of + * VideoView's functionalities. The main differences are + *

          + *
        • VideoView2 inherits FrameLayout and renders videos using SurfaceView and TextureView + * selectively while VideoView inherits SurfaceView class. + *
        • VideoView2 is integrated with MediaControlView2 and a default MediaControlView2 instance is + * attached to VideoView2 by default. If a developer does not want to use the default + * MediaControlView2, needs to set enableControlView attribute to false. For instance, + *
          + * <VideoView2
          + *     android:id="@+id/video_view"
          + *     xmlns:widget="http://schemas.android.com/apk/com.android.media.update"
          + *     widget:enableControlView="false" />
          + * 
          + * If a developer wants to attach a customed MediaControlView2, then set enableControlView attribute + * to false and assign the customed media control widget using {@link #setMediaControlView2}. + *
        • VideoView2 is integrated with MediaPlayer2 while VideoView is integrated with MediaPlayer. + *
        • VideoView2 is integrated with MediaSession and so it responses with media key events. + * A VideoView2 keeps a MediaSession instance internally and connects it to a corresponding + * MediaControlView2 instance. + *

          + *
        + * + *

        + * Audio focus and audio attributes : + * By default, VideoView2 requests audio focus with + * {@link AudioManager#AUDIOFOCUS_GAIN}. Use {@link #setAudioFocusRequest(int)} to change this + * behavior. The default {@link AudioAttributes} used during playback have a usage of + * {@link AudioAttributes#USAGE_MEDIA} and a content type of + * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to + * modify them. + * + *

        + * Note: VideoView2 does not retain its full state when going into the background. In particular, it + * does not restore the current play state, play position, selected tracks. Applications should save + * and restore these on their own in {@link android.app.Activity#onSaveInstanceState} and + * {@link android.app.Activity#onRestoreInstanceState}. + * + * @hide + */ +public class VideoView2 extends FrameLayout { + /** @hide */ + @IntDef({ + VIEW_TYPE_TEXTUREVIEW, + VIEW_TYPE_SURFACEVIEW + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ViewType {} + + /** + * Indicates video is rendering on SurfaceView + * + * @see #setViewType + */ + public static final int VIEW_TYPE_SURFACEVIEW = 1; + + /** + * Indicates video is rendering on TextureView + * + * @see #setViewType + */ + public static final int VIEW_TYPE_TEXTUREVIEW = 2; + + private final VideoView2Provider mProvider; + + public VideoView2(@NonNull Context context) { + this(context, null); + } + + public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public VideoView2( + @NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider(), + attrs, defStyleAttr, defStyleRes); + } + + /** + * @hide + */ + public VideoView2Provider getProvider() { + return mProvider; + } + + /** + * Sets MediaControlView2 instance. It will replace the previously assigned MediaControlView2 + * instance if any. + * + * @param mediaControlView a media control view2 instance. + */ + public void setMediaControlView2(MediaControlView2 mediaControlView) { + mProvider.setMediaControlView2_impl(mediaControlView); + } + + /** + * Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by + * {@link #setMediaControlView2} method. + */ + public MediaControlView2 getMediaControlView2() { + return mProvider.getMediaControlView2_impl(); + } + + + /** + * Returns MediaController instance which is connected with MediaSession that VideoView2 is + * using. This method should be called when VideoView2 is attached to window, or it throws + * IllegalStateException, since internal MediaSession instance is not available until + * this view is attached to window. Please check {@link android.view.View#isAttachedToWindow} + * before calling this method. + * + * @throws IllegalStateException if interal MediaSession is not created yet. + */ + public MediaController getMediaController() { + return mProvider.getMediaController_impl(); + } + + /** + * Starts rendering closed caption or subtitles if there is any. The first subtitle track will + * be chosen by default if there multiple subtitle tracks exist. + */ + public void showSubtitle() { + mProvider.showSubtitle_impl(); + } + + /** + * Stops showing closed captions or subtitles. + */ + public void hideSubtitle() { + mProvider.hideSubtitle_impl(); + } + + /** + * 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. + * @param speed the playback speed. It should be positive. + */ + // TODO: Support this via MediaController2. + public void setSpeed(float speed) { + mProvider.setSpeed_impl(speed); + } + + /** + * 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. + */ + public void setAudioFocusRequest(int focusGain) { + mProvider.setAudioFocusRequest_impl(focusGain); + } + + /** + * Sets the {@link AudioAttributes} to be used during the playback of the video. + * + * @param attributes non-null AudioAttributes. + */ + public void setAudioAttributes(@NonNull AudioAttributes attributes) { + mProvider.setAudioAttributes_impl(attributes); + } + + /** + * Sets a remote player for handling playback of the selected route from MediaControlView2. + * If this is not called, MediaCotrolView2 will not show the route button. + * + * @param routeCategories the list of media control categories in + * {@link android.support.v7.media.MediaControlIntent} + * @param player the player to handle playback of the selected route. + * If null, a default route player will be used. + * @throws IllegalStateException if MediaControlView2 is not set. + * @hide + */ + public void setRouteAttributes(@NonNull List routeCategories, + @Nullable MediaPlayerInterface player) { + mProvider.setRouteAttributes_impl(routeCategories, player); + } + + /** + * Sets a remote player for handling playback of the selected route from MediaControlView2. + * If this is not called, MediaCotrolView2 will not show the route button. + * + * @param routeCategories the list of media control categories in + * {@link android.support.v7.media.MediaControlIntent} + * @param sessionPlayer the player to handle playback of the selected route. + * If null, a default route player will be used. + * @throws IllegalStateException if MediaControlView2 is not set. + * @hide + */ + // TODO: Use MediaPlayerBase once MediaSession2 APIs are ready. + public void setRouteAttributes(@NonNull List routeCategories, + @Nullable MediaSession.Callback sessionPlayer) { + mProvider.setRouteAttributes_impl(routeCategories, sessionPlayer); + } + + /** + * Sets video path. + * + * @param path the path of the video. + */ + public void setVideoPath(String path) { + mProvider.setVideoPath_impl(path); + } + + /** + * Sets video URI. + * + * @param uri the URI of the video. + */ + public void setVideoUri(Uri uri) { + mProvider.setVideoUri_impl(uri); + } + + /** + * Sets video URI using specific headers. + * + * @param uri the URI of the video. + * @param headers the headers for the URI request. + * Note that the cross domain redirection is allowed by default, but that can be + * changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value + * to disallow or allow cross domain redirection. + */ + public void setVideoUri(Uri uri, Map headers) { + mProvider.setVideoUri_impl(uri, headers); + } + + /** + * Selects which view will be used to render video between SurfacView and TextureView. + * + * @param viewType the view type to render video + *

          + *
        • {@link #VIEW_TYPE_SURFACEVIEW} + *
        • {@link #VIEW_TYPE_TEXTUREVIEW} + *
        + */ + public void setViewType(@ViewType int viewType) { + mProvider.setViewType_impl(viewType); + } + + /** + * Returns view type. + * + * @return view type. See {@see setViewType}. + */ + @ViewType + public int getViewType() { + return mProvider.getViewType_impl(); + } + + /** + * Sets custom actions which will be shown as custom buttons in {@link MediaControlView2}. + * + * @param actionList A list of {@link PlaybackState.CustomAction}. The return value of + * {@link PlaybackState.CustomAction#getIcon()} will be used to draw buttons + * in {@link MediaControlView2}. + * @param executor executor to run callbacks on. + * @param listener A listener to be called when a custom button is clicked. + */ + public void setCustomActions(List actionList, + Executor executor, OnCustomActionListener listener) { + mProvider.setCustomActions_impl(actionList, executor, listener); + } + + /** + * Registers a callback to be invoked when the media file is loaded and ready to go. + * + * @param executor executor to run callbacks on. + * @param l the callback that will be run. + */ + public void setOnPreparedListener(Executor executor, OnPreparedListener l) { + mProvider.setOnPreparedListener_impl(executor, l); + } + + /** + * Registers a callback to be invoked when the end of a media file has been reached during + * playback. + * + * @param executor executor to run callbacks on. + * @param l the callback that will be run. + */ + public void setOnCompletionListener(Executor executor, OnCompletionListener l) { + mProvider.setOnCompletionListener_impl(executor, l); + } + + /** + * Registers a callback to be invoked when an error occurs during playback or setup. If no + * listener is specified, or if the listener returned false, VideoView2 will inform the user of + * any errors. + * + * @param executor executor to run callbacks on. + * @param l The callback that will be run + */ + public void setOnErrorListener(Executor executor, OnErrorListener l) { + mProvider.setOnErrorListener_impl(executor, l); + } + + /** + * Registers a callback to be invoked when an informational event occurs during playback or + * setup. + * + * @param executor executor to run callbacks on. + * @param l The callback that will be run + */ + public void setOnInfoListener(Executor executor, OnInfoListener l) { + mProvider.setOnInfoListener_impl(executor, l); + } + + /** + * Registers a callback to be invoked when a view type change is done. + * {@see #setViewType(int)} + * @param executor executor to run callbacks on. + * @param l The callback that will be run + */ + public void setOnViewTypeChangedListener(Executor executor, OnViewTypeChangedListener l) { + mProvider.setOnViewTypeChangedListener_impl(executor, l); + } + + /** + * Registers a callback to be invoked when the fullscreen mode should be changed. + * @param executor executor to run callbacks on. + * @param l The callback that will be run + */ + public void setFullScreenRequestListener(Executor executor, OnFullScreenRequestListener l) { + mProvider.setFullScreenRequestListener_impl(executor, l); + } + + /** + * Interface definition of a callback to be invoked when the view type has been changed. + */ + public interface OnViewTypeChangedListener { + /** + * Called when the view type has been changed. + * @see #setViewType(int) + * @param view the View whose view type is changed + * @param viewType + *
          + *
        • {@link #VIEW_TYPE_SURFACEVIEW} + *
        • {@link #VIEW_TYPE_TEXTUREVIEW} + *
        + */ + void onViewTypeChanged(View view, @ViewType int viewType); + } + + /** + * Interface definition of a callback to be invoked when the media source is ready for playback. + */ + public interface OnPreparedListener { + /** + * Called when the media file is ready for playback. + */ + void onPrepared(View view); + } + + /** + * Interface definition for a callback to be invoked when playback of a media source has + * completed. + */ + public interface OnCompletionListener { + /** + * Called when the end of a media source is reached during playback. + */ + void onCompletion(View view); + } + + /** + * Interface definition of a callback to be invoked when there has been an error during an + * asynchronous operation. + */ + public interface OnErrorListener { + // TODO: Redefine error codes. + /** + * Called to indicate an error. + * @param what the type of error that has occurred + * @param extra an extra code, specific to the error. + * @return true if the method handled the error, false if it didn't. + * @see MediaPlayer#OnErrorListener + */ + boolean onError(View view, int what, int extra); + } + + /** + * Interface definition of a callback to be invoked to communicate some info and/or warning + * about the media or its playback. + */ + public interface OnInfoListener { + /** + * Called to indicate an info or a warning. + * @param what the type of info or warning. + * @param extra an extra code, specific to the info. + * + * @see MediaPlayer#OnInfoListener + */ + void onInfo(View view, int what, int extra); + } + + /** + * Interface definition of a callback to be invoked to inform the fullscreen mode is changed. + * Application should handle the fullscreen mode accordingly. + */ + public interface OnFullScreenRequestListener { + /** + * Called to indicate a fullscreen mode change. + */ + void onFullScreenRequest(View view, boolean fullScreen); + } + + /** + * Interface definition of a callback to be invoked to inform that a custom action is performed. + */ + // TODO: When MediaSession2 is ready, modify the method to match the signature. + public interface OnCustomActionListener { + /** + * Called to indicate that a custom action is performed. + * + * @param action The action that was originally sent in the + * {@link PlaybackState.CustomAction}. + * @param extras Optional extras. + */ + void onCustomAction(String action, Bundle extras); + } + + @Override + protected void onAttachedToWindow() { + mProvider.onAttachedToWindow_impl(); + } + + @Override + protected void onDetachedFromWindow() { + mProvider.onDetachedFromWindow_impl(); + } + + @Override + public CharSequence getAccessibilityClassName() { + return mProvider.getAccessibilityClassName_impl(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mProvider.onTouchEvent_impl(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + return mProvider.onTrackballEvent_impl(ev); + } + + @Override + public void onFinishInflate() { + mProvider.onFinishInflate_impl(); + } + + @Override + public void setEnabled(boolean enabled) { + mProvider.setEnabled_impl(enabled); + } + + private class SuperProvider implements ViewProvider { + @Override + public void onAttachedToWindow_impl() { + VideoView2.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + VideoView2.super.onDetachedFromWindow(); + } + + @Override + public CharSequence getAccessibilityClassName_impl() { + return VideoView2.super.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return VideoView2.super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return VideoView2.super.onTrackballEvent(ev); + } + + @Override + public void onFinishInflate_impl() { + VideoView2.super.onFinishInflate(); + } + + @Override + public void setEnabled_impl(boolean enabled) { + VideoView2.super.setEnabled(enabled); + } + } +} diff --git a/core/java/com/android/internal/app/EventLogTags.logtags b/core/java/com/android/internal/app/EventLogTags.logtags new file mode 100644 index 0000000000000000000000000000000000000000..d681a8d26e8ee2bea67073b12988fa5ed3513bc6 --- /dev/null +++ b/core/java/com/android/internal/app/EventLogTags.logtags @@ -0,0 +1,6 @@ +# See system/core/logcat/event.logtags for a description of the format of this file. + +option java_package com.android.internal.app; + +53000 harmful_app_warning_uninstall (package_name|3) +53001 harmful_app_warning_launch_anyway (package_name|3) \ No newline at end of file diff --git a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java index 042da36c98f1c6342ee96b712a7bab0ddaefd7ff..99666264cb2200dabdeaac9beda3fab38cd81dc8 100644 --- a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java +++ b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java @@ -20,8 +20,12 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Bundle; import android.util.Log; +import android.view.View; +import android.widget.TextView; import com.android.internal.R; /** @@ -31,7 +35,7 @@ import com.android.internal.R; */ public class HarmfulAppWarningActivity extends AlertActivity implements DialogInterface.OnClickListener { - private static final String TAG = "HarmfulAppWarningActivity"; + private static final String TAG = HarmfulAppWarningActivity.class.getSimpleName(); private static final String EXTRA_HARMFUL_APP_WARNING = "harmful_app_warning"; @@ -39,13 +43,11 @@ public class HarmfulAppWarningActivity extends AlertActivity implements private String mHarmfulAppWarning; private IntentSender mTarget; - // [b/63909431] STOPSHIP replace placeholder UI with final Harmful App Warning UI - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Intent intent = getIntent(); + final Intent intent = getIntent(); mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT); mHarmfulAppWarning = intent.getStringExtra(EXTRA_HARMFUL_APP_WARNING); @@ -55,33 +57,56 @@ public class HarmfulAppWarningActivity extends AlertActivity implements finish(); } - AlertController.AlertParams p = mAlertParams; + final ApplicationInfo applicationInfo; + try { + applicationInfo = getPackageManager().getApplicationInfo(mPackageName, 0 /*flags*/); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Could not show warning because package does not exist ", e); + finish(); + return; + } + + final AlertController.AlertParams p = mAlertParams; p.mTitle = getString(R.string.harmful_app_warning_title); - p.mMessage = mHarmfulAppWarning; - p.mPositiveButtonText = getString(R.string.harmful_app_warning_launch_anyway); + p.mView = createView(applicationInfo); + + p.mPositiveButtonText = getString(R.string.harmful_app_warning_uninstall); p.mPositiveButtonListener = this; - p.mNegativeButtonText = getString(R.string.harmful_app_warning_uninstall); + p.mNegativeButtonText = getString(R.string.harmful_app_warning_open_anyway); p.mNegativeButtonListener = this; mAlert.installContent(mAlertParams); } + private View createView(ApplicationInfo applicationInfo) { + final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog, + null /*root*/); + ((TextView) view.findViewById(R.id.app_name_text)) + .setText(applicationInfo.loadSafeLabel(getPackageManager())); + ((TextView) view.findViewById(R.id.message)) + .setText(mHarmfulAppWarning); + return view; + } + @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: - getPackageManager().setHarmfulAppWarning(mPackageName, null); + getPackageManager().deletePackage(mPackageName, null /*observer*/, 0 /*flags*/); + EventLogTags.writeHarmfulAppWarningUninstall(mPackageName); + finish(); + break; + case DialogInterface.BUTTON_NEGATIVE: + getPackageManager().setHarmfulAppWarning(mPackageName, null /*warning*/); - IntentSender target = getIntent().getParcelableExtra(Intent.EXTRA_INTENT); + final IntentSender target = getIntent().getParcelableExtra(Intent.EXTRA_INTENT); try { - startIntentSenderForResult(target, -1, null, 0, 0, 0); + startIntentSenderForResult(target, -1 /*requestCode*/, null /*fillInIntent*/, + 0 /*flagsMask*/, 0 /*flagsValue*/, 0 /*extraFlags*/); } catch (IntentSender.SendIntentException e) { - // ignore.. + Log.e(TAG, "Error while starting intent sender", e); } - finish(); - break; - case DialogInterface.BUTTON_NEGATIVE: - getPackageManager().deletePackage(mPackageName, null, 0); + EventLogTags.writeHarmfulAppWarningLaunchAnyway(mPackageName); finish(); break; } @@ -89,7 +114,7 @@ public class HarmfulAppWarningActivity extends AlertActivity implements public static Intent createHarmfulAppWarningIntent(Context context, String targetPackageName, IntentSender target, CharSequence harmfulAppWarning) { - Intent intent = new Intent(); + final Intent intent = new Intent(); intent.setClass(context, HarmfulAppWarningActivity.class); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName); intent.putExtra(Intent.EXTRA_INTENT, target); diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 388180dc61e090957db4eb1f0e85d7505937d608..514ff76372a93c5cc3ae1d0c99a6e9061b318f57 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -23,6 +23,8 @@ import android.net.wifi.WifiActivityEnergyInfo; import android.os.ParcelFileDescriptor; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; +import android.os.connectivity.WifiBatteryStats; +import android.os.connectivity.GpsBatteryStats; import android.os.health.HealthStatsParceler; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; @@ -89,8 +91,8 @@ interface IBatteryStats { void noteVibratorOn(int uid, long durationMillis); void noteVibratorOff(int uid); - void noteStartGps(int uid); - void noteStopGps(int uid); + void noteGpsChanged(in WorkSource oldSource, in WorkSource newSource); + void noteGpsSignalQuality(int signalLevel); void noteScreenState(int state); void noteScreenBrightness(int brightness); void noteUserActivity(int uid, int event); @@ -140,6 +142,12 @@ interface IBatteryStats { /** {@hide} */ CellularBatteryStats getCellularBatteryStats(); + /** {@hide} */ + WifiBatteryStats getWifiBatteryStats(); + + /** {@hide} */ + GpsBatteryStats getGpsBatteryStats(); + HealthStatsParceler takeUidSnapshot(int uid); HealthStatsParceler[] takeUidSnapshots(in int[] uid); diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java index 2b0b5eec6c56c461415ee4a642629f26736e513a..d24701372453f0fc857b51bf0c6aa3464b9e6dfc 100644 --- a/core/java/com/android/internal/app/LocaleStore.java +++ b/core/java/com/android/internal/app/LocaleStore.java @@ -146,7 +146,11 @@ public class LocaleStore { private String getLangScriptKey() { if (mLangScriptKey == null) { - Locale parentWithScript = getParent(LocaleHelper.addLikelySubtags(mLocale)); + Locale baseLocale = new Locale.Builder() + .setLocale(mLocale) + .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "") + .build(); + Locale parentWithScript = getParent(LocaleHelper.addLikelySubtags(baseLocale)); mLangScriptKey = (parentWithScript == null) ? mLocale.toLanguageTag() diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 2ab2d20ed20de666c266077e49fd9e62c2d2d02e..1dfff5efcab524188f675e2a22ce7e0518cd248c 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -103,11 +103,14 @@ public class ResolverListController { List resolvedComponents = null; for (int i = 0, N = intents.size(); i < N; i++) { final Intent intent = intents.get(i); - final List infos = mpm.queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0) - | PackageManager.MATCH_INSTANT); + int flags = PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); + if (intent.isBrowsableWebIntent() + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { + flags |= PackageManager.MATCH_INSTANT; + } + final List infos = mpm.queryIntentActivities(intent, flags); // Remove any activities that are not exported. int totalSize = infos.size(); for (int j = totalSize - 1; j >= 0 ; j--) { diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java index 0bc8c483313dada6118afd3d985389e86bdc02fc..e38a844fe8fed3bf40666f51b7a5e46ad0583b54 100644 --- a/core/java/com/android/internal/app/procstats/DumpUtils.java +++ b/core/java/com/android/internal/app/procstats/DumpUtils.java @@ -21,6 +21,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.service.procstats.ProcessStatsProto; import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.ArraySet; @@ -46,12 +47,77 @@ import java.util.Objects; * Utilities for dumping. */ public final class DumpUtils { - public static final String[] STATE_NAMES = new String[] { - "Persist", "Top ", "ImpFg ", "ImpBg ", - "Backup ", "HeavyWt", "Service", "ServRst", - "Receivr", "Home ", - "LastAct", "CchAct ", "CchCAct", "CchEmty" - }; + public static final String[] STATE_NAMES; + public static final String[] STATE_NAMES_CSV; + static final String[] STATE_TAGS; + static final int[] STATE_PROTO_ENUMS; + + // Make the mapping easy to update. + static { + STATE_NAMES = new String[STATE_COUNT]; + STATE_NAMES[STATE_PERSISTENT] = "Persist"; + STATE_NAMES[STATE_TOP] = "Top"; + STATE_NAMES[STATE_IMPORTANT_FOREGROUND] = "ImpFg"; + STATE_NAMES[STATE_IMPORTANT_BACKGROUND] = "ImpBg"; + STATE_NAMES[STATE_BACKUP] = "Backup"; + STATE_NAMES[STATE_SERVICE] = "Service"; + STATE_NAMES[STATE_SERVICE_RESTARTING] = "ServRst"; + STATE_NAMES[STATE_RECEIVER] = "Receivr"; + STATE_NAMES[STATE_HEAVY_WEIGHT] = "HeavyWt"; + STATE_NAMES[STATE_HOME] = "Home"; + STATE_NAMES[STATE_LAST_ACTIVITY] = "LastAct"; + STATE_NAMES[STATE_CACHED_ACTIVITY] = "CchAct"; + STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT] = "CchCAct"; + STATE_NAMES[STATE_CACHED_EMPTY] = "CchEmty"; + + STATE_NAMES_CSV = new String[STATE_COUNT]; + STATE_NAMES_CSV[STATE_PERSISTENT] = "pers"; + STATE_NAMES_CSV[STATE_TOP] = "top"; + STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND] = "impfg"; + STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND] = "impbg"; + STATE_NAMES_CSV[STATE_BACKUP] = "backup"; + STATE_NAMES_CSV[STATE_SERVICE] = "service"; + STATE_NAMES_CSV[STATE_SERVICE_RESTARTING] = "service-rs"; + STATE_NAMES_CSV[STATE_RECEIVER] = "receiver"; + STATE_NAMES_CSV[STATE_HEAVY_WEIGHT] = "heavy"; + STATE_NAMES_CSV[STATE_HOME] = "home"; + STATE_NAMES_CSV[STATE_LAST_ACTIVITY] = "lastact"; + STATE_NAMES_CSV[STATE_CACHED_ACTIVITY] = "cch-activity"; + STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT] = "cch-aclient"; + STATE_NAMES_CSV[STATE_CACHED_EMPTY] = "cch-empty"; + + STATE_TAGS = new String[STATE_COUNT]; + STATE_TAGS[STATE_PERSISTENT] = "p"; + STATE_TAGS[STATE_TOP] = "t"; + STATE_TAGS[STATE_IMPORTANT_FOREGROUND] = "f"; + STATE_TAGS[STATE_IMPORTANT_BACKGROUND] = "b"; + STATE_TAGS[STATE_BACKUP] = "u"; + STATE_TAGS[STATE_SERVICE] = "s"; + STATE_TAGS[STATE_SERVICE_RESTARTING] = "x"; + STATE_TAGS[STATE_RECEIVER] = "r"; + STATE_TAGS[STATE_HEAVY_WEIGHT] = "w"; + STATE_TAGS[STATE_HOME] = "h"; + STATE_TAGS[STATE_LAST_ACTIVITY] = "l"; + STATE_TAGS[STATE_CACHED_ACTIVITY] = "a"; + STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT] = "c"; + STATE_TAGS[STATE_CACHED_EMPTY] = "e"; + + STATE_PROTO_ENUMS = new int[STATE_COUNT]; + STATE_PROTO_ENUMS[STATE_PERSISTENT] = ProcessStatsProto.State.PERSISTENT; + STATE_PROTO_ENUMS[STATE_TOP] = ProcessStatsProto.State.TOP; + STATE_PROTO_ENUMS[STATE_IMPORTANT_FOREGROUND] = ProcessStatsProto.State.IMPORTANT_FOREGROUND; + STATE_PROTO_ENUMS[STATE_IMPORTANT_BACKGROUND] = ProcessStatsProto.State.IMPORTANT_BACKGROUND; + STATE_PROTO_ENUMS[STATE_BACKUP] = ProcessStatsProto.State.BACKUP; + STATE_PROTO_ENUMS[STATE_SERVICE] = ProcessStatsProto.State.SERVICE; + STATE_PROTO_ENUMS[STATE_SERVICE_RESTARTING] = ProcessStatsProto.State.SERVICE_RESTARTING; + STATE_PROTO_ENUMS[STATE_RECEIVER] = ProcessStatsProto.State.RECEIVER; + STATE_PROTO_ENUMS[STATE_HEAVY_WEIGHT] = ProcessStatsProto.State.HEAVY_WEIGHT; + STATE_PROTO_ENUMS[STATE_HOME] = ProcessStatsProto.State.HOME; + STATE_PROTO_ENUMS[STATE_LAST_ACTIVITY] = ProcessStatsProto.State.LAST_ACTIVITY; + STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY] = ProcessStatsProto.State.CACHED_ACTIVITY; + STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY_CLIENT] = ProcessStatsProto.State.CACHED_ACTIVITY_CLIENT; + STATE_PROTO_ENUMS[STATE_CACHED_EMPTY] = ProcessStatsProto.State.CACHED_EMPTY; + } public static final String[] ADJ_SCREEN_NAMES_CSV = new String[] { "off", "on" @@ -61,25 +127,26 @@ public final class DumpUtils { "norm", "mod", "low", "crit" }; - public static final String[] STATE_NAMES_CSV = new String[] { - "pers", "top", "impfg", "impbg", "backup", "heavy", - "service", "service-rs", "receiver", "home", "lastact", - "cch-activity", "cch-aclient", "cch-empty" - }; - // State enum is defined in frameworks/base/core/proto/android/service/procstats.proto // Update states must sync enum definition as well, the ordering must not be changed. static final String[] ADJ_SCREEN_TAGS = new String[] { "0", "1" }; + static final int[] ADJ_SCREEN_PROTO_ENUMS = new int[] { + ProcessStatsProto.State.OFF, + ProcessStatsProto.State.ON + }; + static final String[] ADJ_MEM_TAGS = new String[] { "n", "m", "l", "c" }; - static final String[] STATE_TAGS = new String[] { - "p", "t", "f", "b", "u", "w", - "s", "x", "r", "h", "l", "a", "c", "e" + static final int[] ADJ_MEM_PROTO_ENUMS = new int[] { + ProcessStatsProto.State.NORMAL, + ProcessStatsProto.State.MODERATE, + ProcessStatsProto.State.LOW, + ProcessStatsProto.State.CRITICAL }; static final String CSV_SEP = "\t"; @@ -182,9 +249,10 @@ public final class DumpUtils { public static void printProcStateTagProto(ProtoOutputStream proto, long screenId, long memId, long stateId, int state) { - state = printProto(proto, screenId, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD * STATE_COUNT); - state = printProto(proto, memId, ADJ_MEM_TAGS, state, STATE_COUNT); - printProto(proto, stateId, STATE_TAGS, state, 1); + state = printProto(proto, screenId, ADJ_SCREEN_PROTO_ENUMS, + state, ADJ_SCREEN_MOD * STATE_COUNT); + state = printProto(proto, memId, ADJ_MEM_PROTO_ENUMS, state, STATE_COUNT); + printProto(proto, stateId, STATE_PROTO_ENUMS, state, 1); } public static void printAdjTag(PrintWriter pw, int state) { @@ -294,30 +362,6 @@ public final class DumpUtils { } } - /* - * Doesn't seem to be used. - * - public static void dumpProcessList(PrintWriter pw, String prefix, ArrayList procs, - int[] screenStates, int[] memStates, int[] procStates, long now) { - String innerPrefix = prefix + " "; - for (int i=procs.size()-1; i>=0; i--) { - ProcessState proc = procs.get(i); - pw.print(prefix); - pw.print(proc.mName); - pw.print(" / "); - UserHandle.formatUid(pw, proc.mUid); - pw.print(" ("); - pw.print(proc.durations.getKeyCount()); - pw.print(" entries)"); - pw.println(":"); - proc.dumpProcessState(pw, innerPrefix, screenStates, memStates, procStates, now); - if (proc.pssTable.getKeyCount() > 0) { - proc.dumpPss(pw, innerPrefix, screenStates, memStates, procStates); - } - } - } - */ - public static void dumpProcessSummaryLocked(PrintWriter pw, String prefix, ArrayList procs, int[] screenStates, int[] memStates, int[] procStates, long now, long totalTime) { @@ -362,11 +406,11 @@ public final class DumpUtils { return value - index*mod; } - public static int printProto(ProtoOutputStream proto, long fieldId, String[] array, int value, int mod) { + public static int printProto(ProtoOutputStream proto, long fieldId, + int[] enums, int value, int mod) { int index = value/mod; - if (index >= 0 && index < array.length) { - // Valid state enum number starts at 1, 0 stands for unknown. - proto.write(fieldId, index + 1); + if (index >= 0 && index < enums.length) { + proto.write(fieldId, enums[index]); } // else enum default is always zero in proto3 return value - index*mod; } diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index a0be64b85d1aa6bc3a970e0bf9ea161409fc63cc..65bd48f99cbd7f2929c20123bd4559b4218e0b82 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -17,25 +17,18 @@ package com.android.internal.app.procstats; import android.os.Parcel; -import android.os.Parcelable; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; -import android.service.pm.PackageProto; import android.service.procstats.ProcessStatsProto; -import android.text.format.DateFormat; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.DebugUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; -import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoUtils; -import com.android.internal.app.procstats.ProcessStats; import com.android.internal.app.procstats.ProcessStats.PackageState; import com.android.internal.app.procstats.ProcessStats.ProcessStateHolder; import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection; @@ -43,6 +36,9 @@ import static com.android.internal.app.procstats.ProcessStats.PSS_SAMPLE_COUNT; import static com.android.internal.app.procstats.ProcessStats.PSS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE; import static com.android.internal.app.procstats.ProcessStats.PSS_MAXIMUM; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MINIMUM; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_AVERAGE; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MAXIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_AVERAGE; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM; @@ -64,20 +60,10 @@ import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIV import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY; import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT; -import dalvik.system.VMRuntime; -import libcore.util.EmptyArray; - -import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Objects; public final class ProcessState { private static final String TAG = "ProcessStats"; @@ -469,13 +455,21 @@ public final class ProcessState { } } - public void addPss(long pss, long uss, boolean always, int type, long duration, + public void addPss(long pss, long uss, long rss, boolean always, int type, long duration, ArrayMap pkgList) { ensureNotDead(); switch (type) { - case ProcessStats.ADD_PSS_INTERNAL: - mStats.mInternalPssCount++; - mStats.mInternalPssTime += duration; + case ProcessStats.ADD_PSS_INTERNAL_SINGLE: + mStats.mInternalSinglePssCount++; + mStats.mInternalSinglePssTime += duration; + break; + case ProcessStats.ADD_PSS_INTERNAL_ALL_MEM: + mStats.mInternalAllMemPssCount++; + mStats.mInternalAllMemPssTime += duration; + break; + case ProcessStats.ADD_PSS_INTERNAL_ALL_POLL: + mStats.mInternalAllPollPssCount++; + mStats.mInternalAllPollPssTime += duration; break; case ProcessStats.ADD_PSS_EXTERNAL: mStats.mExternalPssCount++; @@ -496,7 +490,8 @@ public final class ProcessState { mLastPssTime = SystemClock.uptimeMillis(); if (mCurState != STATE_NOTHING) { // First update the common process. - mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss); + mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss, + rss, rss, rss); // If the common process is not multi-package, there is nothing else to do. if (!mCommonProcess.mMultiPackage) { @@ -506,7 +501,7 @@ public final class ProcessState { if (pkgList != null) { for (int ip=pkgList.size()-1; ip>=0; ip--) { pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurState, 1, - pss, pss, pss, uss, uss, uss); + pss, pss, pss, uss, uss, uss, rss, rss, rss); } } } @@ -658,6 +653,18 @@ public final class ProcessState { return mPssTable.getValueForId((byte)state, PSS_USS_MAXIMUM); } + public long getPssRssMinimum(int state) { + return mPssTable.getValueForId((byte)state, PSS_RSS_MINIMUM); + } + + public long getPssRssAverage(int state) { + return mPssTable.getValueForId((byte)state, PSS_RSS_AVERAGE); + } + + public long getPssRssMaximum(int state) { + return mPssTable.getValueForId((byte)state, PSS_RSS_MAXIMUM); + } + /** * Sums up the PSS data and adds it to 'data'. * @@ -793,6 +800,8 @@ public final class ProcessState { new int[] {STATE_SERVICE_RESTARTING}, now, totalTime, true); dumpProcessSummaryDetails(pw, prefix, " Receiver: ", screenStates, memStates, new int[] {STATE_RECEIVER}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, " Heavy: ", screenStates, memStates, + new int[] {STATE_HOME}, now, totalTime, true); dumpProcessSummaryDetails(pw, prefix, " (Home): ", screenStates, memStates, new int[] {STATE_HOME}, now, totalTime, true); dumpProcessSummaryDetails(pw, prefix, " (Last Act): ", screenStates, memStates, @@ -897,6 +906,12 @@ public final class ProcessState { DebugUtils.printSizeValue(pw, getPssUssAverage(bucket) * 1024); pw.print(" "); DebugUtils.printSizeValue(pw, getPssUssMaximum(bucket) * 1024); + pw.print(" / "); + DebugUtils.printSizeValue(pw, getPssRssMinimum(bucket) * 1024); + pw.print(" "); + DebugUtils.printSizeValue(pw, getPssRssAverage(bucket) * 1024); + pw.print(" "); + DebugUtils.printSizeValue(pw, getPssRssMaximum(bucket) * 1024); pw.println(); } } @@ -969,7 +984,8 @@ public final class ProcessState { public void computeProcessData(ProcessStats.ProcessDataCollection data, long now) { data.totalTime = 0; data.numPss = data.minPss = data.avgPss = data.maxPss = - data.minUss = data.avgUss = data.maxUss = 0; + data.minUss = data.avgUss = data.maxUss = + data.minRss = data.avgRss = data.maxRss = 0; for (int is=0; is data.maxUss) { data.maxUss = maxUss; } + if (minRss < data.minRss) { + data.minRss = minRss; + } + data.avgRss = (long)( ((data.avgRss*(double)data.numPss) + + (avgRss*(double)samples)) / (data.numPss+samples) ); + if (maxRss > data.maxRss) { + data.maxRss = maxRss; + } } data.numPss += samples; } @@ -1176,6 +1206,12 @@ public final class ProcessState { pw.print(mPssTable.getValue(key, PSS_USS_AVERAGE)); pw.print(':'); pw.print(mPssTable.getValue(key, PSS_USS_MAXIMUM)); + pw.print(':'); + pw.print(mPssTable.getValue(key, PSS_RSS_MINIMUM)); + pw.print(':'); + pw.print(mPssTable.getValue(key, PSS_RSS_AVERAGE)); + pw.print(':'); + pw.print(mPssTable.getValue(key, PSS_RSS_MAXIMUM)); } } @@ -1191,7 +1227,9 @@ public final class ProcessState { return sb.toString(); } - public void toProto(ProtoOutputStream proto, String procName, int uid, long now) { + public void writeToProto(ProtoOutputStream proto, long fieldId, + String procName, int uid, long now) { + final long token = proto.start(fieldId); proto.write(ProcessStatsProto.PROCESS, procName); proto.write(ProcessStatsProto.UID, uid); if (mNumExcessiveCpu > 0 || mNumCachedKill > 0 ) { @@ -1247,6 +1285,10 @@ public final class ProcessState { mPssTable.getValue(key, PSS_USS_MINIMUM), mPssTable.getValue(key, PSS_USS_AVERAGE), mPssTable.getValue(key, PSS_USS_MAXIMUM)); + ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.RSS, + mPssTable.getValue(key, PSS_RSS_MINIMUM), + mPssTable.getValue(key, PSS_RSS_AVERAGE), + mPssTable.getValue(key, PSS_RSS_MAXIMUM)); proto.end(stateToken); } @@ -1261,5 +1303,6 @@ public final class ProcessState { proto.write(ProcessStatsProto.State.DURATION_MS, entry.getValue()); proto.end(stateToken); } + proto.end(token); } } diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 35b2dd23a8315d5ec02338d17416cdd1ac723b10..d35bdddb3c57e125559dece2843fdf9771bd9a05 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -27,7 +27,6 @@ import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; -import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; @@ -35,16 +34,8 @@ import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import com.android.internal.app.ProcessMap; -import com.android.internal.app.procstats.DurationsTable; -import com.android.internal.app.procstats.ProcessState; -import com.android.internal.app.procstats.PssTable; -import com.android.internal.app.procstats.ServiceState; -import com.android.internal.app.procstats.SparseMappingTable; -import com.android.internal.app.procstats.SysMemUsageTable; -import com.android.internal.app.procstats.DumpUtils.*; import dalvik.system.VMRuntime; -import libcore.util.EmptyArray; import java.io.BufferedReader; import java.io.FileReader; @@ -54,7 +45,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.Objects; import java.util.regex.Pattern; import java.util.regex.Matcher; @@ -99,7 +89,10 @@ public final class ProcessStats implements Parcelable { public static final int PSS_USS_MINIMUM = 4; public static final int PSS_USS_AVERAGE = 5; public static final int PSS_USS_MAXIMUM = 6; - public static final int PSS_COUNT = PSS_USS_MAXIMUM+1; + public static final int PSS_RSS_MINIMUM = 7; + public static final int PSS_RSS_AVERAGE = 8; + public static final int PSS_RSS_MAXIMUM = 9; + public static final int PSS_COUNT = PSS_RSS_MAXIMUM+1; public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0; public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1; @@ -134,9 +127,11 @@ public final class ProcessStats implements Parcelable { public static final int FLAG_SHUTDOWN = 1<<1; public static final int FLAG_SYSPROPS = 1<<2; - public static final int ADD_PSS_INTERNAL = 0; - public static final int ADD_PSS_EXTERNAL = 1; - public static final int ADD_PSS_EXTERNAL_SLOW = 2; + public static final int ADD_PSS_INTERNAL_SINGLE = 0; + public static final int ADD_PSS_INTERNAL_ALL_MEM = 1; + public static final int ADD_PSS_INTERNAL_ALL_POLL = 2; + public static final int ADD_PSS_EXTERNAL = 3; + public static final int ADD_PSS_EXTERNAL_SLOW = 4; public static final int[] ALL_MEM_ADJ = new int[] { ADJ_MEM_FACTOR_NORMAL, ADJ_MEM_FACTOR_MODERATE, ADJ_MEM_FACTOR_LOW, ADJ_MEM_FACTOR_CRITICAL }; @@ -162,7 +157,7 @@ public final class ProcessStats implements Parcelable { }; // Current version of the parcel format. - private static final int PARCEL_VERSION = 24; + private static final int PARCEL_VERSION = 27; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535454; @@ -187,9 +182,17 @@ public final class ProcessStats implements Parcelable { boolean mHasSwappedOutPss; - // Count and total time expended doing "quick" pss computations for internal use. - public long mInternalPssCount; - public long mInternalPssTime; + // Count and total time expended doing "quick" single pss computations for internal use. + public long mInternalSinglePssCount; + public long mInternalSinglePssTime; + + // Count and total time expended doing "quick" all mem pss computations for internal use. + public long mInternalAllMemPssCount; + public long mInternalAllMemPssTime; + + // Count and total time expended doing "quick" all poll pss computations for internal use. + public long mInternalAllPollPssCount; + public long mInternalAllPollPssTime; // Count and total time expended doing "quick" pss computations due to external requests. public long mExternalPssCount; @@ -318,8 +321,12 @@ public final class ProcessStats implements Parcelable { mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime; mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime; - mInternalPssCount += other.mInternalPssCount; - mInternalPssTime += other.mInternalPssTime; + mInternalSinglePssCount += other.mInternalSinglePssCount; + mInternalSinglePssTime += other.mInternalSinglePssTime; + mInternalAllMemPssCount += other.mInternalAllMemPssCount; + mInternalAllMemPssTime += other.mInternalAllMemPssTime; + mInternalAllPollPssCount += other.mInternalAllPollPssCount; + mInternalAllPollPssTime += other.mInternalAllPollPssTime; mExternalPssCount += other.mExternalPssCount; mExternalPssTime += other.mExternalPssTime; mExternalSlowPssCount += other.mExternalSlowPssCount; @@ -523,8 +530,12 @@ public final class ProcessStats implements Parcelable { buildTimePeriodStartClockStr(); mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis(); - mInternalPssCount = 0; - mInternalPssTime = 0; + mInternalSinglePssCount = 0; + mInternalSinglePssTime = 0; + mInternalAllMemPssCount = 0; + mInternalAllMemPssTime = 0; + mInternalAllPollPssCount = 0; + mInternalAllPollPssTime = 0; mExternalPssCount = 0; mExternalPssTime = 0; mExternalSlowPssCount = 0; @@ -789,8 +800,12 @@ public final class ProcessStats implements Parcelable { out.writeLong(mTimePeriodEndRealtime); out.writeLong(mTimePeriodStartUptime); out.writeLong(mTimePeriodEndUptime); - out.writeLong(mInternalPssCount); - out.writeLong(mInternalPssTime); + out.writeLong(mInternalSinglePssCount); + out.writeLong(mInternalSinglePssTime); + out.writeLong(mInternalAllMemPssCount); + out.writeLong(mInternalAllMemPssTime); + out.writeLong(mInternalAllPollPssCount); + out.writeLong(mInternalAllPollPssTime); out.writeLong(mExternalPssCount); out.writeLong(mExternalPssTime); out.writeLong(mExternalSlowPssCount); @@ -963,8 +978,12 @@ public final class ProcessStats implements Parcelable { mTimePeriodEndRealtime = in.readLong(); mTimePeriodStartUptime = in.readLong(); mTimePeriodEndUptime = in.readLong(); - mInternalPssCount = in.readLong(); - mInternalPssTime = in.readLong(); + mInternalSinglePssCount = in.readLong(); + mInternalSinglePssTime = in.readLong(); + mInternalAllMemPssCount = in.readLong(); + mInternalAllMemPssTime = in.readLong(); + mInternalAllPollPssCount = in.readLong(); + mInternalAllPollPssTime = in.readLong(); mExternalPssCount = in.readLong(); mExternalPssTime = in.readLong(); mExternalSlowPssCount = in.readLong(); @@ -1526,10 +1545,20 @@ public final class ProcessStats implements Parcelable { totalMem.processStateSamples[STATE_SERVICE_RESTARTING]); pw.println(); pw.println("PSS collection stats:"); - pw.print(" Internal: "); - pw.print(mInternalPssCount); + pw.print(" Internal Single: "); + pw.print(mInternalSinglePssCount); pw.print("x over "); - TimeUtils.formatDuration(mInternalPssTime, pw); + TimeUtils.formatDuration(mInternalSinglePssTime, pw); + pw.println(); + pw.print(" Internal All Procs (Memory Change): "); + pw.print(mInternalAllMemPssCount); + pw.print("x over "); + TimeUtils.formatDuration(mInternalAllMemPssTime, pw); + pw.println(); + pw.print(" Internal All Procs (Polling): "); + pw.print(mInternalAllPollPssCount); + pw.print("x over "); + TimeUtils.formatDuration(mInternalAllPollPssTime, pw); pw.println(); pw.print(" External: "); pw.print(mExternalPssCount); @@ -1776,10 +1805,11 @@ public final class ProcessStats implements Parcelable { } } - public void toProto(ProtoOutputStream proto, long now) { + public void writeToProto(ProtoOutputStream proto, long fieldId, long now) { final ArrayMap>> pkgMap = mPackages.getMap(); + final long token = proto.start(fieldId); proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime); proto.write(ProcessStatsSectionProto.END_REALTIME_MS, mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime); @@ -1811,11 +1841,10 @@ public final class ProcessStats implements Parcelable { for (int iu=0; iu minUss) { + setValue(key, PSS_RSS_MINIMUM, minUss); + } + + val = getValue(key, PSS_RSS_AVERAGE); + setValue(key, PSS_RSS_AVERAGE, + (long)(((val*(double)count)+(avgUss*(double)inCount)) / (count+inCount))); + + val = getValue(key, PSS_RSS_MAXIMUM); + if (val < maxUss) { + setValue(key, PSS_RSS_MAXIMUM, maxUss); + } } } } diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 147438cf47a50a92b1f858c047fa28c794962c95..f8117a7e92608145afed6907c138c2f64468a619 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -306,4 +306,13 @@ interface IBackupTransport { * operation will immediately be finished with no further attempts to restore app data. */ int abortFullRestore(); + + /** + * Returns flags with additional information about the transport, which is accessible to the + * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to backup or + * restore based on properties of the transport. + * + *

        For supported flags see {@link android.app.backup.BackupAgent}. + */ + int getTransportFlags(); } diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index e765ab1eae2fe07dec8dae4a92f8fb5e03c75f2d..8a456d1c6f5815f8ccd90a3cb700cc0d3b5fa062 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/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags index a440ee402294dcd477381f40c74b1b9ebc9f622e..693bd16e61707ba3bfce030f43dc7628956acbcc 100644 --- a/core/java/com/android/internal/logging/EventLogTags.logtags +++ b/core/java/com/android/internal/logging/EventLogTags.logtags @@ -13,3 +13,6 @@ option java_package com.android.internal.logging; # LatencyTracker.java # --------------------------- 36070 sysui_latency (action|1|6),(latency|1|3) + +# Generic event for logging when we write system files. +525000 commit_sys_config_file (name|3),(time|2|3) diff --git a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl index ee01a23af686fb4aa41db78571495db7ee32bde4..d69c7deab375ba4f40a921ad4c6823dca4c7cdcf 100644 --- a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl +++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl @@ -24,4 +24,5 @@ interface INetworkWatchlistManager { boolean stopWatchlistLogging(); void reloadWatchlist(); void reportWatchlistIfNecessary(); + byte[] getWatchlistConfigHash(); } diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index 5eda81baa3642f73ac0a1250824e91c0d2c479e3..43abadec0d188cd14a1397796cdc24b3201a8eb2 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -31,13 +31,17 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ProcFileReader; +import com.google.android.collect.Lists; import libcore.io.IoUtils; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; import java.net.ProtocolException; +import java.util.ArrayList; import java.util.Objects; /** @@ -57,6 +61,8 @@ public class NetworkStatsFactory { // Used for correct stats accounting on clatd interfaces. private static final int IPV4V6_HEADER_DELTA = 20; + /** Path to {@code /proc/net/dev}. */ + private final File mStatsIfaceDev; /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */ private final File mStatsXtIfaceAll; /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */ @@ -64,6 +70,8 @@ public class NetworkStatsFactory { /** Path to {@code /proc/net/xt_qtaguid/stats}. */ private final File mStatsXtUid; + private boolean mUseBpfStats; + // TODO: to improve testability and avoid global state, do not use a static variable. @GuardedBy("sStackedIfaces") private static final ArrayMap sStackedIfaces = new ArrayMap<>(); @@ -79,14 +87,54 @@ public class NetworkStatsFactory { } public NetworkStatsFactory() { - this(new File("/proc/")); + this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists()); } @VisibleForTesting - public NetworkStatsFactory(File procRoot) { + public NetworkStatsFactory(File procRoot, boolean useBpfStats) { + mStatsIfaceDev = new File(procRoot, "net/dev"); mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all"); mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt"); mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats"); + mUseBpfStats = useBpfStats; + } + + @VisibleForTesting + public NetworkStats readNetworkStatsIfaceDev() throws IOException { + final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); + + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(mStatsIfaceDev)); + + // skip first two header lines + reader.readLine(); + reader.readLine(); + + // parse remaining lines + String line; + while ((line = reader.readLine()) != null) { + String[] values = line.trim().split("\\:?\\s+"); + entry.iface = values[0]; + entry.uid = UID_ALL; + entry.set = SET_ALL; + entry.tag = TAG_NONE; + entry.rxBytes = Long.parseLong(values[1]); + entry.rxPackets = Long.parseLong(values[2]); + entry.txBytes = Long.parseLong(values[9]); + entry.txPackets = Long.parseLong(values[10]); + stats.addValues(entry); + } + } catch (NullPointerException|NumberFormatException e) { + throw new ProtocolException("problem parsing stats", e); + } finally { + IoUtils.closeQuietly(reader); + StrictMode.setThreadPolicy(savedPolicy); + } + return stats; } /** @@ -98,6 +146,11 @@ public class NetworkStatsFactory { * @throws IllegalStateException when problem parsing stats. */ public NetworkStats readNetworkStatsSummaryDev() throws IOException { + + // Return the stats get from /proc/net/dev if switched to bpf module. + if (mUseBpfStats) + return readNetworkStatsIfaceDev(); + final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); @@ -149,6 +202,11 @@ public class NetworkStatsFactory { * @throws IllegalStateException when problem parsing stats. */ public NetworkStats readNetworkStatsSummaryXt() throws IOException { + + // Return the stats get from /proc/net/dev if qtaguid module is replaced. + if (mUseBpfStats) + return readNetworkStatsIfaceDev(); + final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); // return null when kernel doesn't support @@ -219,7 +277,7 @@ public class NetworkStatsFactory { } NetworkStats.Entry adjust = - new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L, 0L, 0L); + new NetworkStats.Entry(baseIface, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L); // Subtract any 464lat traffic seen for the root UID on the current base interface. adjust.rxBytes -= (entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA); adjust.txBytes -= (entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA); @@ -254,7 +312,7 @@ public class NetworkStatsFactory { stats = new NetworkStats(SystemClock.elapsedRealtime(), -1); } if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, - limitIfaces, limitTag) != 0) { + limitIfaces, limitTag, mUseBpfStats) != 0) { throw new IOException("Failed to parse network stats"); } if (SANITY_CHECK_NATIVE) { @@ -348,6 +406,6 @@ public class NetworkStatsFactory { * are expected to monotonically increase since device boot. */ @VisibleForTesting - public static native int nativeReadNetworkStatsDetail( - NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag); + public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path, + int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats); } diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 15dc6f5070931f65bab208efb6327e7c07993b64..5a59e70865e5f17ed041a9b6603675892cbd37a5 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -665,14 +665,14 @@ public class BatteryStatsHelper { /** * Calculate the baseline power usage for the device when it is in suspend and idle. - * The device is drawing POWER_CPU_IDLE power at its lowest power state. - * The device is drawing POWER_CPU_IDLE + POWER_CPU_AWAKE power when a wakelock is held. + * The device is drawing POWER_CPU_SUSPEND power at its lowest power state. + * The device is drawing POWER_CPU_SUSPEND + POWER_CPU_IDLE power when a wakelock is held. */ private void addIdleUsage() { final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) * - mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE); + mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND); final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) * - mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE); + mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE); final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000); if (DEBUG && totalPowerMah != 0) { Log.d(TAG, "Suspend: time=" + (mTypeBatteryRealtimeUs / 1000) diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 439e5df7d352c186b40bc8e879f84765658249b8..7def87655ae373a6906918a5426865817759d582 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -34,14 +34,18 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Build; import android.os.connectivity.CellularBatteryStats; +import android.os.connectivity.WifiBatteryStats; +import android.os.connectivity.GpsBatteryStats; import android.os.FileUtils; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; import android.os.Looper; import android.os.Message; +import android.os.OsProtoEnums; import android.os.Parcel; import android.os.ParcelFormatException; import android.os.Parcelable; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -78,6 +82,7 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.location.gnssmetrics.GnssMetrics; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; @@ -129,7 +134,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 173 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 174 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS; @@ -153,11 +158,11 @@ public class BatteryStatsImpl extends BatteryStats { MAX_HISTORY_BUFFER = 96*1024; // 96KB MAX_MAX_HISTORY_BUFFER = 128*1024; // 128KB } else { - MAX_HISTORY_ITEMS = 2000; - MAX_MAX_HISTORY_ITEMS = 3000; - MAX_WAKELOCKS_PER_UID = 100; - MAX_HISTORY_BUFFER = 256*1024; // 256KB - MAX_MAX_HISTORY_BUFFER = 320*1024; // 256KB + MAX_HISTORY_ITEMS = 4000; + MAX_MAX_HISTORY_ITEMS = 6000; + MAX_WAKELOCKS_PER_UID = 200; + MAX_HISTORY_BUFFER = 512*1024; // 512KB + MAX_MAX_HISTORY_BUFFER = 640*1024; // 640KB } } @@ -198,6 +203,12 @@ public class BatteryStatsImpl extends BatteryStats { protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader(); @VisibleForTesting + protected KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader = + new KernelUidCpuActiveTimeReader(); + @VisibleForTesting + protected KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader = + new KernelUidCpuClusterTimeReader(); + @VisibleForTesting protected KernelSingleUidTimeReader mKernelSingleUidTimeReader; private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats @@ -666,6 +677,10 @@ public class BatteryStatsImpl extends BatteryStats { int mCameraOnNesting; StopwatchTimer mCameraOnTimer; + int mGpsSignalQualityBin = -1; + final StopwatchTimer[] mGpsSignalQualityTimer = + new StopwatchTimer[GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS]; + int mPhoneSignalStrengthBin = -1; int mPhoneSignalStrengthBinRaw = -1; final StopwatchTimer[] mPhoneSignalStrengthsTimer = @@ -739,6 +754,8 @@ public class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mWifiSignalStrengthsTimer = new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS]; + StopwatchTimer mWifiActiveTimer; + int mBluetoothScanNesting; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected StopwatchTimer mBluetoothScanTimer; @@ -2773,12 +2790,14 @@ public class BatteryStatsImpl extends BatteryStats { public static class ControllerActivityCounterImpl extends ControllerActivityCounter implements Parcelable { private final LongSamplingCounter mIdleTimeMillis; + private final LongSamplingCounter mScanTimeMillis; private final LongSamplingCounter mRxTimeMillis; private final LongSamplingCounter[] mTxTimeMillis; private final LongSamplingCounter mPowerDrainMaMs; public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates) { mIdleTimeMillis = new LongSamplingCounter(timeBase); + mScanTimeMillis = new LongSamplingCounter(timeBase); mRxTimeMillis = new LongSamplingCounter(timeBase); mTxTimeMillis = new LongSamplingCounter[numTxStates]; for (int i = 0; i < numTxStates; i++) { @@ -2789,6 +2808,7 @@ public class BatteryStatsImpl extends BatteryStats { public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates, Parcel in) { mIdleTimeMillis = new LongSamplingCounter(timeBase, in); + mScanTimeMillis = new LongSamplingCounter(timeBase, in); mRxTimeMillis = new LongSamplingCounter(timeBase, in); final int recordedTxStates = in.readInt(); if (recordedTxStates != numTxStates) { @@ -2804,6 +2824,7 @@ public class BatteryStatsImpl extends BatteryStats { public void readSummaryFromParcel(Parcel in) { mIdleTimeMillis.readSummaryFromParcelLocked(in); + mScanTimeMillis.readSummaryFromParcelLocked(in); mRxTimeMillis.readSummaryFromParcelLocked(in); final int recordedTxStates = in.readInt(); if (recordedTxStates != mTxTimeMillis.length) { @@ -2822,6 +2843,7 @@ public class BatteryStatsImpl extends BatteryStats { public void writeSummaryToParcel(Parcel dest) { mIdleTimeMillis.writeSummaryFromParcelLocked(dest); + mScanTimeMillis.writeSummaryFromParcelLocked(dest); mRxTimeMillis.writeSummaryFromParcelLocked(dest); dest.writeInt(mTxTimeMillis.length); for (LongSamplingCounter counter : mTxTimeMillis) { @@ -2833,6 +2855,7 @@ public class BatteryStatsImpl extends BatteryStats { @Override public void writeToParcel(Parcel dest, int flags) { mIdleTimeMillis.writeToParcel(dest); + mScanTimeMillis.writeToParcel(dest); mRxTimeMillis.writeToParcel(dest); dest.writeInt(mTxTimeMillis.length); for (LongSamplingCounter counter : mTxTimeMillis) { @@ -2843,6 +2866,7 @@ public class BatteryStatsImpl extends BatteryStats { public void reset(boolean detachIfReset) { mIdleTimeMillis.reset(detachIfReset); + mScanTimeMillis.reset(detachIfReset); mRxTimeMillis.reset(detachIfReset); for (LongSamplingCounter counter : mTxTimeMillis) { counter.reset(detachIfReset); @@ -2852,6 +2876,7 @@ public class BatteryStatsImpl extends BatteryStats { public void detach() { mIdleTimeMillis.detach(); + mScanTimeMillis.detach(); mRxTimeMillis.detach(); for (LongSamplingCounter counter : mTxTimeMillis) { counter.detach(); @@ -2868,6 +2893,15 @@ public class BatteryStatsImpl extends BatteryStats { return mIdleTimeMillis; } + /** + * @return a LongSamplingCounter, measuring time spent in the scan state in + * milliseconds. + */ + @Override + public LongSamplingCounter getScanTimeCounter() { + return mScanTimeMillis; + } + /** * @return a LongSamplingCounter, measuring time spent in the receive state in * milliseconds. @@ -3880,6 +3914,10 @@ public class BatteryStatsImpl extends BatteryStats { } mKernelUidCpuTimeReader.removeUid(isolatedUid); mKernelUidCpuFreqTimeReader.removeUid(isolatedUid); + if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { + mKernelUidCpuActiveTimeReader.removeUid(isolatedUid); + mKernelUidCpuClusterTimeReader.removeUid(isolatedUid); + } } public int mapUid(int uid) { @@ -4062,8 +4100,6 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource, String tag) { - final int[] uids = new int[1]; - final String[] tags = new String[1]; if (workSource != null) { for (int i = 0; i < workSource.size(); ++i) { uid = workSource.get(i); @@ -4074,9 +4110,8 @@ public class BatteryStatsImpl extends BatteryStats { workSourceName != null ? workSourceName : packageName); pkg.noteWakeupAlarmLocked(tag); } - uids[0] = workSource.get(i); - tags[0] = workSource.getName(i); - StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag); + StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, workSource.get(i), + workSource.getName(i), tag); } ArrayList workChains = workSource.getWorkChains(); @@ -4097,9 +4132,7 @@ public class BatteryStatsImpl extends BatteryStats { BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName); pkg.noteWakeupAlarmLocked(tag); } - uids[0] = uid; - tags[0] = null; - StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag); + StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, uid, null, tag); } } @@ -4221,12 +4254,11 @@ public class BatteryStatsImpl extends BatteryStats { getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime); if (wc != null) { - StatsLog.write( - StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1); + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), + getPowerManagerWakeLockLevel(type), name, 1); } else { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 1); + StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, + getPowerManagerWakeLockLevel(type), name, 1); } } } @@ -4265,16 +4297,45 @@ public class BatteryStatsImpl extends BatteryStats { getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime); if (wc != null) { - StatsLog.write( - StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0); + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), + getPowerManagerWakeLockLevel(type), name, 0); } else { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 0); + StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, + getPowerManagerWakeLockLevel(type), name, 0); } } } + /** + * Converts BatteryStats wakelock types back into PowerManager wakelock levels. + * This is the inverse map of Notifier.getBatteryStatsWakeLockMonitorType(). + * These are estimations, since batterystats loses some of the original data. + * TODO: Delete this. Instead, StatsLog.write should be called from PowerManager's Notifier. + */ + private int getPowerManagerWakeLockLevel(int battertStatsWakelockType) { + switch (battertStatsWakelockType) { + // PowerManager.PARTIAL_WAKE_LOCK or PROXIMITY_SCREEN_OFF_WAKE_LOCK + case BatteryStats.WAKE_TYPE_PARTIAL: + return PowerManager.PARTIAL_WAKE_LOCK; + + // PowerManager.SCREEN_DIM_WAKE_LOCK or SCREEN_BRIGHT_WAKE_LOCK + case BatteryStats.WAKE_TYPE_FULL: + return PowerManager.FULL_WAKE_LOCK; + + case BatteryStats.WAKE_TYPE_DRAW: + return PowerManager.DRAW_WAKE_LOCK; + + // It appears that nothing can ever make a Window and PowerManager lacks an equivalent. + case BatteryStats.WAKE_TYPE_WINDOW: + Slog.e(TAG, "Illegal window wakelock type observed in batterystats."); + return -1; + + default: + Slog.e(TAG, "Illegal wakelock type in batterystats: " + battertStatsWakelockType); + return -1; + } + } + public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, String historyName, int type, boolean unimportantForLogging) { final long elapsedRealtime = mClocks.elapsedRealtime(); @@ -4364,10 +4425,8 @@ public class BatteryStatsImpl extends BatteryStats { } public void noteLongPartialWakelockStart(String name, String historyName, int uid) { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uids, tags, name, historyName, 1); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + uid, null, name, historyName, 1); uid = mapUid(uid); noteLongPartialWakeLockStartInternal(name, historyName, uid); @@ -4376,15 +4435,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteLongPartialWakelockStartFromSource(String name, String historyName, WorkSource workSource) { final int N = workSource.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i = 0; i < N; ++i) { final int uid = mapUid(workSource.get(i)); noteLongPartialWakeLockStartInternal(name, historyName, uid); - uids[0] = workSource.get(i); - tags[0] = workSource.getName(i); - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uids, tags, name, - historyName, 1); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + workSource.get(i), workSource.getName(i), name, historyName, 1); } final ArrayList workChains = workSource.getWorkChains(); @@ -4415,10 +4470,8 @@ public class BatteryStatsImpl extends BatteryStats { } public void noteLongPartialWakelockFinish(String name, String historyName, int uid) { - int[] uids = new int[] { uid }; - String[] tags = new String[] { null }; - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uids, tags, name, historyName, 0); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + uid, null, name, historyName, 0); uid = mapUid(uid); noteLongPartialWakeLockFinishInternal(name, historyName, uid); @@ -4427,15 +4480,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteLongPartialWakelockFinishFromSource(String name, String historyName, WorkSource workSource) { final int N = workSource.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i = 0; i < N; ++i) { final int uid = mapUid(workSource.get(i)); noteLongPartialWakeLockFinishInternal(name, historyName, uid); - uids[0] = workSource.get(i); - tags[0] = workSource.getName(i); - StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, - uids, tags, name, historyName, 0); + StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, + workSource.get(i), workSource.getName(i), name, historyName, 0); } final ArrayList workChains = workSource.getWorkChains(); @@ -4570,8 +4619,35 @@ public class BatteryStatsImpl extends BatteryStats { int mGpsNesting; - public void noteStartGpsLocked(int uid) { - uid = mapUid(uid); + public void noteGpsChangedLocked(WorkSource oldWs, WorkSource newWs) { + for (int i = 0; i < newWs.size(); ++i) { + noteStartGpsLocked(newWs.get(i), null); + } + + for (int i = 0; i < oldWs.size(); ++i) { + noteStopGpsLocked((oldWs.get(i)), null); + } + + List[] wcs = WorkSource.diffChains(oldWs, newWs); + if (wcs != null) { + if (wcs[0] != null) { + final List newChains = wcs[0]; + for (int i = 0; i < newChains.size(); ++i) { + noteStartGpsLocked(-1, newChains.get(i)); + } + } + + if (wcs[1] != null) { + final List goneChains = wcs[1]; + for (int i = 0; i < goneChains.size(); ++i) { + noteStopGpsLocked(-1, goneChains.get(i)); + } + } + } + } + + private void noteStartGpsLocked(int uid, WorkChain workChain) { + uid = getAttributionUid(uid, workChain); final long elapsedRealtime = mClocks.elapsedRealtime(); final long uptime = mClocks.uptimeMillis(); if (mGpsNesting == 0) { @@ -4581,11 +4657,19 @@ public class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); } mGpsNesting++; + + if (workChain == null) { + StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 1); + } else { + StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, + workChain.getUids(), workChain.getTags(), 1); + } + getUidStatsLocked(uid).noteStartGps(elapsedRealtime); } - public void noteStopGpsLocked(int uid) { - uid = mapUid(uid); + private void noteStopGpsLocked(int uid, WorkChain workChain) { + uid = getAttributionUid(uid, workChain); final long elapsedRealtime = mClocks.elapsedRealtime(); final long uptime = mClocks.uptimeMillis(); mGpsNesting--; @@ -4594,10 +4678,45 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + stopAllGpsSignalQualityTimersLocked(-1); + mGpsSignalQualityBin = -1; + } + + if (workChain == null) { + StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 0); + } else { + StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, workChain.getUids(), + workChain.getTags(), 0); } + getUidStatsLocked(uid).noteStopGps(elapsedRealtime); } + public void noteGpsSignalQualityLocked(int signalLevel) { + if (mGpsNesting == 0) { + return; + } + if (signalLevel < 0 || signalLevel >= GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS) { + stopAllGpsSignalQualityTimersLocked(-1); + return; + } + final long elapsedRealtime = mClocks.elapsedRealtime(); + final long uptime = mClocks.uptimeMillis(); + if (mGpsSignalQualityBin != signalLevel) { + if (mGpsSignalQualityBin >= 0) { + mGpsSignalQualityTimer[mGpsSignalQualityBin].stopRunningLocked(elapsedRealtime); + } + if(!mGpsSignalQualityTimer[signalLevel].isRunningLocked()) { + mGpsSignalQualityTimer[signalLevel].startRunningLocked(elapsedRealtime); + } + mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK) + | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT); + addHistoryRecordLocked(elapsedRealtime, uptime); + mGpsSignalQualityBin = signalLevel; + } + return; + } + public void noteScreenStateLocked(int state) { state = mPretendScreenOff ? Display.STATE_OFF : state; @@ -4931,6 +5050,18 @@ public class BatteryStatsImpl extends BatteryStats { mDailyPackageChanges.add(pc); } + void stopAllGpsSignalQualityTimersLocked(int except) { + final long elapsedRealtime = mClocks.elapsedRealtime(); + for (int i = 0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { + if (i == except) { + continue; + } + while (mGpsSignalQualityTimer[i].isRunningLocked()) { + mGpsSignalQualityTimer[i].stopRunningLocked(elapsedRealtime); + } + } + } + public void notePhoneOnLocked() { if (!mPhoneOn) { final long elapsedRealtime = mClocks.elapsedRealtime(); @@ -5420,11 +5551,10 @@ public class BatteryStatsImpl extends BatteryStats { workChain.getUids(), workChain.getTags(), 1); } } else { - final int[] uids = new int[] {uid}; - final String[] tags = new String[] {null}; - StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 1); if (isUnoptimized) { - StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, + 1); } } @@ -5470,11 +5600,10 @@ public class BatteryStatsImpl extends BatteryStats { workChain.getUids(), workChain.getTags(), 0); } } else { - final int[] uids = new int[] { uid }; - final String[] tags = new String[] {null}; - StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 0); if (isUnoptimized) { - StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, + 0); } } @@ -5547,14 +5676,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteBluetoothScanResultsFromSourceLocked(WorkSource ws, int numNewResults) { final int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i = 0; i < N; i++) { int uid = mapUid(ws.get(i)); getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults); - uids[0] = ws.get(i); - tags[0] = ws.getName(i); - StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uids, tags, numNewResults); + StatsLog.write_non_chained(StatsLog.BLE_SCAN_RESULT_RECEIVED, ws.get(i), ws.getName(i), + numNewResults); } final List workChains = ws.getWorkChains(); @@ -5589,8 +5715,11 @@ public class BatteryStatsImpl extends BatteryStats { noteWifiRadioApWakeupLocked(elapsedRealtime, uptime, uid); } mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; + mWifiActiveTimer.startRunningLocked(elapsedRealtime); } else { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; + mWifiActiveTimer.stopRunningLocked( + timestampNs / (1000 * 1000)); } if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: " + Integer.toHexString(mHistoryCur.states)); @@ -5879,14 +6008,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i workChains = ws.getWorkChains(); @@ -5903,14 +6028,10 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i workChains = ws.getWorkChains(); @@ -5927,14 +6048,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWifiScanStartedFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i workChains = ws.getWorkChains(); @@ -5951,14 +6069,11 @@ public class BatteryStatsImpl extends BatteryStats { public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) { int N = ws.size(); - final int[] uids = new int[1]; - final String[] tags = new String[1]; for (int i=0; i workChains = ws.getWorkChains(); @@ -6161,6 +6276,32 @@ public class BatteryStatsImpl extends BatteryStats { return val; } + @Override public long getGpsSignalQualityTime(int strengthBin, + long elapsedRealtimeUs, int which) { + if (strengthBin < 0 || strengthBin >= GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS) { + return 0; + } + return mGpsSignalQualityTimer[strengthBin].getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public long getGpsBatteryDrainMaMs() { + final double opVolt = mPowerProfile.getAveragePower( + PowerProfile.POWER_GPS_OPERATING_VOLTAGE) / 1000.0; + if (opVolt == 0) { + return 0; + } + double energyUsedMaMs = 0.0; + final int which = STATS_SINCE_CHARGED; + final long rawRealtime = SystemClock.elapsedRealtime() * 1000; + for(int i=0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { + energyUsedMaMs + += mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, i) + * (getGpsSignalQualityTime(i, rawRealtime, which) / 1000); + } + return (long) energyUsedMaMs; + } + @Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) { return mPhoneOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -6241,6 +6382,10 @@ public class BatteryStatsImpl extends BatteryStats { return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } + @Override public long getWifiActiveTime(long elapsedRealtimeUs, int which) { + return mWifiActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + @Override public long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which) { return mGlobalWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -6517,9 +6662,11 @@ public class BatteryStatsImpl extends BatteryStats { LongSamplingCounter mUserCpuTime; LongSamplingCounter mSystemCpuTime; LongSamplingCounter[][] mCpuClusterSpeedTimesUs; + LongSamplingCounter mCpuActiveTimeMs; LongSamplingCounterArray mCpuFreqTimeMs; LongSamplingCounterArray mScreenOffCpuFreqTimeMs; + LongSamplingCounterArray mCpuClusterTimesMs; LongSamplingCounterArray[] mProcStateTimeMs; LongSamplingCounterArray[] mProcStateScreenOffTimeMs; @@ -6589,6 +6736,8 @@ public class BatteryStatsImpl extends BatteryStats { mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase); mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase); + mCpuActiveTimeMs = new LongSamplingCounter(mBsi.mOnBatteryTimeBase); + mCpuClusterTimesMs = new LongSamplingCounterArray(mBsi.mOnBatteryTimeBase); mWakelockStats = mBsi.new OverflowArrayMap(uid) { @Override public Wakelock instantiateObject() { @@ -6635,6 +6784,17 @@ public class BatteryStatsImpl extends BatteryStats { return nullIfAllZeros(mScreenOffCpuFreqTimeMs, which); } + @Override + public long getCpuActiveTime() { + return mCpuActiveTimeMs.getCountLocked(STATS_SINCE_CHARGED); + } + + @Override + public long[] getCpuClusterTimes() { + return nullIfAllZeros(mCpuClusterTimesMs, STATS_SINCE_CHARGED); + } + + @Override public long[] getCpuFreqTimes(int which, int procState) { if (which < 0 || which >= NUM_PROCESS_STATE) { @@ -6874,6 +7034,8 @@ public class BatteryStatsImpl extends BatteryStats { WIFI_MULTICAST_ENABLED, mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase); } mWifiMulticastTimer.startRunningLocked(elapsedRealtimeMs); + StatsLog.write_non_chained( + StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, 1); } } @@ -6882,6 +7044,8 @@ public class BatteryStatsImpl extends BatteryStats { if (mWifiMulticastEnabled) { mWifiMulticastEnabled = false; mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs); + StatsLog.write_non_chained( + StatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, getUid(), null, 0); } } @@ -6934,18 +7098,14 @@ public class BatteryStatsImpl extends BatteryStats { public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) { createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 1); } public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0); } } } @@ -6953,9 +7113,7 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetAudioLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0); } } @@ -6969,18 +7127,15 @@ public class BatteryStatsImpl extends BatteryStats { public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) { createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, 1); } public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) { if (mVideoTurnedOnTimer != null) { mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), + null, 0); } } } @@ -6988,9 +7143,8 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetVideoLocked(long elapsedRealtimeMs) { if (mVideoTurnedOnTimer != null) { mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, + 0); } } @@ -7004,18 +7158,15 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) { createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null,1); } public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) { if (mFlashlightTurnedOnTimer != null) { mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mFlashlightTurnedOnTimer.isRunningLocked()) { - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, + 0); } } } @@ -7023,9 +7174,7 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetFlashlightLocked(long elapsedRealtimeMs) { if (mFlashlightTurnedOnTimer != null) { mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, 0); } } @@ -7039,18 +7188,14 @@ public class BatteryStatsImpl extends BatteryStats { public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) { createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 1); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 1); } public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0); } } } @@ -7058,9 +7203,7 @@ public class BatteryStatsImpl extends BatteryStats { public void noteResetCameraLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0); + StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0); } } @@ -7719,6 +7862,9 @@ public class BatteryStatsImpl extends BatteryStats { mScreenOffCpuFreqTimeMs.reset(false); } + mCpuActiveTimeMs.reset(false); + mCpuClusterTimesMs.reset(false); + if (mProcStateTimeMs != null) { for (LongSamplingCounterArray counters : mProcStateTimeMs) { if (counters != null) { @@ -7923,6 +8069,8 @@ public class BatteryStatsImpl extends BatteryStats { if (mScreenOffCpuFreqTimeMs != null) { mScreenOffCpuFreqTimeMs.detach(); } + mCpuActiveTimeMs.detach(); + mCpuClusterTimesMs.detach(); if (mProcStateTimeMs != null) { for (LongSamplingCounterArray counters : mProcStateTimeMs) { @@ -8198,6 +8346,10 @@ public class BatteryStatsImpl extends BatteryStats { LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs); LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs); + + mCpuActiveTimeMs.writeToParcel(out); + mCpuClusterTimesMs.writeToParcel(out); + if (mProcStateTimeMs != null) { out.writeInt(mProcStateTimeMs.length); for (LongSamplingCounterArray counters : mProcStateTimeMs) { @@ -8515,6 +8667,9 @@ public class BatteryStatsImpl extends BatteryStats { mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel( in, mBsi.mOnBatteryScreenOffTimeBase); + mCpuActiveTimeMs = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in); + mCpuClusterTimesMs = new LongSamplingCounterArray(mBsi.mOnBatteryTimeBase, in); + int length = in.readInt(); if (length == NUM_PROCESS_STATE) { mProcStateTimeMs = new LongSamplingCounterArray[length]; @@ -9622,9 +9777,7 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mSyncStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 1); + StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 1); } } @@ -9633,9 +9786,7 @@ public class BatteryStatsImpl extends BatteryStats { if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); if (!t.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 0); + StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 0); } } } @@ -9644,9 +9795,8 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mJobStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 1); + StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null, + name, 1); } } @@ -9655,9 +9805,8 @@ public class BatteryStatsImpl extends BatteryStats { if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); if (!t.isRunningLocked()) { // only tell statsd if truly stopped - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 0); + StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null, + name, 0); } } if (mBsi.mOnBatteryTimeBase.isRunning()) { @@ -9768,12 +9917,9 @@ public class BatteryStatsImpl extends BatteryStats { public void noteStartSensor(int sensor, long elapsedRealtimeMs) { DualTimer t = getSensorTimerLocked(sensor, /* create= */ true); t.startRunningLocked(elapsedRealtimeMs); - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - if (sensor == Sensor.GPS) { - StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 1); - } else { - StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 1); + if (sensor != Sensor.GPS) { + StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, sensor, + 1); } } @@ -9782,15 +9928,9 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = getSensorTimerLocked(sensor, false); if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); - if (!t.isRunningLocked()) { // only tell statsd if truly stopped - // TODO(statsd): Possibly use a worksource instead of a uid. - final int[] uids = new int[] { getUid() }; - final String[] tags = new String[] { null }; - if (sensor == Sensor.GPS) { - StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 0); - } else { - StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 0); - } + if (sensor != Sensor.GPS) { + StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, + sensor, 0); } } } @@ -9889,6 +10029,11 @@ public class BatteryStatsImpl extends BatteryStats { mWifiSignalStrengthsTimer[i] = new StopwatchTimer(mClocks, null, -800-i, null, mOnBatteryTimeBase); } + mWifiActiveTimer = new StopwatchTimer(mClocks, null, -900, null, mOnBatteryTimeBase); + for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { + mGpsSignalQualityTimer[i] = new StopwatchTimer(mClocks, null, -1000-i, null, + mOnBatteryTimeBase); + } mAudioOnTimer = new StopwatchTimer(mClocks, null, -7, null, mOnBatteryTimeBase); mVideoOnTimer = new StopwatchTimer(mClocks, null, -8, null, mOnBatteryTimeBase); mFlashlightOnTimer = new StopwatchTimer(mClocks, null, -9, null, mOnBatteryTimeBase); @@ -10024,6 +10169,7 @@ public class BatteryStatsImpl extends BatteryStats { updateDailyDeadlineLocked(); if (hasData) { + final long startTime = SystemClock.uptimeMillis(); mDailyItems.add(item); while (mDailyItems.size() > MAX_DAILY_ITEMS) { mDailyItems.remove(0); @@ -10033,10 +10179,12 @@ public class BatteryStatsImpl extends BatteryStats { XmlSerializer out = new FastXmlSerializer(); out.setOutput(memStream, StandardCharsets.UTF_8.name()); writeDailyItemsLocked(out); + final long initialTime = SystemClock.uptimeMillis() - startTime; BackgroundThread.getHandler().post(new Runnable() { @Override public void run() { synchronized (mCheckinFile) { + final long startTime2 = SystemClock.uptimeMillis(); FileOutputStream stream = null; try { stream = mDailyFile.startWrite(); @@ -10045,6 +10193,9 @@ public class BatteryStatsImpl extends BatteryStats { FileUtils.sync(stream); stream.close(); mDailyFile.finishWrite(stream); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "batterystats-daily", + initialTime + SystemClock.uptimeMillis() - startTime2); } catch (IOException e) { Slog.w("BatteryStats", "Error writing battery daily items", e); @@ -10578,7 +10729,11 @@ public class BatteryStatsImpl extends BatteryStats { mWifiSignalStrengthsTimer[i].reset(false); } mWifiMulticastWakelockTimer.reset(false); + mWifiActiveTimer.reset(false); mWifiActivity.reset(false); + for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { + mGpsSignalQualityTimer[i].reset(false); + } mBluetoothActivity.reset(false); mModemActivity.reset(false); mNumConnectivityChange = mLoadedNumConnectivityChange = mUnpluggedNumConnectivityChange = 0; @@ -10741,7 +10896,7 @@ public class BatteryStatsImpl extends BatteryStats { return null; } - /** + /** * Distribute WiFi energy info and network traffic to apps. * @param info The energy information from the WiFi controller. */ @@ -10841,6 +10996,7 @@ public class BatteryStatsImpl extends BatteryStats { // Measured in mAms final long txTimeMs = info.getControllerTxTimeMillis(); final long rxTimeMs = info.getControllerRxTimeMillis(); + final long scanTimeMs = info.getControllerScanTimeMillis(); final long idleTimeMs = info.getControllerIdleTimeMillis(); final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs; @@ -10853,6 +11009,7 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms"); Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms"); Slog.d(TAG, " Total Time: " + totalTimeMs + " ms"); + Slog.d(TAG, " Scan Time: " + scanTimeMs + " ms"); } long totalWifiLockTimeMs = 0; @@ -10986,6 +11143,8 @@ public class BatteryStatsImpl extends BatteryStats { mWifiActivity.getRxTimeCounter().addCountLocked(info.getControllerRxTimeMillis()); mWifiActivity.getTxTimeCounters()[0].addCountLocked( info.getControllerTxTimeMillis()); + mWifiActivity.getScanTimeCounter().addCountLocked( + info.getControllerScanTimeMillis()); mWifiActivity.getIdleTimeCounter().addCountLocked( info.getControllerIdleTimeMillis()); @@ -11001,6 +11160,9 @@ public class BatteryStatsImpl extends BatteryStats { } } + private ModemActivityInfo mLastModemActivityInfo = + new ModemActivityInfo(0, 0, 0, new int[0], 0, 0); + /** * Distribute Cell radio energy info and network traffic to apps. */ @@ -11021,6 +11183,22 @@ public class BatteryStatsImpl extends BatteryStats { } } + int rxTimeMs = 0; + int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS]; + int idleTimeMs = 0; + int sleepTimeMs = 0; + if (activityInfo != null) { + rxTimeMs = activityInfo.getRxTimeMillis() - mLastModemActivityInfo.getRxTimeMillis(); + for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) { + txTimeMs[i] = activityInfo.getTxTimeMillis()[i] + - mLastModemActivityInfo.getTxTimeMillis()[i]; + } + idleTimeMs = + activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(); + sleepTimeMs = + activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(); + } + synchronized (this) { if (!mOnBatteryInternal) { if (delta != null) { @@ -11029,6 +11207,38 @@ public class BatteryStatsImpl extends BatteryStats { return; } + if (activityInfo != null) { + mHasModemReporting = true; + mModemActivity.getIdleTimeCounter().addCountLocked( + idleTimeMs); + mModemActivity.getRxTimeCounter().addCountLocked(rxTimeMs); + for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { + mModemActivity.getTxTimeCounters()[lvl] + .addCountLocked(txTimeMs[lvl]); + } + + // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. + final double opVolt = mPowerProfile.getAveragePower( + PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0; + if (opVolt != 0) { + double energyUsed = + sleepTimeMs * + mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP) + + idleTimeMs * + mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE) + + rxTimeMs * + mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); + for (int i = 0; i < Math.min(txTimeMs.length, + SignalStrength.NUM_SIGNAL_STRENGTH_BINS); i++) { + energyUsed += txTimeMs[i] * mPowerProfile.getAveragePower( + PowerProfile.POWER_MODEM_CONTROLLER_TX, i); + } + + // We store the power drain as mAms. + mModemActivity.getPowerCounter().addCountLocked((long) energyUsed); + } + } + final long elapsedRealtimeMs = mClocks.elapsedRealtime(); long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000); @@ -11101,7 +11311,7 @@ public class BatteryStatsImpl extends BatteryStats { ControllerActivityCounterImpl activityCounter = u.getOrCreateModemControllerActivityLocked(); if (totalRxPackets > 0 && entry.rxPackets > 0) { - final long rxMs = (entry.rxPackets * activityInfo.getRxTimeMillis()) + final long rxMs = (entry.rxPackets * rxTimeMs) / totalRxPackets; activityCounter.getRxTimeCounter().addCountLocked(rxMs); } @@ -11109,7 +11319,7 @@ public class BatteryStatsImpl extends BatteryStats { if (totalTxPackets > 0 && entry.txPackets > 0) { for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { long txMs = - entry.txPackets * activityInfo.getTxTimeMillis()[lvl]; + entry.txPackets * txTimeMs[lvl]; txMs /= totalTxPackets; activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs); } @@ -11127,29 +11337,13 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkStatsPool.release(delta); delta = null; } - - if (activityInfo != null) { - mHasModemReporting = true; - mModemActivity.getIdleTimeCounter().addCountLocked( - activityInfo.getIdleTimeMillis()); - mModemActivity.getRxTimeCounter().addCountLocked(activityInfo.getRxTimeMillis()); - for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { - mModemActivity.getTxTimeCounters()[lvl] - .addCountLocked(activityInfo.getTxTimeMillis()[lvl]); - } - - // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. - final double opVolt = mPowerProfile.getAveragePower( - PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0; - if (opVolt != 0) { - // We store the power drain as mAms. - mModemActivity.getPowerCounter().addCountLocked( - (long) (activityInfo.getEnergyUsed() / opVolt)); - } - } } } + // Cache last value for comparison. + private BluetoothActivityEnergyInfo mLastBluetoothActivityEnergyInfo = + new BluetoothActivityEnergyInfo(0, 0, 0, 0, 0, 0); + /** * Distribute Bluetooth energy info and network traffic to apps. * @param info The energy information from the bluetooth controller. @@ -11166,14 +11360,17 @@ public class BatteryStatsImpl extends BatteryStats { mHasBluetoothReporting = true; final long elapsedRealtimeMs = mClocks.elapsedRealtime(); - final long rxTimeMs = info.getControllerRxTimeMillis(); - final long txTimeMs = info.getControllerTxTimeMillis(); - + final long rxTimeMs = info.getControllerRxTimeMillis() - + mLastBluetoothActivityEnergyInfo.getControllerRxTimeMillis(); + final long txTimeMs = info.getControllerTxTimeMillis() - + mLastBluetoothActivityEnergyInfo.getControllerTxTimeMillis(); + final long idleTimeMs = info.getControllerIdleTimeMillis() - + mLastBluetoothActivityEnergyInfo.getControllerIdleTimeMillis(); if (DEBUG_ENERGY) { Slog.d(TAG, "------ BEGIN BLE power blaming ------"); Slog.d(TAG, " Tx Time: " + txTimeMs + " ms"); Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms"); - Slog.d(TAG, " Idle Time: " + info.getControllerIdleTimeMillis() + " ms"); + Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms"); } long totalScanTimeMs = 0; @@ -11252,9 +11449,25 @@ public class BatteryStatsImpl extends BatteryStats { long totalRxBytes = 0; final UidTraffic[] uidTraffic = info.getUidTraffic(); - final int numUids = uidTraffic != null ? uidTraffic.length : 0; - for (int i = 0; i < numUids; i++) { - final UidTraffic traffic = uidTraffic[i]; + final UidTraffic[] lastUidTraffic = mLastBluetoothActivityEnergyInfo.getUidTraffic(); + final ArrayList deltaTraffic = new ArrayList<>(); + int m = 0, n = 0; + for (; m < uidTraffic.length && n < lastUidTraffic.length; m++) { + final UidTraffic traffic = uidTraffic[m]; + final UidTraffic lastTraffic = lastUidTraffic[n]; + if (traffic.getUid() == lastTraffic.getUid()) { + deltaTraffic.add(new UidTraffic(traffic.getUid(), + traffic.getRxBytes() - lastTraffic.getRxBytes(), + traffic.getTxBytes() - lastTraffic.getTxBytes())); + n++; + } + } + for (; m < uidTraffic.length; m ++) { + deltaTraffic.add(uidTraffic[m]); + } + + for (int i = 0, j = 0; i < deltaTraffic.size(); i++) { + final UidTraffic traffic = deltaTraffic.get(i); // Add to the global counters. mNetworkByteActivityCounters[NETWORK_BT_RX_DATA].addCountLocked( @@ -11274,8 +11487,8 @@ public class BatteryStatsImpl extends BatteryStats { if ((totalTxBytes != 0 || totalRxBytes != 0) && (leftOverRxTimeMs != 0 || leftOverTxTimeMs != 0)) { - for (int i = 0; i < numUids; i++) { - final UidTraffic traffic = uidTraffic[i]; + for (int i = 0; i < deltaTraffic.size(); i++) { + final UidTraffic traffic = deltaTraffic.get(i); final Uid u = getUidStatsLocked(mapUid(traffic.getUid())); final ControllerActivityCounterImpl counter = @@ -11306,12 +11519,9 @@ public class BatteryStatsImpl extends BatteryStats { } } - mBluetoothActivity.getRxTimeCounter().addCountLocked( - info.getControllerRxTimeMillis()); - mBluetoothActivity.getTxTimeCounters()[0].addCountLocked( - info.getControllerTxTimeMillis()); - mBluetoothActivity.getIdleTimeCounter().addCountLocked( - info.getControllerIdleTimeMillis()); + mBluetoothActivity.getRxTimeCounter().addCountLocked(rxTimeMs); + mBluetoothActivity.getTxTimeCounters()[0].addCountLocked(txTimeMs); + mBluetoothActivity.getIdleTimeCounter().addCountLocked(idleTimeMs); // POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( @@ -11319,8 +11529,10 @@ public class BatteryStatsImpl extends BatteryStats { if (opVolt != 0) { // We store the power drain as mAms. mBluetoothActivity.getPowerCounter().addCountLocked( - (long) (info.getControllerEnergyUsed() / opVolt)); + (long) ((info.getControllerEnergyUsed() - + mLastBluetoothActivityEnergyInfo.getControllerEnergyUsed() )/ opVolt)); } + mLastBluetoothActivityEnergyInfo = info; } /** @@ -11504,6 +11716,10 @@ public class BatteryStatsImpl extends BatteryStats { if (!mOnBatteryInternal) { mKernelUidCpuTimeReader.readDelta(null); mKernelUidCpuFreqTimeReader.readDelta(null); + if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { + mKernelUidCpuActiveTimeReader.readDelta(null); + mKernelUidCpuClusterTimeReader.readDelta(null); + } for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) { mKernelCpuSpeedReaders[cluster].readDelta(); } @@ -11520,6 +11736,10 @@ public class BatteryStatsImpl extends BatteryStats { updateClusterSpeedTimes(updatedUids); } readKernelUidCpuFreqTimesLocked(partialTimersToConsider); + if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { + readKernelUidCpuActiveTimesLocked(); + readKernelUidCpuClusterTimesLocked(); + } } /** @@ -11831,6 +12051,64 @@ public class BatteryStatsImpl extends BatteryStats { } } + /** + * Take a snapshot of the cpu active times spent by each uid and update the corresponding + * counters. + */ + @VisibleForTesting + public void readKernelUidCpuActiveTimesLocked() { + final long startTimeMs = mClocks.uptimeMillis(); + mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesUs) -> { + uid = mapUid(uid); + if (Process.isIsolated(uid)) { + mKernelUidCpuActiveTimeReader.removeUid(uid); + Slog.w(TAG, "Got active times for an isolated uid with no mapping: " + uid); + return; + } + if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { + Slog.w(TAG, "Got active times for an invalid user's uid " + uid); + mKernelUidCpuActiveTimeReader.removeUid(uid); + return; + } + final Uid u = getUidStatsLocked(uid); + u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesUs); + }); + + final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs; + if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) { + Slog.d(TAG, "Reading cpu active times took " + elapsedTimeMs + "ms"); + } + } + + /** + * Take a snapshot of the cpu cluster times spent by each uid and update the corresponding + * counters. + */ + @VisibleForTesting + public void readKernelUidCpuClusterTimesLocked() { + final long startTimeMs = mClocks.uptimeMillis(); + mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesUs) -> { + uid = mapUid(uid); + if (Process.isIsolated(uid)) { + mKernelUidCpuClusterTimeReader.removeUid(uid); + Slog.w(TAG, "Got cluster times for an isolated uid with no mapping: " + uid); + return; + } + if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { + Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid); + mKernelUidCpuClusterTimeReader.removeUid(uid); + return; + } + final Uid u = getUidStatsLocked(uid); + u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesUs); + }); + + final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs; + if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) { + Slog.d(TAG, "Reading cpu cluster times took " + elapsedTimeMs + "ms"); + } + } + boolean setChargingLocked(boolean charging) { if (mCharging != charging) { mCharging = charging; @@ -11874,11 +12152,14 @@ public class BatteryStatsImpl extends BatteryStats { // stats to be reported in the next checkin. Only do this if we have // a sufficient amount of data to make it interesting. if (getLowDischargeAmountSinceCharge() >= 20) { + final long startTime = SystemClock.uptimeMillis(); final Parcel parcel = Parcel.obtain(); writeSummaryToParcel(parcel, true); + final long initialTime = SystemClock.uptimeMillis() - startTime; BackgroundThread.getHandler().post(new Runnable() { @Override public void run() { synchronized (mCheckinFile) { + final long startTime2 = SystemClock.uptimeMillis(); FileOutputStream stream = null; try { stream = mCheckinFile.startWrite(); @@ -11887,6 +12168,9 @@ public class BatteryStatsImpl extends BatteryStats { FileUtils.sync(stream); stream.close(); mCheckinFile.finishWrite(stream); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "batterystats-checkin", + initialTime + SystemClock.uptimeMillis() - startTime2); } catch (IOException e) { Slog.w("BatteryStats", "Error writing checkin battery statistics", e); @@ -12015,7 +12299,7 @@ public class BatteryStatsImpl extends BatteryStats { } // This should probably be exposed in the API, though it's not critical - public static final int BATTERY_PLUGGED_NONE = 0; + public static final int BATTERY_PLUGGED_NONE = OsProtoEnums.BATTERY_PLUGGED_NONE; // = 0 public void setBatteryStateLocked(final int status, final int health, final int plugType, final int level, /* not final */ int temp, final int volt, final int chargeUAh, @@ -12434,6 +12718,71 @@ public class BatteryStatsImpl extends BatteryStats { return s; } + /*@hide */ + public WifiBatteryStats getWifiBatteryStats() { + WifiBatteryStats s = new WifiBatteryStats(); + final int which = STATS_SINCE_CHARGED; + final long rawRealTime = SystemClock.elapsedRealtime() * 1000; + final ControllerActivityCounter counter = getWifiControllerActivity(); + final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which); + final long scanTimeMs = counter.getScanTimeCounter().getCountLocked(which); + final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which); + final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which); + final long totalControllerActivityTimeMs + = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000; + final long sleepTimeMs + = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs); + final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which); + long numAppScanRequest = 0; + for (int i = 0; i < mUidStats.size(); i++) { + numAppScanRequest += mUidStats.valueAt(i).mWifiScanTimer.getCountLocked(which); + } + long[] timeInStateMs = new long[NUM_WIFI_STATES]; + for (int i=0; i mLastUidCpuActiveTimeMs = new SparseArray<>(); + + public interface Callback { + void onUidCpuActiveTime(int uid, long cpuActiveTimeMs); + } + + public void readDelta(@Nullable Callback cb) { + final int oldMask = StrictMode.allowThreadDiskReadsMask(); + try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { + mNowTimeMs = SystemClock.elapsedRealtime(); + readDeltaInternal(reader, cb); + mLastTimeReadMs = mNowTimeMs; + } catch (IOException e) { + Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); + } finally { + StrictMode.setThreadPolicyMask(oldMask); + } + } + + public void removeUid(int uid) { + mLastUidCpuActiveTimeMs.delete(uid); + } + + public void removeUidsInRange(int startUid, int endUid) { + if (endUid < startUid) { + Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid); + return; + } + mLastUidCpuActiveTimeMs.put(startUid, null); + mLastUidCpuActiveTimeMs.put(endUid, null); + final int firstIndex = mLastUidCpuActiveTimeMs.indexOfKey(startUid); + final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid); + mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); + } + + @VisibleForTesting + public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException { + String line = reader.readLine(); + if (line == null || !line.startsWith("active:")) { + Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE)); + return; + } + if (mCoreCount == 0) { + mCoreCount = Integer.parseInt(line.substring(line.indexOf(' ')+1)); + } + while ((line = reader.readLine()) != null) { + final int index = line.indexOf(' '); + final int uid = Integer.parseInt(line.substring(0, index - 1), 10); + readTimesForUid(uid, line.substring(index + 1), cb); + } + } + + private void readTimesForUid(int uid, String line, @Nullable Callback cb) { + long[] lastActiveTime = mLastUidCpuActiveTimeMs.get(uid); + if (lastActiveTime == null) { + lastActiveTime = new long[mCoreCount]; + mLastUidCpuActiveTimeMs.put(uid, lastActiveTime); + } + final String[] timesStr = line.split(" "); + if (timesStr.length != mCoreCount) { + Slog.e(TAG, String.format("# readings don't match # cores, readings: %d, CPU cores: %d", + timesStr.length, mCoreCount)); + return; + } + long sumDeltas = 0; + final long[] curActiveTime = new long[mCoreCount]; + boolean notify = false; + for (int i = 0; i < mCoreCount; i++) { + // Times read will be in units of 10ms + curActiveTime[i] = Long.parseLong(timesStr[i], 10) * 10; + long delta = curActiveTime[i] - lastActiveTime[i]; + if (delta < 0 || curActiveTime[i] < 0) { + if (DEBUG) { + final StringBuilder sb = new StringBuilder(); + sb.append(String.format("Malformed cpu active time for UID=%d\n", uid)); + sb.append(String.format("data=(%d,%d)\n", lastActiveTime[i], curActiveTime[i])); + sb.append("times=("); + TimeUtils.formatDuration(mLastTimeReadMs, sb); + sb.append(","); + TimeUtils.formatDuration(mNowTimeMs, sb); + sb.append(")"); + Slog.e(TAG, sb.toString()); + } + return; + } + notify |= delta > 0; + sumDeltas += delta / (i + 1); + } + if (notify) { + System.arraycopy(curActiveTime, 0, lastActiveTime, 0, mCoreCount); + if (cb != null) { + cb.onUidCpuActiveTime(uid, sumDeltas); + } + } + } +} diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java new file mode 100644 index 0000000000000000000000000000000000000000..85153bc45d076fee30c929a95c19039c724de7a7 --- /dev/null +++ b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.Nullable; +import android.os.StrictMode; +import android.os.SystemClock; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TimeUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Reads /proc/uid_concurrent_policy_time which has the format: + * policy0: X policy4: Y (there are X cores on policy0, Y cores on policy4) + * [uid0]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ... + * [uid1]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ... + * ... + * Time-X-Y means the time a UID spent on clusterX running concurrently with Y other processes. + * The file contains a monotonically increasing count of time for a single boot. This class + * maintains the previous results of a call to {@link #readDelta} in order to provide a proper + * delta. + */ +public class KernelUidCpuClusterTimeReader { + + private static final boolean DEBUG = false; + private static final String TAG = "KernelUidCpuClusterTimeReader"; + private static final String UID_TIMES_PROC_FILE = "/proc/uid_concurrent_policy_time"; + + // mCoreOnCluster[i] is the # of cores on cluster i + private int[] mCoreOnCluster; + private int mCores; + private long mLastTimeReadMs; + private long mNowTimeMs; + private SparseArray mLastUidPolicyTimeMs = new SparseArray<>(); + + public interface Callback { + /** + * @param uid + * @param cpuActiveTimeMs the first dimension is cluster, the second dimension is the # of + * processes running concurrently with this uid. + */ + void onUidCpuPolicyTime(int uid, long[] cpuActiveTimeMs); + } + + public void readDelta(@Nullable Callback cb) { + final int oldMask = StrictMode.allowThreadDiskReadsMask(); + try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { + mNowTimeMs = SystemClock.elapsedRealtime(); + readDeltaInternal(reader, cb); + mLastTimeReadMs = mNowTimeMs; + } catch (IOException e) { + Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); + } finally { + StrictMode.setThreadPolicyMask(oldMask); + } + } + + public void removeUid(int uid) { + mLastUidPolicyTimeMs.delete(uid); + } + + public void removeUidsInRange(int startUid, int endUid) { + if (endUid < startUid) { + Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid); + return; + } + mLastUidPolicyTimeMs.put(startUid, null); + mLastUidPolicyTimeMs.put(endUid, null); + final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid); + final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid); + mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); + } + + @VisibleForTesting + public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException { + String line = reader.readLine(); + if (line == null || !line.startsWith("policy")) { + Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE)); + return; + } + if (mCoreOnCluster == null) { + List list = new ArrayList<>(); + String[] policies = line.split(" "); + + if (policies.length == 0 || policies.length % 2 != 0) { + Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE)); + return; + } + + for (int i = 0; i < policies.length; i+=2) { + list.add(Integer.parseInt(policies[i+1])); + } + + mCoreOnCluster = new int[list.size()]; + for(int i=0;i 0; + } + if (notify) { + System.arraycopy(currPolicyTime, 0, lastPolicyTime, 0, mCores); + if (cb != null) { + final long[] times = new long[mCoreOnCluster.length]; + int core = 0; + for (int i = 0; i < mCoreOnCluster.length; i++) { + for (int j = 0; j < mCoreOnCluster[i]; j++) { + times[i] += deltaPolicyTime[core++] / (j+1); + } + } + cb.onUidCpuPolicyTime(uid, times); + } + } + } +} diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 872b465a9ca58aeacbc54ad781817dd8f1861ef8..f4436d383a219a8a631aee353cf6fb2928d6bb38 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -43,23 +44,25 @@ public class PowerProfile { public static final String POWER_NONE = "none"; /** - * Power consumption when CPU is in power collapse mode. + * POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode. + * POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should + * be zero on devices that can go into full CPU power collapse even when a wake + * lock is held. Otherwise, this is the power consumption in addition to + * POWER_CPU_SUSPEND due to a wake lock being held but with no CPU activity. + * POWER_CPU_ACTIVE: Power consumption when CPU is running, excluding power consumed by clusters + * and cores. + * + * CPU Power Equation (assume two clusters): + * Total power = POWER_CPU_SUSPEND (always added) + * + POWER_CPU_IDLE (skip this and below if in power collapse mode) + * + POWER_CPU_ACTIVE (skip this and below if CPU is not running, but a wakelock + * is held) + * + cluster_power.cluster0 + cluster_power.cluster1 (skip cluster not running) + * + core_power.cluster0 * num running cores in cluster 0 + * + core_power.cluster1 * num running cores in cluster 1 */ + public static final String POWER_CPU_SUSPEND = "cpu.suspend"; public static final String POWER_CPU_IDLE = "cpu.idle"; - - /** - * Power consumption when CPU is awake (when a wake lock is held). This - * should be 0 on devices that can go into full CPU power collapse even - * when a wake lock is held. Otherwise, this is the power consumption in - * addition to POWER_CPU_IDLE due to a wake lock being held but with no - * CPU activity. - */ - public static final String POWER_CPU_AWAKE = "cpu.awake"; - - /** - * Power consumption when CPU is in power collapse mode. - */ - @Deprecated public static final String POWER_CPU_ACTIVE = "cpu.active"; /** @@ -94,17 +97,24 @@ public class PowerProfile { public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE = "bluetooth.controller.voltage"; + public static final String POWER_MODEM_CONTROLLER_SLEEP = "modem.controller.sleep"; public static final String POWER_MODEM_CONTROLLER_IDLE = "modem.controller.idle"; public static final String POWER_MODEM_CONTROLLER_RX = "modem.controller.rx"; public static final String POWER_MODEM_CONTROLLER_TX = "modem.controller.tx"; public static final String POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE = "modem.controller.voltage"; - /** + /** * Power consumption when GPS is on. */ public static final String POWER_GPS_ON = "gps.on"; + /** + * GPS power parameters based on signal quality + */ + public static final String POWER_GPS_SIGNAL_QUALITY_BASED = "gps.signalqualitybased"; + public static final String POWER_GPS_OPERATING_VOLTAGE = "gps.voltage"; + /** * Power consumption when Bluetooth driver is on. * @deprecated @@ -182,9 +192,6 @@ public class PowerProfile { */ public static final String POWER_CAMERA = "camera.avg"; - @Deprecated - public static final String POWER_CPU_SPEEDS = "cpu.speeds"; - /** * Power consumed by wif batched scaning. Broken down into bins by * Channels Scanned per Hour. May do 1-720 scans per hour of 1-100 channels @@ -197,7 +204,15 @@ public class PowerProfile { */ public static final String POWER_BATTERY_CAPACITY = "battery.capacity"; - static final HashMap sPowerMap = new HashMap<>(); + /** + * A map from Power Use Item to its power consumption. + */ + static final HashMap sPowerItemMap = new HashMap<>(); + /** + * A map from Power Use Item to an array of its power consumption + * (for items with variable power e.g. CPU). + */ + static final HashMap sPowerArrayMap = new HashMap<>(); private static final String TAG_DEVICE = "device"; private static final String TAG_ITEM = "item"; @@ -207,23 +222,32 @@ public class PowerProfile { private static final Object sLock = new Object(); + @VisibleForTesting public PowerProfile(Context context) { - // Read the XML file for the given profile (normally only one per - // device) + this(context, false); + } + + /** + * For PowerProfileTest + */ + @VisibleForTesting + public PowerProfile(Context context, boolean forTest) { + // Read the XML file for the given profile (normally only one per device) synchronized (sLock) { - if (sPowerMap.size() == 0) { - readPowerValuesFromXml(context); + if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) { + readPowerValuesFromXml(context, forTest); } initCpuClusters(); } } - private void readPowerValuesFromXml(Context context) { - int id = com.android.internal.R.xml.power_profile; + private void readPowerValuesFromXml(Context context, boolean forTest) { + final int id = forTest ? com.android.internal.R.xml.power_profile_test : + com.android.internal.R.xml.power_profile; final Resources resources = context.getResources(); XmlResourceParser parser = resources.getXml(id); boolean parsingArray = false; - ArrayList array = new ArrayList(); + ArrayList array = new ArrayList<>(); String arrayName = null; try { @@ -237,7 +261,7 @@ public class PowerProfile { if (parsingArray && !element.equals(TAG_ARRAYITEM)) { // Finish array - sPowerMap.put(arrayName, array.toArray(new Double[array.size()])); + sPowerArrayMap.put(arrayName, array.toArray(new Double[array.size()])); parsingArray = false; } if (element.equals(TAG_ARRAY)) { @@ -255,7 +279,7 @@ public class PowerProfile { } catch (NumberFormatException nfe) { } if (element.equals(TAG_ITEM)) { - sPowerMap.put(name, value); + sPowerItemMap.put(name, value); } else if (parsingArray) { array.add(value); } @@ -263,7 +287,7 @@ public class PowerProfile { } } if (parsingArray) { - sPowerMap.put(arrayName, array.toArray(new Double[array.size()])); + sPowerArrayMap.put(arrayName, array.toArray(new Double[array.size()])); } } catch (XmlPullParserException e) { throw new RuntimeException(e); @@ -279,10 +303,6 @@ public class PowerProfile { com.android.internal.R.integer.config_bluetooth_rx_cur_ma, com.android.internal.R.integer.config_bluetooth_tx_cur_ma, com.android.internal.R.integer.config_bluetooth_operating_voltage_mv, - com.android.internal.R.integer.config_wifi_idle_receive_cur_ma, - com.android.internal.R.integer.config_wifi_active_rx_cur_ma, - com.android.internal.R.integer.config_wifi_tx_cur_ma, - com.android.internal.R.integer.config_wifi_operating_voltage_mv, }; String[] configResIdKeys = new String[]{ @@ -290,62 +310,62 @@ public class PowerProfile { POWER_BLUETOOTH_CONTROLLER_RX, POWER_BLUETOOTH_CONTROLLER_TX, POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE, - POWER_WIFI_CONTROLLER_IDLE, - POWER_WIFI_CONTROLLER_RX, - POWER_WIFI_CONTROLLER_TX, - POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE, }; for (int i = 0; i < configResIds.length; i++) { String key = configResIdKeys[i]; // if we already have some of these parameters in power_profile.xml, ignore the // value in config.xml - if ((sPowerMap.containsKey(key) && (Double) sPowerMap.get(key) > 0)) { + if ((sPowerItemMap.containsKey(key) && sPowerItemMap.get(key) > 0)) { continue; } int value = resources.getInteger(configResIds[i]); if (value > 0) { - sPowerMap.put(key, (double) value); + sPowerItemMap.put(key, (double) value); } } } private CpuClusterKey[] mCpuClusters; - private static final String POWER_CPU_CLUSTER_CORE_COUNT = "cpu.clusters.cores"; - private static final String POWER_CPU_CLUSTER_SPEED_PREFIX = "cpu.speeds.cluster"; - private static final String POWER_CPU_CLUSTER_ACTIVE_PREFIX = "cpu.active.cluster"; + private static final String CPU_PER_CLUSTER_CORE_COUNT = "cpu.clusters.cores"; + private static final String CPU_CLUSTER_POWER_COUNT = "cpu.cluster_power.cluster"; + private static final String CPU_CORE_SPEED_PREFIX = "cpu.core_speeds.cluster"; + private static final String CPU_CORE_POWER_PREFIX = "cpu.core_power.cluster"; - @SuppressWarnings("deprecation") private void initCpuClusters() { - // Figure out how many CPU clusters we're dealing with - final Object obj = sPowerMap.get(POWER_CPU_CLUSTER_CORE_COUNT); - if (obj == null || !(obj instanceof Double[])) { + if (sPowerArrayMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) { + final Double[] data = sPowerArrayMap.get(CPU_PER_CLUSTER_CORE_COUNT); + mCpuClusters = new CpuClusterKey[data.length]; + for (int cluster = 0; cluster < data.length; cluster++) { + int numCpusInCluster = (int) Math.round(data[cluster]); + mCpuClusters[cluster] = new CpuClusterKey( + CPU_CORE_SPEED_PREFIX + cluster, CPU_CLUSTER_POWER_COUNT + cluster, + CPU_CORE_POWER_PREFIX + cluster, numCpusInCluster); + } + } else { // Default to single. mCpuClusters = new CpuClusterKey[1]; - mCpuClusters[0] = new CpuClusterKey(POWER_CPU_SPEEDS, POWER_CPU_ACTIVE, 1); - - } else { - final Double[] array = (Double[]) obj; - mCpuClusters = new CpuClusterKey[array.length]; - for (int cluster = 0; cluster < array.length; cluster++) { - int numCpusInCluster = (int) Math.round(array[cluster]); - mCpuClusters[cluster] = new CpuClusterKey( - POWER_CPU_CLUSTER_SPEED_PREFIX + cluster, - POWER_CPU_CLUSTER_ACTIVE_PREFIX + cluster, - numCpusInCluster); + int numCpus = 1; + if (sPowerItemMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) { + numCpus = (int) Math.round(sPowerItemMap.get(CPU_PER_CLUSTER_CORE_COUNT)); } + mCpuClusters[0] = new CpuClusterKey(CPU_CORE_SPEED_PREFIX + 0, + CPU_CLUSTER_POWER_COUNT + 0, CPU_CORE_POWER_PREFIX + 0, numCpus); } } public static class CpuClusterKey { - private final String timeKey; - private final String powerKey; + private final String freqKey; + private final String clusterPowerKey; + private final String corePowerKey; private final int numCpus; - private CpuClusterKey(String timeKey, String powerKey, int numCpus) { - this.timeKey = timeKey; - this.powerKey = powerKey; + private CpuClusterKey(String freqKey, String clusterPowerKey, + String corePowerKey, int numCpus) { + this.freqKey = freqKey; + this.clusterPowerKey = clusterPowerKey; + this.corePowerKey = corePowerKey; this.numCpus = numCpus; } } @@ -354,21 +374,30 @@ public class PowerProfile { return mCpuClusters.length; } - public int getNumCoresInCpuCluster(int index) { - return mCpuClusters[index].numCpus; + public int getNumCoresInCpuCluster(int cluster) { + return mCpuClusters[cluster].numCpus; } - public int getNumSpeedStepsInCpuCluster(int index) { - Object value = sPowerMap.get(mCpuClusters[index].timeKey); - if (value != null && value instanceof Double[]) { - return ((Double[])value).length; + public int getNumSpeedStepsInCpuCluster(int cluster) { + if (cluster < 0 || cluster >= mCpuClusters.length) { + return 0; // index out of bound + } + if (sPowerArrayMap.containsKey(mCpuClusters[cluster].freqKey)) { + return sPowerArrayMap.get(mCpuClusters[cluster].freqKey).length; } return 1; // Only one speed } - public double getAveragePowerForCpu(int cluster, int step) { + public double getAveragePowerForCpuCluster(int cluster) { if (cluster >= 0 && cluster < mCpuClusters.length) { - return getAveragePower(mCpuClusters[cluster].powerKey, step); + return getAveragePower(mCpuClusters[cluster].clusterPowerKey); + } + return 0; + } + + public double getAveragePowerForCpuCore(int cluster, int step) { + if (cluster >= 0 && cluster < mCpuClusters.length) { + return getAveragePower(mCpuClusters[cluster].corePowerKey, step); } return 0; } @@ -379,14 +408,10 @@ public class PowerProfile { * @return the number of memory bandwidth buckets. */ public int getNumElements(String key) { - if (sPowerMap.containsKey(key)) { - Object data = sPowerMap.get(key); - if (data instanceof Double[]) { - final Double[] values = (Double[]) data; - return values.length; - } else { - return 1; - } + if (sPowerItemMap.containsKey(key)) { + return 1; + } else if (sPowerArrayMap.containsKey(key)) { + return sPowerArrayMap.get(key).length; } return 0; } @@ -399,13 +424,10 @@ public class PowerProfile { * @return the average current in milliAmps. */ public double getAveragePowerOrDefault(String type, double defaultValue) { - if (sPowerMap.containsKey(type)) { - Object data = sPowerMap.get(type); - if (data instanceof Double[]) { - return ((Double[])data)[0]; - } else { - return (Double) sPowerMap.get(type); - } + if (sPowerItemMap.containsKey(type)) { + return sPowerItemMap.get(type); + } else if (sPowerArrayMap.containsKey(type)) { + return sPowerArrayMap.get(type)[0]; } else { return defaultValue; } @@ -429,19 +451,16 @@ public class PowerProfile { * @return the average current in milliAmps. */ public double getAveragePower(String type, int level) { - if (sPowerMap.containsKey(type)) { - Object data = sPowerMap.get(type); - if (data instanceof Double[]) { - final Double[] values = (Double[]) data; - if (values.length > level && level >= 0) { - return values[level]; - } else if (level < 0 || values.length == 0) { - return 0; - } else { - return values[values.length - 1]; - } + if (sPowerItemMap.containsKey(type)) { + return sPowerItemMap.get(type); + } else if (sPowerArrayMap.containsKey(type)) { + final Double[] values = sPowerArrayMap.get(type); + if (values.length > level && level >= 0) { + return values[level]; + } else if (level < 0 || values.length == 0) { + return 0; } else { - return (Double) data; + return values[values.length - 1]; } } else { return 0; diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java index c7897b2bbc3bd58ac7414d74b8b928dd2cc118a2..486b5842400c3145f56b3107948d0fd10bf7ae6a 100644 --- a/core/java/com/android/internal/os/WakelockPowerCalculator.java +++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java @@ -26,7 +26,7 @@ public class WakelockPowerCalculator extends PowerCalculator { private long mTotalAppWakelockTimeMs = 0; public WakelockPowerCalculator(PowerProfile profile) { - mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_AWAKE); + mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_IDLE); } @Override diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index cbc63cf813cb3482a0eee412992250328e594005..f9a2341fc3e3cc151a5cd62e4e0aa7ba11a43f94 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -53,6 +53,8 @@ public final class Zygote { public static final int DISABLE_VERIFIER = 1 << 9; /** Only use oat files located in /system. Otherwise use dex/jar/apk . */ public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10; + /** Do not enfore hidden API access restrictions. */ + public static final int DISABLE_HIDDEN_API_CHECKS = 1 << 11; /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; @@ -67,6 +69,9 @@ public final class Zygote { private Zygote() {} + /** Called for some security initialization before any fork. */ + native static void nativeSecurityInit(); + /** * Forks a new VM instance. The current VM must have been started * with the -Xzygote flag. NOTE: new instance keeps all @@ -153,6 +158,9 @@ public final class Zygote { */ public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { + // SystemServer is always allowed to use hidden APIs. + runtimeFlags |= DISABLE_HIDDEN_API_CHECKS; + VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index c5fe4cb0177bbe6642ca0cd5d2fb9d2490d3b9b0..39279b5055b1863191e1ed0690747165c5fc356d 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -30,7 +30,6 @@ import android.os.IInstalld; import android.os.Environment; import android.os.Process; import android.os.RemoteException; -import android.os.Seccomp; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.SystemClock; @@ -99,6 +98,10 @@ public class ZygoteInit { private static final String SOCKET_NAME_ARG = "--socket-name="; + /* Dexopt flag to disable hidden API access checks when dexopting SystemServer. + * Must be kept in sync with com.android.server.pm.Installer. */ + private static final int DEXOPT_DISABLE_HIDDEN_API_CHECKS = 1 << 10; + /** * Used to pre-load resources. */ @@ -566,16 +569,21 @@ public class ZygoteInit { if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { final String packageName = "*"; final String outputPath = null; - final int dexFlags = 0; + // Dexopt with a flag which lifts restrictions on hidden API usage. + // Offending methods would otherwise be re-verified at runtime and + // we want to avoid the performance overhead of that. + final int dexFlags = DEXOPT_DISABLE_HIDDEN_API_CHECKS; final String compilerFilter = systemServerFilter; final String uuid = StorageManager.UUID_PRIVATE_INTERNAL; final String seInfo = null; final String classLoaderContext = getSystemServerClassLoaderContext(classPathForElement); + final int targetSdkVersion = 0; // SystemServer targets the system's SDK version try { installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName, instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter, - uuid, classLoaderContext, seInfo, false /* downgrade */); + uuid, classLoaderContext, seInfo, false /* downgrade */, + targetSdkVersion, /*profileName*/ null); } catch (RemoteException | ServiceSpecificException e) { // Ignore (but log), we need this on the classpath for fallback mode. Log.w(TAG, "Failed compiling classpath element for system server: " @@ -779,12 +787,11 @@ public class ZygoteInit { // Zygote. Trace.setTracingEnabled(false, 0); + Zygote.nativeSecurityInit(); + // Zygote process unmounts root storage spaces. Zygote.nativeUnmountStorageOnInit(); - // Set seccomp policy - Seccomp.setPolicy(); - ZygoteHooks.stopZygoteNoThreadCreation(); if (startSystemServer) { diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..245a66e4b566700cbe361a3ea4bd4ea4c8cc7ba3 --- /dev/null +++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java @@ -0,0 +1,99 @@ +/* + * 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.internal.os.logging; + +import android.content.Context; +import android.util.StatsLog; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +/** + * Used to wrap different logging calls in one, so that client side code base is clean and more + * readable. + */ +public class MetricsLoggerWrapper { + + private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0; + private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1; + + public static void logPictureInPictureDismissByTap(Context context) { + MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, + METRIC_VALUE_DISMISSED_BY_TAP); + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED); + } + + public static void logPictureInPictureDismissByDrag(Context context) { + MetricsLogger.action(context, + MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, + METRIC_VALUE_DISMISSED_BY_DRAG); + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED); + } + + public static void logPictureInPictureMinimize(Context context, boolean isMinimized) { + MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED, + isMinimized); + if (isMinimized) { + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__MINIMIZED); + } else { + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__EXPANDED_TO_FULL_SCREEN); + } + } + + public static void logPictureInPictureMenuVisible(Context context, boolean menuStateFull) { + MetricsLogger.visibility(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU, + menuStateFull); + } + + public static void logPictureInPictureEnter(Context context, + boolean supportsEnterPipOnTaskSwitch) { + MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_ENTERED, + supportsEnterPipOnTaskSwitch); + if (supportsEnterPipOnTaskSwitch) { + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__ENTERED); + } + } + + public static void logPictureInPictureFullScreen(Context context) { + MetricsLogger.action(context, + MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN); + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__EXPANDED_TO_FULL_SCREEN); + } +} diff --git a/core/java/com/android/internal/policy/KeyguardDismissCallback.java b/core/java/com/android/internal/policy/KeyguardDismissCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..38337ec6f274a1152e48542ca41cc545e3a7ba8f --- /dev/null +++ b/core/java/com/android/internal/policy/KeyguardDismissCallback.java @@ -0,0 +1,41 @@ +/* + * 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.internal.policy; + +import android.os.RemoteException; +import com.android.internal.policy.IKeyguardDismissCallback; + +/** + * @hide + */ +public class KeyguardDismissCallback extends IKeyguardDismissCallback.Stub { + + @Override + public void onDismissError() throws RemoteException { + // To be overidden + } + + @Override + public void onDismissSucceeded() throws RemoteException { + // To be overidden + } + + @Override + public void onDismissCancelled() throws RemoteException { + // To be overidden + } +} diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index e8ee29dc19f86741fc02ef7938bba080ac446f40..34b5ec8149a7be8493b7215cfa88d2bc2fc5021d 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -3810,6 +3810,19 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } + @Override + public void setNavigationBarDividerColor(int navigationBarDividerColor) { + mNavigationBarDividerColor = navigationBarDividerColor; + if (mDecor != null) { + mDecor.updateColorViews(null, false /* animate */); + } + } + + @Override + public int getNavigationBarDividerColor() { + return mNavigationBarDividerColor; + } + public void setIsStartingWindow(boolean isStartingWindow) { mIsStartingWindow = isStartingWindow; } diff --git a/core/java/com/android/internal/print/DualDumpOutputStream.java b/core/java/com/android/internal/print/DualDumpOutputStream.java new file mode 100644 index 0000000000000000000000000000000000000000..4b10ef2facbbca677b9f3faca431ea1c97341129 --- /dev/null +++ b/core/java/com/android/internal/print/DualDumpOutputStream.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.print; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.IndentingPrintWriter; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedList; + +/** + * Dump either to a proto or a print writer using the same interface. + * + *

        This mirrors the interface of {@link ProtoOutputStream}. + */ +public class DualDumpOutputStream { + private static final String LOG_TAG = DualDumpOutputStream.class.getSimpleName(); + + // When writing to a proto, the proto + private final @Nullable ProtoOutputStream mProtoStream; + + // When printing in clear text, the writer + private final @Nullable IndentingPrintWriter mIpw; + // Temporary storage of data when printing to mIpw + private final LinkedList mDumpObjects = new LinkedList<>(); + + private static abstract class Dumpable { + final String name; + + private Dumpable(String name) { + this.name = name; + } + + abstract void print(IndentingPrintWriter ipw, boolean printName); + } + + private static class DumpObject extends Dumpable { + private final LinkedHashMap> mSubObjects = new LinkedHashMap<>(); + + private DumpObject(String name) { + super(name); + } + + @Override + void print(IndentingPrintWriter ipw, boolean printName) { + if (printName) { + ipw.println(name + "={"); + } else { + ipw.println("{"); + } + ipw.increaseIndent(); + + for (ArrayList subObject: mSubObjects.values()) { + int numDumpables = subObject.size(); + + if (numDumpables == 1) { + subObject.get(0).print(ipw, true); + } else { + ipw.println(subObject.get(0).name + "=["); + ipw.increaseIndent(); + + for (int i = 0; i < numDumpables; i++) { + subObject.get(i).print(ipw, false); + } + + ipw.decreaseIndent(); + ipw.println("]"); + } + } + + ipw.decreaseIndent(); + ipw.println("}"); + } + + /** + * Add new field / subobject to this object. + * + *

        If a name is added twice, they will be printed as a array + * + * @param fieldName name of the field added + * @param d The dumpable to add + */ + public void add(String fieldName, Dumpable d) { + ArrayList l = mSubObjects.get(fieldName); + + if (l == null) { + l = new ArrayList<>(1); + mSubObjects.put(fieldName, l); + } + + l.add(d); + } + } + + private static class DumpField extends Dumpable { + private final String mValue; + + private DumpField(String name, String value) { + super(name); + this.mValue = value; + } + + @Override + void print(IndentingPrintWriter ipw, boolean printName) { + if (printName) { + ipw.println(name + "=" + mValue); + } else { + ipw.println(mValue); + } + } + } + + + /** + * Create a new DualDumpOutputStream. Only one output should be set. + * + * @param proto If dumping to proto the {@link ProtoOutputStream} + * @param ipw If dumping to a print writer, the {@link IndentingPrintWriter} + */ + public DualDumpOutputStream(@Nullable ProtoOutputStream proto, + @Nullable IndentingPrintWriter ipw) { + if ((proto == null) == (ipw == null)) { + Log.e(LOG_TAG, "Cannot dump to clear text and proto at once. Ignoring proto"); + proto = null; + } + + mProtoStream = proto; + mIpw = ipw; + + if (!isProto()) { + // Add root object + mDumpObjects.add(new DumpObject(null)); + } + } + + public void write(@NonNull String fieldName, long fieldId, double val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, boolean val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, int val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, float val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, byte[] val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, Arrays.toString(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, long val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public void write(@NonNull String fieldName, long fieldId, @Nullable String val) { + if (mProtoStream != null) { + mProtoStream.write(fieldId, val); + } else { + mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); + } + } + + public long start(@NonNull String fieldName, long fieldId) { + if (mProtoStream != null) { + return mProtoStream.start(fieldId); + } else { + DumpObject d = new DumpObject(fieldName); + mDumpObjects.getLast().add(fieldName, d); + mDumpObjects.addLast(d); + return System.identityHashCode(d); + } + } + + public void end(long token) { + if (mProtoStream != null) { + mProtoStream.end(token); + } else { + if (System.identityHashCode(mDumpObjects.getLast()) != token) { + Log.w(LOG_TAG, "Unexpected token for ending " + mDumpObjects.getLast().name + + " at " + Arrays.toString(Thread.currentThread().getStackTrace())); + } + mDumpObjects.removeLast(); + } + } + + public void flush() { + if (mProtoStream != null) { + mProtoStream.flush(); + } else { + if (mDumpObjects.size() == 1) { + mDumpObjects.getFirst().print(mIpw, false); + + // Reset root object + mDumpObjects.clear(); + mDumpObjects.add(new DumpObject(null)); + } + + mIpw.flush(); + } + } + + /** + * Add a dump from a different service into this dump. + * + *

        Only for clear text dump. For proto dump use {@link #write(String, long, byte[])}. + * + * @param fieldName The name of the field + * @param nestedState The state of the dump + */ + public void writeNested(@NonNull String fieldName, byte[] nestedState) { + if (mIpw == null) { + Log.w(LOG_TAG, "writeNested does not work for proto logging"); + return; + } + + mDumpObjects.getLast().add(fieldName, + new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim())); + } + + /** + * @return {@code true} iff we are dumping to a proto + */ + public boolean isProto() { + return mProtoStream != null; + } +} diff --git a/core/java/com/android/internal/print/DumpUtils.java b/core/java/com/android/internal/print/DumpUtils.java index 28c7fc2182b29f41c25ee6d599ee35e674f83b03..3192d5cbbd1d57295b5a13c5316a2f8a1c9f807f 100644 --- a/core/java/com/android/internal/print/DumpUtils.java +++ b/core/java/com/android/internal/print/DumpUtils.java @@ -39,7 +39,6 @@ import android.service.print.PrinterCapabilitiesProto; import android.service.print.PrinterIdProto; import android.service.print.PrinterInfoProto; import android.service.print.ResolutionProto; -import android.util.proto.ProtoOutputStream; /** * Utilities for dumping print related proto buffer @@ -49,13 +48,14 @@ public class DumpUtils { * Write a string to a proto if the string is not {@code null}. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the string * @param string The string to write */ - public static void writeStringIfNotNull(@NonNull ProtoOutputStream proto, long id, - @Nullable String string) { + public static void writeStringIfNotNull(@NonNull DualDumpOutputStream proto, String idName, + long id, @Nullable String string) { if (string != null) { - proto.write(id, string); + proto.write(idName, id, string); } } @@ -63,14 +63,15 @@ public class DumpUtils { * Write a {@link ComponentName} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param component The component name to write */ - public static void writeComponentName(@NonNull ProtoOutputStream proto, long id, - @NonNull ComponentName component) { - long token = proto.start(id); - proto.write(ComponentNameProto.PACKAGE_NAME, component.getPackageName()); - proto.write(ComponentNameProto.CLASS_NAME, component.getClassName()); + public static void writeComponentName(@NonNull DualDumpOutputStream proto, String idName, + long id, @NonNull ComponentName component) { + long token = proto.start(idName, id); + proto.write("package_name", ComponentNameProto.PACKAGE_NAME, component.getPackageName()); + proto.write("class_name", ComponentNameProto.CLASS_NAME, component.getClassName()); proto.end(token); } @@ -78,14 +79,16 @@ public class DumpUtils { * Write a {@link PrinterId} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param printerId The printer id to write */ - public static void writePrinterId(@NonNull ProtoOutputStream proto, long id, + public static void writePrinterId(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PrinterId printerId) { - long token = proto.start(id); - writeComponentName(proto, PrinterIdProto.SERVICE_NAME, printerId.getServiceName()); - proto.write(PrinterIdProto.LOCAL_ID, printerId.getLocalId()); + long token = proto.start(idName, id); + writeComponentName(proto, "service_name", PrinterIdProto.SERVICE_NAME, + printerId.getServiceName()); + proto.write("local_id", PrinterIdProto.LOCAL_ID, printerId.getLocalId()); proto.end(token); } @@ -93,71 +96,76 @@ public class DumpUtils { * Write a {@link PrinterCapabilitiesInfo} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param cap The capabilities to write */ public static void writePrinterCapabilities(@NonNull Context context, - @NonNull ProtoOutputStream proto, long id, @NonNull PrinterCapabilitiesInfo cap) { - long token = proto.start(id); - writeMargins(proto, PrinterCapabilitiesProto.MIN_MARGINS, cap.getMinMargins()); + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrinterCapabilitiesInfo cap) { + long token = proto.start(idName, id); + writeMargins(proto, "min_margins", PrinterCapabilitiesProto.MIN_MARGINS, + cap.getMinMargins()); int numMediaSizes = cap.getMediaSizes().size(); for (int i = 0; i < numMediaSizes; i++) { - writeMediaSize(context, proto, PrinterCapabilitiesProto.MEDIA_SIZES, + writeMediaSize(context, proto, "media_sizes", PrinterCapabilitiesProto.MEDIA_SIZES, cap.getMediaSizes().get(i)); } int numResolutions = cap.getResolutions().size(); for (int i = 0; i < numResolutions; i++) { - writeResolution(proto, PrinterCapabilitiesProto.RESOLUTIONS, + writeResolution(proto, "resolutions", PrinterCapabilitiesProto.RESOLUTIONS, cap.getResolutions().get(i)); } if ((cap.getColorModes() & PrintAttributes.COLOR_MODE_MONOCHROME) != 0) { - proto.write(PrinterCapabilitiesProto.COLOR_MODES, + proto.write("color_modes", PrinterCapabilitiesProto.COLOR_MODES, PrintAttributesProto.COLOR_MODE_MONOCHROME); } if ((cap.getColorModes() & PrintAttributes.COLOR_MODE_COLOR) != 0) { - proto.write(PrinterCapabilitiesProto.COLOR_MODES, + proto.write("color_modes", PrinterCapabilitiesProto.COLOR_MODES, PrintAttributesProto.COLOR_MODE_COLOR); } if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_NONE) != 0) { - proto.write(PrinterCapabilitiesProto.DUPLEX_MODES, + proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES, PrintAttributesProto.DUPLEX_MODE_NONE); } if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_LONG_EDGE) != 0) { - proto.write(PrinterCapabilitiesProto.DUPLEX_MODES, + proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES, PrintAttributesProto.DUPLEX_MODE_LONG_EDGE); } if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_SHORT_EDGE) != 0) { - proto.write(PrinterCapabilitiesProto.DUPLEX_MODES, + proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES, PrintAttributesProto.DUPLEX_MODE_SHORT_EDGE); } proto.end(token); } - /** * Write a {@link PrinterInfo} to a proto. * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param info The printer info to write */ - public static void writePrinterInfo(@NonNull Context context, @NonNull ProtoOutputStream proto, - long id, @NonNull PrinterInfo info) { - long token = proto.start(id); - writePrinterId(proto, PrinterInfoProto.ID, info.getId()); - proto.write(PrinterInfoProto.NAME, info.getName()); - proto.write(PrinterInfoProto.STATUS, info.getStatus()); - proto.write(PrinterInfoProto.DESCRIPTION, info.getDescription()); + public static void writePrinterInfo(@NonNull Context context, + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrinterInfo info) { + long token = proto.start(idName, id); + writePrinterId(proto, "id", PrinterInfoProto.ID, info.getId()); + proto.write("name", PrinterInfoProto.NAME, info.getName()); + proto.write("status", PrinterInfoProto.STATUS, info.getStatus()); + proto.write("description", PrinterInfoProto.DESCRIPTION, info.getDescription()); PrinterCapabilitiesInfo cap = info.getCapabilities(); if (cap != null) { - writePrinterCapabilities(context, proto, PrinterInfoProto.CAPABILITIES, cap); + writePrinterCapabilities(context, proto, "capabilities", PrinterInfoProto.CAPABILITIES, + cap); } proto.end(token); @@ -168,16 +176,17 @@ public class DumpUtils { * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param mediaSize The media size to write */ - public static void writeMediaSize(@NonNull Context context, @NonNull ProtoOutputStream proto, - long id, @NonNull PrintAttributes.MediaSize mediaSize) { - long token = proto.start(id); - proto.write(MediaSizeProto.ID, mediaSize.getId()); - proto.write(MediaSizeProto.LABEL, mediaSize.getLabel(context.getPackageManager())); - proto.write(MediaSizeProto.HEIGHT_MILS, mediaSize.getHeightMils()); - proto.write(MediaSizeProto.WIDTH_MILS, mediaSize.getWidthMils()); + public static void writeMediaSize(@NonNull Context context, @NonNull DualDumpOutputStream proto, + String idName, long id, @NonNull PrintAttributes.MediaSize mediaSize) { + long token = proto.start(idName, id); + proto.write("id", MediaSizeProto.ID, mediaSize.getId()); + proto.write("label", MediaSizeProto.LABEL, mediaSize.getLabel(context.getPackageManager())); + proto.write("height_mils", MediaSizeProto.HEIGHT_MILS, mediaSize.getHeightMils()); + proto.write("width_mils", MediaSizeProto.WIDTH_MILS, mediaSize.getWidthMils()); proto.end(token); } @@ -185,16 +194,17 @@ public class DumpUtils { * Write a {@link PrintAttributes.Resolution} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param res The resolution to write */ - public static void writeResolution(@NonNull ProtoOutputStream proto, long id, + public static void writeResolution(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PrintAttributes.Resolution res) { - long token = proto.start(id); - proto.write(ResolutionProto.ID, res.getId()); - proto.write(ResolutionProto.LABEL, res.getLabel()); - proto.write(ResolutionProto.HORIZONTAL_DPI, res.getHorizontalDpi()); - proto.write(ResolutionProto.VERTICAL_DPI, res.getVerticalDpi()); + long token = proto.start(idName, id); + proto.write("id", ResolutionProto.ID, res.getId()); + proto.write("label", ResolutionProto.LABEL, res.getLabel()); + proto.write("horizontal_DPI", ResolutionProto.HORIZONTAL_DPI, res.getHorizontalDpi()); + proto.write("veritical_DPI", ResolutionProto.VERTICAL_DPI, res.getVerticalDpi()); proto.end(token); } @@ -202,16 +212,17 @@ public class DumpUtils { * Write a {@link PrintAttributes.Margins} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param margins The margins to write */ - public static void writeMargins(@NonNull ProtoOutputStream proto, long id, + public static void writeMargins(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PrintAttributes.Margins margins) { - long token = proto.start(id); - proto.write(MarginsProto.TOP_MILS, margins.getTopMils()); - proto.write(MarginsProto.LEFT_MILS, margins.getLeftMils()); - proto.write(MarginsProto.RIGHT_MILS, margins.getRightMils()); - proto.write(MarginsProto.BOTTOM_MILS, margins.getBottomMils()); + long token = proto.start(idName, id); + proto.write("top_mils", MarginsProto.TOP_MILS, margins.getTopMils()); + proto.write("left_mils", MarginsProto.LEFT_MILS, margins.getLeftMils()); + proto.write("right_mils", MarginsProto.RIGHT_MILS, margins.getRightMils()); + proto.write("bottom_mils", MarginsProto.BOTTOM_MILS, margins.getBottomMils()); proto.end(token); } @@ -220,32 +231,34 @@ public class DumpUtils { * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param attributes The attributes to write */ public static void writePrintAttributes(@NonNull Context context, - @NonNull ProtoOutputStream proto, long id, @NonNull PrintAttributes attributes) { - long token = proto.start(id); + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrintAttributes attributes) { + long token = proto.start(idName, id); PrintAttributes.MediaSize mediaSize = attributes.getMediaSize(); if (mediaSize != null) { - writeMediaSize(context, proto, PrintAttributesProto.MEDIA_SIZE, mediaSize); + writeMediaSize(context, proto, "media_size", PrintAttributesProto.MEDIA_SIZE, mediaSize); } - proto.write(PrintAttributesProto.IS_PORTRAIT, attributes.isPortrait()); + proto.write("is_portrait", PrintAttributesProto.IS_PORTRAIT, attributes.isPortrait()); PrintAttributes.Resolution res = attributes.getResolution(); if (res != null) { - writeResolution(proto, PrintAttributesProto.RESOLUTION, res); + writeResolution(proto, "resolution", PrintAttributesProto.RESOLUTION, res); } PrintAttributes.Margins minMargins = attributes.getMinMargins(); if (minMargins != null) { - writeMargins(proto, PrintAttributesProto.MIN_MARGINS, minMargins); + writeMargins(proto, "min_margings", PrintAttributesProto.MIN_MARGINS, minMargins); } - proto.write(PrintAttributesProto.COLOR_MODE, attributes.getColorMode()); - proto.write(PrintAttributesProto.DUPLEX_MODE, attributes.getDuplexMode()); + proto.write("color_mode", PrintAttributesProto.COLOR_MODE, attributes.getColorMode()); + proto.write("duplex_mode", PrintAttributesProto.DUPLEX_MODE, attributes.getDuplexMode()); proto.end(token); } @@ -253,21 +266,22 @@ public class DumpUtils { * Write a {@link PrintDocumentInfo} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param info The info to write */ - public static void writePrintDocumentInfo(@NonNull ProtoOutputStream proto, long id, - @NonNull PrintDocumentInfo info) { - long token = proto.start(id); - proto.write(PrintDocumentInfoProto.NAME, info.getName()); + public static void writePrintDocumentInfo(@NonNull DualDumpOutputStream proto, String idName, + long id, @NonNull PrintDocumentInfo info) { + long token = proto.start(idName, id); + proto.write("name", PrintDocumentInfoProto.NAME, info.getName()); int pageCount = info.getPageCount(); if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) { - proto.write(PrintDocumentInfoProto.PAGE_COUNT, pageCount); + proto.write("page_count", PrintDocumentInfoProto.PAGE_COUNT, pageCount); } - proto.write(PrintDocumentInfoProto.CONTENT_TYPE, info.getContentType()); - proto.write(PrintDocumentInfoProto.DATA_SIZE, info.getDataSize()); + proto.write("content_type", PrintDocumentInfoProto.CONTENT_TYPE, info.getContentType()); + proto.write("data_size", PrintDocumentInfoProto.DATA_SIZE, info.getDataSize()); proto.end(token); } @@ -275,14 +289,15 @@ public class DumpUtils { * Write a {@link PageRange} to a proto. * * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param range The range to write */ - public static void writePageRange(@NonNull ProtoOutputStream proto, long id, + public static void writePageRange(@NonNull DualDumpOutputStream proto, String idName, long id, @NonNull PageRange range) { - long token = proto.start(id); - proto.write(PageRangeProto.START, range.getStart()); - proto.write(PageRangeProto.END, range.getEnd()); + long token = proto.start(idName, id); + proto.write("start", PageRangeProto.START, range.getStart()); + proto.write("end", PageRangeProto.END, range.getEnd()); proto.end(token); } @@ -291,64 +306,70 @@ public class DumpUtils { * * @param context The context used to resolve resources * @param proto The proto to write to + * @param idName Clear text name of the proto-id * @param id The proto-id of the component name * @param printJobInfo The print job info to write */ - public static void writePrintJobInfo(@NonNull Context context, @NonNull ProtoOutputStream proto, - long id, @NonNull PrintJobInfo printJobInfo) { - long token = proto.start(id); - proto.write(PrintJobInfoProto.LABEL, printJobInfo.getLabel()); + public static void writePrintJobInfo(@NonNull Context context, + @NonNull DualDumpOutputStream proto, String idName, long id, + @NonNull PrintJobInfo printJobInfo) { + long token = proto.start(idName, id); + proto.write("label", PrintJobInfoProto.LABEL, printJobInfo.getLabel()); PrintJobId printJobId = printJobInfo.getId(); if (printJobId != null) { - proto.write(PrintJobInfoProto.PRINT_JOB_ID, printJobId.flattenToString()); + proto.write("print_job_id", PrintJobInfoProto.PRINT_JOB_ID, + printJobId.flattenToString()); } int state = printJobInfo.getState(); if (state >= PrintJobInfoProto.STATE_CREATED && state <= PrintJobInfoProto.STATE_CANCELED) { - proto.write(PrintJobInfoProto.STATE, state); + proto.write("state", PrintJobInfoProto.STATE, state); } else { - proto.write(PrintJobInfoProto.STATE, PrintJobInfoProto.STATE_UNKNOWN); + proto.write("state", PrintJobInfoProto.STATE, PrintJobInfoProto.STATE_UNKNOWN); } PrinterId printer = printJobInfo.getPrinterId(); if (printer != null) { - writePrinterId(proto, PrintJobInfoProto.PRINTER, printer); + writePrinterId(proto, "printer", PrintJobInfoProto.PRINTER, printer); } String tag = printJobInfo.getTag(); if (tag != null) { - proto.write(PrintJobInfoProto.TAG, tag); + proto.write("tag", PrintJobInfoProto.TAG, tag); } - proto.write(PrintJobInfoProto.CREATION_TIME, printJobInfo.getCreationTime()); + proto.write("creation_time", PrintJobInfoProto.CREATION_TIME, + printJobInfo.getCreationTime()); PrintAttributes attributes = printJobInfo.getAttributes(); if (attributes != null) { - writePrintAttributes(context, proto, PrintJobInfoProto.ATTRIBUTES, attributes); + writePrintAttributes(context, proto, "attributes", PrintJobInfoProto.ATTRIBUTES, + attributes); } PrintDocumentInfo docInfo = printJobInfo.getDocumentInfo(); if (docInfo != null) { - writePrintDocumentInfo(proto, PrintJobInfoProto.DOCUMENT_INFO, docInfo); + writePrintDocumentInfo(proto, "document_info", PrintJobInfoProto.DOCUMENT_INFO, + docInfo); } - proto.write(PrintJobInfoProto.IS_CANCELING, printJobInfo.isCancelling()); + proto.write("is_canceling", PrintJobInfoProto.IS_CANCELING, printJobInfo.isCancelling()); PageRange[] pages = printJobInfo.getPages(); if (pages != null) { for (int i = 0; i < pages.length; i++) { - writePageRange(proto, PrintJobInfoProto.PAGES, pages[i]); + writePageRange(proto, "pages", PrintJobInfoProto.PAGES, pages[i]); } } - proto.write(PrintJobInfoProto.HAS_ADVANCED_OPTIONS, + proto.write("has_advanced_options", PrintJobInfoProto.HAS_ADVANCED_OPTIONS, printJobInfo.getAdvancedOptions() != null); - proto.write(PrintJobInfoProto.PROGRESS, printJobInfo.getProgress()); + proto.write("progress", PrintJobInfoProto.PROGRESS, printJobInfo.getProgress()); CharSequence status = printJobInfo.getStatus(context.getPackageManager()); if (status != null) { - proto.write(PrintJobInfoProto.STATUS, status.toString()); + proto.write("status", PrintJobInfoProto.STATUS, status.toString()); } proto.end(token); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 5ec90941aa887d40530d4d040d9635eb12448176..ebb5f9f9e446d316e448f8fcb72fef439a625d73 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -18,6 +18,7 @@ package com.android.internal.statusbar; import android.content.ComponentName; import android.graphics.Rect; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -34,6 +35,8 @@ oneway interface IStatusBar void animateCollapsePanels(); void togglePanel(); + void showChargingAnimation(int batteryLevel); + /** * Notifies the status bar of a System UI visibility flag change. * @@ -55,7 +58,7 @@ oneway interface IStatusBar boolean showImeSwitcher); void setWindowState(int window, int state); - void showRecentApps(boolean triggeredFromAltTab, boolean fromHome); + void showRecentApps(boolean triggeredFromAltTab); void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey); void toggleRecentApps(); void toggleSplitScreen(); @@ -115,7 +118,7 @@ oneway interface IStatusBar /** * Notifies the status bar that a new rotation suggestion is available. */ - void onProposedRotationChanged(int rotation); + void onProposedRotationChanged(int rotation, boolean isValid); /** * Set whether the top app currently hides the statusbar. @@ -130,4 +133,15 @@ oneway interface IStatusBar void handleSystemKey(in int key); void showShutdownUi(boolean isReboot, String reason); + + // Used to show the dialog when FingerprintService starts authentication + void showFingerprintDialog(in Bundle bundle, IFingerprintDialogReceiver receiver); + // Used to hide the dialog when a finger is authenticated + void onFingerprintAuthenticated(); + // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc + void onFingerprintHelp(String message); + // Used to set a message - the dialog will dismiss after a certain amount of time + void onFingerprintError(String error); + // Used to hide the fingerprint dialog when the authenticationclient is stopped + void hideFingerprintDialog(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 03603e4011101fbadef93f792778bbe045e3690a..cb0b53c495dcf0d12de5188f5f4ec9df805656e5 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.graphics.Rect; import android.os.Bundle; import android.service.notification.StatusBarNotification; +import android.hardware.fingerprint.IFingerprintDialogReceiver; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; @@ -79,4 +80,15 @@ interface IStatusBarService void remTile(in ComponentName tile); void clickTile(in ComponentName tile); void handleSystemKey(in int key); + + // Used to show the dialog when FingerprintService starts authentication + void showFingerprintDialog(in Bundle bundle, IFingerprintDialogReceiver receiver); + // Used to hide the dialog when a finger is authenticated + void onFingerprintAuthenticated(); + // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc + void onFingerprintHelp(String message); + // Used to set a message - the dialog will dismiss after a certain amount of time + void onFingerprintError(String error); + // Used to hide the fingerprint dialog when the authenticationclient is stopped + void hideFingerprintDialog(); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 7b023f412cbcfc02849b6120b2ec374035ab076e..621619c5134d8dd3a80525844eddb72f8e7ade70 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/util/ConcurrentUtils.java b/core/java/com/android/internal/util/ConcurrentUtils.java index e35f9f45acfe7e46feea6dd7c9a1246781a94c96..e08eb587ab97566fd4e76f1c5902715d2450a112 100644 --- a/core/java/com/android/internal/util/ConcurrentUtils.java +++ b/core/java/com/android/internal/util/ConcurrentUtils.java @@ -18,11 +18,13 @@ package com.android.internal.util; import android.os.Process; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** @@ -86,4 +88,27 @@ public class ConcurrentUtils { } } + /** + * Waits for {@link CountDownLatch#countDown()} to be called on the {@param countDownLatch}. + *

        If {@link CountDownLatch#countDown()} doesn't occur within {@param timeoutMs}, this + * method will throw {@code IllegalStateException} + *

        If {@code InterruptedException} occurs, this method will interrupt the current thread + * and throw {@code IllegalStateException} + * + * @param countDownLatch the CountDownLatch which {@link CountDownLatch#countDown()} is + * being waited on. + * @param timeoutMs the maximum time waited for {@link CountDownLatch#countDown()} + * @param description a short description of the operation + */ + public static void waitForCountDownNoInterrupt(CountDownLatch countDownLatch, long timeoutMs, + String description) { + try { + if (!countDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException(description + " timed out."); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(description + " interrupted."); + } + } } diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java index 66b777e8e8e60b10e8b87f891345e4785c076275..2b5103377ecbfe1b54f09224ca21e7233d0c0b43 100644 --- a/core/java/com/android/internal/util/DumpUtils.java +++ b/core/java/com/android/internal/util/DumpUtils.java @@ -102,6 +102,7 @@ public final class DumpUtils { case android.os.Process.ROOT_UID: case android.os.Process.SYSTEM_UID: case android.os.Process.SHELL_UID: + case android.os.Process.INCIDENTD_UID: return true; } diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java index 379602ac904181b6d75ec66d6b8d2b9ec1b68078..59e5a6402fb82d9d1c9b4a6214c3d9ecd661fe5f 100644 --- a/core/java/com/android/internal/util/ObjectUtils.java +++ b/core/java/com/android/internal/util/ObjectUtils.java @@ -29,9 +29,6 @@ public class ObjectUtils { return a != null ? a : Preconditions.checkNotNull(b); } - /** - * Compares two {@link Nullable} objects with {@code null} values considered the smallest - */ public static int compare(@Nullable T a, @Nullable T b) { if (a != null) { return (b != null) ? a.compareTo(b) : 1; @@ -39,13 +36,4 @@ public class ObjectUtils { return (b != null) ? -1 : 0; } } - - /** - * @return {@code null} if the given instance is not of the given calss, or the given - * instance otherwise - */ - @Nullable - public static T castOrNull(@Nullable S instance, @NonNull Class c) { - return c.isInstance(instance) ? (T) instance : null; - } } diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index f7ea7875c8dfc5f8c9239141ff4eee5c4d9c60d3..7fd94c6859fb27d8153f9b1b0548085a22c43eff 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -1,5 +1,6 @@ package com.android.internal.util; +import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -32,8 +33,18 @@ public class ScreenshotHelper { mContext = context; } + /** + * Request a screenshot be taken. + * + * @param screenshotType The type of screenshot, for example either + * {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN} + * or {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION} + * @param hasStatus {@code true} if the status bar is currently showing. {@code false} if not. + * @param hasNav {@code true} if the navigation bar is currently showing. {@code false} if not. + * @param handler A handler used in case the screenshot times out + */ public void takeScreenshot(final int screenshotType, final boolean hasStatus, - final boolean hasNav, Handler handler) { + final boolean hasNav, @NonNull Handler handler) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 28291aefd036e2d3bb0f5d864c8b4bcb1df4699c..e08caa8e199d5ed0d215d1bb11296d716749167d 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.os.Looper; import android.os.Message; import android.os.RemoteException; @@ -64,6 +65,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140; private static final int DO_CLOSE_CONNECTION = 150; private static final int DO_COMMIT_CONTENT = 160; + private static final int DO_REPORT_LANGUAGE_HINT = 170; @GuardedBy("mLock") @Nullable @@ -217,6 +219,10 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { callback)); } + public void reportLanguageHint(@NonNull LocaleList languageHint) { + dispatchMessage(obtainMessageO(DO_REPORT_LANGUAGE_HINT, languageHint)); + } + void dispatchMessage(Message msg) { // If we are calling this from the main thread, then we can call // right through. Otherwise, we need to send the message to the @@ -577,6 +583,16 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } return; } + case DO_REPORT_LANGUAGE_HINT: { + final LocaleList languageHint = (LocaleList) msg.obj; + final InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "reportLanguageHint on inactive InputConnection"); + return; + } + ic.reportLanguageHint(languageHint); + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); } diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index c22799179b72ebb7ee023142ba08e978eabe4641..e69a87ffc1dd3213a10a1714709105792705ddbd 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -17,6 +17,7 @@ package com.android.internal.view; import android.os.Bundle; +import android.os.LocaleList; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; @@ -78,4 +79,6 @@ import com.android.internal.view.IInputContextCallback; void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec, IInputContextCallback callback); + + void reportLanguageHint(in LocaleList languageHint); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 5b65bbe1e90e6aa49630be44d0dfa25a87ad3caf..34be598dd615768069bc570ad99bb248de5d5d68 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.inputmethodservice.AbstractInputMethodService; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; @@ -619,6 +620,14 @@ public class InputConnectionWrapper implements InputConnection { return result; } + @AnyThread + public void reportLanguageHint(@NonNull LocaleList languageHint) { + try { + mIInputContext.reportLanguageHint(languageHint); + } catch (RemoteException e) { + } + } + @AnyThread private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) { return (mMissingMethods & methodFlag) == methodFlag; diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 6af41a51f0ddbbaacaa1a93ea5ad65c95f4c7a85..324f923674eb192a17e0936da570254f53def73c 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -256,7 +256,7 @@ public class MenuPopupHelper implements MenuHelper { final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity, mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK; if (hgrav == Gravity.RIGHT) { - xOffset += mAnchorView.getWidth(); + xOffset -= mAnchorView.getWidth(); } popup.setHorizontalOffset(xOffset); diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java index d9ca5be0502eb88599e53bf9aa1a10b7ae2510c8..445379b1d9f49cb760e8f609f9d7370f5dd2043a 100644 --- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java +++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java @@ -263,7 +263,6 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On mShownAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes); subPopup.setPresenterCallback(mPresenterCallback); subPopup.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(subMenu)); - subPopup.setGravity(mDropDownGravity); // Pass responsibility for handling onDismiss to the submenu. subPopup.setOnDismissListener(mOnDismissListener); @@ -273,8 +272,17 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On mMenu.close(false /* closeAllMenus */); // Show the new sub-menu popup at the same location as this popup. - final int horizontalOffset = mPopup.getHorizontalOffset(); + int horizontalOffset = mPopup.getHorizontalOffset(); final int verticalOffset = mPopup.getVerticalOffset(); + + // As xOffset of parent menu popup is subtracted with Anchor width for Gravity.RIGHT, + // So, again to display sub-menu popup in same xOffset, add the Anchor width. + final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity, + mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK; + if (hgrav == Gravity.RIGHT) { + horizontalOffset += mAnchorView.getWidth(); + } + if (subPopup.tryShow(horizontalOffset, verticalOffset)) { if (mPresenterCallback != null) { mPresenterCallback.onOpenSubMenu(subMenu); diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 31d22e0f92fd610b58eb69c8f0feb73298b2debc..5673814ca3620511c59655010026f36d92d8702a 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -19,9 +19,9 @@ package com.android.internal.widget; import android.app.PendingIntent; import android.app.trust.IStrongAuthTracker; import android.os.Bundle; -import android.security.keystore.EntryRecoveryData; -import android.security.keystore.RecoveryData; -import android.security.keystore.RecoveryMetadata; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyChainProtectionParams; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.VerifyCredentialResponse; @@ -60,11 +60,11 @@ 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); - RecoveryData getRecoveryData(in byte[] account); + KeyChainSnapshot getKeyChainSnapshot(); byte[] generateAndStoreKey(String alias); void removeKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent); @@ -75,10 +75,11 @@ interface ILockSettings { void setRecoverySecretTypes(in int[] secretTypes); int[] getRecoverySecretTypes(); int[] getPendingRecoverySecretTypes(); - void recoverySecretAvailable(in RecoveryMetadata recoverySecret); + void recoverySecretAvailable(in KeyChainProtectionParams recoverySecret); byte[] startRecoverySession(in String sessionId, in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge, - in List secrets); + in List secrets); Map/**/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, - in List applicationKeys); + in List applicationKeys); + void closeSession(in String sessionId); } diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 20f05e61687fe6657fcfe4dad5698fdb18f7a116..5577d6e86fee95c89170a8b1e13a7c2b6258deae 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -60,6 +60,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private boolean mFirstLayout; private boolean mIsHidingAnimated; private boolean mNeedsGeneratedAvatar; + private Notification.Person mSender; public MessagingGroup(@NonNull Context context) { super(context); @@ -88,8 +89,12 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou mAvatarView = findViewById(R.id.message_icon); } - public void setSender(Notification.Person sender) { - mSenderName.setText(sender.getName()); + public void setSender(Notification.Person sender, CharSequence nameOverride) { + mSender = sender; + if (nameOverride == null) { + nameOverride = sender.getName(); + } + mSenderName.setText(nameOverride); mNeedsGeneratedAvatar = sender.getIcon() == null; if (!mNeedsGeneratedAvatar) { setAvatar(sender.getIcon()); @@ -355,7 +360,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return 0; } - public View getSender() { + public View getSenderView() { return mSenderName; } @@ -370,4 +375,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public boolean needsGeneratedAvatar() { return mNeedsGeneratedAvatar; } + + public Notification.Person getSender() { + return mSender; + } } diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index 834c93a7a98f5d85930f363dab4a730ead833f77..d45c086e3b52ef875ec9e3fffdcccc368f10565b 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -35,7 +35,6 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.RemotableViewMethod; -import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -81,6 +80,7 @@ public class MessagingLayout extends FrameLayout { private boolean mIsOneToOne; private ArrayList mAddedGroups = new ArrayList<>(); private Notification.Person mUser; + private CharSequence mNameReplacement; public MessagingLayout(@NonNull Context context) { super(context); @@ -121,6 +121,11 @@ public class MessagingLayout extends FrameLayout { mLargeIcon = icon; } + @RemotableViewMethod + public void setNameReplacement(CharSequence nameReplacement) { + mNameReplacement = nameReplacement; + } + @RemotableViewMethod public void setData(Bundle extras) { Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); @@ -135,9 +140,24 @@ public class MessagingLayout extends FrameLayout { if (headerText != null) { mConversationTitle = headerText.getText(); } + addRemoteInputHistoryToMessages(newMessages, + extras.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY)); bind(newMessages, newHistoricMessages); } + private void addRemoteInputHistoryToMessages( + List newMessages, + CharSequence[] remoteInputHistory) { + if (remoteInputHistory == null || remoteInputHistory.length == 0) { + return; + } + for (int i = remoteInputHistory.length - 1; i >= 0; i--) { + CharSequence message = remoteInputHistory[i]; + newMessages.add(new Notification.MessagingStyle.Message( + message, 0, (Notification.Person) null)); + } + } + private void bind(List newMessages, List newHistoricMessages) { @@ -189,9 +209,10 @@ public class MessagingLayout extends FrameLayout { for (int i = 0; i < mGroups.size(); i++) { // Let's now set the avatars MessagingGroup group = mGroups.get(i); + boolean isOwnMessage = group.getSender() == mUser; CharSequence senderName = group.getSenderName(); if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName) - || (mIsOneToOne && mLargeIcon != null)) { + || (mIsOneToOne && mLargeIcon != null && !isOwnMessage)) { continue; } String symbol = uniqueNames.get(senderName); @@ -209,7 +230,7 @@ public class MessagingLayout extends FrameLayout { if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) { continue; } - if (mIsOneToOne && mLargeIcon != null) { + if (mIsOneToOne && mLargeIcon != null && group.getSender() != mUser) { group.setAvatar(mLargeIcon); } else { Icon cachedIcon = cachedAvatars.get(senderName); @@ -271,6 +292,12 @@ public class MessagingLayout extends FrameLayout { public void setUser(Notification.Person user) { mUser = user; + if (mUser.getIcon() == null) { + Icon userIcon = Icon.createWithResource(getContext(), + com.android.internal.R.drawable.messaging_user); + userIcon.setTint(mLayoutColor); + mUser.setIcon(userIcon); + } } private void addMessagesToGroups(List historicMessages, @@ -305,7 +332,12 @@ public class MessagingLayout extends FrameLayout { mAddedGroups.add(newGroup); } newGroup.setLayoutColor(mLayoutColor); - newGroup.setSender(senders.get(groupIndex)); + Notification.Person sender = senders.get(groupIndex); + CharSequence nameOverride = null; + if (sender != mUser && mNameReplacement != null) { + nameOverride = mNameReplacement; + } + newGroup.setSender(sender, nameOverride); mGroups.add(newGroup); if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) { @@ -410,7 +442,7 @@ public class MessagingLayout extends FrameLayout { continue; } MessagingPropertyAnimator.fadeIn(group.getAvatar()); - MessagingPropertyAnimator.fadeIn(group.getSender()); + MessagingPropertyAnimator.fadeIn(group.getSenderView()); MessagingPropertyAnimator.startLocalTranslationFrom(group, group.getHeight(), LINEAR_OUT_SLOW_IN); } diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index 6f2246aa39075408289ab562d478eec6a59d8f72..7635a727ae8536c179eab69fc1336fa79a43b721 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -22,7 +22,9 @@ import com.android.internal.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcel; @@ -502,7 +504,7 @@ public class ResolverDrawerLayout extends ViewGroup { } private void onCollapsedChanged(boolean isCollapsed) { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); if (mScrollIndicatorDrawable != null) { diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index 8848e393900863c26eb5a724b65f56b57be99479..e3436e84da71580e2e426d0fe6774c535f17bca0 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -85,7 +85,7 @@ public class BootReceiver extends BroadcastReceiver { private static final String LOG_FILES_FILE = "log-files.xml"; private static final AtomicFile sFile = new AtomicFile(new File( - Environment.getDataSystemDirectory(), LOG_FILES_FILE)); + Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files"); private static final String LAST_HEADER_FILE = "last-header.txt"; private static final File lastHeaderFile = new File( Environment.getDataSystemDirectory(), LAST_HEADER_FILE); diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index c5af8972757598b8aa7fd6f2dd824c298914b69f..111934f9d005c5c9f7f02ee9308e3233a6195cad 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -149,6 +149,9 @@ public class SystemConfig { final ArrayMap> mVendorPrivAppPermissions = new ArrayMap<>(); final ArrayMap> mVendorPrivAppDenyPermissions = new ArrayMap<>(); + final ArrayMap> mProductPrivAppPermissions = new ArrayMap<>(); + final ArrayMap> mProductPrivAppDenyPermissions = new ArrayMap<>(); + final ArrayMap> mOemPermissions = new ArrayMap<>(); public static SystemConfig getInstance() { @@ -240,6 +243,14 @@ public class SystemConfig { return mVendorPrivAppDenyPermissions.get(packageName); } + public ArraySet getProductPrivAppPermissions(String packageName) { + return mProductPrivAppPermissions.get(packageName); + } + + public ArraySet getProductPrivAppDenyPermissions(String packageName) { + return mProductPrivAppDenyPermissions.get(packageName); + } + public Map getOemPermissions(String packageName) { final Map oemPermissions = mOemPermissions.get(packageName); if (oemPermissions != null) { @@ -278,6 +289,14 @@ public class SystemConfig { Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag); readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "permissions"), oemPermissionFlag); + + // Allow Product to customize system configs around libs, features, permissions and apps + int productPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PERMISSIONS | + ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS; + readPermissions(Environment.buildPath( + Environment.getProductDirectory(), "etc", "sysconfig"), productPermissionFlag); + readPermissions(Environment.buildPath( + Environment.getProductDirectory(), "etc", "permissions"), productPermissionFlag); } void readPermissions(File libraryDir, int permissionFlag) { @@ -598,15 +617,20 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } else if ("privapp-permissions".equals(name) && allowPrivappPermissions) { - // privapp permissions from system and vendor partitions are stored + // privapp permissions from system, vendor and product partitions are stored // separately. This is to prevent xml files in the vendor partition from // granting permissions to priv apps in the system partition and vice // versa. boolean vendor = permFile.toPath().startsWith( Environment.getVendorDirectory().toPath()); + boolean product = permFile.toPath().startsWith( + Environment.getProductDirectory().toPath()); if (vendor) { readPrivAppPermissions(parser, mVendorPrivAppPermissions, mVendorPrivAppDenyPermissions); + } else if (product) { + readPrivAppPermissions(parser, mProductPrivAppPermissions, + mProductPrivAppDenyPermissions); } else { readPrivAppPermissions(parser, mPrivAppPermissions, mPrivAppDenyPermissions); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b3f66e9652f6d81e443d4b588c6200f93478b30e..93f95c634ae385e1d8e1affcb7ce6de36e99802e 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -84,7 +84,7 @@ cc_library_shared { "android_view_VelocityTracker.cpp", "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", - "android_text_MeasuredText.cpp", + "android_text_MeasuredParagraph.cpp", "android_text_StaticLayout.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", @@ -97,7 +97,6 @@ cc_library_shared { "android_os_MessageQueue.cpp", "android_os_Parcel.cpp", "android_os_SELinux.cpp", - "android_os_seccomp.cpp", "android_os_SharedMemory.cpp", "android_os_SystemClock.cpp", "android_os_SystemProperties.cpp", @@ -120,6 +119,7 @@ cc_library_shared { "android_util_jar_StrictJarFile.cpp", "android_graphics_Canvas.cpp", "android_graphics_Picture.cpp", + "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/Bitmap.cpp", "android/graphics/BitmapFactory.cpp", "android/graphics/ByteBufferStreamAdaptor.cpp", @@ -163,6 +163,7 @@ cc_library_shared { "android_media_AudioTrack.cpp", "android_media_DeviceCallback.cpp", "android_media_JetPlayer.cpp", + "android_media_MediaMetricsJNI.cpp", "android_media_RemoteDisplay.cpp", "android_media_ToneGenerator.cpp", "android_hardware_Camera.cpp", @@ -228,6 +229,8 @@ cc_library_shared { ], shared_libs: [ + "libbpf", + "libnetdutils", "libmemtrack", "libandroidfw", "libappfuse", @@ -258,6 +261,7 @@ cc_library_shared { "libselinux", "libicuuc", "libmedia", + "libmediametrics", "libaudioclient", "libjpeg", "libusbhost", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6d7fe056acdf87e693ea57fbb9e0e949831c663e..bf7a7794fcc6fed77529a7705603a8b4eb4e2d51 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -63,6 +63,7 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); extern int register_android_graphics_GraphicBuffer(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_ImageDecoder(JNIEnv*); +extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*); extern int register_android_graphics_Interpolator(JNIEnv* env); extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_Movie(JNIEnv* env); @@ -166,7 +167,6 @@ extern int register_android_os_Parcel(JNIEnv* env); extern int register_android_os_SELinux(JNIEnv* env); extern int register_android_os_VintfObject(JNIEnv *env); extern int register_android_os_VintfRuntimeInfo(JNIEnv *env); -extern int register_android_os_seccomp(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv *env); extern int register_android_os_SystemClock(JNIEnv* env); extern int register_android_os_Trace(JNIEnv* env); @@ -178,7 +178,7 @@ extern int register_android_net_LocalSocketImpl(JNIEnv* env); extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); -extern int register_android_text_MeasuredText(JNIEnv* env); +extern int register_android_text_MeasuredParagraph(JNIEnv* env); extern int register_android_text_StaticLayout(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); @@ -973,6 +973,12 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) addOption("--generate-debug-info"); } + // The mini-debug-info makes it possible to backtrace through JIT code. + if (property_get_bool("dalvik.vm.minidebuginfo", 0)) { + addOption("-Xcompiler-option"); + addOption("--generate-mini-debug-info"); + } + /* * Retrieve the build fingerprint and provide it to the runtime. That way, ANR dumps will * contain the fingerprint and can be parsed. @@ -1342,7 +1348,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_XmlBlock), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), - REG_JNI(register_android_text_MeasuredText), + REG_JNI(register_android_text_MeasuredParagraph), REG_JNI(register_android_text_StaticLayout), REG_JNI(register_android_view_InputDevice), REG_JNI(register_android_view_KeyCharacterMap), @@ -1397,6 +1403,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_FontFamily), REG_JNI(register_android_graphics_GraphicBuffer), REG_JNI(register_android_graphics_ImageDecoder), + REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable), REG_JNI(register_android_graphics_Interpolator), REG_JNI(register_android_graphics_MaskFilter), REG_JNI(register_android_graphics_Matrix), @@ -1427,7 +1434,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_GraphicsEnvironment), REG_JNI(register_android_os_MessageQueue), REG_JNI(register_android_os_SELinux), - REG_JNI(register_android_os_seccomp), REG_JNI(register_android_os_Trace), REG_JNI(register_android_os_UEventObserver), REG_JNI(register_android_net_LocalSocketImpl), diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0e562c03b7620b310067be70a07fe989cddc066b --- /dev/null +++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" +#include "ImageDecoder.h" +#include "core_jni_helpers.h" +#include "Utils.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace android; + +static jmethodID gAnimatedImageDrawable_postOnAnimationEndMethodID; + +// Note: jpostProcess holds a handle to the ImageDecoder. +static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, + jlong nativeImageDecoder, jobject jpostProcess, + jint width, jint height, jobject jsubset) { + if (nativeImageDecoder == 0) { + doThrowIOE(env, "Cannot create AnimatedImageDrawable from null!"); + return 0; + } + + auto* imageDecoder = reinterpret_cast(nativeImageDecoder); + auto info = imageDecoder->mCodec->getInfo(); + const SkISize scaledSize = SkISize::Make(width, height); + SkIRect subset; + if (jsubset) { + GraphicsJNI::jrect_to_irect(env, jsubset, &subset); + } else { + subset = SkIRect::MakeWH(width, height); + } + + sk_sp picture; + if (jpostProcess) { + SkRect bounds = SkRect::MakeWH(subset.width(), subset.height()); + + SkPictureRecorder recorder; + SkCanvas* skcanvas = recorder.beginRecording(bounds); + std::unique_ptr canvas(Canvas::create_canvas(skcanvas)); + postProcessAndRelease(env, jpostProcess, std::move(canvas)); + if (env->ExceptionCheck()) { + return 0; + } + picture = recorder.finishRecordingAsPicture(); + } + + + sk_sp animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec), + scaledSize, subset, + std::move(picture)); + if (!animatedImg) { + doThrowIOE(env, "Failed to create drawable"); + return 0; + } + + sk_sp drawable(new AnimatedImageDrawable(animatedImg)); + return reinterpret_cast(drawable.release()); +} + +static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) { + SkSafeUnref(drawable); +} + +static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) { + return static_cast(reinterpret_cast(&AnimatedImageDrawable_destruct)); +} + +static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jlong canvasPtr) { + auto* drawable = reinterpret_cast(nativePtr); + auto* canvas = reinterpret_cast(canvasPtr); + return (jlong) canvas->drawAnimatedImage(drawable); +} + +static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jint alpha) { + auto* drawable = reinterpret_cast(nativePtr); + drawable->setStagingAlpha(alpha); +} + +static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->getStagingAlpha(); +} + +static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jlong nativeFilter) { + auto* drawable = reinterpret_cast(nativePtr); + auto* filter = reinterpret_cast(nativeFilter); + drawable->setStagingColorFilter(sk_ref_sp(filter)); +} + +static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->isRunning(); +} + +// Java's NOT_RUNNING relies on this being -2.0. +static_assert(SkAnimatedImage::kNotRunning == -2.0); + +static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->start(); +} + +static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + drawable->stop(); +} + +// Java's LOOP_INFINITE relies on this being the same. +static_assert(SkCodec::kRepetitionCountInfinite == -1); + +static void AnimatedImageDrawable_nSetLoopCount(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, + jint loopCount) { + auto* drawable = reinterpret_cast(nativePtr); + drawable->setRepetitionCount(loopCount); +} + +class JniAnimationEndListener : public OnAnimationEndListener { +public: + JniAnimationEndListener(JNIEnv* env, jobject javaObject) { + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK); + mJavaObject = env->NewGlobalRef(javaObject); + } + + ~JniAnimationEndListener() override { + auto* env = get_env_or_die(mJvm); + env->DeleteGlobalRef(mJavaObject); + } + + void onAnimationEnd() override { + auto* env = get_env_or_die(mJvm); + env->CallVoidMethod(mJavaObject, gAnimatedImageDrawable_postOnAnimationEndMethodID); + } + +private: + JavaVM* mJvm; + jobject mJavaObject; +}; + +static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/, + jlong nativePtr, jobject jdrawable) { + auto* drawable = reinterpret_cast(nativePtr); + drawable->setOnAnimationEndListener(std::unique_ptr( + new JniAnimationEndListener(env, jdrawable))); +} + +static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + // FIXME: Report the size of the internal SkBitmap etc. + return sizeof(drawable); +} + +static const JNINativeMethod gAnimatedImageDrawableMethods[] = { + { "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate }, + { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer }, + { "nDraw", "(JJ)J", (void*) AnimatedImageDrawable_nDraw }, + { "nSetAlpha", "(JI)V", (void*) AnimatedImageDrawable_nSetAlpha }, + { "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha }, + { "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter }, + { "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning }, + { "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart }, + { "nStop", "(J)V", (void*) AnimatedImageDrawable_nStop }, + { "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount }, + { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, + { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, +}; + +int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { + jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable"); + gAnimatedImageDrawable_postOnAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "postOnAnimationEnd", "()V"); + + return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable", + gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods)); +} + diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 79aa5acac4ee1c96d84fc4d31ab6deefe25641a3..685fcaf152119f66771fe1f7e1f1f0d50e589662 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -237,10 +237,22 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr stream, // Create the codec. NinePatchPeeker peeker; - std::unique_ptr codec = SkAndroidCodec::MakeFromStream( - std::move(stream), &peeker); - if (!codec.get()) { - return nullObjectReturn("SkAndroidCodec::MakeFromStream returned null"); + std::unique_ptr codec; + { + SkCodec::Result result; + std::unique_ptr c = SkCodec::MakeFromStream(std::move(stream), &result, + &peeker); + if (!c) { + SkString msg; + msg.printf("Failed to create image decoder with message '%s'", + SkCodec::ResultToString(result)); + return nullObjectReturn(msg.c_str()); + } + + codec = SkAndroidCodec::MakeFromCodec(std::move(c)); + if (!codec) { + return nullObjectReturn("SkAndroidCodec::MakeFromCodec returned null"); + } } // Do not allow ninepatch decodes to 565. In the past, decodes to 565 diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp index 115edd4fe4f4262808bb89d937345b6dda62ed49..173818b13837ce38cb7805618d8e1374b226dfa0 100644 --- a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp +++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp @@ -1,5 +1,6 @@ #include "ByteBufferStreamAdaptor.h" #include "core_jni_helpers.h" +#include "Utils.h" #include @@ -8,14 +9,6 @@ using namespace android; static jmethodID gByteBuffer_getMethodID; static jmethodID gByteBuffer_setPositionMethodID; -static JNIEnv* get_env_or_die(JavaVM* jvm) { - JNIEnv* env; - if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm); - } - return env; -} - class ByteBufferStream : public SkStreamAsset { private: ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length, @@ -67,7 +60,7 @@ public: } if (!buffer) { - return this->setPosition(mPosition + size); + return this->setPosition(mPosition + size) ? size : 0; } auto* env = get_env_or_die(mJvm); diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp index 4257c981be18a04436245adb25d24c8b326a98ee..7a9fea72a78e7ae3ca4d3d74e00e8b0bd42c8d19 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp @@ -12,15 +12,6 @@ static jmethodID gInputStream_readMethodID; static jmethodID gInputStream_skipMethodID; -// FIXME: Share with ByteBufferStreamAdaptor.cpp? -static JNIEnv* get_env_or_die(JavaVM* jvm) { - JNIEnv* env; - if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm); - } - return env; -} - /** * Wrapper for a Java InputStream. */ @@ -57,13 +48,13 @@ public: } ~JavaInputStreamAdaptor() override { - auto* env = get_env_or_die(fJvm); + auto* env = android::get_env_or_die(fJvm); env->DeleteGlobalRef(fJavaInputStream); env->DeleteGlobalRef(fJavaByteArray); } size_t read(void* buffer, size_t size) override { - auto* env = get_env_or_die(fJvm); + auto* env = android::get_env_or_die(fJvm); if (!fSwallowExceptions && checkException(env)) { // Just in case the caller did not clear from a previous exception. return 0; diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index 7f4b384235e5ace2f0a576f56be6e62fbb38bbc7..dcb81fa623f4ee38fbee66cc1dc7710afe6b0dea 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -66,10 +66,6 @@ public: static SkPixelRef* refSkPixelRef(JNIEnv*, jobject bitmap); static SkRegion* getNativeRegion(JNIEnv*, jobject region); - // Given the 'native' long held by the Rasterizer.java object, return a - // ref to its SkRasterizer* (or NULL). - static sk_sp refNativeRasterizer(jlong rasterizerHandle); - /* * LegacyBitmapConfig is the old enum in Skia that matched the enum int values * in Bitmap.Config. Skia no longer supports this config, but has replaced it diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index a0a4be4590be9bbd866734ef6ce113c4b639c1b2..743a7efa2b24af1e041e95b555e41ade668ad741 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -19,12 +19,11 @@ #include "ByteBufferStreamAdaptor.h" #include "CreateJavaOutputStreamAdaptor.h" #include "GraphicsJNI.h" -#include "NinePatchPeeker.h" +#include "ImageDecoder.h" #include "Utils.h" #include "core_jni_helpers.h" #include -#include #include #include @@ -38,58 +37,54 @@ using namespace android; static jclass gImageDecoder_class; -static jclass gPoint_class; +static jclass gSize_class; static jclass gIncomplete_class; -static jclass gCorrupt_class; static jclass gCanvas_class; static jmethodID gImageDecoder_constructorMethodID; -static jmethodID gPoint_constructorMethodID; +static jmethodID gImageDecoder_postProcessMethodID; +static jmethodID gSize_constructorMethodID; static jmethodID gIncomplete_constructorMethodID; -static jmethodID gCorrupt_constructorMethodID; static jmethodID gCallback_onPartialImageMethodID; -static jmethodID gPostProcess_postProcessMethodID; static jmethodID gCanvas_constructorMethodID; static jmethodID gCanvas_releaseMethodID; -struct ImageDecoder { - // These need to stay in sync with ImageDecoder.java's Allocator constants. - enum Allocator { - kDefault_Allocator = 0, - kSoftware_Allocator = 1, - kSharedMemory_Allocator = 2, - kHardware_Allocator = 3, - }; - - // These need to stay in sync with PixelFormat.java's Format constants. - enum PixelFormat { - kUnknown = 0, - kTranslucent = -3, - kOpaque = -1, - }; - - NinePatchPeeker mPeeker; - std::unique_ptr mCodec; -}; - static jobject native_create(JNIEnv* env, std::unique_ptr stream) { if (!stream.get()) { doThrowIOE(env, "Failed to create a stream"); return nullptr; } std::unique_ptr decoder(new ImageDecoder); - decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker); - if (!decoder->mCodec.get()) { - // FIXME: (b/71578461) Use the error message from - // SkCodec::MakeFromStream to report a more informative error message. - doThrowIOE(env, "Failed to create an SkCodec"); + SkCodec::Result result; + auto codec = SkCodec::MakeFromStream(std::move(stream), &result, decoder->mPeeker.get()); + if (!codec) { + switch (result) { + case SkCodec::kIncompleteInput: + env->ThrowNew(gIncomplete_class, "Incomplete input"); + break; + default: + SkString msg; + msg.printf("Failed to create image decoder with message '%s'", + SkCodec::ResultToString(result)); + doThrowIOE(env, msg.c_str()); + break; + + } return nullptr; } + // FIXME: Avoid parsing the whole image? + const bool animated = codec->getFrameCount() > 1; + decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec)); + if (!decoder->mCodec.get()) { + doThrowIOE(env, "Could not create AndroidCodec"); + return nullptr; + } const auto& info = decoder->mCodec->getInfo(); const int width = info.width(); const int height = info.height(); return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID, - reinterpret_cast(decoder.release()), width, height); + reinterpret_cast(decoder.release()), width, height, + animated); } static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, @@ -160,11 +155,22 @@ static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jby return native_create(env, std::move(stream)); } -static bool supports_any_down_scale(const SkAndroidCodec* codec) { - return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP; +jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr canvas) { + jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID, + reinterpret_cast(canvas.get())); + if (!jcanvas) { + doThrowOOME(env, "Failed to create Java Canvas for PostProcess!"); + return ImageDecoder::kUnknown; + } + + // jcanvas now owns canvas. + canvas.release(); + + return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID, jcanvas); } -// This method should never return null. Instead, it should throw an exception. +// Note: jpostProcess points to an ImageDecoder object if it has a PostProcess object, and nullptr +// otherwise. static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jobject jcallback, jobject jpostProcess, jint desiredWidth, jint desiredHeight, jobject jsubset, @@ -173,33 +179,14 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong jboolean asAlphaMask) { auto* decoder = reinterpret_cast(nativePtr); SkAndroidCodec* codec = decoder->mCodec.get(); - SkImageInfo decodeInfo = codec->getInfo(); - bool scale = false; - int sampleSize = 1; - if (desiredWidth != decodeInfo.width() || desiredHeight != decodeInfo.height()) { - bool match = false; - if (desiredWidth < decodeInfo.width() && desiredHeight < decodeInfo.height()) { - if (supports_any_down_scale(codec)) { - match = true; - decodeInfo = decodeInfo.makeWH(desiredWidth, desiredHeight); - } else { - int sampleX = decodeInfo.width() / desiredWidth; - int sampleY = decodeInfo.height() / desiredHeight; - sampleSize = std::min(sampleX, sampleY); - SkISize sampledSize = codec->getSampledDimensions(sampleSize); - decodeInfo = decodeInfo.makeWH(sampledSize.width(), sampledSize.height()); - if (decodeInfo.width() == desiredWidth && decodeInfo.height() == desiredHeight) { - match = true; - } - } - } - if (!match) { - scale = true; - if (requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) { - doThrowISE(env, "Cannot scale unpremultiplied pixels!"); - return nullptr; - } - } + const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight); + SkISize decodeSize = desiredSize; + const int sampleSize = codec->computeSampleSize(&decodeSize); + const bool scale = desiredSize != decodeSize; + SkImageInfo decodeInfo = codec->getInfo().makeWH(decodeSize.width(), decodeSize.height()); + if (scale && requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) { + doThrowISE(env, "Cannot scale unpremultiplied pixels!"); + return nullptr; } switch (decodeInfo.alphaType()) { @@ -280,39 +267,47 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong if (jexception) { env->ExceptionClear(); } + int onPartialImageError = jexception ? 1 // ImageDecoder.java's ERROR_SOURCE_EXCEPTION + : 0; // No error. switch (result) { case SkCodec::kSuccess: // Ignore the exception, since the decode was successful anyway. jexception = nullptr; + onPartialImageError = 0; break; case SkCodec::kIncompleteInput: - if (jcallback && !jexception) { - jexception = (jthrowable) env->NewObject(gIncomplete_class, - gIncomplete_constructorMethodID); + if (!jexception) { + onPartialImageError = 2; // ImageDecoder.java's ERROR_SOURCE_EXCEPTION } break; case SkCodec::kErrorInInput: - if (jcallback && !jexception) { - jexception = (jthrowable) env->NewObject(gCorrupt_class, - gCorrupt_constructorMethodID); + if (!jexception) { + onPartialImageError = 3; // ImageDecoder.java's ERROR_SOURCE_ERROR } break; default: SkString msg; - msg.printf("getPixels failed with error %i", result); + msg.printf("getPixels failed with error %s", SkCodec::ResultToString(result)); doThrowIOE(env, msg.c_str()); return nullptr; } - if (jexception) { - bool throwException = !env->CallBooleanMethod(jcallback, gCallback_onPartialImageMethodID, - jexception); + if (jexception || onPartialImageError) { + bool throwException = !jcallback || + !env->CallBooleanMethod(jcallback, gCallback_onPartialImageMethodID, + onPartialImageError); if (env->ExceptionCheck()) { return nullptr; } if (throwException) { - env->Throw(jexception); + if (jexception) { + env->Throw(jexception); + } else if (onPartialImageError == 2) { + env->ThrowNew(gIncomplete_class, "Incomplete input"); + } else { + doThrowIOE(env, "image has an error!"); + } return nullptr; } } @@ -330,23 +325,23 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong // Ignore ninepatch when post-processing. if (!jpostProcess) { // FIXME: Share more code with BitmapFactory.cpp. - if (decoder->mPeeker.mPatch != nullptr) { + if (decoder->mPeeker->mPatch != nullptr) { if (scale) { - decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight); + decoder->mPeeker->scale(scaleX, scaleY, desiredWidth, desiredHeight); } - size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize(); + size_t ninePatchArraySize = decoder->mPeeker->mPatch->serializedSize(); ninePatchChunk = env->NewByteArray(ninePatchArraySize); if (ninePatchChunk == nullptr) { doThrowOOME(env, "Failed to allocate nine patch chunk."); return nullptr; } - env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize, - reinterpret_cast(decoder->mPeeker.mPatch)); + env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize, + reinterpret_cast(decoder->mPeeker->mPatch)); } - if (decoder->mPeeker.mHasInsets) { - ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f); + if (decoder->mPeeker->mHasInsets) { + ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f); if (ninePatchInsets == nullptr) { doThrowOOME(env, "Failed to allocate nine patch insets."); return nullptr; @@ -402,28 +397,13 @@ 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) { std::unique_ptr canvas(Canvas::create_canvas(bm)); - jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID, - reinterpret_cast(canvas.get())); - if (!jcanvas) { - doThrowOOME(env, "Failed to create Java Canvas for PostProcess!"); - return nullptr; - } - // jcanvas will now own canvas. - canvas.release(); - - jint pixelFormat = env->CallIntMethod(jpostProcess, gPostProcess_postProcessMethodID, - jcanvas, bm.width(), bm.height()); - if (env->ExceptionCheck()) { - return nullptr; - } - // The Canvas objects are no longer needed, and will not remain valid. - env->CallVoidMethod(jcanvas, gCanvas_releaseMethodID); + jint pixelFormat = postProcessAndRelease(env, jpostProcess, std::move(canvas)); if (env->ExceptionCheck()) { return nullptr; } @@ -495,13 +475,13 @@ static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlon jint sampleSize) { auto* decoder = reinterpret_cast(nativePtr); SkISize size = decoder->mCodec->getSampledDimensions(sampleSize); - return env->NewObject(gPoint_class, gPoint_constructorMethodID, size.width(), size.height()); + return env->NewObject(gSize_class, gSize_constructorMethodID, size.width(), size.height()); } static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jobject outPadding) { auto* decoder = reinterpret_cast(nativePtr); - decoder->mPeeker.getPadding(env, outPadding); + decoder->mPeeker->getPadding(env, outPadding); } static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) { @@ -519,9 +499,9 @@ static const JNINativeMethod gImageDecoderMethods[] = { { "nCreate", "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, { "nCreate", "(Ljava/io/InputStream;[B)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, { "nCreate", "(Ljava/io/FileDescriptor;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, - { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder$OnPartialImageListener;Landroid/graphics/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;", + { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;Landroid/graphics/ImageDecoder;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;", (void*) ImageDecoder_nDecodeBitmap }, - { "nGetSampledSize","(JI)Landroid/graphics/Point;", (void*) ImageDecoder_nGetSampledSize }, + { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize }, { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding }, { "nClose", "(J)V", (void*) ImageDecoder_nClose}, { "nGetMimeType", "(J)Ljava/lang/String;", (void*) ImageDecoder_nGetMimeType }, @@ -529,22 +509,16 @@ static const JNINativeMethod gImageDecoderMethods[] = { int register_android_graphics_ImageDecoder(JNIEnv* env) { gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder")); - gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "", "(JII)V"); + gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "", "(JIIZ)V"); + gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;)I"); - gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point")); - gPoint_constructorMethodID = GetMethodIDOrDie(env, gPoint_class, "", "(II)V"); + gSize_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/util/Size")); + gSize_constructorMethodID = GetMethodIDOrDie(env, gSize_class, "", "(II)V"); gIncomplete_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$IncompleteException")); gIncomplete_constructorMethodID = GetMethodIDOrDie(env, gIncomplete_class, "", "()V"); - gCorrupt_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$CorruptException")); - gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "", "()V"); - - jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnPartialImageListener"); - gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, callback_class, "onPartialImage", "(Ljava/io/IOException;)Z"); - - jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess"); - gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I"); + gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(I)Z"); gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas")); gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "", "(J)V"); diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h new file mode 100644 index 0000000000000000000000000000000000000000..5d7e67690c2281947cfc218d540584e2385e5ab0 --- /dev/null +++ b/core/jni/android/graphics/ImageDecoder.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NinePatchPeeker.h" + +#include + +#include + +class SkAndroidCodec; + +using namespace android; + +struct ImageDecoder { + // These need to stay in sync with ImageDecoder.java's Allocator constants. + enum Allocator { + kDefault_Allocator = 0, + kSoftware_Allocator = 1, + kSharedMemory_Allocator = 2, + kHardware_Allocator = 3, + }; + + // These need to stay in sync with PixelFormat.java's Format constants. + enum PixelFormat { + kUnknown = 0, + kTranslucent = -3, + kOpaque = -1, + }; + + std::unique_ptr mCodec; + sk_sp mPeeker; + + ImageDecoder() + :mPeeker(new NinePatchPeeker) + {} +}; + +// Creates a Java Canvas object from canvas, calls jimageDecoder's PostProcess on it, and then +// releases the Canvas. +// Caller needs to check for exceptions. +jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr canvas); diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 1e7f5f5c10226e8ea185f5166045432eaee2db7a..115d0d5a608b25c0911d8962b151a61240834b47 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -24,6 +24,7 @@ #include "core_jni_helpers.h" #include #include +#include #include "SkBlurDrawLooper.h" #include "SkColorFilter.h" @@ -307,7 +308,8 @@ namespace PaintGlue { static void getTextPath(JNIEnv* env, Paint* paint, const Typeface* typeface, const jchar* text, jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) { minikin::Layout layout = MinikinUtils::doLayout( - paint, static_cast(bidiFlags), typeface, text, 0, count, count); + paint, static_cast(bidiFlags), typeface, text, 0, count, count, + nullptr, 0); size_t nGlyphs = layout.nGlyphs(); uint16_t* glyphs = new uint16_t[nGlyphs]; SkPoint* pos = new SkPoint[nGlyphs]; @@ -349,7 +351,8 @@ namespace PaintGlue { SkIRect ir; minikin::Layout layout = MinikinUtils::doLayout(&paint, - static_cast(bidiFlags), typeface, text, 0, count, count); + static_cast(bidiFlags), typeface, text, 0, count, count, nullptr, + 0); minikin::MinikinRect rect; layout.getBounds(&rect); r.fLeft = rect.mLeft; @@ -465,7 +468,7 @@ namespace PaintGlue { } minikin::Layout layout = MinikinUtils::doLayout(paint, static_cast(bidiFlags), typeface, str.get(), 0, str.size(), - str.size()); + str.size(), nullptr, 0); size_t nGlyphs = countNonSpaceGlyphs(layout); if (nGlyphs != 1 && nChars > 1) { // multiple-character input, and was not a ligature @@ -485,7 +488,8 @@ namespace PaintGlue { // U+1F1FF (REGIONAL INDICATOR SYMBOL LETTER Z) is \uD83C\uDDFF in UTF16. static const jchar ZZ_FLAG_STR[] = { 0xD83C, 0xDDFF, 0xD83C, 0xDDFF }; minikin::Layout zzLayout = MinikinUtils::doLayout(paint, - static_cast(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4); + static_cast(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4, + nullptr, 0); if (zzLayout.nGlyphs() != 1 || layoutContainsNotdef(zzLayout)) { // The font collection doesn't have a glyph for unknown flag. Just return true. return true; @@ -512,11 +516,10 @@ namespace PaintGlue { jint start, jint end, jint contextStart, jint contextEnd, jboolean isRtl, jint offset) { const Paint* paint = reinterpret_cast(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); - jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, nullptr); - jfloat result = doRunAdvance(paint, typeface, textArray + contextStart, + ScopedCharArrayRO textArray(env, text); + jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, isRtl, offset - contextStart); - env->ReleasePrimitiveArrayCritical(text, textArray, JNI_ABORT); return result; } @@ -534,11 +537,10 @@ namespace PaintGlue { jboolean isRtl, jfloat advance) { const Paint* paint = reinterpret_cast(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); - jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, nullptr); - jint result = doOffsetForAdvance(paint, typeface, textArray + contextStart, + ScopedCharArrayRO textArray(env, text); + jint result = doOffsetForAdvance(paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, isRtl, advance); result += contextStart; - env->ReleasePrimitiveArrayCritical(text, textArray, JNI_ABORT); return result; } diff --git a/core/jni/android/graphics/Utils.cpp b/core/jni/android/graphics/Utils.cpp index 630220a4c9ed0ae4168a6268a742adf606c61fa7..dd9bafe3b4111503ac197df31820e6860ab4faa1 100644 --- a/core/jni/android/graphics/Utils.cpp +++ b/core/jni/android/graphics/Utils.cpp @@ -117,3 +117,11 @@ jobject android::nullObjectReturn(const char msg[]) { bool android::isSeekable(int descriptor) { return ::lseek64(descriptor, 0, SEEK_CUR) != -1; } + +JNIEnv* android::get_env_or_die(JavaVM* jvm) { + JNIEnv* env; + if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm); + } + return env; +} diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h index 69930a581468a8d854a52571bd4152b59ee06114..2f2ee9654489a1882400146c77cd3bd2eb8a9b68 100644 --- a/core/jni/android/graphics/Utils.h +++ b/core/jni/android/graphics/Utils.h @@ -74,6 +74,8 @@ jobject nullObjectReturn(const char msg[]); */ bool isSeekable(int descriptor); +JNIEnv* get_env_or_die(JavaVM* jvm); + }; // namespace android #endif // _ANDROID_GRAPHICS_UTILS_H_ diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index 1676d4b91e3d2b364e2476b6e8be71a5607188dd..888db32fdfac9aa55609c96460247023933a55d8 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -621,7 +622,7 @@ void util_multiplyMV(JNIEnv *env, jclass clazz, // --------------------------------------------------------------------------- -static int checkFormat(SkColorType colorType, int format, int type) +static int checkInternalFormat(SkColorType colorType, int format, int type) { switch(colorType) { case kN32_SkColorType: @@ -641,7 +642,7 @@ static int checkFormat(SkColorType colorType, int format, int type) } break; case kRGBA_F16_SkColorType: - if (type == GL_HALF_FLOAT_OES && format == PIXEL_FORMAT_RGBA_FP16) + if (type == GL_HALF_FLOAT && format == GL_RGBA16F) return 0; break; default: @@ -650,6 +651,20 @@ static int checkFormat(SkColorType colorType, int format, int type) return -1; } +// The internal format is no longer the same as pixel format, per Table 2 in +// https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glTexImage2D.xhtml +static int getPixelFormatFromInternalFormat(uint32_t internalFormat) { + switch (internalFormat) { + // For sized internal format. + case GL_RGBA16F: + return GL_RGBA; + // Base internal formats and pixel formats are still the same, see Table 1 in + // https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glTexImage2D.xhtml + default: + return internalFormat; + } +} + static int getInternalFormat(SkColorType colorType) { switch(colorType) { @@ -662,7 +677,7 @@ static int getInternalFormat(SkColorType colorType) case kRGB_565_SkColorType: return GL_RGB; case kRGBA_F16_SkColorType: - return PIXEL_FORMAT_RGBA_FP16; + return GL_RGBA16F; default: return -1; } @@ -680,7 +695,7 @@ static int getType(SkColorType colorType) case kRGB_565_SkColorType: return GL_UNSIGNED_SHORT_5_6_5; case kRGBA_F16_SkColorType: - return GL_HALF_FLOAT_OES; + return GL_HALF_FLOAT; default: return -1; } @@ -715,7 +730,7 @@ static jint util_texImage2D(JNIEnv *env, jclass clazz, if (type < 0) { type = getType(colorType); } - int err = checkFormat(colorType, internalformat, type); + int err = checkInternalFormat(colorType, internalformat, type); if (err) return err; const int w = bitmap.width(); @@ -724,7 +739,8 @@ static jint util_texImage2D(JNIEnv *env, jclass clazz, if (internalformat == GL_PALETTE8_RGBA8_OES) { err = -1; } else { - glTexImage2D(target, level, internalformat, w, h, border, internalformat, type, p); + glTexImage2D(target, level, internalformat, w, h, border, + getPixelFormatFromInternalFormat(internalformat), type, p); } return err; } @@ -736,12 +752,13 @@ static jint util_texSubImage2D(JNIEnv *env, jclass clazz, SkBitmap bitmap; GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); SkColorType colorType = bitmap.colorType(); + int internalFormat = getInternalFormat(colorType); if (format < 0) { - format = getInternalFormat(colorType); + format = getPixelFormatFromInternalFormat(internalFormat); if (format == GL_PALETTE8_RGBA8_OES) return -1; // glCompressedTexSubImage2D() not supported } - int err = checkFormat(colorType, format, type); + int err = checkInternalFormat(colorType, internalFormat, type); if (err) return err; const int w = bitmap.width(); diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp index 0d09b56a29350b93d5a0aa28ff76c725abe304b3..86cda44557343f817c2ca09a1c12b7ae09522054 100644 --- a/core/jni/android_database_CursorWindow.cpp +++ b/core/jni/android_database_CursorWindow.cpp @@ -519,8 +519,21 @@ static const JNINativeMethod sMethods[] = (void*)nativeDispose }, { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel }, + { "nativeGetName", "(J)Ljava/lang/String;", (void*)nativeGetName }, + { "nativeGetBlob", "(JII)[B", + (void*)nativeGetBlob }, + { "nativeGetString", "(JII)Ljava/lang/String;", + (void*)nativeGetString }, + { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V", + (void*)nativeCopyStringToBuffer }, + { "nativePutBlob", "(J[BII)Z", + (void*)nativePutBlob }, + { "nativePutString", "(JLjava/lang/String;II)Z", + (void*)nativePutString }, + + // ------- @FastNative below here ---------------------- { "nativeClear", "(J)V", (void*)nativeClear }, { "nativeGetNumRows", "(J)I", @@ -533,20 +546,11 @@ static const JNINativeMethod sMethods[] = (void*)nativeFreeLastRow }, { "nativeGetType", "(JII)I", (void*)nativeGetType }, - { "nativeGetBlob", "(JII)[B", - (void*)nativeGetBlob }, - { "nativeGetString", "(JII)Ljava/lang/String;", - (void*)nativeGetString }, { "nativeGetLong", "(JII)J", (void*)nativeGetLong }, { "nativeGetDouble", "(JII)D", (void*)nativeGetDouble }, - { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V", - (void*)nativeCopyStringToBuffer }, - { "nativePutBlob", "(J[BII)Z", - (void*)nativePutBlob }, - { "nativePutString", "(JLjava/lang/String;II)Z", - (void*)nativePutString }, + { "nativePutLong", "(JJII)Z", (void*)nativePutLong }, { "nativePutDouble", "(JDII)Z", diff --git a/core/jni/android_database_SQLiteCommon.cpp b/core/jni/android_database_SQLiteCommon.cpp index eefcb74e0f70281adee1ba232f1ad8e829635321..daa20876ba65424f7c279ae8d41baeb06715bb5c 100644 --- a/core/jni/android_database_SQLiteCommon.cpp +++ b/core/jni/android_database_SQLiteCommon.cpp @@ -18,8 +18,108 @@ #include +#include + namespace android { +static const std::map sErrorCodesMap = { + // Primary Result Code List + {4, "SQLITE_ABORT"}, + {23, "SQLITE_AUTH"}, + {5, "SQLITE_BUSY"}, + {14, "SQLITE_CANTOPEN"}, + {19, "SQLITE_CONSTRAINT"}, + {11, "SQLITE_CORRUPT"}, + {101, "SQLITE_DONE"}, + {16, "SQLITE_EMPTY"}, + {1, "SQLITE_ERROR"}, + {24, "SQLITE_FORMAT"}, + {13, "SQLITE_FULL"}, + {2, "SQLITE_INTERNAL"}, + {9, "SQLITE_INTERRUPT"}, + {10, "SQLITE_IOERR"}, + {6, "SQLITE_LOCKED"}, + {20, "SQLITE_MISMATCH"}, + {21, "SQLITE_MISUSE"}, + {22, "SQLITE_NOLFS"}, + {7, "SQLITE_NOMEM"}, + {26, "SQLITE_NOTADB"}, + {12, "SQLITE_NOTFOUND"}, + {27, "SQLITE_NOTICE"}, + {0, "SQLITE_OK"}, + {3, "SQLITE_PERM"}, + {15, "SQLITE_PROTOCOL"}, + {25, "SQLITE_RANGE"}, + {8, "SQLITE_READONLY"}, + {100, "SQLITE_ROW"}, + {17, "SQLITE_SCHEMA"}, + {18, "SQLITE_TOOBIG"}, + {28, "SQLITE_WARNING"}, + // Extended Result Code List + {516, "SQLITE_ABORT_ROLLBACK"}, + {261, "SQLITE_BUSY_RECOVERY"}, + {517, "SQLITE_BUSY_SNAPSHOT"}, + {1038, "SQLITE_CANTOPEN_CONVPATH"}, + {782, "SQLITE_CANTOPEN_FULLPATH"}, + {526, "SQLITE_CANTOPEN_ISDIR"}, + {270, "SQLITE_CANTOPEN_NOTEMPDIR"}, + {275, "SQLITE_CONSTRAINT_CHECK"}, + {531, "SQLITE_CONSTRAINT_COMMITHOOK"}, + {787, "SQLITE_CONSTRAINT_FOREIGNKEY"}, + {1043, "SQLITE_CONSTRAINT_FUNCTION"}, + {1299, "SQLITE_CONSTRAINT_NOTNULL"}, + {1555, "SQLITE_CONSTRAINT_PRIMARYKEY"}, + {2579, "SQLITE_CONSTRAINT_ROWID"}, + {1811, "SQLITE_CONSTRAINT_TRIGGER"}, + {2067, "SQLITE_CONSTRAINT_UNIQUE"}, + {2323, "SQLITE_CONSTRAINT_VTAB"}, + {267, "SQLITE_CORRUPT_VTAB"}, + {3338, "SQLITE_IOERR_ACCESS"}, + {2826, "SQLITE_IOERR_BLOCKED"}, + {3594, "SQLITE_IOERR_CHECKRESERVEDLOCK"}, + {4106, "SQLITE_IOERR_CLOSE"}, + {6666, "SQLITE_IOERR_CONVPATH"}, + {2570, "SQLITE_IOERR_DELETE"}, + {5898, "SQLITE_IOERR_DELETE_NOENT"}, + {4362, "SQLITE_IOERR_DIR_CLOSE"}, + {1290, "SQLITE_IOERR_DIR_FSYNC"}, + {1802, "SQLITE_IOERR_FSTAT"}, + {1034, "SQLITE_IOERR_FSYNC"}, + {6410, "SQLITE_IOERR_GETTEMPPATH"}, + {3850, "SQLITE_IOERR_LOCK"}, + {6154, "SQLITE_IOERR_MMAP"}, + {3082, "SQLITE_IOERR_NOMEM"}, + {2314, "SQLITE_IOERR_RDLOCK"}, + {266, "SQLITE_IOERR_READ"}, + {5642, "SQLITE_IOERR_SEEK"}, + {5130, "SQLITE_IOERR_SHMLOCK"}, + {5386, "SQLITE_IOERR_SHMMAP"}, + {4618, "SQLITE_IOERR_SHMOPEN"}, + {4874, "SQLITE_IOERR_SHMSIZE"}, + {522, "SQLITE_IOERR_SHORT_READ"}, + {1546, "SQLITE_IOERR_TRUNCATE"}, + {2058, "SQLITE_IOERR_UNLOCK"}, + {778, "SQLITE_IOERR_WRITE"}, + {262, "SQLITE_LOCKED_SHAREDCACHE"}, + {539, "SQLITE_NOTICE_RECOVER_ROLLBACK"}, + {283, "SQLITE_NOTICE_RECOVER_WAL"}, + {256, "SQLITE_OK_LOAD_PERMANENTLY"}, + {520, "SQLITE_READONLY_CANTLOCK"}, + {1032, "SQLITE_READONLY_DBMOVED"}, + {264, "SQLITE_READONLY_RECOVERY"}, + {776, "SQLITE_READONLY_ROLLBACK"}, + {284, "SQLITE_WARNING_AUTOINDEX"}, +}; + +static std::string sqlite3_error_code_to_msg(int errcode) { + auto it = sErrorCodesMap.find(errcode); + if (it != sErrorCodesMap.end()) { + return std::to_string(errcode) + " " + it->second; + } else { + return std::to_string(errcode); + } +} + /* throw a SQLiteException with a message appropriate for the error in handle */ void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) { throw_sqlite3_exception(env, handle, NULL); @@ -123,7 +223,8 @@ void throw_sqlite3_exception(JNIEnv* env, int errcode, if (sqlite3Message) { String8 fullMessage; fullMessage.append(sqlite3Message); - fullMessage.appendFormat(" (code %d)", errcode); // print extended error code + std::string errcode_msg = sqlite3_error_code_to_msg(errcode); + fullMessage.appendFormat(" (code %s)", errcode_msg.c_str()); // print extended error code if (message) { fullMessage.append(": "); fullMessage.append(message); diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index f08b89c8c988bc0d2f4b024ef55b1a7948903c61..6b961f54cf885e04ca0f750cd55d54a38aa42626 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -30,6 +30,10 @@ #include "SkRegion.h" #include "SkVertices.h" +namespace minikin { +class MeasuredText; +} // namespace minikin + namespace android { namespace CanvasJNI { @@ -480,7 +484,7 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray t const Typeface* typeface = paint->getAndroidTypeface(); jchar* jchars = env->GetCharArrayElements(text, NULL); get_canvas(canvasHandle)->drawText(jchars + index, 0, count, count, x, y, - static_cast(bidiFlags), *paint, typeface); + static_cast(bidiFlags), *paint, typeface, nullptr, 0); env->ReleaseCharArrayElements(text, jchars, JNI_ABORT); } @@ -492,20 +496,22 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring tex const int count = end - start; const jchar* jchars = env->GetStringChars(text, NULL); get_canvas(canvasHandle)->drawText(jchars + start, 0, count, count, x, y, - static_cast(bidiFlags), *paint, typeface); + static_cast(bidiFlags), *paint, typeface, nullptr, 0); env->ReleaseStringChars(text, jchars); } static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, jint contextIndex, jint contextCount, jfloat x, jfloat y, - jboolean isRtl, jlong paintHandle) { + jboolean isRtl, jlong paintHandle, jlong mtHandle, jint mtOffset) { Paint* paint = reinterpret_cast(paintHandle); + minikin::MeasuredText* mt = reinterpret_cast(mtHandle); const Typeface* typeface = paint->getAndroidTypeface(); const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; jchar* jchars = env->GetCharArrayElements(text, NULL); get_canvas(canvasHandle)->drawText(jchars + contextIndex, index - contextIndex, count, - contextCount, x, y, bidiFlags, *paint, typeface); + contextCount, x, y, bidiFlags, *paint, typeface, mt, + mtOffset); env->ReleaseCharArrayElements(text, jchars, JNI_ABORT); } @@ -520,7 +526,7 @@ static void drawTextRunString(JNIEnv* env, jobject obj, jlong canvasHandle, jstr jint contextCount = contextEnd - contextStart; const jchar* jchars = env->GetStringChars(text, NULL); get_canvas(canvasHandle)->drawText(jchars + contextStart, start - contextStart, count, - contextCount, x, y, bidiFlags, *paint, typeface); + contextCount, x, y, bidiFlags, *paint, typeface, nullptr, 0); env->ReleaseStringChars(text, jchars); } @@ -628,7 +634,7 @@ static const JNINativeMethod gDrawMethods[] = { {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray}, {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars}, {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString}, - {"nDrawTextRun","(J[CIIIIFFZJ)V", (void*) CanvasJNI::drawTextRunChars}, + {"nDrawTextRun","(J[CIIIIFFZJJI)V", (void*) CanvasJNI::drawTextRunChars}, {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString}, {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars}, {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString}, diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index 092aaf62bbc0a6c1de681b1abbcd76bf2c2fe89d..c79f5bd96e99d15590185289eb5ce92856dc0770 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -33,6 +33,9 @@ #define ENCODING_AAC_HE_V2 12 #define ENCODING_IEC61937 13 #define ENCODING_DOLBY_TRUEHD 14 +#define ENCODING_AAC_ELD 15 +#define ENCODING_AAC_XHE 16 +#define ENCODING_AC4 17 #define ENCODING_INVALID 0 #define ENCODING_DEFAULT 1 @@ -71,6 +74,12 @@ static inline audio_format_t audioFormatToNative(int audioFormat) return AUDIO_FORMAT_DOLBY_TRUEHD; case ENCODING_IEC61937: return AUDIO_FORMAT_IEC61937; + case ENCODING_AAC_ELD: + return AUDIO_FORMAT_AAC_ELD; + case ENCODING_AAC_XHE: + return AUDIO_FORMAT_AAC; // FIXME temporary value, needs addition of xHE-AAC + case ENCODING_AC4: + return AUDIO_FORMAT_AC4; case ENCODING_DEFAULT: return AUDIO_FORMAT_DEFAULT; default: @@ -114,6 +123,13 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat) return ENCODING_IEC61937; case AUDIO_FORMAT_DOLBY_TRUEHD: return ENCODING_DOLBY_TRUEHD; + case AUDIO_FORMAT_AAC_ELD: + return ENCODING_AAC_ELD; + // FIXME needs addition of AUDIO_FORMAT_AAC_XHE + //case AUDIO_FORMAT_AAC_XHE: + // return ENCODING_AAC_XHE; + case AUDIO_FORMAT_AC4: + return ENCODING_AC4; case AUDIO_FORMAT_DEFAULT: return ENCODING_DEFAULT; default: @@ -121,6 +137,25 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat) } } +// This function converts Java channel masks to a native channel mask. +// validity should be checked with audio_is_output_channel(). +static inline audio_channel_mask_t nativeChannelMaskFromJavaChannelMasks( + jint channelPositionMask, jint channelIndexMask) +{ + // 0 is the java android.media.AudioFormat.CHANNEL_INVALID value + if (channelIndexMask != 0) { // channel index mask takes priority + // To convert to a native channel mask, the Java channel index mask + // requires adding the index representation. + return audio_channel_mask_from_representation_and_bits( + AUDIO_CHANNEL_REPRESENTATION_INDEX, + channelIndexMask); + } + // To convert to a native channel mask, the Java channel position mask + // requires a shift by 2 to skip the two deprecated channel + // configurations "default" and "mono". + return (audio_channel_mask_t)((uint32_t)channelPositionMask >> 2); +} + static inline audio_channel_mask_t outChannelMaskToNative(int channelMask) { switch (channelMask) { diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index e4da3c6a6ac8d066e856495a9844e1154acf367b..ebd16c7084ac579b61adcea9cd66ed1861139141 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -31,6 +31,7 @@ #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" #include "android_media_DeviceCallback.h" +#include "android_media_MediaMetricsJNI.h" // ---------------------------------------------------------------------------- @@ -750,6 +751,39 @@ static jint android_media_AudioRecord_get_timestamp(JNIEnv *env, jobject thiz, return status; } +// ---------------------------------------------------------------------------- +static jobject +android_media_AudioRecord_native_getMetrics(JNIEnv *env, jobject thiz) +{ + ALOGV("android_media_AudioRecord_native_getMetrics"); + + sp lpRecord = getAudioRecord(env, thiz); + + if (lpRecord == NULL) { + ALOGE("Unable to retrieve AudioRecord pointer for getMetrics()"); + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return (jobject) NULL; + } + + // get what we have for the metrics from the record session + MediaAnalyticsItem *item = NULL; + + status_t err = lpRecord->getMetrics(item); + if (err != OK) { + ALOGE("getMetrics failed"); + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return (jobject) NULL; + } + + jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL /* mybundle */); + + // housekeeping + delete item; + item = NULL; + + return mybundle; +} + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -781,6 +815,8 @@ static const JNINativeMethod gMethods[] = { "()I", (void *)android_media_AudioRecord_get_pos_update_period}, {"native_get_min_buff_size", "(III)I", (void *)android_media_AudioRecord_get_min_buff_size}, + {"native_getMetrics", "()Landroid/os/PersistableBundle;", + (void *)android_media_AudioRecord_native_getMetrics}, {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice}, {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId}, {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback}, diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 7ec68edddf52ec4128e697900945bf0a64dc6e38..376a7971767798fca917840b8c064678b492bf38 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -608,9 +608,10 @@ android_media_AudioSystem_getOutputLatency(JNIEnv *env, jobject clazz, jint stre } static jint -android_media_AudioSystem_setLowRamDevice(JNIEnv *env, jobject clazz, jboolean isLowRamDevice) +android_media_AudioSystem_setLowRamDevice( + JNIEnv *env, jobject clazz, jboolean isLowRamDevice, jlong totalMemory) { - return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice); + return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice, (int64_t) totalMemory); } static jint @@ -1770,6 +1771,24 @@ android_media_AudioSystem_getStreamVolumeDB(JNIEnv *env, jobject thiz, (audio_devices_t)device); } +static jboolean +android_media_AudioSystem_isOffloadSupported(JNIEnv *env, jobject thiz, + jint encoding, jint sampleRate, jint channelMask, jint channelIndexMask) +{ + audio_offload_info_t format = AUDIO_INFO_INITIALIZER; + format.format = (audio_format_t) audioFormatToNative(encoding); + format.sample_rate = (uint32_t) sampleRate; + format.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask); + format.stream_type = AUDIO_STREAM_MUSIC; + format.has_video = false; + format.is_streaming = false; + // offload duration unknown at this point: + // client side code cannot access "audio.offload.min.duration.secs" property to make a query + // agnostic of duration, so using acceptable estimate of 2mn + format.duration_us = 120 * 1000000; + return AudioSystem::isOffloadSupported(format); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -1801,7 +1820,7 @@ static const JNINativeMethod gMethods[] = { {"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate}, {"getPrimaryOutputFrameCount", "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount}, {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency}, - {"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice}, + {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice}, {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger}, {"listAudioPorts", "(Ljava/util/ArrayList;[I)I", (void *)android_media_AudioSystem_listAudioPorts}, @@ -1823,6 +1842,7 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_AudioSystem_registerRecordingCallback}, {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady}, {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB}, + {"native_is_offload_supported", "(IIII)Z", (void *)android_media_AudioSystem_isOffloadSupported}, }; diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 556ac27dfe9ed529210a3d156650b28354090f0b..afbc579136880955ae59e63371a603b684da0a90 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -34,6 +34,7 @@ #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" +#include "android_media_MediaMetricsJNI.h" #include "android_media_PlaybackParams.h" #include "android_media_DeviceCallback.h" #include "android_media_VolumeShaper.h" @@ -73,6 +74,7 @@ struct audiotrack_callback_cookie { jobject audioTrack_ref; bool busy; Condition cond; + bool isOffload; }; // keep these values in sync with AudioTrack.java @@ -90,6 +92,7 @@ class AudioTrackJniStorage { AudioTrackJniStorage() { mCallbackData.audioTrack_class = 0; mCallbackData.audioTrack_ref = 0; + mCallbackData.isOffload = false; } ~AudioTrackJniStorage() { @@ -132,27 +135,34 @@ static void audioCallback(int event, void* user, void *info) { } switch (event) { - case AudioTrack::EVENT_MARKER: { - JNIEnv *env = AndroidRuntime::getJNIEnv(); - if (user != NULL && env != NULL) { - env->CallStaticVoidMethod( - callbackInfo->audioTrack_class, - javaAudioTrackFields.postNativeEventInJava, - callbackInfo->audioTrack_ref, event, 0,0, NULL); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); + // Offload only events + case AudioTrack::EVENT_STREAM_END: + case AudioTrack::EVENT_MORE_DATA: + // a.k.a. tear down + case AudioTrack::EVENT_NEW_IAUDIOTRACK: + if (callbackInfo->isOffload) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (user != NULL && env != NULL) { + env->CallStaticVoidMethod( + callbackInfo->audioTrack_class, + javaAudioTrackFields.postNativeEventInJava, + callbackInfo->audioTrack_ref, event, 0,0, NULL); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } } - } } break; + // PCM and offload events + case AudioTrack::EVENT_MARKER: case AudioTrack::EVENT_NEW_POS: { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (user != NULL && env != NULL) { env->CallStaticVoidMethod( - callbackInfo->audioTrack_class, - javaAudioTrackFields.postNativeEventInJava, - callbackInfo->audioTrack_ref, event, 0,0, NULL); + callbackInfo->audioTrack_class, + javaAudioTrackFields.postNativeEventInJava, + callbackInfo->audioTrack_ref, event, 0,0, NULL); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); @@ -198,30 +208,12 @@ sp android_media_AudioTrack_getAudioTrack(JNIEnv* env, jobject audio return getAudioTrack(env, audioTrackObj); } -// This function converts Java channel masks to a native channel mask. -// validity should be checked with audio_is_output_channel(). -static inline audio_channel_mask_t nativeChannelMaskFromJavaChannelMasks( - jint channelPositionMask, jint channelIndexMask) -{ - if (channelIndexMask != 0) { // channel index mask takes priority - // To convert to a native channel mask, the Java channel index mask - // requires adding the index representation. - return audio_channel_mask_from_representation_and_bits( - AUDIO_CHANNEL_REPRESENTATION_INDEX, - channelIndexMask); - } - // To convert to a native channel mask, the Java channel position mask - // requires a shift by 2 to skip the two deprecated channel - // configurations "default" and "mono". - return (audio_channel_mask_t)(channelPositionMask >> 2); -} - // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa, jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask, jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession, - jlong nativeAudioTrack) { + jlong nativeAudioTrack, jboolean offload) { ALOGV("sampleRates=%p, channel mask=%x, index mask=%x, audioFormat(Java)=%d, buffSize=%d" "nativeAudioTrack=0x%" PRIX64, @@ -322,8 +314,19 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz); // we use a weak reference so the AudioTrack object can be garbage collected. lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); + lpJniStorage->mCallbackData.isOffload = offload; lpJniStorage->mCallbackData.busy = false; + audio_offload_info_t offloadInfo; + if (offload) { + offloadInfo = AUDIO_INFO_INITIALIZER; + offloadInfo.format = format; + offloadInfo.sample_rate = sampleRateInHertz; + offloadInfo.channel_mask = nativeChannelMask; + offloadInfo.has_video = false; + offloadInfo.stream_type = AUDIO_STREAM_MUSIC; //required for offload + } + // initialize the native AudioTrack object status_t status = NO_ERROR; switch (memoryMode) { @@ -342,7 +345,7 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job true,// thread can call Java sessionId,// audio session ID AudioTrack::TRANSFER_SYNC, - NULL, // default offloadInfo + offload ? &offloadInfo : NULL, -1, -1, // default uid, pid values paa); break; @@ -1009,6 +1012,39 @@ static jint android_media_AudioTrack_get_timestamp(JNIEnv *env, jobject thiz, j return (jint) nativeToJavaStatus(status); } +// ---------------------------------------------------------------------------- +static jobject +android_media_AudioTrack_native_getMetrics(JNIEnv *env, jobject thiz) +{ + ALOGD("android_media_AudioTrack_native_getMetrics"); + + sp lpTrack = getAudioTrack(env, thiz); + + if (lpTrack == NULL) { + ALOGE("Unable to retrieve AudioTrack pointer for getMetrics()"); + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return (jobject) NULL; + } + + // get what we have for the metrics from the track + MediaAnalyticsItem *item = NULL; + + status_t err = lpTrack->getMetrics(item); + if (err != OK) { + ALOGE("getMetrics failed"); + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return (jobject) NULL; + } + + jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL /* mybundle */); + + // housekeeping + delete item; + item = NULL; + + return mybundle; +} + // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_set_loop(JNIEnv *env, jobject thiz, @@ -1234,7 +1270,7 @@ static const JNINativeMethod gMethods[] = { {"native_stop", "()V", (void *)android_media_AudioTrack_stop}, {"native_pause", "()V", (void *)android_media_AudioTrack_pause}, {"native_flush", "()V", (void *)android_media_AudioTrack_flush}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJ)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZ)I", (void *)android_media_AudioTrack_setup}, {"native_finalize", "()V", (void *)android_media_AudioTrack_finalize}, {"native_release", "()V", (void *)android_media_AudioTrack_release}, @@ -1273,6 +1309,8 @@ static const JNINativeMethod gMethods[] = { {"native_get_underrun_count", "()I", (void *)android_media_AudioTrack_get_underrun_count}, {"native_get_flags", "()I", (void *)android_media_AudioTrack_get_flags}, {"native_get_timestamp", "([J)I", (void *)android_media_AudioTrack_get_timestamp}, + {"native_getMetrics", "()Landroid/os/PersistableBundle;", + (void *)android_media_AudioTrack_native_getMetrics}, {"native_set_loop", "(III)I", (void *)android_media_AudioTrack_set_loop}, {"native_reload_static", "()I", (void *)android_media_AudioTrack_reload}, {"native_get_output_sample_rate", diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp similarity index 100% rename from media/jni/android_media_MediaMetricsJNI.cpp rename to core/jni/android_media_MediaMetricsJNI.cpp diff --git a/media/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h similarity index 100% rename from media/jni/android_media_MediaMetricsJNI.h rename to core/jni/android_media_MediaMetricsJNI.h diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index b9ff0a787931bacbf3e7e42d75d1e5fea539ef70..bf183cc8c0aa01fa6302d303644a8e60ac01aa40 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -107,6 +107,7 @@ enum { struct stat_fields { jfieldID pss_field; jfieldID pssSwappable_field; + jfieldID rss_field; jfieldID privateDirty_field; jfieldID sharedDirty_field; jfieldID privateClean_field; @@ -118,6 +119,7 @@ struct stat_fields { struct stat_field_names { const char* pss_name; const char* pssSwappable_name; + const char* rss_name; const char* privateDirty_name; const char* sharedDirty_name; const char* privateClean_name; @@ -129,11 +131,11 @@ struct stat_field_names { static stat_fields stat_fields[_NUM_CORE_HEAP]; static stat_field_names stat_field_names[_NUM_CORE_HEAP] = { - { "otherPss", "otherSwappablePss", "otherPrivateDirty", "otherSharedDirty", + { "otherPss", "otherSwappablePss", "otherRss", "otherPrivateDirty", "otherSharedDirty", "otherPrivateClean", "otherSharedClean", "otherSwappedOut", "otherSwappedOutPss" }, - { "dalvikPss", "dalvikSwappablePss", "dalvikPrivateDirty", "dalvikSharedDirty", + { "dalvikPss", "dalvikSwappablePss", "dalvikRss", "dalvikPrivateDirty", "dalvikSharedDirty", "dalvikPrivateClean", "dalvikSharedClean", "dalvikSwappedOut", "dalvikSwappedOutPss" }, - { "nativePss", "nativeSwappablePss", "nativePrivateDirty", "nativeSharedDirty", + { "nativePss", "nativeSwappablePss", "nativeRss", "nativePrivateDirty", "nativeSharedDirty", "nativePrivateClean", "nativeSharedClean", "nativeSwappedOut", "nativeSwappedOutPss" } }; @@ -143,6 +145,7 @@ jfieldID hasSwappedOutPss_field; struct stats_t { int pss; int swappablePss; + int rss; int privateDirty; int sharedDirty; int privateClean; @@ -246,7 +249,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) int len, nameLen; bool skip, done = false; - unsigned pss = 0, swappable_pss = 0; + unsigned pss = 0, swappable_pss = 0, rss = 0; float sharing_proportion = 0.0; unsigned shared_clean = 0, shared_dirty = 0; unsigned private_clean = 0, private_dirty = 0; @@ -412,7 +415,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) if (line[0] == 'S' && sscanf(line, "Size: %d kB", &temp) == 1) { /* size = temp; */ } else if (line[0] == 'R' && sscanf(line, "Rss: %d kB", &temp) == 1) { - /* resident = temp; */ + rss = temp; } else if (line[0] == 'P' && sscanf(line, "Pss: %d kB", &temp) == 1) { pss = temp; } else if (line[0] == 'S' && sscanf(line, "Shared_Clean: %d kB", &temp) == 1) { @@ -450,6 +453,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) stats[whichHeap].pss += pss; stats[whichHeap].swappablePss += swappable_pss; + stats[whichHeap].rss += rss; stats[whichHeap].privateDirty += private_dirty; stats[whichHeap].sharedDirty += shared_dirty; stats[whichHeap].privateClean += private_clean; @@ -460,6 +464,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) whichHeap == HEAP_DEX || whichHeap == HEAP_ART) { stats[subHeap].pss += pss; stats[subHeap].swappablePss += swappable_pss; + stats[subHeap].rss += rss; stats[subHeap].privateDirty += private_dirty; stats[subHeap].sharedDirty += shared_dirty; stats[subHeap].privateClean += private_clean; @@ -495,15 +500,19 @@ static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, if (read_memtrack_memory(pid, &graphics_mem) == 0) { stats[HEAP_GRAPHICS].pss = graphics_mem.graphics; stats[HEAP_GRAPHICS].privateDirty = graphics_mem.graphics; + stats[HEAP_GRAPHICS].rss = graphics_mem.graphics; stats[HEAP_GL].pss = graphics_mem.gl; stats[HEAP_GL].privateDirty = graphics_mem.gl; + stats[HEAP_GL].rss = graphics_mem.gl; stats[HEAP_OTHER_MEMTRACK].pss = graphics_mem.other; stats[HEAP_OTHER_MEMTRACK].privateDirty = graphics_mem.other; + stats[HEAP_OTHER_MEMTRACK].rss = graphics_mem.other; } for (int i=_NUM_CORE_HEAP; i<_NUM_EXCLUSIVE_HEAP; i++) { stats[HEAP_UNKNOWN].pss += stats[i].pss; stats[HEAP_UNKNOWN].swappablePss += stats[i].swappablePss; + stats[HEAP_UNKNOWN].rss += stats[i].rss; stats[HEAP_UNKNOWN].privateDirty += stats[i].privateDirty; stats[HEAP_UNKNOWN].sharedDirty += stats[i].sharedDirty; stats[HEAP_UNKNOWN].privateClean += stats[i].privateClean; @@ -515,6 +524,7 @@ static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, for (int i=0; i<_NUM_CORE_HEAP; i++) { env->SetIntField(object, stat_fields[i].pss_field, stats[i].pss); env->SetIntField(object, stat_fields[i].pssSwappable_field, stats[i].swappablePss); + env->SetIntField(object, stat_fields[i].rss_field, stats[i].rss); env->SetIntField(object, stat_fields[i].privateDirty_field, stats[i].privateDirty); env->SetIntField(object, stat_fields[i].sharedDirty_field, stats[i].sharedDirty); env->SetIntField(object, stat_fields[i].privateClean_field, stats[i].privateClean); @@ -536,6 +546,7 @@ static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) { otherArray[j++] = stats[i].pss; otherArray[j++] = stats[i].swappablePss; + otherArray[j++] = stats[i].rss; otherArray[j++] = stats[i].privateDirty; otherArray[j++] = stats[i].sharedDirty; otherArray[j++] = stats[i].privateClean; @@ -580,10 +591,11 @@ UniqueFile OpenSmapsOrRollup(int pid) } static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, - jlongArray outUssSwapPss, jlongArray outMemtrack) + jlongArray outUssSwapPssRss, jlongArray outMemtrack) { - char line[1024]; + char lineBuffer[1024]; jlong pss = 0; + jlong rss = 0; jlong swapPss = 0; jlong uss = 0; jlong memtrack = 0; @@ -597,50 +609,70 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, UniqueFile fp = OpenSmapsOrRollup(pid); if (fp != nullptr) { + char* line; + while (true) { - if (fgets(line, sizeof (line), fp.get()) == NULL) { + if (fgets(lineBuffer, sizeof (lineBuffer), fp.get()) == NULL) { break; } - - if (line[0] == 'P') { - if (strncmp(line, "Pss:", 4) == 0) { - char* c = line + 4; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; + line = lineBuffer; + + switch (line[0]) { + case 'P': + if (strncmp(line, "Pss:", 4) == 0) { + char* c = line + 4; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + pss += atoi(c); + } else if (strncmp(line, "Private_Clean:", 14) == 0 + || strncmp(line, "Private_Dirty:", 14) == 0) { + char* c = line + 14; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + uss += atoi(c); } - pss += atoi(c); - } else if (strncmp(line, "Private_Clean:", 14) == 0 - || strncmp(line, "Private_Dirty:", 14) == 0) { - char* c = line + 14; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; + break; + case 'R': + if (strncmp(line, "Rss:", 4) == 0) { + char* c = line + 4; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + rss += atoi(c); } - uss += atoi(c); - } - } else if (line[0] == 'S' && strncmp(line, "SwapPss:", 8) == 0) { - char* c = line + 8; - jlong lSwapPss; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; - } - lSwapPss = atoi(c); - swapPss += lSwapPss; - pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP + break; + case 'S': + if (strncmp(line, "SwapPss:", 8) == 0) { + char* c = line + 8; + jlong lSwapPss; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + lSwapPss = atoi(c); + swapPss += lSwapPss; + pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP + } + break; } } } } - if (outUssSwapPss != NULL) { - if (env->GetArrayLength(outUssSwapPss) >= 1) { - jlong* outUssSwapPssArray = env->GetLongArrayElements(outUssSwapPss, 0); - if (outUssSwapPssArray != NULL) { - outUssSwapPssArray[0] = uss; - if (env->GetArrayLength(outUssSwapPss) >= 2) { - outUssSwapPssArray[1] = swapPss; + if (outUssSwapPssRss != NULL) { + if (env->GetArrayLength(outUssSwapPssRss) >= 1) { + jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0); + if (outUssSwapPssRssArray != NULL) { + outUssSwapPssRssArray[0] = uss; + if (env->GetArrayLength(outUssSwapPssRss) >= 2) { + outUssSwapPssRssArray[1] = swapPss; + } + if (env->GetArrayLength(outUssSwapPssRss) >= 3) { + outUssSwapPssRssArray[2] = rss; } } - env->ReleaseLongArrayElements(outUssSwapPss, outUssSwapPssArray, 0); + env->ReleaseLongArrayElements(outUssSwapPssRss, outUssSwapPssRssArray, 0); } } @@ -1209,6 +1241,8 @@ int register_android_os_Debug(JNIEnv *env) env->GetFieldID(clazz, stat_field_names[i].pss_name, "I"); stat_fields[i].pssSwappable_field = env->GetFieldID(clazz, stat_field_names[i].pssSwappable_name, "I"); + stat_fields[i].rss_field = + env->GetFieldID(clazz, stat_field_names[i].rss_name, "I"); stat_fields[i].privateDirty_field = env->GetFieldID(clazz, stat_field_names[i].privateDirty_name, "I"); stat_fields[i].sharedDirty_field = diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp index 9494fb8e7eef7b8a9b90f45e6235ece2674864b4..061349aee96fc2d80bd8d22bd15aa96934158561 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_os_seccomp.cpp b/core/jni/android_os_seccomp.cpp deleted file mode 100644 index 06e2a167de0a8d5139ad0b31f1575decb2093753..0000000000000000000000000000000000000000 --- a/core/jni/android_os_seccomp.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "core_jni_helpers.h" -#include -#include "utils/Log.h" -#include - -#include "seccomp_policy.h" - -static void Seccomp_setPolicy(JNIEnv* /*env*/) { - if (security_getenforce() == 0) { - ALOGI("seccomp disabled by setenforce 0"); - return; - } - - if (!set_seccomp_filter()) { - ALOGE("Failed to set seccomp policy - killing"); - exit(1); - } -} - -static const JNINativeMethod method_table[] = { - NATIVE_METHOD(Seccomp, setPolicy, "()V"), -}; - -namespace android { - -int register_android_os_seccomp(JNIEnv* env) { - return android::RegisterMethodsOrDie(env, "android/os/Seccomp", - method_table, NELEM(method_table)); -} - -} diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp similarity index 68% rename from core/jni/android_text_MeasuredText.cpp rename to core/jni/android_text_MeasuredParagraph.cpp index af9d1312220111263692fee3b7db486f79e175ce..dddddbbb37960d8430cf8cd34f028c4a7d6a7c7d 100644 --- a/core/jni/android_text_MeasuredText.cpp +++ b/core/jni/android_text_MeasuredParagraph.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "MeasuredText" +#define LOG_TAG "MeasuredParagraph" #include "ScopedIcuLocale.h" #include "unicode/locid.h" @@ -49,7 +49,7 @@ static inline Paint* toPaint(jlong ptr) { return reinterpret_cast(ptr); } -static inline minikin::MeasuredText* toMeasuredText(jlong ptr) { +static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) { return reinterpret_cast(ptr); } @@ -57,8 +57,8 @@ template static inline jlong toJLong(Ptr ptr) { return reinterpret_cast(ptr); } -static void releaseMeasuredText(jlong measuredTextPtr) { - delete toMeasuredText(measuredTextPtr); +static void releaseMeasuredParagraph(jlong measuredTextPtr) { + delete toMeasuredParagraph(measuredTextPtr); } // Regular JNI @@ -84,13 +84,15 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong } // Regular JNI -static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, - jcharArray javaText) { +static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr, + jcharArray javaText, jboolean computeHyphenation, + jboolean computeLayout) { 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, + computeLayout).release()); } // Regular JNI @@ -98,25 +100,41 @@ static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { delete toBuilder(builderPtr); } +// CriticalNative +static jfloat nGetWidth(jlong ptr, jint start, jint end) { + minikin::MeasuredText* mt = toMeasuredParagraph(ptr); + float r = 0.0f; + for (int i = start; i < end; ++i) { + r += mt->widths[i]; + } + return r; +} + // CriticalNative static jlong nGetReleaseFunc() { - return toJLong(&releaseMeasuredText); + return toJLong(&releaseMeasuredParagraph); +} + +static jint nGetMemoryUsage(jlong ptr) { + return static_cast(toMeasuredParagraph(ptr)->getMemoryUsage()); } static const JNINativeMethod gMethods[] = { - // MeasuredTextBuilder native functions. + // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText}, + {"nBuildNativeMeasuredParagraph", "(J[CZZ)J", (void*) nBuildNativeMeasuredParagraph}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, - // MeasuredText native functions. + // MeasuredParagraph native functions. + {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives + {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native }; -int register_android_text_MeasuredText(JNIEnv* env) { - return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods)); +int register_android_text_MeasuredParagraph(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods)); } } diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index b5c23dfa873ddf0b4c4e2485752f15827e037edc..682dc87398699f054c77f30067a3ee78cf6dfe32 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -174,7 +174,7 @@ static const JNINativeMethod gMethods[] = { // Inputs "[C" // text - "J" // MeasuredText ptr. + "J" // MeasuredParagraph ptr. "I" // length "F" // firstWidth "I" // firstWidthLineCount diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index c6828c4f60de752f4e596ac8f2f941e5355ffdb0..683b4c490ec39f124616c5d9bbcf7a63e3c4c75e 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -189,6 +189,10 @@ static void verifySystemIdmaps() argv[argc++] = AssetManager::OVERLAY_DIR; } + if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) { + argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR; + } + // Finally, invoke idmap (if any overlay directory exists) if (argc > 5) { execv(AssetManager::IDMAP_BIN, (char* const*)argv); @@ -205,6 +209,7 @@ static void verifySystemIdmaps() } } + // ---------------------------------------------------------------------------- // this guy is exported to other jni routines diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index dec6c0226baa7b0ac65a8c2488300aecb5506421..62aa1f38ca30b3fb30dc2f9f8592279878bdbd76 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -610,7 +610,7 @@ static jlong getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], return -1; } - char buffer[256]; + char buffer[2048]; const int len = read(fd, buffer, sizeof(buffer)-1); close(fd); @@ -719,7 +719,7 @@ void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileSt int fd = open(file.string(), O_RDONLY | O_CLOEXEC); if (fd >= 0) { - const size_t BUFFER_SIZE = 2048; + const size_t BUFFER_SIZE = 4096; char* buffer = (char*)malloc(BUFFER_SIZE); int len = read(fd, buffer, BUFFER_SIZE-1); close(fd); diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp index 0cb69359901d1b357d8a61f9846c0051d46af674..99d983957075aa3a21892beb7e200ca60a83db8f 100644 --- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp +++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp @@ -30,7 +30,14 @@ #include #include -#include + +#include "android-base/unique_fd.h" +#include "bpf/BpfNetworkStats.h" +#include "bpf/BpfUtils.h" + +using android::bpf::hasBpfSupport; +using android::bpf::parseBpfNetworkStatsDetail; +using android::bpf::stats_line; namespace android { @@ -45,6 +52,7 @@ static struct { jfieldID tag; jfieldID metered; jfieldID roaming; + jfieldID defaultNetwork; jfieldID rxBytes; jfieldID rxPackets; jfieldID txBytes; @@ -52,17 +60,6 @@ static struct { jfieldID operations; } gNetworkStatsClassInfo; -struct stats_line { - char iface[32]; - int32_t uid; - int32_t set; - int32_t tag; - int64_t rxBytes; - int64_t rxPackets; - int64_t txBytes; - int64_t txPackets; -}; - static jobjectArray get_string_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow) { if (!grow) { @@ -96,33 +93,14 @@ static jlongArray get_long_array(JNIEnv* env, jobject obj, jfieldID field, int s return env->NewLongArray(size); } -static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, - jstring path, jint limitUid, jobjectArray limitIfacesObj, jint limitTag) { - ScopedUtfChars path8(env, path); - if (path8.c_str() == NULL) { - return -1; - } - - FILE *fp = fopen(path8.c_str(), "r"); +static int legacyReadNetworkStatsDetail(std::vector* lines, + const std::vector& limitIfaces, + int limitTag, int limitUid, const char* path) { + FILE* fp = fopen(path, "r"); if (fp == NULL) { return -1; } - Vector limitIfaces; - if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) { - int num = env->GetArrayLength(limitIfacesObj); - limitIfaces.setCapacity(num); - for (int i=0; iGetObjectArrayElement(limitIfacesObj, i); - ScopedUtfChars string8(env, string); - if (string8.c_str() != NULL) { - limitIfaces.add(String8(string8.c_str())); - } - } - } - - Vector lines; - int lastIdx = 1; int idx; char buffer[384]; @@ -214,7 +192,7 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, //ALOGI("skipping due to uid: %s", buffer); continue; } - lines.push_back(s); + lines->push_back(s); } else { //ALOGI("skipping due to bad remaining fields: %s", pos); } @@ -224,8 +202,42 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, ALOGE("Failed to close netstats file"); return -1; } + return 0; +} + +static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jstring path, + jint limitUid, jobjectArray limitIfacesObj, jint limitTag, + jboolean useBpfStats) { + ScopedUtfChars path8(env, path); + if (path8.c_str() == NULL) { + return -1; + } + + std::vector limitIfaces; + if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) { + int num = env->GetArrayLength(limitIfacesObj); + for (int i = 0; i < num; i++) { + jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i); + ScopedUtfChars string8(env, string); + if (string8.c_str() != NULL) { + limitIfaces.push_back(std::string(string8.c_str())); + } + } + } + std::vector lines; + + + if (useBpfStats) { + if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0) + return -1; + } else { + if (legacyReadNetworkStatsDetail(&lines, limitIfaces, limitTag, + limitUid, path8.c_str()) < 0) + return -1; + } int size = lines.size(); + bool grow = size > env->GetIntField(stats, gNetworkStatsClassInfo.capacity); ScopedLocalRef iface(env, get_string_array(env, stats, @@ -246,6 +258,9 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, ScopedIntArrayRW roaming(env, get_int_array(env, stats, gNetworkStatsClassInfo.roaming, size, grow)); if (roaming.get() == NULL) return -1; + ScopedIntArrayRW defaultNetwork(env, get_int_array(env, stats, + gNetworkStatsClassInfo.defaultNetwork, size, grow)); + if (defaultNetwork.get() == NULL) return -1; ScopedLongArrayRW rxBytes(env, get_long_array(env, stats, gNetworkStatsClassInfo.rxBytes, size, grow)); if (rxBytes.get() == NULL) return -1; @@ -269,7 +284,7 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, uid[i] = lines[i].uid; set[i] = lines[i].set; tag[i] = lines[i].tag; - // Metered and Roaming are populated in Java-land by inspecting the iface properties. + // Metered, roaming and defaultNetwork are populated in Java-land. rxBytes[i] = lines[i].rxBytes; rxPackets[i] = lines[i].rxPackets; txBytes[i] = lines[i].txBytes; @@ -285,6 +300,8 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, env->SetObjectField(stats, gNetworkStatsClassInfo.tag, tag.getJavaArray()); env->SetObjectField(stats, gNetworkStatsClassInfo.metered, metered.getJavaArray()); env->SetObjectField(stats, gNetworkStatsClassInfo.roaming, roaming.getJavaArray()); + env->SetObjectField(stats, gNetworkStatsClassInfo.defaultNetwork, + defaultNetwork.getJavaArray()); env->SetObjectField(stats, gNetworkStatsClassInfo.rxBytes, rxBytes.getJavaArray()); env->SetObjectField(stats, gNetworkStatsClassInfo.rxPackets, rxPackets.getJavaArray()); env->SetObjectField(stats, gNetworkStatsClassInfo.txBytes, txBytes.getJavaArray()); @@ -297,7 +314,7 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, static const JNINativeMethod gMethods[] = { { "nativeReadNetworkStatsDetail", - "(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;I)I", + "(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;IZ)I", (void*) readNetworkStatsDetail } }; @@ -318,6 +335,7 @@ int register_com_android_internal_net_NetworkStatsFactory(JNIEnv* env) { gNetworkStatsClassInfo.tag = GetFieldIDOrDie(env, clazz, "tag", "[I"); gNetworkStatsClassInfo.metered = GetFieldIDOrDie(env, clazz, "metered", "[I"); gNetworkStatsClassInfo.roaming = GetFieldIDOrDie(env, clazz, "roaming", "[I"); + gNetworkStatsClassInfo.defaultNetwork = GetFieldIDOrDie(env, clazz, "defaultNetwork", "[I"); gNetworkStatsClassInfo.rxBytes = GetFieldIDOrDie(env, clazz, "rxBytes", "[J"); gNetworkStatsClassInfo.rxPackets = GetFieldIDOrDie(env, clazz, "rxPackets", "[J"); gNetworkStatsClassInfo.txBytes = GetFieldIDOrDie(env, clazz, "txBytes", "[J"); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 1407ae4c4bfe82b7b7d7c5c98a52df4299fc428d..13e6fcdc842617b4b596cecbacc204dc75e96ce1 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include "core_jni_helpers.h" @@ -76,6 +77,8 @@ static const char kZygoteClassName[] = "com/android/internal/os/Zygote"; static jclass gZygoteClass; static jmethodID gCallPostForkChildHooks; +static bool g_is_security_enforced = true; + // Must match values in com.android.internal.os.Zygote. enum MountExternalKind { MOUNT_EXTERNAL_NONE = 0, @@ -229,6 +232,20 @@ static void PreApplicationInit() { mallopt(M_DECAY_TIME, 1); } +static void SetUpSeccompFilter(uid_t uid) { + if (!g_is_security_enforced) { + ALOGI("seccomp disabled by setenforce 0"); + return; + } + + // Apply system or app filter based on uid. + if (getuid() >= AID_APP_START) { + set_app_seccomp_filter(); + } else { + set_system_seccomp_filter(); + } +} + static void EnableKeepCapabilities(JNIEnv* env) { int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); if (rc == -1) { @@ -541,6 +558,11 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra RuntimeAbort(env, __LINE__, "Call to sigprocmask(SIG_UNBLOCK, { SIGCHLD }) failed."); } + // Must be called when the new process still has CAP_SYS_ADMIN. The other alternative is to + // call prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that breaks SELinux domain transition (see + // b/71859146). + SetUpSeccompFilter(uid); + // Keep capabilities across UID change, unless we're staying root. if (uid != 0) { EnableKeepCapabilities(env); @@ -698,6 +720,12 @@ static uint64_t GetEffectiveCapabilityMask(JNIEnv* env) { namespace android { +static void com_android_internal_os_Zygote_nativeSecurityInit(JNIEnv*, jclass) { + // security_getenforce is not allowed on app process. Initialize and cache the value before + // zygote forks. + g_is_security_enforced = security_getenforce(); +} + static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) { PreApplicationInit(); } @@ -835,6 +863,8 @@ static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* en } static const JNINativeMethod gMethods[] = { + { "nativeSecurityInit", "()V", + (void *) com_android_internal_os_Zygote_nativeSecurityInit }, { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 956b7249660f5fd7637db03d04d2d91afdb42662..3b7b14c58d0a4474687547efe76ed67246cfb3d1 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -85,11 +85,15 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { static const char* kOverlayDir = "/system/vendor/overlay/"; static const char* kVendorOverlayDir = "/vendor/overlay"; static const char* kOverlaySubdir = "/system/vendor/overlay-subdir/"; + static const char* kSystemProductOverlayDir = "/system/product/overlay/"; + static const char* kProductOverlayDir = "/product/overlay"; static const char* kApkSuffix = ".apk"; if ((android::base::StartsWith(path, kOverlayDir) || android::base::StartsWith(path, kOverlaySubdir) - || android::base::StartsWith(path, kVendorOverlayDir)) + || android::base::StartsWith(path, kVendorOverlayDir) + || android::base::StartsWith(path, kSystemProductOverlayDir) + || android::base::StartsWith(path, kProductOverlayDir)) && android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) { return true; diff --git a/core/proto/android/app/activitymanager.proto b/core/proto/android/app/activitymanager.proto index 3412a32e7b750b34deea4f05287c1c7abf5aa185..4756c13d71db11d015da2db5b4602c8762526a17 100644 --- a/core/proto/android/app/activitymanager.proto +++ b/core/proto/android/app/activitymanager.proto @@ -19,63 +19,18 @@ syntax = "proto2"; package android.app; option java_multiple_files = true; +option java_outer_classname = "ActivityManagerProto"; -// ActivityManager.java PROCESS_STATEs -enum ProcessState { - // Order matters for process states, so values have been spaced to provide - // room for future additions. - - // Not a real process state. - PROCESS_STATE_UNKNOWN = -100; - // Process is a persistent system process. - PROCESS_STATE_PERSISTENT = 0; - // Process is a persistent system process and is doing UI. - PROCESS_STATE_PERSISTENT_UI = 100; - // Process is hosting the current top activities. Note that this covers - // all activities that are visible to the user. - PROCESS_STATE_TOP = 200; - // Process is hosting a foreground service due to a system binding. - PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 300; - // Process is hosting a foreground service. - PROCESS_STATE_FOREGROUND_SERVICE = 400; - // Process is important to the user, and something they are aware of. - PROCESS_STATE_IMPORTANT_FOREGROUND = 500; - // Process is important to the user, but not something they are aware of. - PROCESS_STATE_IMPORTANT_BACKGROUND = 600; - // Process is in the background transient so we will try to keep running. - PROCESS_STATE_TRANSIENT_BACKGROUND = 700; - // Process is in the background running a backup/restore operation. - PROCESS_STATE_BACKUP = 800; - // Process is in the background running a service. Unlike oom_adj, this - // level is used for both the normal running in background state and the - // executing operations state. - PROCESS_STATE_SERVICE = 900; - // Process is in the background running a receiver. Note that from the - // perspective of oom_adj, receivers run at a higher foreground level, but - // for our prioritization here that is not necessary and putting them - // below services means many fewer changes in some process states as they - // receive broadcasts. - PROCESS_STATE_RECEIVER = 1000; - // Same as PROCESS_STATE_TOP but while device is sleeping. - PROCESS_STATE_TOP_SLEEPING = 1100; - // Process is in the background, but it can't restore its state so we want - // to try to avoid killing it. - PROCESS_STATE_HEAVY_WEIGHT = 1200; - // Process is in the background but hosts the home activity. - PROCESS_STATE_HOME = 1300; - // Process is in the background but hosts the last shown activity. - PROCESS_STATE_LAST_ACTIVITY = 1400; - // Process is being cached for later use and contains activities. - PROCESS_STATE_CACHED_ACTIVITY = 1500; - // Process is being cached for later use and is a client of another cached - // process that contains activities. - PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 1600; - // Process is being cached for later use and has an activity that corresponds - // to an existing recent task. - PROCESS_STATE_CACHED_RECENT = 1700; - // Process is being cached for later use and is empty. - PROCESS_STATE_CACHED_EMPTY = 1800; - // Process does not exist. - PROCESS_STATE_NONEXISTENT = 1900; +// ActivityManager.java UID_OBSERVERs flags +enum UidObserverFlag { + // report changes in process state, original value is 1 << 0 + UID_OBSERVER_FLAG_PROCSTATE = 1; + // report uid gone, original value is 1 << 1 + UID_OBSERVER_FLAG_GONE = 2; + // report uid has become idle, original value is 1 << 2 + UID_OBSERVER_FLAG_IDLE = 3; + // report uid has become active, original value is 1 << 3 + UID_OBSERVER_FLAG_ACTIVE = 4; + // report uid cached state has changed, original value is 1 << 4 + UID_OBSERVER_FLAG_CACHED = 5; } - diff --git a/core/proto/android/app/alarmmanager.proto b/core/proto/android/app/alarmmanager.proto index 789e3d6293c3b29347f88c4f96f222d8381ee1d2..7ef08cae435a62636a3c6413c7792abfc9168319 100644 --- a/core/proto/android/app/alarmmanager.proto +++ b/core/proto/android/app/alarmmanager.proto @@ -17,6 +17,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/app/pendingintent.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; option java_multiple_files = true; @@ -47,6 +48,8 @@ message AlarmManagerProto { // An android.app.AlarmManager.AlarmClockInfo object. message AlarmClockInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // This value is UTC wall clock time in milliseconds, as returned by // System#currentTimeMillis() for example. optional int64 trigger_time_ms = 1; diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto new file mode 100644 index 0000000000000000000000000000000000000000..5eb05be1a1d1e4d78a14a4f69f3201f1c96ab449 --- /dev/null +++ b/core/proto/android/app/enums.proto @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.app; + +option java_outer_classname = "AppProtoEnums"; +option java_multiple_files = true; + +// ActivityManagerInternal.java's APP_TRANSITION reasons. +enum AppTransitionReasonEnum { + APP_TRANSITION_REASON_UNKNOWN = 0; + // The transition was started because we drew the splash screen. + APP_TRANSITION_SPLASH_SCREEN = 1; + // The transition was started because we all app windows were drawn. + APP_TRANSITION_WINDOWS_DRAWN = 2; + // The transition was started because of a timeout. + APP_TRANSITION_TIMEOUT = 3; + // The transition was started because of a we drew a task snapshot. + APP_TRANSITION_SNAPSHOT = 4; +} + +// ActivityManager.java PROCESS_STATEs +enum ProcessStateEnum { + // Unlike the ActivityManager PROCESS_STATE values, the ordering and numerical values + // here are completely fixed and arbitrary. Order is irrelevant. + // No attempt need be made to keep them in sync. + // The values here must not be modified. Any new process states can be appended to the end. + + // Process state that is unknown to this proto file (i.e. is not mapped + // by ActivityManager.processStateAmToProto()). Can only happen if there's a bug in the mapping. + PROCESS_STATE_UNKNOWN_TO_PROTO = 998; + // Not a real process state. + PROCESS_STATE_UNKNOWN = 999; + // Process is a persistent system process. + PROCESS_STATE_PERSISTENT = 1000; + // Process is a persistent system process and is doing UI. + PROCESS_STATE_PERSISTENT_UI = 1001; + // Process is hosting the current top activities. Note that this covers + // all activities that are visible to the user. + PROCESS_STATE_TOP = 1002; + // Process is hosting a foreground service. + PROCESS_STATE_FOREGROUND_SERVICE = 1003; + // Process is hosting a foreground service due to a system binding. + PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 1004; + // Process is important to the user, and something they are aware of. + PROCESS_STATE_IMPORTANT_FOREGROUND = 1005; + // Process is important to the user, but not something they are aware of. + PROCESS_STATE_IMPORTANT_BACKGROUND = 1006; + // Process is in the background transient so we will try to keep running. + PROCESS_STATE_TRANSIENT_BACKGROUND = 1007; + // Process is in the background running a backup/restore operation. + PROCESS_STATE_BACKUP = 1008; + // Process is in the background running a service. Unlike oom_adj, this + // level is used for both the normal running in background state and the + // executing operations state. + PROCESS_STATE_SERVICE = 1009; + // Process is in the background running a receiver. Note that from the + // perspective of oom_adj, receivers run at a higher foreground level, but + // for our prioritization here that is not necessary and putting them + // below services means many fewer changes in some process states as they + // receive broadcasts. + PROCESS_STATE_RECEIVER = 1010; + // Same as PROCESS_STATE_TOP but while device is sleeping. + PROCESS_STATE_TOP_SLEEPING = 1011; + // Process is in the background, but it can't restore its state so we want + // to try to avoid killing it. + PROCESS_STATE_HEAVY_WEIGHT = 1012; + // Process is in the background but hosts the home activity. + PROCESS_STATE_HOME = 1013; + // Process is in the background but hosts the last shown activity. + PROCESS_STATE_LAST_ACTIVITY = 1014; + // Process is being cached for later use and contains activities. + PROCESS_STATE_CACHED_ACTIVITY = 1015; + // Process is being cached for later use and is a client of another cached + // process that contains activities. + PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 1016; + // Process is being cached for later use and has an activity that corresponds + // to an existing recent task. + PROCESS_STATE_CACHED_RECENT = 1017; + // Process is being cached for later use and is empty. + PROCESS_STATE_CACHED_EMPTY = 1018; + // Process does not exist. + PROCESS_STATE_NONEXISTENT = 1019; +} + diff --git a/core/proto/android/app/notification.proto b/core/proto/android/app/notification.proto index 5376b0ed5c4c54c99c010152b147c06cd1f5d011..c7e313aa5801b520160a9e3330429c6fb5310a05 100644 --- a/core/proto/android/app/notification.proto +++ b/core/proto/android/app/notification.proto @@ -20,18 +20,22 @@ option java_multiple_files = true; package android.app; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * An android.app.Notification object. * Deprecated fields are not included in the proto. */ message NotificationProto { - optional string channel_id = 1; + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string channel_id = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional bool has_ticker_text = 2; optional int32 flags = 3; optional int32 color = 4; - optional string category = 5; - optional string group_key = 6; - optional string sort_key = 7; + optional string category = 5 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string group_key = 6 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string sort_key = 7 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional int32 action_length = 8; // If this field is not set, then the value is unknown. diff --git a/core/proto/android/app/notification_channel.proto b/core/proto/android/app/notification_channel.proto index 0388547e009f33f5d7a316e195ed7dc9ba37c1f2..337aa1c20c7ae14c707901c2fe733cc3294d5a46 100644 --- a/core/proto/android/app/notification_channel.proto +++ b/core/proto/android/app/notification_channel.proto @@ -21,19 +21,22 @@ option java_multiple_files = true; package android.app; import "frameworks/base/core/proto/android/media/audioattributes.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; /** * An android.app.NotificationChannel object. */ message NotificationChannelProto { - optional string id = 1; - optional string name = 2; - optional string description = 3; + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string id = 1 [ (android.privacy).dest = DEST_EXPLICIT ]; + optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; + optional string description = 3 [ (android.privacy).dest = DEST_EXPLICIT ]; optional int32 importance = 4; optional bool can_bypass_dnd = 5; // Default is VISIBILITY_NO_OVERRIDE (-1000). optional int32 lockscreen_visibility = 6; - optional string sound = 7; + optional string sound = 7 [ (android.privacy).dest = DEST_EXPLICIT ]; optional bool use_lights = 8; // Default is 0. optional int32 light_color = 9; @@ -46,7 +49,7 @@ message NotificationChannelProto { optional bool show_badge = 13; // Default is false. optional bool is_deleted = 14; - optional string group = 15; + optional string group = 15 [ (android.privacy).dest = DEST_EXPLICIT ]; optional android.media.AudioAttributesProto audio_attributes = 16; // If this is a blockable system notification channel. optional bool is_blockable_system = 17; diff --git a/core/proto/android/app/notification_channel_group.proto b/core/proto/android/app/notification_channel_group.proto index 89a540f2012c3e1155711f95a9eae33286bec6f8..7b270d74f2dee1f273e495856f130f64210902e7 100644 --- a/core/proto/android/app/notification_channel_group.proto +++ b/core/proto/android/app/notification_channel_group.proto @@ -21,11 +21,14 @@ option java_multiple_files = true; package android.app; import "frameworks/base/core/proto/android/app/notification_channel.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; /** * An android.app.NotificationChannelGroup object. */ message NotificationChannelGroupProto { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + optional string id = 1; optional string name = 2; optional string description = 3; diff --git a/core/proto/android/app/notificationmanager.proto b/core/proto/android/app/notificationmanager.proto index 7d774aeab5510d1df6399a7576af3ab461baadc3..e991688c218c154276dc464aea2f37499dd03563 100644 --- a/core/proto/android/app/notificationmanager.proto +++ b/core/proto/android/app/notificationmanager.proto @@ -20,10 +20,14 @@ option java_multiple_files = true; package android.app; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** - * An android.app.NotificationMananger.Policy object. + * An android.app.NotificationManager.Policy object. */ message PolicyProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + enum Category { CATEGORY_UNKNOWN = 0; // Reminder notifications are prioritized. @@ -36,6 +40,11 @@ message PolicyProto { CALLS = 4; // Calls from repeat callers are prioritized. REPEAT_CALLERS = 5; + // Alarms are prioritized. + ALARMS = 6; + // Media, system, game (catch-all for non-never suppressible sounds) are + // prioritized. + MEDIA_SYSTEM_OTHER = 7; } repeated Category priority_categories = 1; diff --git a/core/proto/android/app/pendingintent.proto b/core/proto/android/app/pendingintent.proto index b562c0bb38e55221a1e8be0ebfe94ac01a8848f0..ab0d34eda5dfeb87e359adf2c2d7d08ec43ec83a 100644 --- a/core/proto/android/app/pendingintent.proto +++ b/core/proto/android/app/pendingintent.proto @@ -20,9 +20,13 @@ option java_multiple_files = true; package android.app; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * An android.app.PendingIntent object. */ message PendingIntentProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string target = 1; } diff --git a/core/proto/android/app/profilerinfo.proto b/core/proto/android/app/profilerinfo.proto new file mode 100644 index 0000000000000000000000000000000000000000..6b28318f9fb91b1d7b85ed842b5b5c2f462b8df2 --- /dev/null +++ b/core/proto/android/app/profilerinfo.proto @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +option java_package = "android.app"; +option java_multiple_files = true; + +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + +package android.app; + +/** + * An android.app.ProfilerInfo object. + */ +message ProfilerInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string profile_file = 1; + optional int32 profile_fd = 2; + optional int32 sampling_interval = 3; + optional bool auto_stop_profiler = 4; + optional bool streaming_output = 5; + optional string agent = 6; +} diff --git a/core/proto/android/content/clipdata.proto b/core/proto/android/content/clipdata.proto index 6967b69352bd00d074e4b025d9de2a31643bd65f..aeeef977d4013367dd2402568cb145a4cc5edcd5 100644 --- a/core/proto/android/content/clipdata.proto +++ b/core/proto/android/content/clipdata.proto @@ -21,13 +21,18 @@ option java_multiple_files = true; import "frameworks/base/core/proto/android/content/clipdescription.proto"; import "frameworks/base/core/proto/android/content/intent.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; // An android.content.ClipData object. message ClipDataProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional android.content.ClipDescriptionProto description = 1; // Custom dump of an android.graphics.Bitmap object. message Icon { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 width = 1; optional int32 height = 2; } @@ -35,6 +40,8 @@ message ClipDataProto { // An android.content.ClipData.Item object. message Item { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + oneof data { string html_text = 1; string text = 2; diff --git a/core/proto/android/content/clipdescription.proto b/core/proto/android/content/clipdescription.proto index 40f4ad3ed724b58577edc217a7e3844a9e661411..bc0e9407faf8a01b0d2dc3404893d48333cace26 100644 --- a/core/proto/android/content/clipdescription.proto +++ b/core/proto/android/content/clipdescription.proto @@ -20,11 +20,14 @@ package android.content; option java_multiple_files = true; import "frameworks/base/core/proto/android/os/persistablebundle.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; // An android.content.ClipDescription object. message ClipDescriptionProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated string mime_types = 1; - optional string label = 2; + optional string label = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional android.os.PersistableBundleProto extras = 3; optional int64 timestamp_ms = 4; } diff --git a/core/proto/android/content/component_name.proto b/core/proto/android/content/component_name.proto index fc0c8c55c2b1075fcc911b32de0a3a7e0647ffd1..4e49cf2bfcd54e1a3a89595beba18a899a5b0ec8 100644 --- a/core/proto/android/content/component_name.proto +++ b/core/proto/android/content/component_name.proto @@ -20,10 +20,14 @@ option java_multiple_files = true; package android.content; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * An android.content.ComponentName object. */ message ComponentNameProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string package_name = 1; optional string class_name = 2; } diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto index 111b27ff986006813a745ffd03880b7333954e97..a62d56ca5a86aa3fd82c8905017fef38a5094ce0 100644 --- a/core/proto/android/content/configuration.proto +++ b/core/proto/android/content/configuration.proto @@ -22,11 +22,14 @@ package android.content; import "frameworks/base/core/proto/android/app/window_configuration.proto"; import "frameworks/base/core/proto/android/content/locale.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; /** * An android resource configuration. */ message ConfigurationProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional float font_scale = 1; optional uint32 mcc = 2; optional uint32 mnc = 3; diff --git a/core/proto/android/content/featureinfo.proto b/core/proto/android/content/featureinfo.proto index a7501204b43fb31b448a3833af88223fcdc68fe5..6878f0ecd84c363a96ebfe87212ec07f634ad9c3 100644 --- a/core/proto/android/content/featureinfo.proto +++ b/core/proto/android/content/featureinfo.proto @@ -16,14 +16,20 @@ syntax = "proto2"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_package = "android.content.pm"; option java_multiple_files = true; package android.content.pm; message FeatureInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + // Some hard coded feature name optional string name = 1; optional int32 version = 2; + // String representation of reqGlEsVersion. optional string gles_version = 3; optional int32 flags = 4; } diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto index 3e5265ad9c130fbb3d05a45a3a084fbea3d6c1c2..5e0ed111d7ea0d328ff36031264a48522bd70eaf 100644 --- a/core/proto/android/content/intent.proto +++ b/core/proto/android/content/intent.proto @@ -20,10 +20,14 @@ package android.content; option java_package = "android.content"; option java_multiple_files = true; +import "frameworks/base/core/proto/android/content/component_name.proto"; import "frameworks/base/core/proto/android/os/patternmatcher.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; // Next Tag: 13 message IntentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + enum DockState { // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that // the phone is not in any dock. @@ -48,23 +52,25 @@ message IntentProto { optional string action = 1; repeated string categories = 2; - optional string data = 3; + optional string data = 3 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional string type = 4; optional string flag = 5; optional string package = 6; - optional string component = 7; + optional ComponentNameProto component = 7; optional string source_bounds = 8; - optional string clip_data = 9; - optional string extras = 10; + optional string clip_data = 9 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string extras = 10 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional int32 content_user_hint = 11; optional string selector = 12; } // Next Tag: 11 message IntentFilterProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated string actions = 1; repeated string categories = 2; - repeated string data_schemes = 3; + repeated string data_schemes = 3 [ (.android.privacy).dest = DEST_EXPLICIT ]; repeated android.os.PatternMatcherProto data_scheme_specs = 4; repeated AuthorityEntryProto data_authorities = 5; repeated android.os.PatternMatcherProto data_paths = 6; @@ -75,6 +81,8 @@ message IntentFilterProto { } message AuthorityEntryProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string host = 1; optional bool wild = 2; optional int32 port = 3; diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto index f0de31cbb5e9c699073a5dd9cf7b98aed9f621db..2be3ab97973311a181f5ba8ef7bf4d8683414ca5 100644 --- a/core/proto/android/content/locale.proto +++ b/core/proto/android/content/locale.proto @@ -18,9 +18,13 @@ syntax = "proto2"; option java_package = "android.content"; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + package android.content; message LocaleProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string language = 1; optional string country = 2; optional string variant = 3; diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto new file mode 100644 index 0000000000000000000000000000000000000000..6e99bec681a235c2561b8647577835354c6d4458 --- /dev/null +++ b/core/proto/android/content/package_item_info.proto @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +option java_package = "android.content.pm"; +option java_multiple_files = true; + +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + +package android.content.pm; + +message PackageItemInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1; + optional string package_name = 2; + optional int32 label_res = 3; + optional string non_localized_label = 4; + optional int32 icon = 5; + optional int32 banner = 6; +} + +// Proto of android.content.pm.ApplicationInfo which extends PackageItemInfo +message ApplicationInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional PackageItemInfoProto package = 1; + optional string permission = 2; + optional string process_name = 3; + optional int32 uid = 4; + optional int32 flags = 5; + optional int32 private_flags = 6; + optional int32 theme = 7; + optional string source_dir = 8; + optional string public_source_dir = 9; + repeated string split_source_dirs = 10; + repeated string split_public_source_dirs = 11; + repeated string resource_dirs = 12; + optional string data_dir = 13; + optional string class_loader_name = 14; + repeated string split_class_loader_names = 15; + + message Version { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional bool enabled = 1; + optional int32 min_sdk_version = 2; + optional int32 target_sdk_version = 3; + optional int32 version_code = 4; + optional int32 target_sandbox_version = 5; + } + optional Version version = 16; + + message Detail { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + + optional string class_name = 1; + optional string task_affinity = 2; + optional int32 requires_smallest_width_dp = 3; + optional int32 compatible_width_limit_dp = 4; + optional int32 largest_width_limit_dp = 5; + optional string seinfo = 6; + optional string seinfo_user = 7; + optional string device_protected_data_dir = 8; + optional string credential_protected_data_dir = 9; + repeated string shared_library_files = 10; + optional string manage_space_activity_name = 11; + optional int32 description_res = 12; + optional int32 ui_options = 13; + optional bool supports_rtl = 14; + oneof full_backup_content { + string content = 15; + bool is_full_backup = 16; + } + optional int32 networkSecurity_config_res = 17; + optional int32 category = 18; + } + optional Detail detail = 17; +} diff --git a/core/proto/android/graphics/point.proto b/core/proto/android/graphics/point.proto index 5ae17cb5df5d031d0c733f2843079043e3075174..035b9fe36cd121c8384788231a167918878b7ff7 100644 --- a/core/proto/android/graphics/point.proto +++ b/core/proto/android/graphics/point.proto @@ -17,9 +17,13 @@ syntax = "proto2"; package android.graphics; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; message PointProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 x = 1; optional int32 y = 2; } diff --git a/core/proto/android/graphics/rect.proto b/core/proto/android/graphics/rect.proto index 562ffce7f43052fa0ff602f2c80094c3cad2ca8c..eb403fe953ff3ed73ab8332b506f843c29cd522f 100644 --- a/core/proto/android/graphics/rect.proto +++ b/core/proto/android/graphics/rect.proto @@ -17,9 +17,13 @@ syntax = "proto2"; package android.graphics; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; message RectProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 left = 1; optional int32 top = 2; optional int32 right = 3; diff --git a/core/proto/android/internal/locallog.proto b/core/proto/android/internal/locallog.proto index 51f6c1ce2fd74858876529205cea1ce16ab33f45..73d1492c68e1a7ed2f8284c281b4748cf87d793a 100644 --- a/core/proto/android/internal/locallog.proto +++ b/core/proto/android/internal/locallog.proto @@ -19,6 +19,10 @@ package com.android.internal.util; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message LocalLogProto { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + repeated string lines = 1; } diff --git a/core/proto/android/net/network.proto b/core/proto/android/net/network.proto index 9c7ea5da8c84830c0244517fbbdc7184f29337d9..e13ca9f682ebdd3e342fa1b29288c0ec3dce6d91 100644 --- a/core/proto/android/net/network.proto +++ b/core/proto/android/net/network.proto @@ -19,9 +19,14 @@ option java_multiple_files = true; package android.net; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * An android.net.Network object. */ message NetworkProto { - optional int32 net_id = 1; + // The netId is an implementation detail which might be changed in the + // future, or which alone (i.e. in the absence of some additional context) + // might not be sufficient to fully identify a Network. + optional int32 net_id = 1 [ (.android.privacy).dest = DEST_AUTOMATIC ]; } diff --git a/core/proto/android/net/networkcapabilities.proto b/core/proto/android/net/networkcapabilities.proto index e1c2af191f71949a8f678bc469821933d8dfcf8b..0338bf8f37b68c02a127af29557ce1ef0e0122ad 100644 --- a/core/proto/android/net/networkcapabilities.proto +++ b/core/proto/android/net/networkcapabilities.proto @@ -20,10 +20,14 @@ package android.net; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * An android.net.NetworkCapabilities object. */ message NetworkCapabilitiesProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + enum Transport { // Indicates this network uses a Cellular transport. TRANSPORT_CELLULAR = 0; @@ -118,7 +122,7 @@ message NetworkCapabilitiesProto { optional int32 link_up_bandwidth_kbps = 3; optional int32 link_down_bandwidth_kbps = 4; - optional string network_specifier = 5; + optional string network_specifier = 5 [ (.android.privacy).dest = DEST_EXPLICIT ]; // True if this object specifies a signal strength. optional bool can_report_signal_strength = 6; diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto index 9884464c5a3a6355bf954939426776ee90e4d99a..d260b13a29ebcb8e6f2ff1f1cdd4272a21760999 100644 --- a/core/proto/android/net/networkrequest.proto +++ b/core/proto/android/net/networkrequest.proto @@ -21,11 +21,14 @@ package android.net; option java_multiple_files = true; import "frameworks/base/core/proto/android/net/networkcapabilities.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; /** * An android.net.NetworkRequest object. */ message NetworkRequestProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + enum Type { TYPE_UNKNOWN = 0; // Only used by applications. When an application creates a diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto index 331f80f897f503086b0babf20bc690d42354d230..9f9fd05d8874b6c4cc51db2aa324bf6b3f2719ca 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -21,9 +21,12 @@ package android.os; import "frameworks/base/core/proto/android/app/jobparameters.proto"; import "frameworks/base/core/proto/android/os/powermanager.proto"; -import "frameworks/base/core/proto/android/telephony/signalstrength.proto"; +import "frameworks/base/core/proto/android/telephony/enums.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message BatteryStatsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 report_version = 1; optional int64 parcel_version = 2; optional string start_platform_version = 3; @@ -33,6 +36,8 @@ message BatteryStatsProto { } message ControllerActivityProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Time (milliseconds) spent in the idle state. optional int64 idle_duration_ms = 1; // Time (milliseconds) spent in the receive state. @@ -45,6 +50,8 @@ message ControllerActivityProto { // of power. The levels themselves are controller-specific (and may possibly // be device specific...yet to be confirmed). message TxLevel { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Transmit level. Higher levels draw more power. optional int32 level = 1; // Time spent in this specific transmit level state. @@ -54,7 +61,11 @@ message ControllerActivityProto { } message SystemProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + message Battery { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Wall clock time when the data collection started. // In case of device time manually reset by users: // start_clock_time_ms keeps the same value in the current collection @@ -92,6 +103,8 @@ message SystemProto { optional Battery battery = 1; message BatteryDischarge { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Discharged battery percentage points since the stats were last reset // after charging (lower bound approximation). optional int32 lower_bound_since_charge = 1; @@ -142,6 +155,8 @@ message SystemProto { // the entire duration. Field for which the conditions were not consistent // for the entire duration should be marked MIXED. message BatteryLevelStep { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // How long the battery was at the current level. optional int64 duration_ms = 1; // Battery level @@ -192,6 +207,8 @@ message SystemProto { repeated int64 cpu_frequency = 7; message DataConnection { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Name { NONE = 0; GPRS = 1; @@ -221,6 +238,8 @@ message SystemProto { optional ControllerActivityProto global_wifi_controller = 11; message GlobalNetwork { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Total Bytes received on mobile connections. optional int64 mobile_bytes_rx = 1; // Total Bytes transmitted on mobile connections. @@ -245,6 +264,8 @@ message SystemProto { optional GlobalNetwork global_network = 12; message GlobalWifi { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // The amount of time that wifi has been on while the device was running on // battery. optional int64 on_duration_ms = 1; @@ -257,6 +278,8 @@ message SystemProto { // Kernel wakelock metrics are only recorded when the device is unplugged // *and* the screen is off. message KernelWakelock { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; // Kernel wakelock stats aren't apportioned across all kernel wakelocks (as // app wakelocks stats are). @@ -267,6 +290,8 @@ message SystemProto { repeated KernelWakelock kernel_wakelock = 14; message Misc { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 screen_on_duration_ms = 1; optional int64 phone_on_duration_ms = 2; optional int64 full_wakelock_total_duration_ms = 3; @@ -312,12 +337,16 @@ message SystemProto { optional Misc misc = 15; message PhoneSignalStrength { - optional android.telephony.SignalStrengthProto.StrengthName name = 1; + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional android.telephony.SignalStrengthEnum name = 1; optional TimerProto total = 2; }; repeated PhoneSignalStrength phone_signal_strength = 16; message PowerUseItem { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Sipper { UNKNOWN_SIPPER = 0; IDLE = 1; @@ -352,6 +381,8 @@ message SystemProto { repeated PowerUseItem power_use_item = 17; message PowerUseSummary { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional double battery_capacity_mah = 1; optional double computed_power_mah = 2; // Lower bound of actual power drained. @@ -362,6 +393,8 @@ message SystemProto { optional PowerUseSummary power_use_summary = 18; message ResourcePowerManager { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Either StateName or StateName.VoterName. optional string name = 1; optional TimerProto total = 2; @@ -370,6 +403,8 @@ message SystemProto { repeated ResourcePowerManager resource_power_manager = 19; message ScreenBrightness { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Name { DARK = 0; // Not screen-off. DIM = 1; @@ -386,18 +421,24 @@ message SystemProto { optional TimerProto signal_scanning = 21; message WakeupReason { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; optional TimerProto total = 2; }; repeated WakeupReason wakeup_reason = 22; message WifiMulticastWakelockTotal { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 duration_ms = 1; optional int32 count = 2; } optional WifiMulticastWakelockTotal wifi_multicast_wakelock_total = 23; message WifiSignalStrength { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Name { NONE = 0; POOR = 1; @@ -411,6 +452,8 @@ message SystemProto { repeated WifiSignalStrength wifi_signal_strength = 24; message WifiState { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Name { OFF = 0; OFF_SCANNING = 1; @@ -427,6 +470,8 @@ message SystemProto { repeated WifiState wifi_state = 25; message WifiSupplicantState { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Name { INVALID = 0; DISCONNECTED = 1; @@ -449,6 +494,8 @@ message SystemProto { } message TimerProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // This may be an apportioned time. optional int64 duration_ms = 1; optional int64 count = 2; @@ -468,14 +515,20 @@ message TimerProto { } message UidProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Combination of app ID and user ID. optional int32 uid = 1; // The statistics associated with a particular package. message Package { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; message Service { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; // Time spent started. optional int64 start_duration_ms = 2; @@ -492,6 +545,8 @@ message UidProto { // Bluetooth misc data. message BluetoothMisc { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Duration spent BLE scanning blamed on this App (i.e. apportioned to this // app amongst all apps doing BLE scanning; see explanation of 'apportioned' // in App's comment). @@ -515,6 +570,8 @@ message UidProto { optional BluetoothMisc bluetooth_misc = 6; message Cpu { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Total CPU time with processes executing in userspace. Summed up across // multiple cores. optional int64 user_duration_ms = 1; @@ -529,6 +586,8 @@ message UidProto { // system_duration_millis, which are just approximations. Data is not // tracked when device is charging. message ByFrequency { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Index of the frequency in system.cpu_frequency. It starts from 1, to // make it easier to analyze. optional int32 frequency_index = 1; @@ -551,6 +610,8 @@ message UidProto { } // CPU times at different process states. message ByProcessState { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional ProcessState process_state = 1; repeated ByFrequency by_frequency = 2; } @@ -574,7 +635,11 @@ message UidProto { optional TimerProto video = 14; message Job { - optional string name = 1; + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1 [ + (android.privacy).dest = DEST_EXPLICIT + ]; // Job times aren't apportioned. optional TimerProto total = 2; optional TimerProto background = 3; @@ -582,10 +647,16 @@ message UidProto { repeated Job jobs = 15; message JobCompletion { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Job name. - optional string name = 1; + optional string name = 1 [ + (android.privacy).dest = DEST_EXPLICIT + ]; message ReasonCount { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional android.app.JobParametersProto.CancelReason name = 1; optional int32 count = 2; } @@ -594,6 +665,8 @@ message UidProto { repeated JobCompletion job_completion = 16; message Network { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Mobile data traffic (total, background + foreground). optional int64 mobile_bytes_rx = 1; optional int64 mobile_bytes_tx = 2; @@ -631,6 +704,8 @@ message UidProto { // TODO: combine System and App messages? message PowerUseItem { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Estimated power use in mAh. optional double computed_power_mah = 1; // Starting in Oreo, Battery Settings has two modes to display the battery @@ -648,6 +723,8 @@ message UidProto { // Durations are not pooled/apportioned. message Process { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; // Time spent executing in user code. optional int64 user_duration_ms = 2; @@ -665,6 +742,8 @@ message UidProto { repeated Process process = 19; message StateTime { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // All of these (non-deprecated) states are mutually exclusive and can be // added together to find the total time a uid has had any processes running // at all. @@ -706,6 +785,8 @@ message UidProto { repeated StateTime states = 20; message Sensor { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 id = 1; optional TimerProto apportioned = 2; // Background times aren't apportioned. @@ -714,7 +795,11 @@ message UidProto { repeated Sensor sensors = 21; message Sync { - optional string name = 1; + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1 [ + (android.privacy).dest = DEST_EXPLICIT + ]; // Sync times aren't apportioned. optional TimerProto total = 2; optional TimerProto background = 3; @@ -722,6 +807,8 @@ message UidProto { repeated Sync syncs = 22; message UserActivity { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional android.os.PowerManagerProto.UserActivityEvent name = 1; optional int32 count = 2; }; @@ -736,6 +823,8 @@ message UidProto { // wakelocks. AggregatedWakelock, on the other hand, holds overall per-app // wakelock data. message AggregatedWakelock { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // The total duration that the app spent holding partial wakelocks. // It includes both foreground + background use. optional int64 partial_duration_ms = 1; @@ -747,7 +836,11 @@ message UidProto { optional AggregatedWakelock aggregated_wakelock = 24; message Wakelock { - optional string name = 1; + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1 [ + (android.privacy).dest = DEST_EXPLICIT + ]; // Full wakelocks keep the screen on. Based on // PowerManager.SCREEN_BRIGHT_WAKE_LOCK (deprecated in API 13) and @@ -776,14 +869,20 @@ message UidProto { repeated Wakelock wakelocks = 25; message WakeupAlarm { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Wakeup alarm name. - optional string name = 1; + optional string name = 1 [ + (android.privacy).dest = DEST_EXPLICIT + ]; // Only includes counts when screen-off (& on battery). optional int32 count = 2; } repeated WakeupAlarm wakeup_alarm = 26; message Wifi { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Duration holding Wifi-lock. This time is apportioned. optional int64 full_wifi_lock_duration_ms = 1; // Duration running Wifi. This time is apportioned. diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto index 75d0dd3504e3aec450690f874806ec8aa671b713..2388c1edea7e4f1f0abea1267970c676f5b7c409 100644 --- a/core/proto/android/os/batterytype.proto +++ b/core/proto/android/os/batterytype.proto @@ -20,6 +20,10 @@ package android.os; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message BatteryTypeProto { - optional string type = 1; + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string type = 1; } diff --git a/core/proto/android/os/bundle.proto b/core/proto/android/os/bundle.proto index 69902816ae7df5d7c87aae0d6419a63fa07a6eae..5556936c3c78e0dbb71d1b32f7b9e1318eff25bd 100644 --- a/core/proto/android/os/bundle.proto +++ b/core/proto/android/os/bundle.proto @@ -19,10 +19,14 @@ package android.os; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + // An android.os.Bundle object. message BundleProto { oneof data { - int32 parcelled_data_size = 1; - string map_data = 2; + int32 parcelled_data_size = 1 [ + (.android.privacy).dest = DEST_AUTOMATIC + ]; + string map_data = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; } } diff --git a/core/proto/android/os/cpufreq.proto b/core/proto/android/os/cpufreq.proto index a8da0bf2f849bfd1f8e5e460ebf7839a8b67f8f7..8481ffc112d4582e7cebe7d74374bb702622351e 100644 --- a/core/proto/android/os/cpufreq.proto +++ b/core/proto/android/os/cpufreq.proto @@ -18,10 +18,13 @@ syntax = "proto2"; option java_multiple_files = true; option java_outer_classname = "CpuFreqProto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + package android.os; // cpu frequency time from /sys/devices/system/cpu/cpufreq/all_time_in_state message CpuFreq { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 jiffy_hz = 1; // obtain by system config. @@ -30,10 +33,13 @@ message CpuFreq { // frequency time pre cpu, unit in jiffy, TODO: obtain jiffies. message CpuFreqStats { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional string cpu_name = 1; message TimeInState { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 state_khz = 1; // cpu frequency optional int64 time_jiffy = 2; // number of jiffies } diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto index cd151e253e7a454ee33c3d34527980b1bc4e52ab..ca4360238c6bb71f6a26cf48cc7ef3f3d8b96952 100644 --- a/core/proto/android/os/cpuinfo.proto +++ b/core/proto/android/os/cpuinfo.proto @@ -18,6 +18,8 @@ syntax = "proto2"; option java_multiple_files = true; option java_outer_classname = "CpuInfoProto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + package android.os; /** @@ -27,8 +29,11 @@ package android.os; * Next Tag: 6 */ message CpuInfo { + option (android.msg_privacy).dest = DEST_AUTOMATIC; message TaskStats { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 total = 1; // total number of cpu tasks optional int32 running = 2; // number of running tasks optional int32 sleeping = 3; // number of sleeping tasks @@ -38,6 +43,8 @@ message CpuInfo { optional TaskStats task_stats = 1; message MemStats { // unit in kB + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 total = 1; optional int32 used = 2; optional int32 free = 3; @@ -48,6 +55,8 @@ message CpuInfo { optional MemStats swap = 3; message CpuUsage { // unit is percentage % + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 cpu = 1; // 400% cpu indicates 4 cores optional int32 user = 2; optional int32 nice = 3; @@ -62,9 +71,11 @@ message CpuInfo { // Next Tag: 13 message Task { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 pid = 1; optional int32 tid = 2; - optional string user = 3; + optional string user = 3; // the process name which uses cpu optional string pr = 4; // priority of each task, using string type is because special value RT (real time) optional sint32 ni = 5; // niceness value optional float cpu = 6; // precentage of cpu usage of the task diff --git a/core/proto/android/os/enums.proto b/core/proto/android/os/enums.proto new file mode 100644 index 0000000000000000000000000000000000000000..fe9b7ac01291a817349b35f96f6383e90991718f --- /dev/null +++ b/core/proto/android/os/enums.proto @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.os; + +option java_outer_classname = "OsProtoEnums"; +option java_multiple_files = true; + +// These constants are defined in hardware/interfaces/health/1.0/types.hal +// They are primarily used by android/os/BatteryManager.java. +enum BatteryHealthEnum { + BATTERY_HEALTH_INVALID = 0; + BATTERY_HEALTH_UNKNOWN = 1; + BATTERY_HEALTH_GOOD = 2; + BATTERY_HEALTH_OVERHEAT = 3; + BATTERY_HEALTH_DEAD = 4; + BATTERY_HEALTH_OVER_VOLTAGE = 5; + BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6; + BATTERY_HEALTH_COLD = 7; +} + +// Plug states, primarily used by android/os/BatteryManager.java. +enum BatteryPluggedStateEnum { + // Note that NONE is not in BatteryManager.java's constants. + BATTERY_PLUGGED_NONE = 0; + // Power source is an AC charger. + BATTERY_PLUGGED_AC = 1; + // Power source is a USB port. + BATTERY_PLUGGED_USB = 2; + // Power source is wireless. + BATTERY_PLUGGED_WIRELESS = 4; +} + +// These constants are defined in hardware/interfaces/health/1.0/types.hal +// They are primarily used by android/os/BatteryManager.java. +enum BatteryStatusEnum { + BATTERY_STATUS_INVALID = 0; + BATTERY_STATUS_UNKNOWN = 1; + BATTERY_STATUS_CHARGING = 2; + BATTERY_STATUS_DISCHARGING = 3; + BATTERY_STATUS_NOT_CHARGING = 4; + BATTERY_STATUS_FULL = 5; +} + +// Wakelock types, primarily used by android/os/PowerManager.java. +enum WakeLockLevelEnum { + // NOTE: Wake lock levels were previously defined as a bit field, except + // that only a few combinations were actually supported so the bit field + // was removed. This explains why the numbering scheme is so odd. If + // adding a new wake lock level, any unused value can be used. + + // Ensures that the CPU is running; the screen and keyboard backlight + // will be allowed to go off. + PARTIAL_WAKE_LOCK = 1; + + // Ensures that the screen is on (but may be dimmed); the keyboard + // backlight will be allowed to go off. If the user presses the power + // button, then the SCREEN_DIM_WAKE_LOCK will be implicitly released by + // the system, causing both the screen and the CPU to be turned off. + SCREEN_DIM_WAKE_LOCK = 6 [deprecated = true]; + + // Ensures that the screen is on at full brightness; the keyboard + // backlight will be allowed to go off. If the user presses the power + // button, then the SCREEN_BRIGHT_WAKE_LOCK will be implicitly released + // by the system, causing both the screen and the CPU to be turned off. + SCREEN_BRIGHT_WAKE_LOCK = 10 [deprecated = true]; + + // Ensures that the screen and keyboard backlight are on at full + // brightness. If the user presses the power button, then the + // FULL_WAKE_LOCK will be implicitly released by the system, causing + // both the screen and the CPU to be turned off. + FULL_WAKE_LOCK = 26 [deprecated = true]; + + // Turns the screen off when the proximity sensor activates. If the + // proximity sensor detects that an object is nearby, the screen turns + // off immediately. Shortly after the object moves away, the screen + // turns on again. + // A proximity wake lock does not prevent the device from falling asleep + // unlike FULL_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK and + // SCREEN_DIM_WAKE_LOCK. If there is no user activity and no other wake + // locks are held, then the device will fall asleep (and lock) as usual. + // However, the device will not fall asleep while the screen has been + // turned off by the proximity sensor because it effectively counts as + // ongoing user activity. + PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; + + // Put the screen in a low power state and allow the CPU to suspend if + // no other wake locks are held. This is used by the dream manager to + // implement doze mode. It currently has no effect unless the power + // manager is in the dozing state. + DOZE_WAKE_LOCK = 64; + + // Keep the device awake enough to allow drawing to occur. This is used + // by the window manager to allow applications to draw while the system + // is dozing. It currently has no effect unless the power manager is in + // the dozing state. + DRAW_WAKE_LOCK = 128; +} diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 2752a7e07ff489945c8daf8c101eb55136d95bf5..3ec6f057af882b59eda3d7a9d60f7d6fa6d20009 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -21,7 +21,6 @@ option java_outer_classname = "IncidentProtoMetadata"; import "frameworks/base/core/proto/android/os/batterytype.proto"; import "frameworks/base/core/proto/android/os/cpufreq.proto"; import "frameworks/base/core/proto/android/os/cpuinfo.proto"; -import "frameworks/base/core/proto/android/os/incidentheader.proto"; import "frameworks/base/core/proto/android/os/kernelwake.proto"; import "frameworks/base/core/proto/android/os/pagetypeinfo.proto"; import "frameworks/base/core/proto/android/os/procrank.proto"; @@ -46,6 +45,7 @@ import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; import "frameworks/base/core/proto/android/util/event_log_tags.proto"; import "frameworks/base/core/proto/android/util/log.proto"; +import "frameworks/base/libs/incident/proto/android/os/header.proto"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; import "frameworks/base/libs/incident/proto/android/section.proto"; @@ -123,7 +123,7 @@ message IncidentProto { // Linux services optional Procrank procrank = 2000 [ - (section).type = SECTION_COMMAND, + (section).type = SECTION_NONE, // disable procrank until figure out permission (section).args = "/system/xbin/procrank" ]; @@ -226,7 +226,10 @@ message IncidentProto { (section).args = "activity --proto service" ]; - optional com.android.server.am.proto.ProcessProto amprocesses = 3015; + optional com.android.server.am.proto.ProcessesProto amprocesses = 3015 [ + (section).type = SECTION_DUMPSYS, + (section).args = "activity --proto processes" + ]; optional com.android.server.AlarmManagerServiceProto alarm = 3016 [ (section).type = SECTION_DUMPSYS, diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto index 7e5be9d39f6994c8399895057aa7c9450f5ee7ff..c296dab5a1efd1b0e2d3194a5151ffccf5926957 100644 --- a/core/proto/android/os/kernelwake.proto +++ b/core/proto/android/os/kernelwake.proto @@ -18,15 +18,21 @@ syntax = "proto2"; option java_multiple_files = true; option java_outer_classname = "WakeupSourcesProto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + package android.os; message KernelWakeSources { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Kernel records of what caused the application processor to wake up repeated WakeupSourceProto wakeup_sources = 1; } // Next Tag: 11 message WakeupSourceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Name of the event which triggers application processor optional string name = 1; diff --git a/core/proto/android/os/looper.proto b/core/proto/android/os/looper.proto index ef84bb15dd6f7e1154d7a2f754a8ee868e697c05..435c648d89d8d89033a2d41d682e70434c746449 100644 --- a/core/proto/android/os/looper.proto +++ b/core/proto/android/os/looper.proto @@ -20,9 +20,12 @@ package android.os; option java_multiple_files = true; import "frameworks/base/core/proto/android/os/messagequeue.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message LooperProto { - optional string thread_name = 1; + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string thread_name = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional int64 thread_id = 2; optional int32 identity_hash_code = 3; optional android.os.MessageQueueProto queue = 4; diff --git a/core/proto/android/os/message.proto b/core/proto/android/os/message.proto index 38e27a13eb5fe6c92ed8adbde148268b425cc524..048d031f72e09d21e5e8839b5da88497fdb77048 100644 --- a/core/proto/android/os/message.proto +++ b/core/proto/android/os/message.proto @@ -17,9 +17,12 @@ syntax = "proto2"; package android.os; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; option java_multiple_files = true; message MessageProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 when = 1; // Name of callback class. optional string callback = 2; @@ -29,7 +32,7 @@ message MessageProto { optional int32 arg1 = 4; optional int32 arg2 = 5; // String representation of an arbitrary object to send to the recipient. - optional string obj = 6; + optional string obj = 6 [ (.android.privacy).dest = DEST_EXPLICIT ]; // Name of target class. optional string target = 7; optional int32 barrier = 8; diff --git a/core/proto/android/os/messagequeue.proto b/core/proto/android/os/messagequeue.proto index 5d4bff0b11b7695d94481765ced9f023d3f426a2..4bfcb81d599617a1d6f23e7baced56c75d67be32 100644 --- a/core/proto/android/os/messagequeue.proto +++ b/core/proto/android/os/messagequeue.proto @@ -20,8 +20,11 @@ package android.os; option java_multiple_files = true; import "frameworks/base/core/proto/android/os/message.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message MessageQueueProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated android.os.MessageProto messages = 1; optional bool is_polling_locked = 2; optional bool is_quitting = 3; diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto index f82ea7672879a1969420b45f9ff69651fc48cbc2..b8f618b5d4c22c9139280c5697d9acb00514cf7b 100644 --- a/core/proto/android/os/pagetypeinfo.proto +++ b/core/proto/android/os/pagetypeinfo.proto @@ -18,6 +18,8 @@ syntax = "proto2"; option java_multiple_files = true; option java_outer_classname = "PageTypeInfoProto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + package android.os; /* @@ -36,6 +38,7 @@ package android.os; * Next tag: 5 */ message PageTypeInfo { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 page_block_order = 1; @@ -48,6 +51,7 @@ message PageTypeInfo { // Next tag: 5 message MigrateTypeProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 node = 1; @@ -61,6 +65,7 @@ message MigrateTypeProto { // Next tag: 9 message BlockProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 node = 1; diff --git a/core/proto/android/os/patternmatcher.proto b/core/proto/android/os/patternmatcher.proto index d30315b889ac83c3a980a0b7da9ce3d6de15a778..520f2f5b8542a93a15dfa44367b86737216c237b 100644 --- a/core/proto/android/os/patternmatcher.proto +++ b/core/proto/android/os/patternmatcher.proto @@ -16,10 +16,13 @@ syntax = "proto2"; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package android.os; message PatternMatcherProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + optional string pattern = 1; enum Type { diff --git a/core/proto/android/os/persistablebundle.proto b/core/proto/android/os/persistablebundle.proto index 75ff78718cbc070007797e7faaf4594d69db068d..712f87c9b1473c8c8fa761f7def5d7d68a7aa365 100644 --- a/core/proto/android/os/persistablebundle.proto +++ b/core/proto/android/os/persistablebundle.proto @@ -19,10 +19,14 @@ package android.os; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + // An android.os.PersistableBundle object. message PersistableBundleProto { oneof data { - int32 parcelled_data_size = 1; - string map_data = 2; + int32 parcelled_data_size = 1 [ + (.android.privacy).dest = DEST_AUTOMATIC + ]; + string map_data = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; } } diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto index e9f409d0d75d18d120a8d0f74bb5ef97dd3b3bfd..78a28ed4a0e0737c36a1b123853fd5ace629dac5 100644 --- a/core/proto/android/os/powermanager.proto +++ b/core/proto/android/os/powermanager.proto @@ -19,6 +19,9 @@ package android.os; option java_multiple_files = true; +import "frameworks/base/core/proto/android/os/worksource.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message PowerManagerProto { /* User activity events in PowerManager.java. */ enum UserActivityEvent { @@ -32,58 +35,14 @@ message PowerManagerProto { USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; } - enum WakeLockLevel { - // NOTE: Wake lock levels were previously defined as a bit field, except - // that only a few combinations were actually supported so the bit field - // was removed. This explains why the numbering scheme is so odd. If - // adding a new wake lock level, any unused value can be used. - - // Ensures that the CPU is running; the screen and keyboard backlight - // will be allowed to go off. - PARTIAL_WAKE_LOCK = 1; - - // Ensures that the screen is on (but may be dimmed); the keyboard - // backlight will be allowed to go off. If the user presses the power - // button, then the SCREEN_DIM_WAKE_LOCK will be implicitly released by - // the system, causing both the screen and the CPU to be turned off. - SCREEN_DIM_WAKE_LOCK = 6 [deprecated = true]; - - // Ensures that the screen is on at full brightness; the keyboard - // backlight will be allowed to go off. If the user presses the power - // button, then the SCREEN_BRIGHT_WAKE_LOCK will be implicitly released - // by the system, causing both the screen and the CPU to be turned off. - SCREEN_BRIGHT_WAKE_LOCK = 10 [deprecated = true]; - - // Ensures that the screen and keyboard backlight are on at full - // brightness. If the user presses the power button, then the - // FULL_WAKE_LOCK will be implicitly released by the system, causing - // both the screen and the CPU to be turned off. - FULL_WAKE_LOCK = 26 [deprecated = true]; - - // Turns the screen off when the proximity sensor activates. If the - // proximity sensor detects that an object is nearby, the screen turns - // off immediately. Shortly after the object moves away, the screen - // turns on again. - // A proximity wake lock does not prevent the device from falling asleep - // unlike FULL_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK and - // SCREEN_DIM_WAKE_LOCK. If there is no user activity and no other wake - // locks are held, then the device will fall asleep (and lock) as usual. - // However, the device will not fall asleep while the screen has been - // turned off by the proximity sensor because it effectively counts as - // ongoing user activity. - PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; - - // Put the screen in a low power state and allow the CPU to suspend if - // no other wake locks are held. This is used by the dream manager to - // implement doze mode. It currently has no effect unless the power - // manager is in the dozing state. - DOZE_WAKE_LOCK = 64; + // WakeLock class in android.os.PowerManager, it is the one used by sdk + message WakeLockProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; - // Keep the device awake enough to allow drawing to occur. This is used - // by the window manager to allow applications to draw while the system - // is dozing. It currently has no effect unless the power manager is in - // the dozing state. - DRAW_WAKE_LOCK = 128; + optional string hex_string = 1; + optional bool held = 2; + optional int32 internal_count = 3; + optional WorkSourceProto work_source = 4; } } diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto index 88c6609a92b6aaa2921cf5901eba32f1b59c9b99..9cce7274d000810526839174e33bb8d3869b04e9 100644 --- a/core/proto/android/os/ps.proto +++ b/core/proto/android/os/ps.proto @@ -20,10 +20,17 @@ package android.os; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message PsDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + message Process { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Security label, most commonly used for SELinux context data. optional string label = 1; + // String representation of uid. optional string user = 2; // Process ID number. optional int32 pid = 3; diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto index 07b9ad00dc5fea6589b1987da50df6f6d81772d6..694b94b6fd2e003ec37807dfabf98fa31a9d608a 100644 --- a/core/proto/android/os/system_properties.proto +++ b/core/proto/android/os/system_properties.proto @@ -134,6 +134,8 @@ message SystemPropertiesProto { optional bool hal_instrumentation_enable = 11; message InitSvc { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Status { STATUS_UNKNOWN = 0; STATUS_RUNNING = 1; @@ -230,7 +232,7 @@ message SystemPropertiesProto { // Read only properites on the device. message Ro { - optional bool adb_secure = 1; + optional bool adb_secure = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional string arch = 2; optional bool audio_ignore_effects = 3; optional bool audio_monitorRotation = 4; @@ -265,6 +267,8 @@ message SystemPropertiesProto { // boot.img's properties. message BootImage { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // When the boot.img is built. optional string build_date = 1; // UTC timestamp of build date. @@ -278,12 +282,14 @@ message SystemPropertiesProto { optional BootImage bootimage = 8; // Version of bootloader on device. - optional string bootloader = 9; + optional string bootloader = 9 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Kernel bootmode, e.g. charger. - optional string bootmode = 10; + optional string bootmode = 10 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Android Platform build metadata. message Build { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string date = 1; optional int64 date_utc = 2; optional string description = 3; @@ -297,6 +303,8 @@ message SystemPropertiesProto { optional string user = 11; message Version { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string base_os = 1; optional string codename = 2; optional string incremental = 3; @@ -313,10 +321,10 @@ message SystemPropertiesProto { } optional Build build = 11; - optional bool camera_notify_nfc = 12; + optional bool camera_notify_nfc = 12 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional string carrier = 13; - optional bool com_android_dataroaming = 14; - optional bool com_android_prov_mobiledata = 15; + optional bool com_android_dataroaming = 14 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional bool com_android_prov_mobiledata = 15 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional string com_google_clientidbase = 16; message Config { @@ -341,6 +349,8 @@ message SystemPropertiesProto { optional string gfx_driver_0 = 26; message Hardware { + option (android.msg_privacy).dest = DEST_LOCAL; + optional string value = 1; // value of ro.hardware itself optional string activity_recognition = 2; @@ -392,6 +402,8 @@ message SystemPropertiesProto { optional int32 opengles_version = 31; message Product { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string board = 1; optional string brand = 2; optional string cpu_abi = 3; @@ -405,6 +417,8 @@ message SystemPropertiesProto { optional string name = 11; message Vendor { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string brand = 1; optional string device = 2; optional string manufacturer = 3; diff --git a/core/proto/android/os/worksource.proto b/core/proto/android/os/worksource.proto index 2f8b2fbbaa2bf201c40b1d1831eba950eb88307f..0a9c2ed6e2591d0682ef494e9053214f3961ed80 100644 --- a/core/proto/android/os/worksource.proto +++ b/core/proto/android/os/worksource.proto @@ -17,15 +17,23 @@ syntax = "proto2"; package android.os; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; message WorkSourceProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message WorkSourceContentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string name = 2; } message WorkChain { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated WorkSourceContentProto nodes = 1; } diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index d4bdb9b577dc0b3a61db6ed461a9622a8de64202..bfd575a5b18d6502802b4e036b6933ec85e5cddb 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -20,7 +20,11 @@ package android.providers.settings; option java_multiple_files = true; option java_outer_classname = "SettingsServiceProto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message SettingsServiceDumpProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + // Per user settings repeated UserSettingsProto user_settings = 1; @@ -29,8 +33,10 @@ message SettingsServiceDumpProto { } message UserSettingsProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + // Should be 0, 10, 11, 12, etc. where 0 is the owner. - optional int32 user_id = 1; + optional int32 user_id = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; // The secure settings for this user optional SecureSettingsProto secure_settings = 2; @@ -42,246 +48,266 @@ message UserSettingsProto { // Note: it's a conscious decision to add each setting as a separate field. This // allows annotating each setting with its own privacy tag. message GlobalSettingsProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + repeated SettingsOperationProto historical_operations = 1; - optional SettingProto add_users_when_locked = 2; - optional SettingProto enable_accessibility_global_gesture_enabled = 3; - optional SettingProto airplane_mode_on = 4; - optional SettingProto theater_mode_on = 5; - optional SettingProto radio_bluetooth = 6; - optional SettingProto radio_wifi = 7; - optional SettingProto radio_wimax = 8; - optional SettingProto radio_cell = 9; - optional SettingProto radio_nfc = 10; - optional SettingProto airplane_mode_radios = 11; - optional SettingProto airplane_mode_toggleable_radios = 12; - optional SettingProto bluetooth_class_of_device = 293; + optional SettingProto add_users_when_locked = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_accessibility_global_gesture_enabled = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto airplane_mode_on = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto theater_mode_on = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; + reserved 6,7,8,9,10; // Accidentally used. They are currently free to be reused. + // A comma-separated list of radios that need to be disabled when airplane + // mode is on. This overrides wifi_on and bluetooth_on if wifi and bluetooth + // are included in the comma-separated list. + optional SettingProto airplane_mode_radios = 11 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto airplane_mode_toggleable_radios = 12 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_class_of_device = 293 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto bluetooth_disabled_profiles = 13; optional SettingProto bluetooth_interoperability_list = 14; - optional SettingProto wifi_sleep_policy = 15; - optional SettingProto auto_time = 16; - optional SettingProto auto_time_zone = 17; + optional SettingProto wifi_sleep_policy = 15 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto auto_time = 16 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto auto_time_zone = 17 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto car_dock_sound = 18; optional SettingProto car_undock_sound = 19; optional SettingProto desk_dock_sound = 20; optional SettingProto desk_undock_sound = 21; - optional SettingProto dock_sounds_enabled = 22; - optional SettingProto dock_sounds_enabled_when_accessibility = 23; + optional SettingProto dock_sounds_enabled = 22 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dock_sounds_enabled_when_accessibility = 23 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto lock_sound = 24; optional SettingProto unlock_sound = 25; optional SettingProto trusted_sound = 26; optional SettingProto low_battery_sound = 27; - optional SettingProto power_sounds_enabled = 28; + optional SettingProto power_sounds_enabled = 28 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto wireless_charging_started_sound = 29; - optional SettingProto charging_sounds_enabled = 30; - optional SettingProto stay_on_while_plugged_in = 31; - optional SettingProto bugreport_in_power_menu = 32; - optional SettingProto adb_enabled = 33; - optional SettingProto debug_view_attributes = 34; - optional SettingProto assisted_gps_enabled = 35; - optional SettingProto bluetooth_on = 36; - optional SettingProto cdma_cell_broadcast_sms = 37; - optional SettingProto cdma_roaming_mode = 38; - optional SettingProto cdma_subscription_mode = 39; - optional SettingProto data_activity_timeout_mobile = 40; - optional SettingProto data_activity_timeout_wifi = 41; - optional SettingProto data_roaming = 42; - optional SettingProto mdc_initial_max_retry = 43; - optional SettingProto force_allow_on_external = 44; - optional SettingProto euicc_provisioned = 294; - optional SettingProto development_force_resizable_activities = 45; - optional SettingProto development_enable_freeform_windows_support = 46; - optional SettingProto development_settings_enabled = 47; - optional SettingProto device_provisioned = 48; - optional SettingProto device_provisioning_mobile_data_enabled = 49; - optional SettingProto display_size_forced = 50; - optional SettingProto display_scaling_force = 51; - optional SettingProto download_max_bytes_over_mobile = 52; - optional SettingProto download_recommended_max_bytes_over_mobile = 53; - optional SettingProto hdmi_control_enabled = 54; - optional SettingProto hdmi_system_audio_control_enabled = 55; - optional SettingProto hdmi_control_auto_wakeup_enabled = 56; - optional SettingProto hdmi_control_auto_device_off_enabled = 57; - optional SettingProto location_background_throttle_interval_ms = 295; - optional SettingProto location_background_throttle_proximity_alert_interval_ms = 296; - optional SettingProto location_background_throttle_package_whitelist = 297; - optional SettingProto wifi_scan_background_throttle_interval_ms = 298; - optional SettingProto wifi_scan_background_throttle_package_whitelist = 299; - optional SettingProto mhl_input_switching_enabled = 58; - optional SettingProto mhl_power_charge_enabled = 59; - optional SettingProto mobile_data = 60; - optional SettingProto mobile_data_always_on = 61; - optional SettingProto connectivity_metrics_buffer_size = 62; - optional SettingProto netstats_enabled = 63; - optional SettingProto netstats_poll_interval = 64; - optional SettingProto netstats_time_cache_max_age = 65; - optional SettingProto netstats_global_alert_bytes = 66; - optional SettingProto netstats_sample_enabled = 67; - optional SettingProto netstats_augment_enabled = 300; - optional SettingProto netstats_dev_bucket_duration = 68; - optional SettingProto netstats_dev_persist_bytes = 69; - optional SettingProto netstats_dev_rotate_age = 70; - optional SettingProto netstats_dev_delete_age = 71; - optional SettingProto netstats_uid_bucket_duration = 72; - optional SettingProto netstats_uid_persist_bytes = 73; - optional SettingProto netstats_uid_rotate_age = 74; - optional SettingProto netstats_uid_delete_age = 75; - optional SettingProto netstats_uid_tag_bucket_duration = 76; - optional SettingProto netstats_uid_tag_persist_bytes = 77; - optional SettingProto netstats_uid_tag_rotate_age = 78; - optional SettingProto netstats_uid_tag_delete_age = 79; + optional SettingProto charging_sounds_enabled = 30 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto stay_on_while_plugged_in = 31 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bugreport_in_power_menu = 32 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto adb_enabled = 33 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Whether views are allowed to save their attribute data. + optional SettingProto debug_view_attributes = 34 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assisted_gps_enabled = 35 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_on = 36 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto cdma_cell_broadcast_sms = 37 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto cdma_roaming_mode = 38 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto cdma_subscription_mode = 39 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto data_activity_timeout_mobile = 40 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto data_activity_timeout_wifi = 41 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto data_roaming = 42 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mdc_initial_max_retry = 43 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto force_allow_on_external = 44 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto euicc_provisioned = 294 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto development_force_resizable_activities = 45 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto development_enable_freeform_windows_support = 46 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto development_settings_enabled = 47 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto device_provisioned = 48 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto device_provisioning_mobile_data_enabled = 49 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto display_size_forced = 50 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto display_scaling_force = 51 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto download_max_bytes_over_mobile = 52 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto download_recommended_max_bytes_over_mobile = 53 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto hdmi_control_enabled = 54 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto hdmi_system_audio_control_enabled = 55 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto hdmi_control_auto_wakeup_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto hdmi_control_auto_device_off_enabled = 57 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto location_background_throttle_interval_ms = 295 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto location_background_throttle_proximity_alert_interval_ms = 296 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Packages that are whitelisted for background throttling (throttling will + // not be applied). + optional SettingProto location_background_throttle_package_whitelist = 297 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_scan_background_throttle_interval_ms = 298 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_scan_background_throttle_package_whitelist = 299 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mhl_input_switching_enabled = 58 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mhl_power_charge_enabled = 59 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mobile_data = 60 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mobile_data_always_on = 61 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto connectivity_metrics_buffer_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_enabled = 63 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_poll_interval = 64 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_time_cache_max_age = 65 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_global_alert_bytes = 66 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_sample_enabled = 67 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_augment_enabled = 300 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_dev_bucket_duration = 68 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_dev_persist_bytes = 69 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_dev_rotate_age = 70 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_dev_delete_age = 71 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_bucket_duration = 72 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_persist_bytes = 73 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_rotate_age = 74 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_delete_age = 75 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_tag_bucket_duration = 76 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_tag_persist_bytes = 77 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_tag_rotate_age = 78 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto netstats_uid_tag_delete_age = 79 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // User preference for which network(s) should be used. optional SettingProto network_preference = 80; - optional SettingProto network_scorer_app = 81; - optional SettingProto nitz_update_diff = 82; - optional SettingProto nitz_update_spacing = 83; + optional SettingProto network_scorer_app = 81 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto nitz_update_diff = 82 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto nitz_update_spacing = 83 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto ntp_server = 84; - optional SettingProto ntp_timeout = 85; - optional SettingProto storage_benchmark_interval = 86; - optional SettingProto dns_resolver_sample_validity_seconds = 87; - optional SettingProto dns_resolver_success_threshold_percent = 88; - optional SettingProto dns_resolver_min_samples = 89; - optional SettingProto dns_resolver_max_samples = 90; - optional SettingProto ota_disable_automatic_update = 91; - optional SettingProto package_verifier_enable = 92; - optional SettingProto package_verifier_timeout = 93; + optional SettingProto ntp_timeout = 85 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto storage_benchmark_interval = 86 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dns_resolver_sample_validity_seconds = 87 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dns_resolver_success_threshold_percent = 88 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dns_resolver_min_samples = 89 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dns_resolver_max_samples = 90 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Whether to disable the automatic scheduling of system updates. + optional SettingProto ota_disable_automatic_update = 91 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto package_verifier_enable = 92 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto package_verifier_timeout = 93 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto package_verifier_default_response = 94; - optional SettingProto package_verifier_setting_visible = 95; - optional SettingProto package_verifier_include_adb = 96; - optional SettingProto fstrim_mandatory_interval = 97; - optional SettingProto pdp_watchdog_poll_interval_ms = 98; - optional SettingProto pdp_watchdog_long_poll_interval_ms = 99; - optional SettingProto pdp_watchdog_error_poll_interval_ms = 100; - optional SettingProto pdp_watchdog_trigger_packet_count = 101; - optional SettingProto pdp_watchdog_error_poll_count = 102; - optional SettingProto pdp_watchdog_max_pdp_reset_fail_count = 103; + optional SettingProto package_verifier_setting_visible = 95 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto package_verifier_include_adb = 96 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto fstrim_mandatory_interval = 97 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pdp_watchdog_poll_interval_ms = 98 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pdp_watchdog_long_poll_interval_ms = 99 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pdp_watchdog_error_poll_interval_ms = 100 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pdp_watchdog_trigger_packet_count = 101 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pdp_watchdog_error_poll_count = 102 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pdp_watchdog_max_pdp_reset_fail_count = 103 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto setup_prepaid_data_service_url = 105; optional SettingProto setup_prepaid_detection_target_url = 106; optional SettingProto setup_prepaid_detection_redir_host = 107; - optional SettingProto sms_outgoing_check_interval_ms = 108; - optional SettingProto sms_outgoing_check_max_count = 109; - optional SettingProto sms_short_code_confirmation = 110; - optional SettingProto sms_short_code_rule = 111; - optional SettingProto tcp_default_init_rwnd = 112; - optional SettingProto tether_supported = 113; - optional SettingProto tether_dun_required = 114; + optional SettingProto sms_outgoing_check_interval_ms = 108 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sms_outgoing_check_max_count = 109 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Used to disable SMS short code confirmation. Defaults to true. + optional SettingProto sms_short_code_confirmation = 110 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sms_short_code_rule = 111 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tcp_default_init_rwnd = 112 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tether_supported = 113 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tether_dun_required = 114 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto tether_dun_apn = 115; - optional SettingProto tether_offload_disabled = 301; - optional SettingProto carrier_app_whitelist = 116; - optional SettingProto usb_mass_storage_enabled = 117; - optional SettingProto use_google_mail = 118; + optional SettingProto tether_offload_disabled = 301 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // List of carrier apps which are whitelisted to prompt the user for install + // when a SIM card with marchin UICC carrier privilege rules is inserted. + optional SettingProto carrier_app_whitelist = 116 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto usb_mass_storage_enabled = 117 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto use_google_mail = 118 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto webview_data_reduction_proxy_key = 119; - optional SettingProto webview_fallback_logic_enabled = 120; - optional SettingProto webview_provider = 121; - optional SettingProto webview_multiprocess = 122; - optional SettingProto network_switch_notification_daily_limit = 123; - optional SettingProto network_switch_notification_rate_limit_millis = 124; - optional SettingProto network_avoid_bad_wifi = 125; - optional SettingProto network_metered_multipath_preference = 302; - optional SettingProto network_watchlist_last_report_time = 303; - optional SettingProto wifi_badging_thresholds = 304; - optional SettingProto wifi_display_on = 126; - optional SettingProto wifi_display_certification_on = 127; - optional SettingProto wifi_display_wps_config = 128; - optional SettingProto wifi_networks_available_notification_on = 129; - optional SettingProto wimax_networks_available_notification_on = 130; - optional SettingProto wifi_networks_available_repeat_delay = 131; + optional SettingProto webview_fallback_logic_enabled = 120 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Name of the package used as WebView provider. + optional SettingProto webview_provider = 121 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto webview_multiprocess = 122 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_switch_notification_daily_limit = 123 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_switch_notification_rate_limit_millis = 124 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_avoid_bad_wifi = 125 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_metered_multipath_preference = 302 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_watchlist_last_report_time = 303 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_badging_thresholds = 304 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_display_on = 126 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_display_certification_on = 127 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_display_wps_config = 128 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_networks_available_notification_on = 129 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wimax_networks_available_notification_on = 130 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_networks_available_repeat_delay = 131 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto wifi_country_code = 132; - optional SettingProto wifi_framework_scan_interval_ms = 133; - optional SettingProto wifi_idle_ms = 134; - optional SettingProto wifi_num_open_networks_kept = 135; - optional SettingProto wifi_on = 136; - optional SettingProto wifi_scan_always_available = 137; - optional SettingProto wifi_wakeup_enabled = 138; - optional SettingProto wifi_wakeup_available = 305; - optional SettingProto network_scoring_ui_enabled = 306; - optional SettingProto speed_label_cache_eviction_age_millis = 307; - optional SettingProto recommended_network_evaluator_cache_expiry_ms = 308; - optional SettingProto network_recommendations_enabled = 139; - optional SettingProto network_recommendations_package = 286; - optional SettingProto use_open_wifi_package = 309; - optional SettingProto network_recommendation_request_timeout_ms = 310; - optional SettingProto ble_scan_always_available = 140; - optional SettingProto wifi_saved_state = 141; - optional SettingProto wifi_supplicant_scan_interval_ms = 142; - optional SettingProto wifi_enhanced_auto_join = 143; - optional SettingProto wifi_network_show_rssi = 144; - optional SettingProto wifi_scan_interval_when_p2p_connected_ms = 145; - optional SettingProto wifi_watchdog_on = 146; - optional SettingProto wifi_watchdog_poor_network_test_enabled = 147; - optional SettingProto wifi_suspend_optimizations_enabled = 148; - optional SettingProto wifi_verbose_logging_enabled = 149; - optional SettingProto wifi_max_dhcp_retry_count = 150; - optional SettingProto wifi_mobile_data_transition_wakelock_timeout_ms = 151; - optional SettingProto wifi_device_owner_configs_lockdown = 152; - optional SettingProto wifi_frequency_band = 153; + optional SettingProto wifi_framework_scan_interval_ms = 133 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_idle_ms = 134 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_num_open_networks_kept = 135 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_on = 136 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_scan_always_available = 137 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_wakeup_enabled = 138 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_wakeup_available = 305 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_scoring_ui_enabled = 306 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto speed_label_cache_eviction_age_millis = 307 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto recommended_network_evaluator_cache_expiry_ms = 308 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_recommendations_enabled = 139 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_recommendations_package = 286 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto use_open_wifi_package = 309 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_recommendation_request_timeout_ms = 310 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto ble_scan_always_available = 140 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_saved_state = 141 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_supplicant_scan_interval_ms = 142 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_enhanced_auto_join = 143 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_network_show_rssi = 144 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_scan_interval_when_p2p_connected_ms = 145 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_watchdog_on = 146 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_watchdog_poor_network_test_enabled = 147 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_suspend_optimizations_enabled = 148 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_verbose_logging_enabled = 149 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_connected_mac_randomization_enabled = 350 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_max_dhcp_retry_count = 150 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_mobile_data_transition_wakelock_timeout_ms = 151 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_device_owner_configs_lockdown = 152 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_frequency_band = 153 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto wifi_p2p_device_name = 154; - optional SettingProto wifi_reenable_delay_ms = 155; - optional SettingProto wifi_ephemeral_out_of_range_timeout_ms = 156; - optional SettingProto data_stall_alarm_non_aggressive_delay_in_ms = 157; - optional SettingProto data_stall_alarm_aggressive_delay_in_ms = 158; - optional SettingProto provisioning_apn_alarm_delay_in_ms = 159; - optional SettingProto gprs_register_check_period_ms = 160; - optional SettingProto wtf_is_fatal = 161; - optional SettingProto mode_ringer = 162; - optional SettingProto overlay_display_devices = 163; - optional SettingProto battery_discharge_duration_threshold = 164; - optional SettingProto battery_discharge_threshold = 165; - optional SettingProto send_action_app_error = 166; - optional SettingProto dropbox_age_seconds = 167; - optional SettingProto dropbox_max_files = 168; - optional SettingProto dropbox_quota_kb = 169; - optional SettingProto dropbox_quota_percent = 170; - optional SettingProto dropbox_reserve_percent = 171; - optional SettingProto dropbox_tag_prefix = 172; - optional SettingProto error_logcat_prefix = 173; - optional SettingProto sys_free_storage_log_interval = 174; - optional SettingProto disk_free_change_reporting_threshold = 175; - optional SettingProto sys_storage_threshold_percentage = 176; - optional SettingProto sys_storage_threshold_max_bytes = 177; - optional SettingProto sys_storage_full_threshold_bytes = 178; - optional SettingProto sys_storage_cache_percentage = 311; - optional SettingProto sys_storage_cache_max_bytes = 312; - optional SettingProto sync_max_retry_delay_in_seconds = 179; - optional SettingProto connectivity_change_delay = 180; - optional SettingProto connectivity_sampling_interval_in_seconds = 181; - optional SettingProto pac_change_delay = 182; - optional SettingProto captive_portal_mode = 183; - optional SettingProto captive_portal_detection_enabled = 313; + optional SettingProto wifi_reenable_delay_ms = 155 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_ephemeral_out_of_range_timeout_ms = 156 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto data_stall_alarm_non_aggressive_delay_in_ms = 157 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto data_stall_alarm_aggressive_delay_in_ms = 158 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto provisioning_apn_alarm_delay_in_ms = 159 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto gprs_register_check_period_ms = 160 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wtf_is_fatal = 161 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Ringer mode. A change in this value will not reflect as a change in the + // ringer mode. + optional SettingProto mode_ringer = 162 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Overlay display devices setting. + // The value is a specially formatted string that describes the size and + // density of simulated secondary devices. + // Format: {width}x{height}/dpi;... + optional SettingProto overlay_display_devices = 163 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto battery_discharge_duration_threshold = 164 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto battery_discharge_threshold = 165 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto send_action_app_error = 166 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dropbox_age_seconds = 167 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dropbox_max_files = 168 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dropbox_quota_kb = 169 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dropbox_quota_percent = 170 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dropbox_reserve_percent = 171 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dropbox_tag_prefix = 172 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto error_logcat_prefix = 173 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sys_free_storage_log_interval = 174 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto disk_free_change_reporting_threshold = 175 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sys_storage_threshold_percentage = 176 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sys_storage_threshold_max_bytes = 177 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sys_storage_full_threshold_bytes = 178 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sys_storage_cache_percentage = 311 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sys_storage_cache_max_bytes = 312 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sync_max_retry_delay_in_seconds = 179 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto connectivity_change_delay = 180 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto connectivity_sampling_interval_in_seconds = 181 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pac_change_delay = 182 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto captive_portal_mode = 183 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto captive_portal_detection_enabled = 313 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto captive_portal_server = 184; optional SettingProto captive_portal_https_url = 185; optional SettingProto captive_portal_http_url = 186; optional SettingProto captive_portal_fallback_url = 187; optional SettingProto captive_portal_other_fallback_urls = 314; - optional SettingProto captive_portal_use_https = 188; + optional SettingProto captive_portal_use_https = 188 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto captive_portal_user_agent = 189; - optional SettingProto nsd_on = 190; - optional SettingProto set_install_location = 191; - optional SettingProto default_install_location = 192; - optional SettingProto inet_condition_debounce_up_delay = 193; - optional SettingProto inet_condition_debounce_down_delay = 194; - optional SettingProto read_external_storage_enforced_default = 195; + optional SettingProto nsd_on = 190 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Let user pick default install location. + optional SettingProto set_install_location = 191 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto default_install_location = 192 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto inet_condition_debounce_up_delay = 193 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto inet_condition_debounce_down_delay = 194 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto read_external_storage_enforced_default = 195 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto http_proxy = 196; optional SettingProto global_http_proxy_host = 197; optional SettingProto global_http_proxy_port = 198; optional SettingProto global_http_proxy_exclusion_list = 199; optional SettingProto global_http_proxy_pac = 200; - optional SettingProto set_global_http_proxy = 201; + // Enables the UI setting to allow the user to specify the global HTTP proxy + // and associated exclusion list. + optional SettingProto set_global_http_proxy = 201 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto default_dns_server = 202; // The requested Private DNS mode and an accompanying specifier. optional SettingProto private_dns_mode = 315; optional SettingProto private_dns_specifier = 316; - optional SettingProto bluetooth_headset_priority_prefix = 203; - optional SettingProto bluetooth_a2dp_sink_priority_prefix = 204; - optional SettingProto bluetooth_a2dp_src_priority_prefix = 205; - optional SettingProto bluetooth_a2dp_supports_optional_codecs_prefix = 287; - optional SettingProto bluetooth_a2dp_optional_codecs_enabled_prefix = 288; - optional SettingProto bluetooth_input_device_priority_prefix = 206; - optional SettingProto bluetooth_map_priority_prefix = 207; - optional SettingProto bluetooth_map_client_priority_prefix = 208; - optional SettingProto bluetooth_pbap_client_priority_prefix = 209; - optional SettingProto bluetooth_sap_priority_prefix = 210; - optional SettingProto bluetooth_pan_priority_prefix = 211; - optional SettingProto bluetooth_hearing_aid_priority_prefix = 345; + optional SettingProto bluetooth_headset_priority_prefix = 203 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_a2dp_sink_priority_prefix = 204 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_a2dp_src_priority_prefix = 205 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_a2dp_supports_optional_codecs_prefix = 287 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_a2dp_optional_codecs_enabled_prefix = 288 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_input_device_priority_prefix = 206 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_map_priority_prefix = 207 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_map_client_priority_prefix = 208 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_pbap_client_priority_prefix = 209 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_sap_priority_prefix = 210 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_pan_priority_prefix = 211 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_hearing_aid_priority_prefix = 345 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto activity_manager_constants = 317; optional SettingProto device_idle_constants = 212; optional SettingProto device_idle_constants_watch = 213; @@ -295,26 +321,28 @@ message GlobalSettingsProto { optional SettingProto shortcut_manager_constants = 217; optional SettingProto device_policy_constants = 322; optional SettingProto text_classifier_constants = 323; - optional SettingProto window_animation_scale = 218; - optional SettingProto transition_animation_scale = 219; - optional SettingProto animator_duration_scale = 220; - optional SettingProto fancy_ime_animations = 221; - optional SettingProto compatibility_mode = 222; - optional SettingProto emergency_tone = 223; - optional SettingProto call_auto_retry = 224; - optional SettingProto emergency_affordance_needed = 225; - optional SettingProto preferred_network_mode = 226; + optional SettingProto window_animation_scale = 218 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto transition_animation_scale = 219 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto animator_duration_scale = 220 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto fancy_ime_animations = 221 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto compatibility_mode = 222 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto emergency_tone = 223 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto call_auto_retry = 224 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto emergency_affordance_needed = 225 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto preferred_network_mode = 226 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Name of an application package to be debugged. optional SettingProto debug_app = 227; - optional SettingProto wait_for_debugger = 228; - optional SettingProto enable_gpu_debug_layers = 342; + optional SettingProto wait_for_debugger = 228 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_gpu_debug_layers = 342 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // App allowed to load GPU debug layers. optional SettingProto gpu_debug_app = 343; - optional SettingProto gpu_debug_layers = 344; - optional SettingProto low_power_mode = 229; - optional SettingProto low_power_mode_trigger_level = 230; - optional SettingProto always_finish_activities = 231; - optional SettingProto dock_audio_media_enabled = 232; - optional SettingProto encoded_surround_output = 233; - optional SettingProto audio_safe_volume_state = 234; + optional SettingProto gpu_debug_layers = 344 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto low_power_mode = 229 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto low_power_mode_trigger_level = 230 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto always_finish_activities = 231 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dock_audio_media_enabled = 232 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto encoded_surround_output = 233 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto audio_safe_volume_state = 234 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto tzinfo_update_content_url = 235; optional SettingProto tzinfo_update_metadata_url = 236; optional SettingProto selinux_update_content_url = 237; @@ -331,300 +359,349 @@ message GlobalSettingsProto { optional SettingProto lang_id_update_metadata_url = 325; optional SettingProto smart_selection_update_content_url = 326; optional SettingProto smart_selection_update_metadata_url = 327; - optional SettingProto selinux_status = 247; - optional SettingProto development_force_rtl = 248; - optional SettingProto low_battery_sound_timeout = 249; - optional SettingProto wifi_bounce_delay_override_ms = 250; - optional SettingProto policy_control = 251; - optional SettingProto zen_mode = 252; - optional SettingProto zen_mode_ringer_level = 253; + optional SettingProto selinux_status = 247 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto development_force_rtl = 248 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto low_battery_sound_timeout = 249 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wifi_bounce_delay_override_ms = 250 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto policy_control = 251 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto zen_mode = 252 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto zen_mode_ringer_level = 253 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto zen_mode_config_etag = 254; - optional SettingProto heads_up_notifications_enabled = 255; + optional SettingProto heads_up_notifications_enabled = 255 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto device_name = 256; - optional SettingProto network_scoring_provisioned = 257; - optional SettingProto require_password_to_decrypt = 258; - optional SettingProto enhanced_4g_mode_enabled = 259; - optional SettingProto vt_ims_enabled = 260; - optional SettingProto wfc_ims_enabled = 261; - optional SettingProto wfc_ims_mode = 262; - optional SettingProto wfc_ims_roaming_mode = 263; - optional SettingProto wfc_ims_roaming_enabled = 264; - optional SettingProto lte_service_forced = 265; - optional SettingProto ephemeral_cookie_max_size_bytes = 266; - optional SettingProto enable_ephemeral_feature = 267; - optional SettingProto instant_app_dexopt_enabled = 328; - optional SettingProto installed_instant_app_min_cache_period = 268; - optional SettingProto installed_instant_app_max_cache_period = 289; - optional SettingProto uninstalled_instant_app_min_cache_period = 290; - optional SettingProto uninstalled_instant_app_max_cache_period = 291; - optional SettingProto unused_static_shared_lib_min_cache_period = 292; - optional SettingProto allow_user_switching_when_system_user_locked = 269; - optional SettingProto boot_count = 270; - optional SettingProto safe_boot_disallowed = 271; - optional SettingProto device_demo_mode = 272; - optional SettingProto network_access_timeout_ms = 329; + optional SettingProto network_scoring_provisioned = 257 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto require_password_to_decrypt = 258 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enhanced_4g_mode_enabled = 259 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto vt_ims_enabled = 260 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wfc_ims_enabled = 261 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wfc_ims_mode = 262 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wfc_ims_roaming_mode = 263 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wfc_ims_roaming_enabled = 264 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lte_service_forced = 265 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto ephemeral_cookie_max_size_bytes = 266 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_ephemeral_feature = 267 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto instant_app_dexopt_enabled = 328 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto installed_instant_app_min_cache_period = 268 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto installed_instant_app_max_cache_period = 289 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto uninstalled_instant_app_min_cache_period = 290 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto uninstalled_instant_app_max_cache_period = 291 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto unused_static_shared_lib_min_cache_period = 292 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto allow_user_switching_when_system_user_locked = 269 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto boot_count = 270 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto safe_boot_disallowed = 271 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto device_demo_mode = 272 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto network_access_timeout_ms = 329 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto database_downgrade_reason = 274; - optional SettingProto database_creation_buildid = 330; - optional SettingProto contacts_database_wal_enabled = 275; - optional SettingProto location_settings_link_to_permissions_enabled = 331; + optional SettingProto database_creation_buildid = 330 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto contacts_database_wal_enabled = 275 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto location_settings_link_to_permissions_enabled = 331 [ (android.privacy).dest = DEST_AUTOMATIC ]; reserved 332; // Removed backup_refactored_service_disabled - optional SettingProto euicc_factory_reset_timeout_millis = 333; - optional SettingProto storage_settings_clobber_threshold = 334; - optional SettingProto multi_sim_voice_call_subscription = 276; - optional SettingProto multi_sim_voice_prompt = 277; - optional SettingProto multi_sim_data_call_subscription = 278; - optional SettingProto multi_sim_sms_subscription = 279; - optional SettingProto multi_sim_sms_prompt = 280; - optional SettingProto new_contact_aggregator = 281; - optional SettingProto contact_metadata_sync_enabled = 282; - optional SettingProto enable_cellular_on_boot = 283; - optional SettingProto max_notification_enqueue_rate = 284; - optional SettingProto show_notification_channel_warnings = 335; - optional SettingProto cell_on = 285; - optional SettingProto show_temperature_warning = 336; - optional SettingProto warning_temperature = 337; - optional SettingProto enable_diskstats_logging = 338; - optional SettingProto enable_cache_quota_calculation = 339; - optional SettingProto enable_deletion_helper_no_threshold_toggle = 340; - optional SettingProto notification_snooze_options = 341; - optional SettingProto enable_gnss_raw_meas_full_tracking = 346; - optional SettingProto zram_enabled = 347; - - // Next tag = 348; + optional SettingProto euicc_factory_reset_timeout_millis = 333 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto storage_settings_clobber_threshold = 334 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto chained_battery_attribution_enabled = 353 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Subscription to be used for voice call on a multi sim device. The + // supported values are 0 = SUB1, 1 = SUB2 and etc. + optional SettingProto multi_sim_voice_call_subscription = 276 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto multi_sim_voice_prompt = 277 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto multi_sim_data_call_subscription = 278 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto multi_sim_sms_subscription = 279 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto multi_sim_sms_prompt = 280 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Whether to enable new contacts aggregator or not. + // 1 = enable, 0 = disable. + optional SettingProto new_contact_aggregator = 281 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto contact_metadata_sync_enabled = 282 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_cellular_on_boot = 283 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto max_notification_enqueue_rate = 284 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_notification_channel_warnings = 335 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto cell_on = 285 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_temperature_warning = 336 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto warning_temperature = 337 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_diskstats_logging = 338 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_cache_quota_calculation = 339 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_deletion_helper_no_threshold_toggle = 340 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto notification_snooze_options = 341 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_gnss_raw_meas_full_tracking = 346 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto zram_enabled = 347 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_smart_replies_in_notifications = 348 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_first_crash_dialog = 349 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_restart_in_crash_dialog = 351 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_mute_in_crash_dialog = 352 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + // Please insert fields in the same order as in + // frameworks/base/core/java/android/provider/Settings.java. + // Next tag = 354; } message SecureSettingsProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + repeated SettingsOperationProto historical_operations = 1; optional SettingProto android_id = 2; - optional SettingProto default_input_method = 3; - optional SettingProto selected_input_method_subtype = 4; - optional SettingProto input_methods_subtype_history = 5; - optional SettingProto input_method_selector_visibility = 6; - optional SettingProto voice_interaction_service = 7; - optional SettingProto autofill_service = 8; + optional SettingProto default_input_method = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto selected_input_method_subtype = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto input_methods_subtype_history = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto input_method_selector_visibility = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The currently selected voice interaction service flattened ComponentName. + optional SettingProto voice_interaction_service = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The currently selected autofill service flattened ComponentName. + optional SettingProto autofill_service = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto bluetooth_hci_log = 9; - optional SettingProto user_setup_complete = 10; + optional SettingProto user_setup_complete = 10 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Whether the current user has been set up via setup wizard (0 = false, // 1 = true). This value differs from USER_SETUP_COMPLETE in that it can be // reset back to 0 in case SetupWizard has been re-enabled on TV devices. - optional SettingProto tv_user_setup_complete = 170; - optional SettingProto completed_category_prefix = 11; - optional SettingProto enabled_input_methods = 12; - optional SettingProto disabled_system_input_methods = 13; - optional SettingProto show_ime_with_hard_keyboard = 14; + optional SettingProto tv_user_setup_complete = 170 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto completed_category_prefix = 11 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enabled_input_methods = 12 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto disabled_system_input_methods = 13 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_ime_with_hard_keyboard = 14 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto always_on_vpn_app = 15; - optional SettingProto always_on_vpn_lockdown = 16; - optional SettingProto install_non_market_apps = 17; - optional SettingProto unknown_sources_default_reversed = 171; - optional SettingProto location_mode = 18; + optional SettingProto always_on_vpn_lockdown = 16 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto install_non_market_apps = 17 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto unknown_sources_default_reversed = 171 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The degree of location access enabled by the user. + optional SettingProto location_mode = 18 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto location_previous_mode = 19; - optional SettingProto lock_to_app_exit_locked = 20; - optional SettingProto lock_screen_lock_after_timeout = 21; - optional SettingProto lock_screen_allow_private_notifications = 172; - optional SettingProto lock_screen_allow_remote_input = 22; - optional SettingProto show_note_about_notification_hiding = 23; - optional SettingProto trust_agents_initialized = 24; - optional SettingProto parental_control_enabled = 25; - optional SettingProto parental_control_last_update = 26; - optional SettingProto parental_control_redirect_url = 27; - optional SettingProto settings_classname = 28; - optional SettingProto accessibility_enabled = 29; - optional SettingProto accessibility_shortcut_enabled = 173; - optional SettingProto accessibility_shortcut_on_lock_screen = 174; - optional SettingProto accessibility_shortcut_dialog_shown = 175; - optional SettingProto accessibility_shortcut_target_service = 176; - optional SettingProto accessibility_button_target_component = 177; - optional SettingProto touch_exploration_enabled = 30; + // Whether lock-to-app will lock the keyguard when exiting. + optional SettingProto lock_to_app_exit_locked = 20 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lock_screen_lock_after_timeout = 21 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lock_screen_allow_private_notifications = 172 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lock_screen_allow_remote_input = 22 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_note_about_notification_hiding = 23 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto trust_agents_initialized = 24 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto parental_control_enabled = 25 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto parental_control_last_update = 26 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto parental_control_redirect_url = 27 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto settings_classname = 28 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_enabled = 29 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_shortcut_enabled = 173 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_shortcut_on_lock_screen = 174 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_shortcut_dialog_shown = 175 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_shortcut_target_service = 176 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Setting specifying the accessibility service or feature to be toggled via + // the accessibility button in the navigation bar. This is either a + // flattened ComponentName or the class name of a system class implementing + // a supported accessibility feature. + optional SettingProto accessibility_button_target_component = 177 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto touch_exploration_enabled = 30 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // List of the enabled accessibility providers. optional SettingProto enabled_accessibility_services = 31; + // List of the accessibility services to which the user has granted + // permission to put the device into touch exploration mode. optional SettingProto touch_exploration_granted_accessibility_services = 32; - optional SettingProto accessibility_speak_password = 33; - optional SettingProto accessibility_high_text_contrast_enabled = 34; + // Whether to speak passwords while in accessibility mode. + optional SettingProto accessibility_speak_password = 33 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_high_text_contrast_enabled = 34 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_script_injection = 35; optional SettingProto accessibility_screen_reader_url = 36; optional SettingProto accessibility_web_content_key_bindings = 37; - optional SettingProto accessibility_display_magnification_enabled = 38; - optional SettingProto accessibility_display_magnification_navbar_enabled = 178; - optional SettingProto accessibility_display_magnification_scale = 39; - optional SettingProto accessibility_display_magnification_auto_update = 179; - optional SettingProto accessibility_soft_keyboard_mode = 40; - optional SettingProto accessibility_captioning_enabled = 41; - optional SettingProto accessibility_captioning_locale = 42; - optional SettingProto accessibility_captioning_preset = 43; - optional SettingProto accessibility_captioning_background_color = 44; - optional SettingProto accessibility_captioning_foreground_color = 45; - optional SettingProto accessibility_captioning_edge_type = 46; - optional SettingProto accessibility_captioning_edge_color = 47; - optional SettingProto accessibility_captioning_window_color = 48; - optional SettingProto accessibility_captioning_typeface = 49; - optional SettingProto accessibility_captioning_font_scale = 50; - optional SettingProto accessibility_display_inversion_enabled = 51; - optional SettingProto accessibility_display_daltonizer_enabled = 52; - optional SettingProto accessibility_display_daltonizer = 53; - optional SettingProto accessibility_autoclick_enabled = 54; - optional SettingProto accessibility_autoclick_delay = 55; - optional SettingProto accessibility_large_pointer_icon = 56; - optional SettingProto long_press_timeout = 57; - optional SettingProto multi_press_timeout = 58; + optional SettingProto accessibility_display_magnification_enabled = 38 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_display_magnification_navbar_enabled = 178 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_display_magnification_scale = 39 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_display_magnification_auto_update = 179 [deprecated = true]; + optional SettingProto accessibility_soft_keyboard_mode = 40 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_enabled = 41 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_locale = 42 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_preset = 43 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_background_color = 44 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_foreground_color = 45 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_edge_type = 46 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_edge_color = 47 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_window_color = 48 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_typeface = 49 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_captioning_font_scale = 50 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_display_inversion_enabled = 51 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_display_daltonizer_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_display_daltonizer = 53 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_autoclick_enabled = 54 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_autoclick_delay = 55 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accessibility_large_pointer_icon = 56 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto long_press_timeout = 57 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto multi_press_timeout = 58 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto enabled_print_services = 59; optional SettingProto disabled_print_services = 60; - optional SettingProto display_density_forced = 61; - optional SettingProto tts_default_rate = 62; - optional SettingProto tts_default_pitch = 63; - optional SettingProto tts_default_synth = 64; - optional SettingProto tts_default_locale = 65; + optional SettingProto display_density_forced = 61 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tts_default_rate = 62 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tts_default_pitch = 63 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tts_default_synth = 64 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tts_default_locale = 65 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto tts_enabled_plugins = 66; - optional SettingProto connectivity_release_pending_intent_delay_ms = 67; + optional SettingProto connectivity_release_pending_intent_delay_ms = 67 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto allowed_geolocation_origins = 68; - optional SettingProto preferred_tty_mode = 69; - optional SettingProto enhanced_voice_privacy_enabled = 70; - optional SettingProto tty_mode_enabled = 71; - optional SettingProto backup_enabled = 72; - optional SettingProto backup_auto_restore = 73; - optional SettingProto backup_provisioned = 74; - optional SettingProto backup_transport = 75; - optional SettingProto last_setup_shown = 76; - optional SettingProto search_global_search_activity = 77; - optional SettingProto search_num_promoted_sources = 78; - optional SettingProto search_max_results_to_display = 79; - optional SettingProto search_max_results_per_source = 80; - optional SettingProto search_web_results_override_limit = 81; - optional SettingProto search_promoted_source_deadline_millis = 82; - optional SettingProto search_source_timeout_millis = 83; - optional SettingProto search_prefill_millis = 84; - optional SettingProto search_max_stat_age_millis = 85; - optional SettingProto search_max_source_event_age_millis = 86; - optional SettingProto search_min_impressions_for_source_ranking = 87; - optional SettingProto search_min_clicks_for_source_ranking = 88; - optional SettingProto search_max_shortcuts_returned = 89; - optional SettingProto search_query_thread_core_pool_size = 90; - optional SettingProto search_query_thread_max_pool_size = 91; - optional SettingProto search_shortcut_refresh_core_pool_size = 92; - optional SettingProto search_shortcut_refresh_max_pool_size = 93; - optional SettingProto search_thread_keepalive_seconds = 94; - optional SettingProto search_per_source_concurrent_query_limit = 95; - optional SettingProto mount_play_notification_snd = 96; - optional SettingProto mount_ums_autostart = 97; - optional SettingProto mount_ums_prompt = 98; - optional SettingProto mount_ums_notify_enabled = 99; - optional SettingProto anr_show_background = 100; - optional SettingProto voice_recognition_service = 101; - optional SettingProto package_verifier_user_consent = 102; - optional SettingProto selected_spell_checker = 103; - optional SettingProto selected_spell_checker_subtype = 104; - optional SettingProto spell_checker_enabled = 105; - optional SettingProto incall_power_button_behavior = 106; - optional SettingProto incall_back_button_behavior = 107; - optional SettingProto wake_gesture_enabled = 108; - optional SettingProto doze_enabled = 109; - optional SettingProto doze_always_on = 110; - optional SettingProto doze_pulse_on_pick_up = 111; - optional SettingProto doze_pulse_on_long_press = 180; - optional SettingProto doze_pulse_on_double_tap = 112; - optional SettingProto ui_night_mode = 113; - optional SettingProto screensaver_enabled = 114; - optional SettingProto screensaver_components = 115; - optional SettingProto screensaver_activate_on_dock = 116; - optional SettingProto screensaver_activate_on_sleep = 117; - optional SettingProto screensaver_default_component = 118; - optional SettingProto nfc_payment_default_component = 119; - optional SettingProto nfc_payment_foreground = 120; - optional SettingProto sms_default_application = 121; - optional SettingProto dialer_default_application = 122; - optional SettingProto emergency_assistance_application = 123; - optional SettingProto assist_structure_enabled = 124; - optional SettingProto assist_screenshot_enabled = 125; - optional SettingProto assist_disclosure_enabled = 126; - optional SettingProto enabled_notification_assistant = 127; - optional SettingProto enabled_notification_listeners = 128; - optional SettingProto enabled_notification_policy_access_packages = 129; - optional SettingProto sync_parent_sounds = 130; - optional SettingProto immersive_mode_confirmations = 131; - optional SettingProto print_service_search_uri = 132; - optional SettingProto payment_service_search_uri = 133; - optional SettingProto autofill_service_search_uri = 181; - optional SettingProto skip_first_use_hints = 134; - optional SettingProto unsafe_volume_music_active_ms = 135; - optional SettingProto lock_screen_show_notifications = 136; + optional SettingProto preferred_tty_mode = 69 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enhanced_voice_privacy_enabled = 70 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tty_mode_enabled = 71 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto backup_enabled = 72 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto backup_auto_restore = 73 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto backup_provisioned = 74 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto backup_transport = 75 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto last_setup_shown = 76 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_global_search_activity = 77 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_num_promoted_sources = 78 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_max_results_to_display = 79 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_max_results_per_source = 80 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_web_results_override_limit = 81 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_promoted_source_deadline_millis = 82 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_source_timeout_millis = 83 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_prefill_millis = 84 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_max_stat_age_millis = 85 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_max_source_event_age_millis = 86 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_min_impressions_for_source_ranking = 87 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_min_clicks_for_source_ranking = 88 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_max_shortcuts_returned = 89 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_query_thread_core_pool_size = 90 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_query_thread_max_pool_size = 91 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_shortcut_refresh_core_pool_size = 92 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_shortcut_refresh_max_pool_size = 93 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_thread_keepalive_seconds = 94 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto search_per_source_concurrent_query_limit = 95 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Whether or not alert sounds are played on StorageManagerService events. + optional SettingProto mount_play_notification_snd = 96 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mount_ums_autostart = 97 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mount_ums_prompt = 98 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mount_ums_notify_enabled = 99 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto anr_show_background = 100 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The ComponentName string of the service to be used as the voice + // recognition service. + optional SettingProto voice_recognition_service = 101 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto package_verifier_user_consent = 102 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto selected_spell_checker = 103 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto selected_spell_checker_subtype = 104 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto spell_checker_enabled = 105 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto incall_power_button_behavior = 106 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto incall_back_button_behavior = 107 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto wake_gesture_enabled = 108 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto doze_enabled = 109 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto doze_always_on = 110 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto doze_pulse_on_pick_up = 111 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto doze_pulse_on_long_press = 180 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto doze_pulse_on_double_tap = 112 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto ui_night_mode = 113 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screensaver_enabled = 114 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screensaver_components = 115 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screensaver_activate_on_dock = 116 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screensaver_activate_on_sleep = 117 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screensaver_default_component = 118 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto nfc_payment_default_component = 119 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto nfc_payment_foreground = 120 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sms_default_application = 121 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dialer_default_application = 122 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto emergency_assistance_application = 123 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_structure_enabled = 124 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_screenshot_enabled = 125 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_disclosure_enabled = 126 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Read only list of the service components that the current user has + // explicitly allowed to see and assist with all of the user's + // notifications. + optional SettingProto enabled_notification_assistant = 127 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enabled_notification_listeners = 128 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enabled_notification_policy_access_packages = 129 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Defines whether managed profile ringtones should be synced from its + // parent profile. + optional SettingProto sync_parent_sounds = 130 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto immersive_mode_confirmations = 131 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The query URI to find a print service to install. + optional SettingProto print_service_search_uri = 132 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The query URI to find an NFC service to install. + optional SettingProto payment_service_search_uri = 133 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The query URI to find an auto fill service to install. + optional SettingProto autofill_service_search_uri = 181 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto skip_first_use_hints = 134 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto unsafe_volume_music_active_ms = 135 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lock_screen_show_notifications = 136 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto tv_input_hidden_inputs = 137; optional SettingProto tv_input_custom_labels = 138; - optional SettingProto usb_audio_automatic_routing_disabled = 139; - optional SettingProto sleep_timeout = 140; - optional SettingProto double_tap_to_wake = 141; - optional SettingProto assistant = 142; - optional SettingProto camera_gesture_disabled = 143; - optional SettingProto camera_double_tap_power_gesture_disabled = 144; - optional SettingProto camera_double_twist_to_flip_enabled = 145; - optional SettingProto camera_lift_trigger_enabled = 182; - optional SettingProto assist_gesture_enabled = 183; - optional SettingProto assist_gesture_sensitivity = 184; - optional SettingProto assist_gesture_silence_alerts_enabled = 185; - optional SettingProto assist_gesture_wake_enabled = 186; - optional SettingProto assist_gesture_setup_complete = 187; - optional SettingProto night_display_activated = 146; - optional SettingProto night_display_auto_mode = 147; - optional SettingProto night_display_color_temperature = 188; - optional SettingProto night_display_custom_start_time = 148; - optional SettingProto night_display_custom_end_time = 149; - optional SettingProto night_display_last_activated_time = 189; + optional SettingProto usb_audio_automatic_routing_disabled = 139 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sleep_timeout = 140 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto double_tap_to_wake = 141 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // The current assistant component. It could be a voice interaction service, + // or an activity that handles ACTION_ASSIST, or empty, which means using + // the default handling. + optional SettingProto assistant = 142 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto camera_gesture_disabled = 143 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto camera_double_tap_power_gesture_disabled = 144 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto camera_double_twist_to_flip_enabled = 145 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto camera_lift_trigger_enabled = 182 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_gesture_enabled = 183 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_gesture_sensitivity = 184 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_gesture_silence_alerts_enabled = 185 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_gesture_wake_enabled = 186 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto assist_gesture_setup_complete = 187 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto night_display_activated = 146 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto night_display_auto_mode = 147 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto night_display_color_temperature = 188 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto night_display_custom_start_time = 148 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto night_display_custom_end_time = 149 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto night_display_last_activated_time = 189 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto brightness_use_twilight = 150; - optional SettingProto enabled_vr_listeners = 151; - optional SettingProto vr_display_mode = 152; - optional SettingProto carrier_apps_handled = 153; - optional SettingProto managed_profile_contact_remote_search = 154; - optional SettingProto automatic_storage_manager_enabled = 155; - optional SettingProto automatic_storage_manager_days_to_retain = 156; - optional SettingProto automatic_storage_manager_bytes_cleared = 157; - optional SettingProto automatic_storage_manager_last_run = 158; - optional SettingProto automatic_storage_manager_turned_off_by_policy = 190; - optional SettingProto system_navigation_keys_enabled = 159; + optional SettingProto enabled_vr_listeners = 151 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto vr_display_mode = 152 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto carrier_apps_handled = 153 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto managed_profile_contact_remote_search = 154 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto automatic_storage_manager_enabled = 155 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto automatic_storage_manager_days_to_retain = 156 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto automatic_storage_manager_bytes_cleared = 157 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto automatic_storage_manager_last_run = 158 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto automatic_storage_manager_turned_off_by_policy = 190 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto system_navigation_keys_enabled = 159 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto downloads_backup_enabled = 160; optional SettingProto downloads_backup_allow_metered = 161; optional SettingProto downloads_backup_charging_only = 162; optional SettingProto automatic_storage_manager_downloads_days_to_retain = 163; - optional SettingProto qs_tiles = 164; + // Holds comma-separated list of ordering of QuickSettings tiles. + optional SettingProto qs_tiles = 164 [ (android.privacy).dest = DEST_AUTOMATIC ]; reserved 165; // Removed demo_user_setup_complete - optional SettingProto instant_apps_enabled = 166; - optional SettingProto device_paired = 167; - optional SettingProto package_verifier_state = 191; - optional SettingProto cmas_additional_broadcast_pkg = 192; - optional SettingProto notification_badging = 168; - optional SettingProto qs_auto_added_tiles = 193; - optional SettingProto lockdown_in_power_menu = 194; + optional SettingProto instant_apps_enabled = 166 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto device_paired = 167 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto package_verifier_state = 191 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto cmas_additional_broadcast_pkg = 192 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto notification_badging = 168 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto qs_auto_added_tiles = 193 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lockdown_in_power_menu = 194 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto backup_manager_constants = 169; + optional SettingProto show_first_crash_dialog_dev_option = 195 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_on_while_driving = 196 [ (android.privacy).dest = DEST_AUTOMATIC ]; - // Next tag = 195 + // Please insert fields in the same order as in + // frameworks/base/core/java/android/provider/Settings.java. + // Next tag = 197 } message SystemSettingsProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + repeated SettingsOperationProto historical_operations = 1; - optional SettingProto end_button_behavior = 2; - optional SettingProto advanced_settings = 3; - optional SettingProto bluetooth_discoverability = 4; - optional SettingProto bluetooth_discoverability_timeout = 5; - optional SettingProto font_scale = 6; - optional SettingProto system_locales = 7; - optional SettingProto display_color_mode = 67; - optional SettingProto screen_off_timeout = 8; - optional SettingProto screen_brightness = 9; - optional SettingProto screen_brightness_for_vr = 10; - optional SettingProto screen_brightness_mode = 11; - optional SettingProto screen_auto_brightness_adj = 12; - optional SettingProto mode_ringer_streams_affected = 13; - optional SettingProto mute_streams_affected = 14; - optional SettingProto vibrate_on = 15; - optional SettingProto vibrate_input_devices = 16; - optional SettingProto volume_ring = 17; - optional SettingProto volume_system = 18; - optional SettingProto volume_voice = 19; - optional SettingProto volume_music = 20; - optional SettingProto volume_alarm = 21; - optional SettingProto volume_notification = 22; - optional SettingProto volume_bluetooth_sco = 23; - optional SettingProto volume_accessibility = 68; - optional SettingProto volume_master = 24; - optional SettingProto master_mono = 25; + optional SettingProto end_button_behavior = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto advanced_settings = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_discoverability = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto bluetooth_discoverability_timeout = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto font_scale = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto system_locales = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto display_color_mode = 67 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screen_off_timeout = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screen_brightness = 9 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screen_brightness_for_vr = 10 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screen_brightness_mode = 11 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screen_auto_brightness_adj = 12 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Determines which streams are affected by ringer mode changes. The stream + // type's bit will be set to 1 if it should be muted when going into an + // inaudible ringer mode. + optional SettingProto mode_ringer_streams_affected = 13 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto mute_streams_affected = 14 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto vibrate_on = 15 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto vibrate_input_devices = 16 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_ring = 17 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_system = 18 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_voice = 19 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_music = 20 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_alarm = 21 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_notification = 22 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_bluetooth_sco = 23 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_accessibility = 68 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto volume_master = 24 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto master_mono = 25 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Whether silent mode should allow vibration feedback. This is used // internally in AudioService and the Sound settings activity to coordinate // decoupling of vibrate and silent modes. This setting will likely be @@ -633,8 +710,10 @@ message SystemSettingsProto { // Not used anymore. On devices with vibrator, the user explicitly selects // silent or vibrate mode. Kept for use by legacy database upgrade code in // DatabaseHelper. - optional SettingProto vibrate_in_silent = 26; - optional SettingProto append_for_last_audible = 27; + optional SettingProto vibrate_in_silent = 26 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Appended to various volume related settings to record the previous values + // before the settings were affected by a silent/vibrate ringer mode change. + optional SettingProto append_for_last_audible = 27 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto ringtone = 28; optional SettingProto ringtone_cache = 29; optional SettingProto notification_sound = 30; @@ -642,44 +721,47 @@ message SystemSettingsProto { optional SettingProto alarm_alert = 32; optional SettingProto alarm_alert_cache = 33; optional SettingProto media_button_receiver = 34; - optional SettingProto text_auto_replace = 35; - optional SettingProto text_auto_caps = 36; - optional SettingProto text_auto_punctuate = 37; - optional SettingProto text_show_password = 38; - optional SettingProto show_gtalk_service_status = 39; - optional SettingProto time_12_24 = 40; - optional SettingProto date_format = 41; - optional SettingProto setup_wizard_has_run = 42; - optional SettingProto accelerometer_rotation = 43; - optional SettingProto user_rotation = 44; - optional SettingProto hide_rotation_lock_toggle_for_accessibility = 45; - optional SettingProto vibrate_when_ringing = 46; - optional SettingProto dtmf_tone_when_dialing = 47; - optional SettingProto dtmf_tone_type_when_dialing = 48; - optional SettingProto hearing_aid = 49; - optional SettingProto tty_mode = 50; - optional SettingProto sound_effects_enabled = 51; - optional SettingProto haptic_feedback_enabled = 52; - optional SettingProto notification_light_pulse = 53; - optional SettingProto pointer_location = 54; - optional SettingProto show_touches = 55; + optional SettingProto text_auto_replace = 35 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto text_auto_caps = 36 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto text_auto_punctuate = 37 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto text_show_password = 38 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_gtalk_service_status = 39 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto time_12_24 = 40 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto date_format = 41 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto setup_wizard_has_run = 42 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto accelerometer_rotation = 43 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto user_rotation = 44 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto hide_rotation_lock_toggle_for_accessibility = 45 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto vibrate_when_ringing = 46 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dtmf_tone_when_dialing = 47 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto dtmf_tone_type_when_dialing = 48 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto hearing_aid = 49 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto tty_mode = 50 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sound_effects_enabled = 51 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto haptic_feedback_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto notification_light_pulse = 53 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Show pointer location on screen? 0 = no, 1 = yes. + optional SettingProto pointer_location = 54 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_touches = 55 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Log raw orientation data from {@link // com.android.server.policy.WindowOrientationListener} for use with the // orientationplot.py tool. // 0 = no, 1 = yes - optional SettingProto window_orientation_listener_log = 56; - optional SettingProto lockscreen_sounds_enabled = 57; - optional SettingProto lockscreen_disabled = 58; - optional SettingProto sip_receive_calls = 59; - optional SettingProto sip_call_options = 60; - optional SettingProto sip_always = 61; - optional SettingProto sip_address_only = 62; - optional SettingProto pointer_speed = 63; - optional SettingProto lock_to_app_enabled = 64; - optional SettingProto egg_mode = 65; - optional SettingProto show_battery_percent = 69; - optional SettingProto when_to_make_wifi_calls = 66; + optional SettingProto window_orientation_listener_log = 56 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lockscreen_sounds_enabled = 57 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lockscreen_disabled = 58 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sip_receive_calls = 59 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sip_call_options = 60 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sip_always = 61 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto sip_address_only = 62 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pointer_speed = 63 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto lock_to_app_enabled = 64 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto egg_mode = 65 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_battery_percent = 69 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto when_to_make_wifi_calls = 66 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Please insert fields in the same order as in + // frameworks/base/core/java/android/provider/Settings.java. // Next tag = 70; } diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index d3ca496995a93cad828dbb16836a395dd3e5ecb7..788d90127c3d79ad83c5abab52c6dcdaab40b1bf 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -18,29 +18,41 @@ syntax = "proto2"; package com.android.server.am.proto; +import "frameworks/base/core/proto/android/app/activitymanager.proto"; +import "frameworks/base/core/proto/android/app/enums.proto"; import "frameworks/base/core/proto/android/app/notification.proto"; +import "frameworks/base/core/proto/android/app/profilerinfo.proto"; +import "frameworks/base/core/proto/android/content/component_name.proto"; +import "frameworks/base/core/proto/android/content/configuration.proto"; import "frameworks/base/core/proto/android/content/intent.proto"; +import "frameworks/base/core/proto/android/content/package_item_info.proto"; import "frameworks/base/core/proto/android/graphics/rect.proto"; import "frameworks/base/core/proto/android/internal/processstats.proto"; import "frameworks/base/core/proto/android/os/looper.proto"; +import "frameworks/base/core/proto/android/os/powermanager.proto"; import "frameworks/base/core/proto/android/server/intentresolver.proto"; import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; import "frameworks/base/core/proto/android/util/common.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; option java_multiple_files = true; message ActivityManagerServiceProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional ActivityStackSupervisorProto activities = 1; optional BroadcastProto broadcasts = 2; optional ActiveServicesProto services = 3; - optional ProcessProto processes = 4; + optional ProcessesProto processes = 4; } // "dumpsys activity --proto activities" message ActivityStackSupervisorProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1; repeated ActivityDisplayProto displays = 2; optional KeyguardControllerProto keyguard_controller = 3; @@ -50,12 +62,16 @@ message ActivityStackSupervisorProto { /* represents ActivityStackSupervisor.ActivityDisplay */ message ActivityDisplayProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1; optional int32 id = 2; repeated ActivityStackProto stacks = 3; } message ActivityStackProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1; optional int32 id = 2; repeated TaskRecordProto tasks = 3; @@ -66,6 +82,8 @@ message ActivityStackProto { } message TaskRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1; optional int32 id = 2; repeated ActivityRecordProto activities = 3; @@ -82,6 +100,8 @@ message TaskRecordProto { } message ActivityRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1; optional .com.android.server.wm.proto.IdentifierProto identifier = 2; optional string state = 3; @@ -91,12 +111,16 @@ message ActivityRecordProto { } message KeyguardControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool keyguard_showing = 1; optional bool keyguard_occluded = 2; } // "dumpsys activity --proto broadcasts" message BroadcastProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated ReceiverListProto receiver_list = 1; optional .com.android.server.IntentResolverProto receiver_resolver = 2; @@ -106,6 +130,8 @@ message BroadcastProto { repeated StickyBroadcastProto sticky_broadcasts = 4; message MainHandler { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string handler = 1; optional .android.os.LooperProto looper = 2; } @@ -113,6 +139,8 @@ message BroadcastProto { } message ReceiverListProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional ProcessRecordProto app = 1; optional int32 pid = 2; optional int32 uid = 3; @@ -124,20 +152,27 @@ message ReceiverListProto { } message ProcessRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 pid = 1; optional string process_name = 2; optional int32 uid = 3; optional int32 user_id = 4; optional int32 app_id = 5; optional int32 isolated_app_id = 6; + optional bool persistent = 7; } message BroadcastRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 user_id = 1; optional string intent_action = 2; } message BroadcastFilterProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.content.IntentFilterProto intent_filter = 1; optional string required_permission = 2; optional string hex_hash = 3; // used to find the object in IntentResolver @@ -145,6 +180,8 @@ message BroadcastFilterProto { } message BroadcastQueueProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string queue_name = 1; repeated BroadcastRecordProto parallel_broadcasts = 2; repeated BroadcastRecordProto ordered_broadcasts = 3; @@ -152,6 +189,8 @@ message BroadcastQueueProto { repeated BroadcastRecordProto historical_broadcasts = 5; message BroadcastSummary { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.content.IntentProto intent = 1; optional int64 enqueue_clock_time_ms = 2; optional int64 dispatch_clock_time_ms = 3; @@ -161,14 +200,20 @@ message BroadcastQueueProto { } message MemInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 uptime_duration_ms = 1; optional int64 elapsed_realtime_ms = 2; message ProcessMemory { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 pid = 1; optional string process_name = 2; message MemoryInfo { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; // The proportional set size for the heap. optional int32 total_pss_kb = 2; @@ -190,6 +235,8 @@ message MemInfoProto { } } message HeapInfo { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional MemoryInfo mem_info = 1; optional int32 heap_size_kb = 2; optional int32 heap_alloc_kb = 3; @@ -205,6 +252,8 @@ message MemInfoProto { repeated MemoryInfo dalvik_details = 8; message AppSummary { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 java_heap_pss_kb = 1; optional int32 native_heap_pss_kb = 2; optional int32 code_pss_kb = 3; @@ -223,9 +272,13 @@ message MemInfoProto { repeated ProcessMemory native_processes = 3; message AppData { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional ProcessMemory process_memory = 1; message ObjectStats { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 view_instance_count = 1; optional int32 view_root_instance_count = 2; optional int32 app_context_instance_count = 3; @@ -243,11 +296,15 @@ message MemInfoProto { optional ObjectStats objects = 2; message SqlStats { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 memory_used_kb = 1; optional int32 pagecache_overflow_kb = 2; optional int32 malloc_size_kb = 3; message Database { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; optional int32 page_size = 2; optional int32 db_size = 3; @@ -267,6 +324,8 @@ message MemInfoProto { repeated AppData app_processes = 4; message MemItem { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string tag = 1; optional string label = 2; optional int32 id = 3; @@ -330,9 +389,13 @@ message MemInfoProto { } message StickyBroadcastProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 user = 1; message StickyAction { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; repeated .android.content.IntentProto intents = 2; } @@ -341,6 +404,7 @@ message StickyBroadcastProto { // "dumpsys activity --proto service" message ActiveServicesProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; message ServicesByUser { optional int32 user_id = 1; @@ -351,11 +415,15 @@ message ActiveServicesProto { // corresponds to ActivityManagerService.GrantUri Java class message GrantUriProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 source_user_id = 1; - optional string uri = 2; + optional string uri = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; } message NeededUriGrantsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string target_package = 1; optional int32 target_uid = 2; optional int32 flags = 3; @@ -364,12 +432,16 @@ message NeededUriGrantsProto { } message UriPermissionOwnerProto { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + optional string owner = 1; repeated GrantUriProto read_perms = 2; repeated GrantUriProto write_perms = 3; } message ServiceRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string short_name = 1; optional string hex_hash = 2; optional bool is_running = 3; // false if the application service is null @@ -380,6 +452,8 @@ message ServiceRecordProto { optional string permission = 8; message AppInfo { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + optional string base_dir = 1; optional string res_dir = 2; optional string data_dir = 3; @@ -391,6 +465,8 @@ message ServiceRecordProto { optional bool delayed = 13; message Foreground { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 id = 1; optional .android.app.NotificationProto notification = 2; } @@ -404,6 +480,8 @@ message ServiceRecordProto { // variables used to track states related to service start message Start { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool start_requested = 1; optional bool delayed_stop = 2; optional bool stop_if_killed = 3; @@ -413,6 +491,8 @@ message ServiceRecordProto { optional Start start = 20; message ExecuteNesting { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 execute_nesting = 1; optional bool execute_fg = 2; optional .android.util.Duration executing_start = 3; @@ -422,6 +502,8 @@ message ServiceRecordProto { optional .android.util.Duration destory_time = 22; message Crash { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 restart_count = 1; optional .android.util.Duration restart_delay = 2; optional .android.util.Duration next_restart_time = 3; @@ -430,6 +512,8 @@ message ServiceRecordProto { optional Crash crash = 23; message StartItemProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 id = 1; optional .android.util.Duration duration = 2; optional int32 delivery_count = 3; @@ -446,6 +530,8 @@ message ServiceRecordProto { } message ConnectionRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string hex_hash = 1; optional int32 user_id = 2; @@ -459,7 +545,7 @@ message ConnectionRecordProto { WAIVE_PRIORITY = 6; IMPORTANT = 7; ADJUST_WITH_ACTIVITY = 8; - FG_SERVICE_WHILE_WAKE = 9; + FG_SERVICE_WHILE_AWAKE = 9; FG_SERVICE = 10; TREAT_LIKE_ACTIVITY = 11; VISIBLE = 12; @@ -473,12 +559,16 @@ message ConnectionRecordProto { } message AppBindRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string hex_hash = 1; optional ProcessRecordProto client = 2; repeated ConnectionRecordProto connections = 3; } message IntentBindRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string hex_hash = 1; optional bool is_create = 2; optional .android.content.IntentProto intent = 3; @@ -492,5 +582,431 @@ message IntentBindRecordProto { } // TODO: "dumpsys activity --proto processes" -message ProcessProto { +message ProcessesProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + repeated ProcessRecordProto procs = 1; + repeated ProcessRecordProto isolated_procs = 2; + repeated ActiveInstrumentationProto active_instrumentations = 3; + repeated UidRecordProto active_uids = 4; + repeated UidRecordProto validate_uids = 5; + + // Process LRU list (sorted by oom_adj) + message LruProcesses { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 size = 1; + optional int32 non_act_at = 2; + optional int32 non_svc_at = 3; + repeated ProcessOomProto list = 4; + } + optional LruProcesses lru_procs = 6; + repeated ProcessRecordProto pids_self_locked = 7; + // Foreground Processes + repeated ImportanceTokenProto important_procs = 8; + // Persisent processes that are starting + repeated ProcessRecordProto persistent_starting_procs = 9; + // Processes that are being removed + repeated ProcessRecordProto removed_procs = 10; + // Processes that are on old until the system is ready + repeated ProcessRecordProto on_hold_procs = 11; + // Processes that are waiting to GC + repeated ProcessToGcProto gc_procs = 12; + optional AppErrorsProto app_errors = 13; + optional UserControllerProto user_controller = 14; + optional ProcessRecordProto home_proc = 15; + optional ProcessRecordProto previous_proc = 16; + optional int64 previous_proc_visible_time_ms = 17; + optional ProcessRecordProto heavy_weight_proc = 18; + optional .android.content.ConfigurationProto global_configuration = 19; + // ActivityStackSupervisorProto dumps these values as well, still here? + // repeated ActivityDisplayProto displays = 20; + + optional bool config_will_change = 21; + + message ScreenCompatPackage { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string package = 1; + optional int32 mode = 2; + } + repeated ScreenCompatPackage screen_compat_packages = 22; + + message UidObserverRegistrationProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 uid = 1; + optional string package = 2; + repeated .android.app.UidObserverFlag flags = 3; + optional int32 cut_point = 4; // only available when UID_OBSERVER_PROCSTATE is on + + message ProcState { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 uid = 1; + optional int32 state = 2; + } + repeated ProcState last_proc_states = 5; + } + repeated UidObserverRegistrationProto uid_observers = 23; + repeated int32 device_idle_whitelist = 24; + repeated int32 device_idle_temp_whitelist = 25; + + message PendingTempWhitelist { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 target_uid = 1; + optional int64 duration_ms = 2; + optional string tag = 3; + } + repeated PendingTempWhitelist pending_temp_whitelist = 26; + + message SleepStatus { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional .android.os.PowerManagerInternalProto.Wakefulness wakefulness = 1; + repeated string sleep_tokens = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional bool sleeping = 3; + optional bool shutting_down = 4; + optional bool test_pss_mode = 5; + } + optional SleepStatus sleep_status = 27; + + message VoiceProto { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + + optional string session = 1; + optional .android.os.PowerManagerProto.WakeLockProto wakelock = 2; + } + optional VoiceProto running_voice = 28; + + message VrControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + enum VrMode { + FLAG_NON_VR_MODE = 0; + FLAG_VR_MODE = 1; + FLAG_PERSISTENT_VR_MODE = 2; + } + repeated VrMode vr_mode = 1; + optional int32 render_thread_id = 2; + } + optional VrControllerProto vr_controller = 29; + + message DebugApp { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string debug_app = 1; + optional string orig_debug_app = 2; + optional bool debug_transient = 3; + optional bool orig_wait_for_debugger = 4; + } + optional DebugApp debug = 30; + optional AppTimeTrackerProto current_tracker = 31; + + message MemWatchProcess { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + message Process { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1; + + message MemStats { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 uid = 1; + optional string size = 2; + optional string report_to = 3; + } + repeated MemStats mem_stats = 2; + } + repeated Process procs = 1; + + message Dump { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string proc_name = 1; + optional string file = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional int32 pid = 3; + optional int32 uid = 4; + } + optional Dump dump = 2; + } + optional MemWatchProcess mem_watch_processes = 32; + optional string track_allocation_app = 33; + + message Profile { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string app_name = 1; + optional ProcessRecordProto proc = 2; + optional .android.app.ProfilerInfoProto info = 3; + optional int32 type = 4; + } + optional Profile profile = 34; + optional string native_debugging_app = 35; + optional bool always_finish_activities = 36; + + message Controller { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string controller = 1; + optional bool is_a_monkey = 2; + } + optional Controller controller = 37; + + optional int32 total_persistent_procs = 38; + optional bool processes_ready = 39; + optional bool system_ready = 40; + optional bool booted = 41; + optional int32 factory_test = 42; + optional bool booting = 43; + optional bool call_finish_booting = 44; + optional bool boot_animation_complete = 45; + optional int64 last_power_check_uptime_ms = 46; + optional .android.os.PowerManagerProto.WakeLockProto going_to_sleep = 47; + optional .android.os.PowerManagerProto.WakeLockProto launching_activity = 48; + optional int32 adj_seq = 49; + optional int32 lru_seq = 50; + optional int32 num_non_cached_procs = 51; + optional int32 num_cached_hidden_procs = 52; + optional int32 num_service_procs = 53; + optional int32 new_num_service_procs = 54; + optional bool allow_lower_mem_level = 55; + optional int32 last_memory_level = 56; + optional int32 last_num_processes = 57; + optional .android.util.Duration last_idle_time = 58; + optional int64 low_ram_since_last_idle_ms = 59; +} + +message ActiveInstrumentationProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional .android.content.ComponentNameProto class = 1; + optional bool finished = 2; + repeated ProcessRecordProto running_processes = 3; + repeated string target_processes = 4; + optional .android.content.pm.ApplicationInfoProto target_info = 5; + optional string profile_file = 6; + optional string watcher = 7; + optional string ui_automation_connection = 8; + optional string arguments = 9 [ (.android.privacy).dest = DEST_EXPLICIT ]; +} + +// Proto definition of com.android.server.am.UidRecord.java +message UidRecordProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string hex_hash = 1; + optional int32 uid = 2; + optional .android.app.ProcessStateEnum current = 3; + optional bool ephemeral = 4; + optional bool fg_services = 5; + optional bool whilelist = 6; + optional .android.util.Duration last_background_time = 7; + optional bool idle = 8; + + enum Change { + CHANGE_GONE = 0; + CHANGE_IDLE = 1; + CHANGE_ACTIVE = 2; + CHANGE_CACHED = 3; + CHANGE_UNCACHED = 4; + } + repeated Change last_reported_changes = 9; + optional int32 num_procs = 10; + + message ProcStateSequence { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int64 cururent = 1; + optional int64 last_network_updated = 2; + optional int64 last_dispatched = 3; + } + optional ProcStateSequence network_state_update = 11; +} + +// proto of class ImportanceToken in ActivityManagerService +message ImportanceTokenProto { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + + optional int32 pid = 1; + optional string token = 2; + optional string reason = 3; +} + +message ProcessOomProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional bool persistent = 1; + optional int32 num = 2; + optional string oom_adj = 3; + + // Activity manager's version of Process enum, see ProcessList.java + enum SchedGroup { + SCHED_GROUP_UNKNOWN = -1; + SCHED_GROUP_BACKGROUND = 0; + SCHED_GROUP_DEFAULT = 1; + SCHED_GROUP_TOP_APP = 2; + SCHED_GROUP_TOP_APP_BOUND = 3; + } + optional SchedGroup sched_group = 4 [ default = SCHED_GROUP_UNKNOWN]; + + oneof Foreground { + bool activities = 5; + bool services = 6; + } + + optional .android.app.ProcessStateEnum state = 7; + optional int32 trim_memory_level = 8; + optional ProcessRecordProto proc = 9; + optional string adj_type = 10; + + oneof AdjTarget { + .android.content.ComponentNameProto adj_target_component_name = 11; + string adj_target_object = 12; + } + + oneof AdjSource { + ProcessRecordProto adj_source_proc = 13; + string adj_source_object = 14; + } + + message Detail { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 max_adj = 1; + optional int32 cur_raw_adj = 2; + optional int32 set_raw_adj = 3; + optional int32 cur_adj = 4; + optional int32 set_adj = 5; + optional .android.app.ProcessStateEnum current_state = 7; + optional .android.app.ProcessStateEnum set_state = 8; + optional string last_pss = 9; + optional string last_swap_pss = 10; + optional string last_cached_pss = 11; + optional bool cached = 12; + optional bool empty = 13; + optional bool has_above_client = 14; + + // only make sense if process is a service + message CpuRunTime { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int64 over_ms = 1; + optional int64 used_ms = 2; + optional float ultilization = 3; // ratio of cpu time usage + } + optional CpuRunTime service_run_time = 15; + } + optional Detail detail = 15; +} + +message ProcessToGcProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional ProcessRecordProto proc = 1; + optional bool report_low_memory = 2; + optional int64 now_uptime_ms = 3; + optional int64 last_gced_ms = 4; + optional int64 last_low_memory_ms = 5; +} + +// sync with com.android.server.am.AppErrors.java +message AppErrorsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int64 now_uptime_ms = 1; + + message ProcessCrashTime { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string process_name = 1; + + message Entry { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 uid = 1; + optional int64 last_crashed_at_ms = 2; + } + repeated Entry entries = 2; + } + repeated ProcessCrashTime process_crash_times = 2; + + message BadProcess { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string process_name = 1; + + message Entry { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 uid = 1; + optional int64 crashed_at_ms = 2; + optional string short_msg = 3; + optional string long_msg = 4 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string stack = 5 [ (.android.privacy).dest = DEST_EXPLICIT ]; + } + repeated Entry entries = 2; + } + repeated BadProcess bad_processes = 3; +} + +// sync with com.android.server.am.UserState.java +message UserStateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + enum State { + STATE_BOOTING = 0; + STATE_RUNNING_LOCKED = 1; + STATE_RUNNING_UNLOCKING = 2; + STATE_RUNNING_UNLOCKED = 3; + STATE_STOPPING = 4; + STATE_SHUTDOWN = 5; + } + optional State state = 1; + optional bool switching = 2; +} + +// sync with com.android.server.am.UserController.java +message UserControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + message User { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 id = 1; + optional UserStateProto state = 2; + } + repeated User started_users = 1; + repeated int32 started_user_array = 2; + repeated int32 user_lru = 3; + + message UserProfile { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 user = 1; + optional int32 profile = 2; + } + repeated UserProfile user_profile_group_ids = 4; +} + +// sync with com.android.server.am.AppTimeTracker.java +message AppTimeTrackerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string receiver = 1; + optional int64 total_duration_ms = 2; + + message PackageTime { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string package = 1; + optional int64 duration_ms = 2; + } + repeated PackageTime package_times = 3; + + optional .android.util.Duration started_time = 4; + optional string started_package = 5; } diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto index 87d302e7e6a2e955ebe7be6de7b53b15d0eb89e8..aa2663fa2cc4c105057568d81e79c51ff5ccdcb2 100644 --- a/core/proto/android/server/alarmmanagerservice.proto +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -21,12 +21,15 @@ import "frameworks/base/core/proto/android/app/pendingintent.proto"; import "frameworks/base/core/proto/android/internal/locallog.proto"; import "frameworks/base/core/proto/android/os/worksource.proto"; import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package com.android.server; option java_multiple_files = true; message AlarmManagerServiceProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 current_time = 1; optional int64 elapsed_realtime = 2; optional int64 last_time_change_clock_time = 3; @@ -47,6 +50,8 @@ message AlarmManagerServiceProto { // Only valid if is_interactive is false. optional int64 time_until_next_non_wakeup_delivery_ms = 11; + // Can be negative if the non-wakeup alarm time is in the past (non-wakeup + // alarms aren't delivered unil the next time the device wakes up). optional int64 time_until_next_non_wakeup_alarm_ms = 12; optional int64 time_until_next_wakeup_ms = 13; optional int64 time_since_last_wakeup_ms = 14; @@ -104,6 +109,8 @@ message AlarmManagerServiceProto { optional int64 allow_while_idle_min_duration_ms = 35; message LastAllowWhileIdleDispatch { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; // In the 'elapsed' timebase. optional int64 time_ms = 2; @@ -115,6 +122,8 @@ message AlarmManagerServiceProto { optional com.android.internal.util.LocalLogProto recent_problems = 37; message TopAlarm { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string package_name = 2; optional FilterStatsProto filter = 3; @@ -122,6 +131,8 @@ message AlarmManagerServiceProto { repeated TopAlarm top_alarms = 38; message AlarmStat { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional BroadcastStatsProto broadcast = 1; repeated FilterStatsProto filters = 2; } @@ -134,6 +145,8 @@ message AlarmManagerServiceProto { // This is a soft wrapper for alarm clock information. It is not representative // of an android.app.AlarmManager.AlarmClockInfo object. message AlarmClockMetadataProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 user = 1; optional bool is_pending_send = 2; // This value is UTC wall clock time in milliseconds, as returned by @@ -143,7 +156,9 @@ message AlarmClockMetadataProto { // A com.android.server.AlarmManagerService.Alarm object. message AlarmProto { - optional string tag = 1; + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string tag = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional .android.app.AlarmManagerProto.AlarmType type = 2; // How long until the alarm goes off, in the 'elapsed' timebase. Can be // negative if 'when' is in the past. @@ -154,11 +169,13 @@ message AlarmProto { optional int32 flags = 7; optional .android.app.AlarmClockInfoProto alarm_clock = 8; optional .android.app.PendingIntentProto operation = 9; - optional string listener = 10; + optional string listener = 10 [ (.android.privacy).dest = DEST_EXPLICIT ]; } // A com.android.server.AlarmManagerService.Batch object. message BatchProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Start time in terms of elapsed realtime. optional int64 start_realtime = 1; // End time in terms of elapsed realtime. @@ -169,6 +186,8 @@ message BatchProto { // A com.android.server.AlarmManagerService.BroadcastStats object. message BroadcastStatsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string package_name = 2; // The total amount of time this broadcast was in flight. @@ -184,6 +203,8 @@ message BroadcastStatsProto { // A com.android.server.AlarmManagerService.Constants object. message ConstantsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Minimum futurity of a new alarm. optional int64 min_futurity_duration_ms = 1; // Minimum alarm recurrence interval. @@ -200,7 +221,9 @@ message ConstantsProto { // A com.android.server.AlarmManagerService.FilterStats object. message FilterStatsProto { - optional string tag = 1; + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string tag = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; // The last time this filter when in flight, in terms of elapsed realtime. optional int64 last_flight_time_realtime = 2; // The total amount of time this filter was in flight. @@ -216,9 +239,11 @@ message FilterStatsProto { // A com.android.server.AlarmManagerService.IdleDispatchEntry object. message IdleDispatchEntryProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string pkg = 2; - optional string tag = 3; + optional string tag = 3 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional string op = 4; // Time when this entry was created, in terms of elapsed realtime. optional int64 entry_creation_realtime = 5; @@ -230,8 +255,10 @@ message IdleDispatchEntryProto { // A com.android.server.AlarmManagerService.InFlight object. message InFlightProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; - optional string tag = 2; + optional string tag = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional int64 when_elapsed_ms = 3; optional .android.app.AlarmManagerProto.AlarmType alarm_type = 4; optional .android.app.PendingIntentProto pending_intent = 5; @@ -242,6 +269,8 @@ message InFlightProto { // A com.android.server.AlarmManagerService.WakeupEvent object. message WakeupEventProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string action = 2; optional int64 when = 3; diff --git a/core/proto/android/server/appwindowthumbnail.proto b/core/proto/android/server/appwindowthumbnail.proto index e67b854d09982c59d98386a1852e1ecba954a8b3..8f48d75a7f76440752f10ce256e8b2b6cd250350 100644 --- a/core/proto/android/server/appwindowthumbnail.proto +++ b/core/proto/android/server/appwindowthumbnail.proto @@ -17,6 +17,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/server/surfaceanimator.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package com.android.server.wm.proto; option java_multiple_files = true; @@ -25,6 +26,8 @@ option java_multiple_files = true; * Represents a {@link com.android.server.wm.AppWindowThumbnail} object. */ message AppWindowThumbnailProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 width = 1; optional int32 height = 2; optional SurfaceAnimatorProto surface_animator = 3; diff --git a/core/proto/android/telephony/signalstrength.proto b/core/proto/android/server/enums.proto similarity index 60% rename from core/proto/android/telephony/signalstrength.proto rename to core/proto/android/server/enums.proto index 366f1d19f46a461d05b647c1474ed3e2c53076ac..ef024382d8a99992b827d4889ef1eace98498998 100644 --- a/core/proto/android/telephony/signalstrength.proto +++ b/core/proto/android/server/enums.proto @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,16 @@ */ syntax = "proto2"; -option java_package = "android.telephony"; -option java_multiple_files = true; +package android.server; -package android.telephony; +option java_outer_classname = "ServerProtoEnums"; +option java_multiple_files = true; -/** - * An android.telephony.SignalStrength object. - */ -message SignalStrengthProto { - enum StrengthName { - SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; - SIGNAL_STRENGTH_POOR = 1; - SIGNAL_STRENGTH_MODERATE = 2; - SIGNAL_STRENGTH_GOOD = 3; - SIGNAL_STRENGTH_GREAT = 4; - } +enum DeviceIdleModeEnum { + // Device idle mode - not active. + DEVICE_IDLE_MODE_OFF = 0; + // Device idle mode - active in lightweight mode. + DEVICE_IDLE_MODE_LIGHT = 1; + // Device idle mode - active in full mode. + DEVICE_IDLE_MODE_DEEP = 2; } diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto index ec4ffe0ae4e169afa0bf00fbb4bceba97a6297b2..2a7fbc3a7063c1da33607a7d24cb90ee7e659178 100644 --- a/core/proto/android/server/fingerprint.proto +++ b/core/proto/android/server/fingerprint.proto @@ -17,15 +17,21 @@ syntax = "proto2"; package com.android.server.fingerprint; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; option java_outer_classname = "FingerprintServiceProto"; message FingerprintServiceDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Each log may include multiple tuples of (user_id, num_fingerprints). repeated FingerprintUserStatsProto users = 1; } message FingerprintUserStatsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Should be 0, 10, 11, 12, etc. where 0 is the owner. optional int32 user_id = 1; @@ -42,6 +48,8 @@ message FingerprintUserStatsProto { // A com.android.server.fingerprint.FingerpintService.PerformanceStats object. message PerformanceStatsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Number of accepted fingerprints. optional int32 accept = 1; diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto index 8753bf768321b53cf32d35967f78e05f099cf272..d0fd0b4bbc4b9cdb8029a3b41e83955d16e6e456 100644 --- a/core/proto/android/server/forceappstandbytracker.proto +++ b/core/proto/android/server/forceappstandbytracker.proto @@ -16,12 +16,18 @@ syntax = "proto2"; +import "frameworks/base/core/proto/android/server/statlogger.proto"; + package com.android.server; option java_multiple_files = true; -// Dump from ForceAppStandbyTracker. +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + +// Dump from com.android.server.ForceAppStandbyTracker. message ForceAppStandbyTrackerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Whether all apps are forced standby or not. optional bool force_all_apps_standby = 1; @@ -35,10 +41,31 @@ message ForceAppStandbyTrackerProto { repeated int32 temp_power_save_whitelist_app_ids = 4; message RunAnyInBackgroundRestrictedPackages { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string package_name = 2; } - // 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 plugged in to the charger + optional bool is_plugged_in = 8; + + // Performance stats. + optional StatLoggerProto stats = 9; + + message ExemptedPackage { + optional int32 userId = 1; + optional string package_name = 2; + } + + // Packages that are in the EXEMPT bucket. + repeated ExemptedPackage exempted_packages = 10; } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index f72ca623de10dd16dc81bbd78b0d09608dd7fca4..304e63f28151ca190ae981eb2ebab30f7086affd 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -29,13 +29,18 @@ import "frameworks/base/core/proto/android/net/networkrequest.proto"; import "frameworks/base/core/proto/android/os/bundle.proto"; import "frameworks/base/core/proto/android/os/persistablebundle.proto"; import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message JobSchedulerServiceDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional ConstantsProto settings = 1; repeated int32 started_users = 2; message RegisteredJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional JobStatusDumpProto dump = 2; @@ -56,6 +61,8 @@ message JobSchedulerServiceDumpProto { // Which uids are currently in the foreground. message PriorityOverride { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; // Use sint32 instead of an enum since priorities can technically be // negative. @@ -71,6 +78,8 @@ message JobSchedulerServiceDumpProto { optional JobPackageTrackerDumpProto package_tracker = 8; message PendingJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional JobStatusDumpProto dump = 2; optional sint32 evaluated_priority = 3; @@ -81,12 +90,18 @@ message JobSchedulerServiceDumpProto { // From a service that has currently active or pending jobs. message ActiveJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message InactiveJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 time_since_stopped_ms = 1; // This is not always provided. optional string stopped_reason = 2; } message RunningJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; // How long this job has been running for. optional int64 running_duration_ms = 2; @@ -119,6 +134,8 @@ message JobSchedulerServiceDumpProto { // A com.android.server.job.JobSchedulerService.Constants object. message ConstantsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Minimum # of idle jobs that must be ready in order to force the JMS to // schedule things early. optional int32 min_idle_count = 1; @@ -187,10 +204,16 @@ message ConstantsProto { } message StateControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message AppIdleController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_parole_on = 1; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; optional string source_package_name = 3; @@ -201,9 +224,13 @@ message StateControllerProto { repeated TrackedJob tracked_jobs = 2; } message BackgroundJobsController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional com.android.server.ForceAppStandbyTrackerProto force_app_standby_tracker = 1; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; optional string source_package_name = 3; @@ -217,6 +244,8 @@ message StateControllerProto { repeated TrackedJob tracked_jobs = 2; } message BatteryController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_on_stable_power = 1; optional bool is_battery_not_low = 2; @@ -226,15 +255,21 @@ message StateControllerProto { optional int32 last_broadcast_sequence_number = 4; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; } repeated TrackedJob tracked_jobs = 5; } message ConnectivityController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_connected = 1; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; optional .android.net.NetworkRequestProto required_network = 3; @@ -242,31 +277,47 @@ message StateControllerProto { repeated TrackedJob tracked_jobs = 2; } message ContentObserverController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; } repeated TrackedJob tracked_jobs = 1; message Observer { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 user_id = 1; message TriggerContentData { - optional string uri = 1; + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string uri = 1 [ + (.android.privacy).dest = DEST_EXPLICIT + ]; optional int32 flags = 2; // A // com.android.server.job.controllers.ContentObserverController.JobInstance // object. message JobInstance { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; optional int64 trigger_content_update_delay_ms = 3; optional int64 trigger_content_max_delay_ms = 4; - repeated string changed_authorities = 5; - repeated string changed_uris = 6; + repeated string changed_authorities = 5 [ + (.android.privacy).dest = DEST_EXPLICIT + ]; + repeated string changed_uris = 6 [ + (.android.privacy).dest = DEST_EXPLICIT + ]; } repeated JobInstance jobs = 3; } @@ -275,10 +326,14 @@ message StateControllerProto { repeated Observer observers = 2; } message DeviceIdleJobsController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // True when in device idle mode. optional bool is_device_idle_mode = 1; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; optional string source_package_name = 3; @@ -293,30 +348,42 @@ message StateControllerProto { repeated TrackedJob tracked_jobs = 2; } message IdleController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_idle = 1; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; } repeated TrackedJob tracked_jobs = 2; } message StorageController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_storage_not_low = 1; optional int32 last_broadcast_sequence_number = 2; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; } repeated TrackedJob tracked_jobs = 3; } message TimeController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 now_elapsed_realtime = 1; optional int64 time_until_next_delay_alarm_ms = 2; optional int64 time_until_next_deadline_alarm_ms = 3; message TrackedJob { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; @@ -347,6 +414,8 @@ message StateControllerProto { // A com.android.server.job.JobPackageTracker.DataSet object. message DataSetProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 start_clock_time_ms = 1; // How much time has elapsed since the DataSet was instantiated. optional int64 elapsed_time_ms = 2; @@ -355,10 +424,14 @@ message DataSetProto { // Represents a com.android.server.job.JobPackageTracker.PackageEntry // object, but with some extra data. message PackageEntryProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string package_name = 2; message State { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 duration_ms = 1; optional int32 count = 2; } @@ -377,6 +450,8 @@ message DataSetProto { optional bool active_top = 8; message StopReasonCount { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.app.JobParametersProto.CancelReason reason = 1; optional int32 count = 2; } @@ -390,19 +465,27 @@ message DataSetProto { // Dump from com.android.server.job.GrantedUriPermissions. message GrantedUriPermissionsDumpProto { - optional int32 flags = 1; - optional int32 source_user_id = 2; + option (.android.msg_privacy).dest = DEST_EXPLICIT; + + optional int32 flags = 1 [ (.android.privacy).dest = DEST_AUTOMATIC ]; + optional int32 source_user_id = 2 [ + (.android.privacy).dest = DEST_AUTOMATIC + ]; optional string tag = 3; optional string permission_owner = 4; repeated string uris = 5; } message JobPackageTrackerDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated DataSetProto historical_stats = 1; optional DataSetProto current_stats = 2; } message JobPackageHistoryProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + enum Event { UNKNOWN = 0; START_JOB = 1; @@ -411,12 +494,14 @@ message JobPackageHistoryProto { STOP_PERIODIC_JOB = 4; } message HistoryEvent { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional Event event = 1; optional int64 time_since_event_ms = 2; optional int32 uid = 3; // Job IDs can technically be negative. optional int32 job_id = 4; - optional string tag = 5; + optional string tag = 5 [ (.android.privacy).dest = DEST_EXPLICIT ]; // Only valid for STOP_JOB or STOP_PERIODIC_JOB Events. optional .android.app.JobParametersProto.CancelReason stop_reason = 6; } @@ -424,17 +509,23 @@ message JobPackageHistoryProto { } message JobStatusShortInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 calling_uid = 1; // Job IDs can technically be negative. optional int32 job_id = 2; - optional string battery_name = 3; + optional string battery_name = 3 [ + (.android.privacy).dest = DEST_EXPLICIT + ]; } // Dump from a com.android.server.job.controllers.JobStatus object. message JobStatusDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // The UID that scheduled the job. optional int32 calling_uid = 1; - optional string tag = 2; + optional string tag = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; // The UID for which the job is being run. optional int32 source_uid = 3; @@ -444,6 +535,8 @@ message JobStatusDumpProto { // Custom dump of android.app.job.JobInfo object. message JobInfo { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.content.ComponentNameProto service = 1; optional bool is_periodic = 2; @@ -461,8 +554,10 @@ message JobStatusDumpProto { optional bool requires_device_idle = 10; message TriggerContentUri { - optional int32 flags = 1; - optional string uri = 2; + optional int32 flags = 1 [ + (.android.privacy).dest = DEST_AUTOMATIC + ]; + optional string uri = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; } repeated TriggerContentUri trigger_content_uris = 11; optional int64 trigger_content_update_delay_ms = 12; @@ -482,6 +577,8 @@ message JobStatusDumpProto { optional int64 max_execution_delay_ms = 21; message Backoff { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + enum Policy { BACKOFF_POLICY_LINEAR = 0; BACKOFF_POLICY_EXPONENTIAL = 1; @@ -524,13 +621,19 @@ message JobStatusDumpProto { // Controllers that are currently tracking the job. repeated TrackingController tracking_controllers = 11; - repeated string changed_authorities = 12; - repeated string changed_uris = 13; + repeated string changed_authorities = 12 [ + (.android.privacy).dest = DEST_EXPLICIT + ]; + repeated string changed_uris = 13 [ + (.android.privacy).dest = DEST_EXPLICIT + ]; optional .android.net.NetworkProto network = 14; // Only the desired data from an android.app.job.JobWorkItem object. message JobWorkItem { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 work_id = 1; optional int32 delivery_count = 2; optional .android.content.IntentProto intent = 3; @@ -558,4 +661,6 @@ message JobStatusDumpProto { optional int64 last_successful_run_time = 22; optional int64 last_failed_run_time = 23; + + optional int64 internal_flags = 24; } diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index c1bd69218d64823080df19d1be4ffea92101972f..b5c3ac08651216435f52ca91faabff3ce837fc6a 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -19,22 +19,29 @@ package com.android.server.power; option java_multiple_files = true; -import "frameworks/base/core/proto/android/app/activitymanager.proto"; +import "frameworks/base/core/proto/android/app/enums.proto"; import "frameworks/base/core/proto/android/content/intent.proto"; -import "frameworks/base/core/proto/android/os/batterymanager.proto"; +import "frameworks/base/core/proto/android/os/enums.proto"; import "frameworks/base/core/proto/android/os/looper.proto"; import "frameworks/base/core/proto/android/os/powermanager.proto"; import "frameworks/base/core/proto/android/os/worksource.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto"; -import "frameworks/base/core/proto/android/view/display.proto"; +import "frameworks/base/core/proto/android/view/enums.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message PowerManagerServiceDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // A com.android.server.power.PowerManagerService.Constants object. message ConstantsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_no_cached_wake_locks = 1; } message ActiveWakeLocksProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_cpu = 1; optional bool is_screen_bright = 2; optional bool is_screen_dim = 3; @@ -46,18 +53,21 @@ message PowerManagerServiceDumpProto { optional bool is_draw = 8; } message UserActivityProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_screen_bright = 1; optional bool is_screen_dim = 2; optional bool is_screen_dream = 3; } // A com.android.server.power.PowerManagerService.UidState object. message UidStateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 uid = 1; optional string uid_string = 2; optional bool is_active = 3; optional int32 num_wake_locks = 4; - optional bool is_process_state_unknown = 5; - optional .android.app.ProcessState process_state = 6; + optional .android.app.ProcessStateEnum process_state = 5; } optional ConstantsProto constants = 1; @@ -70,7 +80,7 @@ message PowerManagerServiceDumpProto { // True if the device is plugged into a power source. optional bool is_powered = 5; // The current plug type - optional .android.os.BatteryManagerProto.PlugType plug_type = 6; + optional .android.os.BatteryPluggedStateEnum plug_type = 6; // The current battery level percentage. optional int32 battery_level = 7; // The battery level percentage at the time the dream started. @@ -167,13 +177,19 @@ message PowerManagerServiceDumpProto { // A com.android.server.power.PowerManagerService.SuspendBlockerImpl object. message SuspendBlockerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; optional int32 reference_count = 2; } // A com.android.server.power.PowerManagerService.WakeLock object. message WakeLockProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message WakeLockFlagsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Turn the screen on when the wake lock is acquired. optional bool is_acquire_causes_wakeup = 1; // When this wake lock is released, poke the user activity timer @@ -181,8 +197,8 @@ message WakeLockProto { optional bool is_on_after_release = 2; } - optional .android.os.PowerManagerProto.WakeLockLevel lock_level = 1; - optional string tag = 2; + optional .android.os.WakeLockLevelEnum lock_level = 1; + optional string tag = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional WakeLockFlagsProto flags = 3; optional bool is_disabled = 4; // Acquire time in ms @@ -196,12 +212,18 @@ message WakeLockProto { } message PowerServiceSettingsAndConfigurationDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message StayOnWhilePluggedInProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool is_stay_on_while_plugged_in_ac = 1; optional bool is_stay_on_while_plugged_in_usb = 2; optional bool is_stay_on_while_plugged_in_wireless = 3; } message ScreenBrightnessSettingLimitsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 setting_minimum = 1; optional int32 setting_maximum = 2; optional int32 setting_default = 3; @@ -300,7 +322,7 @@ message PowerServiceSettingsAndConfigurationDumpProto { // Use NaN to disable. optional float temporary_screen_auto_brightness_adjustment_setting_override = 37; // The screen state to use while dozing. - optional .android.view.DisplayProto.DisplayState doze_screen_state_override_from_dream_manager = 38; + optional .android.view.DisplayStateEnum doze_screen_state_override_from_dream_manager = 38; // The screen brightness to use while dozing. optional float dozed_screen_brightness_override_from_dream_manager = 39; // Screen brightness settings limits. diff --git a/core/proto/android/os/batterymanager.proto b/core/proto/android/server/statlogger.proto similarity index 65% rename from core/proto/android/os/batterymanager.proto rename to core/proto/android/server/statlogger.proto index 669bf2d0a6e73df792a3680d9dd07daba7243828..fa430d8264d0e0c943eb828fce389cdaaf87fdce 100644 --- a/core/proto/android/os/batterymanager.proto +++ b/core/proto/android/server/statlogger.proto @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,19 @@ */ syntax = "proto2"; -package android.os; + +package com.android.server; option java_multiple_files = true; -message BatteryManagerProto { - enum PlugType { - PLUG_TYPE_NONE = 0; - PLUG_TYPE_AC = 1; - PLUG_TYPE_USB = 2; - PLUG_TYPE_WIRELESS = 4; - } +// Dump from StatLogger. +message StatLoggerProto { + message Event { + optional int32 eventId = 1; + optional string label = 2; + optional int32 count = 3; + optional int64 total_duration_micros = 4; + } + + repeated Event events = 1; } diff --git a/core/proto/android/server/surfaceanimator.proto b/core/proto/android/server/surfaceanimator.proto index 60713d7d1fe8d9c63b22a80e99f925a727634bad..7f7839e6f7f165d2c45cfd9b729bf099ed3a5033 100644 --- a/core/proto/android/server/surfaceanimator.proto +++ b/core/proto/android/server/surfaceanimator.proto @@ -17,6 +17,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/view/surfacecontrol.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package com.android.server.wm.proto; option java_multiple_files = true; @@ -25,6 +26,8 @@ option java_multiple_files = true; * Represents a {@link com.android.server.wm.SurfaceAnimator} object. */ message SurfaceAnimatorProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string animation_adapter = 1; optional .android.view.SurfaceControlProto leash = 2; optional bool animation_start_delayed = 3; diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 71f33c75205c0f471c84e1e1e9e09294612d7ffd..c11058a2210729487e642d7c5b0be98d71709ea4 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -26,12 +26,15 @@ import "frameworks/base/core/proto/android/view/displaycutout.proto"; import "frameworks/base/core/proto/android/view/displayinfo.proto"; import "frameworks/base/core/proto/android/view/surface.proto"; import "frameworks/base/core/proto/android/view/windowlayoutparams.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package com.android.server.wm.proto; option java_multiple_files = true; message WindowManagerServiceProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional WindowManagerPolicyProto policy = 1; /* window hierarchy root */ optional RootWindowContainerProto root_window_container = 2; @@ -46,6 +49,8 @@ message WindowManagerServiceProto { /* represents DisplayContent */ message RootWindowContainerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional WindowContainerProto window_container = 1; repeated DisplayProto displays = 2; /* window references in top down z order */ @@ -53,16 +58,22 @@ message RootWindowContainerProto { } message BarControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.app.StatusBarManagerProto.WindowState state = 1; optional .android.app.StatusBarManagerProto.TransientWindowState transient_state = 2; } message WindowOrientationListenerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool enabled = 1; optional .android.view.SurfaceProto.Rotation rotation = 2; } message KeyguardServiceDelegateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool showing = 1; optional bool occluded = 2; optional bool secure = 3; @@ -84,6 +95,8 @@ message KeyguardServiceDelegateProto { /* represents PhoneWindowManager */ message WindowManagerPolicyProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 last_system_ui_flags = 1; enum UserRotationMode { USER_ROTATION_FREE = 0; @@ -112,6 +125,8 @@ message WindowManagerPolicyProto { /* represents AppTransition */ message AppTransitionProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + enum AppState { APP_STATE_IDLE = 0; APP_STATE_READY = 1; @@ -147,6 +162,8 @@ message AppTransitionProto { /* represents DisplayContent */ message DisplayProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional WindowContainerProto window_container = 1; optional int32 id = 2; repeated StackProto stacks = 3; @@ -165,22 +182,30 @@ message DisplayProto { /* represents DisplayFrames */ message DisplayFramesProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.graphics.RectProto stable_bounds = 1; } /* represents DockedStackDividerController */ message DockedStackDividerControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool minimized_dock = 1; } /* represents PinnedStackController */ message PinnedStackControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.graphics.RectProto default_bounds = 1; optional .android.graphics.RectProto movement_bounds = 2; } /* represents TaskStack */ message StackProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional WindowContainerProto window_container = 1; optional int32 id = 2; repeated TaskProto tasks = 3; @@ -197,6 +222,8 @@ message StackProto { /* represents Task */ message TaskProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional WindowContainerProto window_container = 1; optional int32 id = 2; repeated AppWindowTokenProto app_window_tokens = 3; @@ -208,8 +235,10 @@ message TaskProto { /* represents AppWindowToken */ message AppWindowTokenProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + /* obtained from ActivityRecord */ - optional string name = 1; + optional string name = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional WindowTokenProto window_token = 2; optional bool last_surface_showing = 3; optional bool is_waiting_for_transition_start = 4; @@ -236,6 +265,8 @@ message AppWindowTokenProto { /* represents WindowToken */ message WindowTokenProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional WindowContainerProto window_container = 1; optional int32 hash_code = 2; repeated WindowStateProto windows = 3; @@ -246,6 +277,8 @@ message WindowTokenProto { /* represents WindowState */ message WindowStateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional WindowContainerProto window_container = 1; optional IdentifierProto identifier = 2; optional int32 display_id = 3; @@ -287,13 +320,17 @@ message WindowStateProto { } message IdentifierProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 hash_code = 1; optional int32 user_id = 2; - optional string title = 3; + optional string title = 3 [ (.android.privacy).dest = DEST_EXPLICIT ]; } /* represents WindowStateAnimator */ message WindowStateAnimatorProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.graphics.RectProto last_clip_rect = 1; optional WindowSurfaceControllerProto surface = 2; enum DrawState { @@ -309,18 +346,24 @@ message WindowStateAnimatorProto { /* represents WindowSurfaceController */ message WindowSurfaceControllerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool shown = 1; optional int32 layer = 2; } /* represents ScreenRotationAnimation */ message ScreenRotationAnimationProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional bool started = 1; optional bool animation_running = 2; } /* represents WindowContainer */ message WindowContainerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional ConfigurationContainerProto configuration_container = 1; optional int32 orientation = 2; optional bool visible = 3; @@ -329,6 +372,8 @@ message WindowContainerProto { /* represents ConfigurationContainer */ message ConfigurationContainerProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.content.ConfigurationProto override_configuration = 1; optional .android.content.ConfigurationProto full_configuration = 2; optional .android.content.ConfigurationProto merged_override_configuration = 3; diff --git a/core/proto/android/service/battery.proto b/core/proto/android/service/battery.proto index 4cb7fd3540a58caaa1d714fb550a713dde8c17f6..34cb2292fc5f53db6a837ae9233fc88c2ef08b57 100644 --- a/core/proto/android/service/battery.proto +++ b/core/proto/android/service/battery.proto @@ -20,32 +20,16 @@ package android.service.battery; option java_multiple_files = true; option java_outer_classname = "BatteryServiceProto"; -import "frameworks/base/core/proto/android/os/batterymanager.proto"; +import "frameworks/base/core/proto/android/os/enums.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message BatteryServiceDumpProto { - enum BatteryStatus { - BATTERY_STATUS_INVALID = 0; - BATTERY_STATUS_UNKNOWN = 1; - BATTERY_STATUS_CHARGING = 2; - BATTERY_STATUS_DISCHARGING = 3; - BATTERY_STATUS_NOT_CHARGING = 4; - BATTERY_STATUS_FULL = 5; - } - enum BatteryHealth { - BATTERY_HEALTH_INVALID = 0; - BATTERY_HEALTH_UNKNOWN = 1; - BATTERY_HEALTH_GOOD = 2; - BATTERY_HEALTH_OVERHEAT = 3; - BATTERY_HEALTH_DEAD = 4; - BATTERY_HEALTH_OVER_VOLTAGE = 5; - BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6; - BATTERY_HEALTH_COLD = 7; - } + option (android.msg_privacy).dest = DEST_AUTOMATIC; // If true: UPDATES STOPPED -- use 'reset' to restart optional bool are_updates_stopped = 1; // Plugged status of power sources - optional android.os.BatteryManagerProto.PlugType plugged = 2; + optional android.os.BatteryPluggedStateEnum plugged = 2; // Max current in microamperes. This may be 0 if the device's kernel drivers // don't support it. optional int32 max_charging_current = 3; @@ -55,9 +39,9 @@ message BatteryServiceDumpProto { // Battery capacity in microampere-hours optional int32 charge_counter = 5; // Charging status - optional BatteryStatus status = 6; + optional android.os.BatteryStatusEnum status = 6; // Battery health - optional BatteryHealth health = 7; + optional android.os.BatteryHealthEnum health = 7; // True if the battery is present optional bool is_present = 8; // Charge level, from 0 through "scale" inclusive diff --git a/core/proto/android/service/batterystats.proto b/core/proto/android/service/batterystats.proto index 54d3f40130ca4e9d7a2392372f4c08635eff0c87..e31e7f3e0c1e770f3bb38d147c782e4e2ea34f85 100644 --- a/core/proto/android/service/batterystats.proto +++ b/core/proto/android/service/batterystats.proto @@ -21,7 +21,10 @@ option java_multiple_files = true; option java_outer_classname = "BatteryStatsServiceProto"; import "frameworks/base/core/proto/android/os/batterystats.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message BatteryStatsServiceDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional android.os.BatteryStatsProto batterystats = 1; } diff --git a/core/proto/android/service/diskstats.proto b/core/proto/android/service/diskstats.proto index f725e8a8c7dab35a94f65126971509d85a5895ee..3d7ee914074e684d101216c813438c25771838b9 100644 --- a/core/proto/android/service/diskstats.proto +++ b/core/proto/android/service/diskstats.proto @@ -17,10 +17,14 @@ syntax = "proto2"; package android.service.diskstats; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; option java_outer_classname = "DiskStatsServiceProto"; message DiskStatsServiceDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum EncryptionType { // Unknown encryption type ENCRYPTION_UNKNOWN = 0; @@ -34,7 +38,7 @@ message DiskStatsServiceDumpProto { // Whether the latency test resulted in an error optional bool has_test_error = 1; // If the test errored, error message is contained here - optional string error_message = 2; + optional string error_message = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; // 512B write latency in milliseconds, if the test was successful optional int32 write_512b_latency_millis = 3; // Free Space in the major partitions @@ -43,9 +47,13 @@ message DiskStatsServiceDumpProto { optional EncryptionType encryption = 5; // Cached values of folder sizes, etc. optional DiskStatsCachedValuesProto cached_folder_sizes = 6; + // Average write speed of storaged benchmark for last 24 hours + optional int32 benchmarked_write_speed_kbps = 7; } message DiskStatsCachedValuesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Total app code size, in kilobytes optional int64 agg_apps_size = 1; // Total app cache size, in kilobytes @@ -69,6 +77,8 @@ message DiskStatsCachedValuesProto { } message DiskStatsAppSizesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Name of the package optional string package_name = 1; // App's code size in kilobytes @@ -80,6 +90,8 @@ message DiskStatsAppSizesProto { } message DiskStatsFreeSpaceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum Folder { // Data folder FOLDER_DATA = 0; diff --git a/core/proto/android/service/netstats.proto b/core/proto/android/service/netstats.proto index 23613fdabf7613e3e18505de12823c5dbed629af..29fd1959fea1206da530676db09a7c89770b3dbd 100644 --- a/core/proto/android/service/netstats.proto +++ b/core/proto/android/service/netstats.proto @@ -17,11 +17,15 @@ syntax = "proto2"; package android.service; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; option java_outer_classname = "NetworkStatsServiceProto"; // Represents dumpsys from NetworkStatsService (netstats). message NetworkStatsServiceDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + repeated NetworkInterfaceProto active_interfaces = 1; repeated NetworkInterfaceProto active_uid_interfaces = 2; @@ -41,6 +45,8 @@ message NetworkStatsServiceDumpProto { // Corresponds to NetworkStatsService.mActiveIfaces/mActiveUidIfaces. message NetworkInterfaceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string interface = 1; optional NetworkIdentitySetProto identities = 2; @@ -48,25 +54,33 @@ message NetworkInterfaceProto { // Corresponds to NetworkIdentitySet. message NetworkIdentitySetProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + repeated NetworkIdentityProto identities = 1; } // Corresponds to NetworkIdentity. message NetworkIdentityProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Constats from ConnectivityManager.TYPE_*. optional int32 type = 1; - optional string subscriber_id = 2; + optional string subscriber_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; - optional string network_id = 3; + optional string network_id = 3 [ (android.privacy).dest = DEST_EXPLICIT ]; optional bool roaming = 4; optional bool metered = 5; + + optional bool default_network = 6; } // Corresponds to NetworkStatsRecorder. message NetworkStatsRecorderProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int64 pending_total_bytes = 1; optional NetworkStatsCollectionProto complete_history = 2; @@ -74,11 +88,15 @@ message NetworkStatsRecorderProto { // Corresponds to NetworkStatsCollection. message NetworkStatsCollectionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + repeated NetworkStatsCollectionStatsProto stats = 1; } // Corresponds to NetworkStatsCollection.mStats. message NetworkStatsCollectionStatsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional NetworkStatsCollectionKeyProto key = 1; optional NetworkStatsHistoryProto history = 2; @@ -86,6 +104,8 @@ message NetworkStatsCollectionStatsProto { // Corresponds to NetworkStatsCollection.Key. message NetworkStatsCollectionKeyProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional NetworkIdentitySetProto identity = 1; optional int32 uid = 2; @@ -97,6 +117,8 @@ message NetworkStatsCollectionKeyProto { // Corresponds to NetworkStatsHistory. message NetworkStatsHistoryProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Duration for this bucket in milliseconds. optional int64 bucket_duration_ms = 1; @@ -105,6 +127,8 @@ message NetworkStatsHistoryProto { // Corresponds to each bucket in NetworkStatsHistory. message NetworkStatsHistoryBucketProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Bucket start time in milliseconds since epoch. optional int64 bucket_start_ms = 1; diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index 7a0e152c84518205e8da44103d3c479356f95e72..9013a23664f69cc602f6cf385e6519a533f6a6e1 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -24,8 +24,12 @@ import "frameworks/base/core/proto/android/app/notification_channel.proto"; import "frameworks/base/core/proto/android/app/notification_channel_group.proto"; import "frameworks/base/core/proto/android/app/notificationmanager.proto"; import "frameworks/base/core/proto/android/content/component_name.proto"; +import "frameworks/base/core/proto/android/media/audioattributes.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; message NotificationServiceDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated NotificationRecordProto records = 1; optional ZenModeProto zen = 2; @@ -44,7 +48,9 @@ message NotificationServiceDumpProto { } message NotificationRecordProto { - optional string key = 1; + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string key = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; enum State { ENQUEUED = 0; @@ -53,21 +59,25 @@ message NotificationRecordProto { } optional State state = 2; optional int32 flags = 3; - optional string channelId = 4; - optional string sound = 5; - optional int32 sound_usage = 6; + optional string channelId = 4 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string sound = 5 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional .android.media.AudioAttributesProto audio_attributes = 6; optional bool can_vibrate = 7; optional bool can_show_light = 8; - optional string group_key = 9; + optional string group_key = 9 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional int32 importance = 10; } message ListenersDisablingEffectsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 hint = 1; repeated ManagedServiceInfoProto listeners = 2; } message ManagedServiceInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional android.content.ComponentNameProto component = 1; optional int32 user_id = 2; optional string service = 3; @@ -76,10 +86,14 @@ message ManagedServiceInfoProto { } message ManagedServicesProto { - optional string caption = 1; + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string caption = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; message ServiceProto { - repeated string name = 1; + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + repeated string name = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional int32 user_id = 2; optional bool is_primary = 3; } @@ -96,9 +110,13 @@ message ManagedServicesProto { } message RankingHelperProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + repeated string notification_signal_extractors = 1; message RecordProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string package = 1; // Default value is UNKNOWN_UID = USER_NULL = -10000. optional int32 uid = 2; @@ -117,16 +135,72 @@ message RankingHelperProto { repeated RecordProto records_restored_without_uid = 3; } -message ZenModeProto { - enum ZenMode { - ZEN_MODE_OFF = 0; - ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; - ZEN_MODE_NO_INTERRUPTIONS = 2; - ZEN_MODE_ALARMS = 3; +enum ZenMode { + ZEN_MODE_OFF = 0; + ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + ZEN_MODE_NO_INTERRUPTIONS = 2; + ZEN_MODE_ALARMS = 3; +} + +// An android.service.notification.Condition object. +message ConditionProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional string id = 1; + optional string summary = 2; + optional string line_1 = 3; + optional string line_2 = 4; + optional int32 icon = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + enum State { + // Indicates that Do Not Disturb should be turned off. + STATE_FALSE = 0; + // Indicates that Do Not Disturb should be turned on. + STATE_TRUE = 1; + STATE_UNKNOWN = 2; + STATE_ERROR = 3; } + optional State state = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + optional int32 flags = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; +} + +// An android.service.notification.ZenModeConfig.ZenRule object. +message ZenRuleProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + // Required for automatic (unique). + optional string id = 1; + // Required for automatic. + optional string name = 2; + // Required for automatic. + optional int64 creation_time_ms = 3 [ + (android.privacy).dest = DEST_AUTOMATIC + ]; + optional bool enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Package name, only used for manual rules. + optional string enabler = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // User manually disabled this instance. + optional bool is_snoozing = 6 [ + (android.privacy).dest = DEST_AUTOMATIC + ]; + optional ZenMode zen_mode = 7 [ + (android.privacy).dest = DEST_AUTOMATIC + ]; + + // Required for automatic. + optional string condition_id = 8; + optional ConditionProto condition = 9; + optional android.content.ComponentNameProto component = 10; +} + +// A dump from com.android.server.notification.ZenModeHelper. +message ZenModeProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional ZenMode zen_mode = 1; - repeated string enabled_active_conditions = 2; + repeated ZenRuleProto enabled_active_conditions = 2; optional int32 suppressed_effects = 3; - repeated string suppressors = 4; + repeated android.content.ComponentNameProto suppressors = 4; optional android.app.PolicyProto policy = 5; } diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index aa1a575a5c9323c086d0238465f3e6cd349e04b2..ef777de061631882956d8a7482440be07f50197e 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -18,18 +18,25 @@ syntax = "proto2"; package android.service.pm; import "frameworks/base/core/proto/android/content/featureinfo.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "PackageServiceProto"; message PackageServiceDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + message PackageShortProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Name of package. e.g. "com.android.providers.telephony". optional string name = 1; // UID for this package as assigned by Android OS. optional int32 uid = 2; } message SharedLibraryProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; // True if library path is not null (jar), false otherwise (apk) optional bool is_jar = 2; @@ -39,8 +46,10 @@ message PackageServiceDumpProto { optional string apk = 4; } message SharedUserProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 user_id = 1; - optional string name = 2; + optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; } // Installed packages. @@ -51,15 +60,22 @@ message PackageServiceDumpProto { repeated PackageProto packages = 5; repeated SharedUserProto shared_users = 6; // Messages from the settings problem file - repeated string messages = 7; + repeated string messages = 7 [ (android.privacy).dest = DEST_EXPLICIT ]; } message PackageProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + message SplitProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + // The split name of package, e.g. base optional string name = 1; optional int32 revision_code = 2; } message UserInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum InstallType { NOT_INSTALLED_FOR_USER = 0; FULL_APP_INSTALL = 1; diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto index b2e0373000128882d0dc4809aaadbfb2f556a4e4..15ede0c5e19243d8ad311a8bdb3f6876f8298cdb 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -19,6 +19,7 @@ option java_multiple_files = true; option java_outer_classname = "ProcessStatsServiceProto"; import "frameworks/base/core/proto/android/util/common.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package android.service.procstats; @@ -28,6 +29,7 @@ package android.service.procstats; * Next Tag: 4 */ message ProcessStatsServiceDumpProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional ProcessStatsSectionProto procstats_now = 1; @@ -43,6 +45,7 @@ message ProcessStatsServiceDumpProto { * Next Tag: 9 */ message ProcessStatsSectionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; // Elapsed realtime at start of report. optional int64 start_realtime_ms = 1; @@ -78,6 +81,7 @@ message ProcessStatsSectionProto { // Next Tag: 6 message ProcessStatsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; // Name of process. optional string process = 1; @@ -87,6 +91,8 @@ message ProcessStatsProto { // Information about how often kills occurred message Kill { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + // Count of excessive CPU kills optional int32 cpu = 1; @@ -99,6 +105,8 @@ message ProcessStatsProto { optional Kill kill = 3; message State { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum ScreenState { SCREEN_UNKNOWN = 0; OFF = 1; @@ -115,6 +123,8 @@ message ProcessStatsProto { } optional MemoryState memory_state = 2; + // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java + // and not frameworks/base/core/java/android/app/ActivityManager.java enum ProcessState { PROCESS_UNKNOWN = 0; // Persistent system process. @@ -127,14 +137,14 @@ message ProcessStatsProto { IMPORTANT_BACKGROUND = 4; // Performing backup operation. BACKUP = 5; - // Heavy-weight process (currently not used). - HEAVY_WEIGHT = 6; // Background process running a service. - SERVICE = 7; + SERVICE = 6; // Process not running, but would be if there was enough RAM. - SERVICE_RESTARTING = 8; + SERVICE_RESTARTING = 7; // Process running a receiver. - RECEIVER = 9; + RECEIVER = 8; + // Heavy-weight process (currently not used). + HEAVY_WEIGHT = 9; // Process hosting home/launcher app when not on top. HOME = 10; // Process hosting the last app the user was in. @@ -159,6 +169,9 @@ message ProcessStatsProto { // USS is memory shared between processes, divided evenly for accounting optional android.util.AggStats uss = 7; + + // RSS is memory resident for this process + optional android.util.AggStats rss = 8; } repeated State states = 5; } diff --git a/core/proto/android/telephony/enums.proto b/core/proto/android/telephony/enums.proto new file mode 100644 index 0000000000000000000000000000000000000000..60f8d8d78545a97252e66917294ef96d7de07081 --- /dev/null +++ b/core/proto/android/telephony/enums.proto @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.telephony; + +option java_outer_classname = "TelephonyProtoEnums"; +option java_multiple_files = true; + +// Data conn. power states, primarily used by android/telephony/DataConnectionRealTimeInfo.java. +enum DataConnectionPowerStateEnum { + DATA_CONNECTION_POWER_STATE_LOW = 1; + DATA_CONNECTION_POWER_STATE_MEDIUM = 2; + DATA_CONNECTION_POWER_STATE_HIGH = 3; + DATA_CONNECTION_POWER_STATE_UNKNOWN = 2147483647; // Java Integer.MAX_VALUE; +} + +// Signal strength levels, primarily used by android/telephony/SignalStrength.java. +enum SignalStrengthEnum { + SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; + SIGNAL_STRENGTH_POOR = 1; + SIGNAL_STRENGTH_MODERATE = 2; + SIGNAL_STRENGTH_GOOD = 3; + SIGNAL_STRENGTH_GREAT = 4; +} diff --git a/core/proto/android/util/common.proto b/core/proto/android/util/common.proto index 308ef703bddb1169cb4e9cab77744f4df51d05fd..f8f78851d74a587ec0d44a37e395f5ddb8faacdb 100644 --- a/core/proto/android/util/common.proto +++ b/core/proto/android/util/common.proto @@ -17,12 +17,15 @@ syntax = "proto2"; package android.util; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; /** * Very basic data structure used by aggregated stats. */ message AggStats { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional int64 min = 1; @@ -35,6 +38,7 @@ message AggStats { * Very basic data structure to represent Duration. */ message Duration { + option (android.msg_privacy).dest = DEST_AUTOMATIC; optional int64 start_ms = 1; diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto index fd4fa9e5e91462e30120e54f4dc578d2b53457c7..416c055fe4016aebfd989ad5c302ecf90ad31bac 100644 --- a/core/proto/android/util/log.proto +++ b/core/proto/android/util/log.proto @@ -17,11 +17,15 @@ syntax = "proto2"; package android.util; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; // Represents a Text Log in logd // Next Tag: 9 message TextLogEntry { + option (android.msg_privacy).dest = DEST_EXPLICIT; + optional uint64 sec = 1; optional uint64 nanosec = 2; @@ -47,6 +51,8 @@ message TextLogEntry { // Represents a Binary Log in logd, need to look event-log-tags for more info. // Next Tag: 8 message BinaryLogEntry { + option (android.msg_privacy).dest = DEST_EXPLICIT; + optional uint64 sec = 1; optional uint64 nanosec = 2; optional int32 uid = 3; @@ -81,6 +87,8 @@ message BinaryLogEntry { } message LogProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + repeated TextLogEntry text_logs = 1; repeated BinaryLogEntry binary_logs = 2; diff --git a/core/proto/android/view/display.proto b/core/proto/android/view/display.proto index cac083075ec30eb35b8fa6e13227f00db47c3389..30046c3bdd811b81fff2c84071f8bc441b71adec 100644 --- a/core/proto/android/view/display.proto +++ b/core/proto/android/view/display.proto @@ -20,24 +20,6 @@ package android.view; option java_multiple_files = true; message DisplayProto { - enum DisplayState { - // The display state is unknown. - DISPLAY_STATE_UNKNOWN = 0; - // The display state is off. - DISPLAY_STATE_OFF = 1; - // The display state is on. - DISPLAY_STATE_ON = 2; - // The display is dozing in a low power state; it is still on but is - // optimized for showing system-provided content while the device is - // non-interactive. - DISPLAY_STATE_DOZE = 3; - // The display is dozing in a suspended low power state; it is still on - // but is optimized for showing static system-provided content while the - // device is non-interactive. - DISPLAY_STATE_DOZE_SUSPEND = 4; - // The display is on and optimized for VR mode. - DISPLAY_STATE_VR = 5; - } enum ColorMode { COLOR_MODE_INVALID = -1; COLOR_MODE_BT601_625 = 1; diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto index 9ca404605cb596f2de41910ed05c9fe5789d05b1..3ac8f3b22fd22fad4329fab9a2e886963aaaff6f 100644 --- a/core/proto/android/view/displayinfo.proto +++ b/core/proto/android/view/displayinfo.proto @@ -17,10 +17,14 @@ syntax = "proto2"; package android.view; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + option java_multiple_files = true; /* represents DisplayInfo */ message DisplayInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 logical_width = 1; optional int32 logical_height = 2; optional int32 app_width = 3; diff --git a/core/proto/android/view/enums.proto b/core/proto/android/view/enums.proto new file mode 100644 index 0000000000000000000000000000000000000000..10785cebe239fd01774d6e4330b5d5739d34f5d6 --- /dev/null +++ b/core/proto/android/view/enums.proto @@ -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. + */ + +syntax = "proto2"; +package android.view; + +option java_outer_classname = "ViewProtoEnums"; +option java_multiple_files = true; + +// Screen states, primarily used by android/view/Display.java. +enum DisplayStateEnum { + // The display state is unknown. + DISPLAY_STATE_UNKNOWN = 0; + // The display state is off. + DISPLAY_STATE_OFF = 1; + // The display state is on. + DISPLAY_STATE_ON = 2; + // The display is dozing in a low power state; it is still on but is + // optimized for showing system-provided content while the device is + // non-interactive. + DISPLAY_STATE_DOZE = 3; + // The display is dozing in a suspended low power state; it is still on + // but is optimized for showing static system-provided content while the + // device is non-interactive. + DISPLAY_STATE_DOZE_SUSPEND = 4; + // The display is on and optimized for VR mode. + DISPLAY_STATE_VR = 5; + // The display is in a suspended full power state; it is still on but the + // CPU is not updating it. + DISPLAY_STATE_ON_SUSPEND = 6; +} diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto index b81cd1f50f91efd8df7c0ec90cd2dd261dcef62b..0362ab176666b96dbc43fc8705974ec8e5e6b621 100644 --- a/core/proto/android/view/windowlayoutparams.proto +++ b/core/proto/android/view/windowlayoutparams.proto @@ -18,12 +18,15 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/graphics/pixelformat.proto"; import "frameworks/base/core/proto/android/view/display.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package android.view; option java_multiple_files = true; /* represents WindowManager.LayoutParams */ message WindowLayoutParamsProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 type = 1; optional int32 x = 2; optional int32 y = 3; @@ -58,7 +61,6 @@ message WindowLayoutParamsProto { optional NeedsMenuState needs_menu_key = 22; optional .android.view.DisplayProto.ColorMode color_mode = 23; optional uint32 flags = 24; - optional uint64 flags_extra = 25; optional uint32 private_flags = 26; optional uint32 system_ui_visibility_flags = 27; optional uint32 subtree_system_ui_visibility_flags = 28; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index cb6701ec6d4c515a36d56104d23fdf1ed885c944..a0ba3ade4b34a85529c8b80974ffd87e1da8c11d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -327,6 +327,10 @@ + + + + @@ -510,6 +514,7 @@ + @@ -554,8 +559,6 @@ - - @@ -575,9 +578,9 @@ - - + + @@ -969,6 +972,23 @@ android:description="@string/permdesc_manageOwnCalls" android:protectionLevel="normal" /> + + + @@ -1437,6 +1457,12 @@ android:label="@string/permlab_nfc" android:protectionLevel="normal" /> + + + + + + + + + + + + @@ -2834,6 +2878,14 @@ + + + + @hide + @SystemApi --> + android:protectionLevel="signature|privileged|development" /> + @hide + @SystemApi --> + + + @@ -3182,10 +3240,14 @@ + + + +

        Not for use by third-party applications.--> @@ -3688,11 +3750,26 @@ + + + + + + + + + diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml index a67b0ca3851945e324802b00cdab248fdff78421..371bcfef7ec97d34df69654c834e1f56890ca2ac 100644 --- a/core/res/res/anim/activity_close_enter.xml +++ b/core/res/res/anim/activity_close_enter.xml @@ -17,9 +17,17 @@ */ --> - - + + + \ No newline at end of file diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml index d8c42ed808f11130f352435dcb6e64bc28809c38..d87f1003def59c2415f4f155db7fcea958f734ee 100644 --- a/core/res/res/anim/activity_close_exit.xml +++ b/core/res/res/anim/activity_close_exit.xml @@ -18,15 +18,27 @@ --> - - - \ No newline at end of file + android:shareInterpolator="false" + android:zAdjustment="top"> + + + + diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml index 1d949d2f744656b113a409c0695c5fd1e6bec5fb..cb0307026e2fe27f537a9955478f00aa9afe4b79 100644 --- a/core/res/res/anim/activity_open_enter.xml +++ b/core/res/res/anim/activity_open_enter.xml @@ -1,5 +1,4 @@ - - - - - \ No newline at end of file + android:shareInterpolator="false"> + + + diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml index 3a84197f8f63d9f9fed7fd90f32be16b014eec0d..d52b150391fba6fdbe827ae330e755f6db0ca667 100644 --- a/core/res/res/anim/activity_open_exit.xml +++ b/core/res/res/anim/activity_open_exit.xml @@ -1,5 +1,4 @@ - - - + android:shareInterpolator="false"> + + \ No newline at end of file diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml index 3254ebba2f6609eac20a5ae49255f6948b847f65..6f3dc8c7de0e85e6676d031585575b25f0b33f82 100644 --- a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml +++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml @@ -20,19 +20,54 @@ - + + + + + - + - + - - + android:shareInterpolator="false" + android:zAdjustment="top"> + + - + - + - + \ No newline at end of file diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml index b6a08070c3ce47ea7077a6d5f4857bb51bc1a3f2..ab8b89ce7b06c45e7982c80b7f69c6ee76a01d7f 100644 --- a/core/res/res/anim/task_close_exit.xml +++ b/core/res/res/anim/task_close_exit.xml @@ -1,5 +1,4 @@ - - + android:shareInterpolator="false"> - + - + + + - + \ No newline at end of file diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml index b73e14fc637f3ac724a64e5160506246a294991c..0e66eda0e29ed7e9ad0ff8a06e99f16bfa8737e4 100644 --- a/core/res/res/anim/task_open_enter.xml +++ b/core/res/res/anim/task_open_enter.xml @@ -1,5 +1,4 @@ - - + + android:shareInterpolator="false" + android:zAdjustment="top"> - + - + + + + + \ No newline at end of file diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml index ad89fde9206592de87a4f7e69137610f86a98f74..a92425e89960a78aad6283b60b038754cf77444c 100644 --- a/core/res/res/anim/task_open_enter_cross_profile_apps.xml +++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml @@ -18,24 +18,61 @@ --> + android:shareInterpolator="false" + android:zAdjustment="top"> - + - + + + + + - + \ No newline at end of file diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml index 78d0fb015ce3c21ace4adcdc3503c0e0ee0c4749..ecb98ce2cb167a9c40f8d9e1f268939a0e2cf4e2 100644 --- a/core/res/res/anim/task_open_exit.xml +++ b/core/res/res/anim/task_open_exit.xml @@ -1,5 +1,4 @@ - - + android:shareInterpolator="false"> - + - + - + - + \ No newline at end of file diff --git a/core/res/res/drawable/ic_info_outline_24.xml b/core/res/res/drawable/ic_info_outline_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..abba8cf788e648995e9762f9b4890fc7b393dcb3 --- /dev/null +++ b/core/res/res/drawable/ic_info_outline_24.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/core/res/res/drawable/ic_screenshot.xml b/core/res/res/drawable/ic_screenshot.xml index 3074b28497cf04442e6e8317563a784b770af4fb..24dd4d86edaf8dfd722d51a57664bc44f4aad4fd 100644 --- a/core/res/res/drawable/ic_screenshot.xml +++ b/core/res/res/drawable/ic_screenshot.xml @@ -17,10 +17,8 @@ Copyright (C) 2018 The Android Open Source Project android:width="24.0dp" android:height="24.0dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> - + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> diff --git a/core/res/res/drawable/messaging_user.xml b/core/res/res/drawable/messaging_user.xml new file mode 100644 index 0000000000000000000000000000000000000000..3137698ab90bd5234816010135e40ca933535ece --- /dev/null +++ b/core/res/res/drawable/messaging_user.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/core/res/res/drawable/red_shield.xml b/core/res/res/drawable/red_shield.xml new file mode 100644 index 0000000000000000000000000000000000000000..7f425c78c70800b5ec8b3bc4974dbccdcf0e1594 --- /dev/null +++ b/core/res/res/drawable/red_shield.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/core/res/res/interpolator/emphasized_deceleration.xml b/core/res/res/interpolator/activity_close_dim.xml similarity index 80% rename from core/res/res/interpolator/emphasized_deceleration.xml rename to core/res/res/interpolator/activity_close_dim.xml index 60c315c0c4d4be4d2d1be87ed5d523d3c2043ad1..faad1399ff490e6cfce0991741a17e0d92ee4cdf 100644 --- a/core/res/res/interpolator/emphasized_deceleration.xml +++ b/core/res/res/interpolator/activity_close_dim.xml @@ -1,6 +1,6 @@ \ No newline at end of file + android:controlX1="0.33" + android:controlY1="0" + android:controlX2="1" + android:controlY2="1"/> diff --git a/core/res/res/interpolator/exaggerated_ease.xml b/core/res/res/interpolator/fast_out_extra_slow_in.xml similarity index 87% rename from core/res/res/interpolator/exaggerated_ease.xml rename to core/res/res/interpolator/fast_out_extra_slow_in.xml index 4961c1c72984bddf4ca368b73f055eca815bcd81..f16057417a632b2c920b8b14333ef25cf5a5391e 100644 --- a/core/res/res/interpolator/exaggerated_ease.xml +++ b/core/res/res/interpolator/fast_out_extra_slow_in.xml @@ -16,4 +16,4 @@ --> + android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1"/> diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml index d78ce59872ffd66e7755d2b22fef779512c3aca1..c3b149a1e2959596ba3989b5b9a7e1483c9960b9 100644 --- a/core/res/res/layout/app_error_dialog.xml +++ b/core/res/res/layout/app_error_dialog.xml @@ -18,48 +18,50 @@ */ --> + +