diff options
550 files changed, 31306 insertions, 9014 deletions
diff --git a/Android.bp b/Android.bp index 2dcbc92974a9..69ee848cadad 100644 --- a/Android.bp +++ b/Android.bp @@ -256,6 +256,7 @@ java_library { "core/java/android/service/euicc/IGetEidCallback.aidl", "core/java/android/service/euicc/IGetEuiccInfoCallback.aidl", "core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl", + "core/java/android/service/euicc/IGetOtaStatusCallback.aidl", "core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl", "core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl", "core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl", @@ -721,11 +722,13 @@ gensrcs { ], srcs: [ + "core/proto/android/os/batterytype.proto", "core/proto/android/os/cpufreq.proto", "core/proto/android/os/cpuinfo.proto", "core/proto/android/os/kernelwake.proto", "core/proto/android/os/pagetypeinfo.proto", "core/proto/android/os/procrank.proto", + "core/proto/android/os/ps.proto", "core/proto/android/os/system_properties.proto", ], diff --git a/Android.mk b/Android.mk index 1f373264b0db..0e363d2491c8 100644 --- a/Android.mk +++ b/Android.mk @@ -40,6 +40,10 @@ aidl_files := \ frameworks/base/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl \ frameworks/base/telephony/java/android/telephony/ServiceState.aidl \ frameworks/base/telephony/java/android/telephony/SubscriptionInfo.aidl \ + frameworks/base/telephony/java/android/telephony/CellIdentityCdma.aidl \ + frameworks/base/telephony/java/android/telephony/CellIdentityGsm.aidl \ + frameworks/base/telephony/java/android/telephony/CellIdentityLte.aidl \ + frameworks/base/telephony/java/android/telephony/CellIdentityWcdma.aidl \ frameworks/base/telephony/java/android/telephony/CellInfo.aidl \ frameworks/base/telephony/java/android/telephony/SignalStrength.aidl \ frameworks/base/telephony/java/android/telephony/IccOpenLogicalChannelResponse.aidl \ @@ -248,12 +252,26 @@ aidl_files := \ system/netd/server/binder/android/net/UidRange.aidl \ frameworks/base/telephony/java/android/telephony/PcoData.aidl \ +aidl_parcelables := +define stubs-to-aidl-parcelables + gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/$1.aidl + aidl_parcelables += $$(gen) + $$(gen): $(call java-lib-header-files,$1) | $(HOST_OUT_EXECUTABLES)/sdkparcelables + @echo Extract SDK parcelables: $$@ + rm -f $$@ + $(HOST_OUT_EXECUTABLES)/sdkparcelables $$< $$@ +endef + +$(foreach stubs,android_stubs_current android_test_stubs_current android_system_stubs_current,\ + $(eval $(call stubs-to-aidl-parcelables,$(stubs)))) + gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl -$(gen): PRIVATE_SRC_FILES := $(aidl_files) -ALL_SDK_FILES += $(gen) -$(gen): $(aidl_files) | $(AIDL) - @echo Aidl Preprocess: $@ - $(hide) $(AIDL) --preprocess $@ $(PRIVATE_SRC_FILES) +.KATI_RESTAT: $(gen) +$(gen): $(aidl_parcelables) + @echo Combining SDK parcelables: $@ + rm -f $@.tmp + cat $^ | sort -u > $@.tmp + $(call commit-change-for-toc,$@) # the documentation # ============================================================ @@ -550,8 +568,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) @@ -587,8 +603,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_SYSTEM_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE)) @@ -625,8 +639,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_TEST_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_TEST_API_FILE)) @@ -656,9 +668,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) - # Run this for checkbuild checkbuild: doc-comment-check-docs # Check comment when you are updating the API diff --git a/api/current.txt b/api/current.txt index 1269b9fdcfda..32deabb97850 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6338,7 +6338,7 @@ package android.app.admin { method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int); method public void enableSystemApp(android.content.ComponentName, java.lang.String); method public int enableSystemApp(android.content.ComponentName, android.content.Intent); - method public android.security.AttestedKeyPair generateKeyPair(android.content.ComponentName, java.lang.String, android.security.keystore.KeyGenParameterSpec); + method public android.security.AttestedKeyPair generateKeyPair(android.content.ComponentName, java.lang.String, android.security.keystore.KeyGenParameterSpec, int); method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); method public java.util.Set<java.lang.String> getAffiliationIds(android.content.ComponentName); @@ -6368,6 +6368,7 @@ package android.app.admin { method public int getOrganizationColor(android.content.ComponentName); method public java.lang.CharSequence getOrganizationName(android.content.ComponentName); method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); + method public java.lang.String getPasswordBlacklistName(android.content.ComponentName); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -6468,6 +6469,7 @@ package android.app.admin { method public void setOrganizationColor(android.content.ComponentName, int); method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence); method public java.lang.String[] setPackagesSuspended(android.content.ComponentName, java.lang.String[], boolean); + method public boolean setPasswordBlacklist(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>); method public void setPasswordExpirationTimeout(android.content.ComponentName, long); method public void setPasswordHistoryLength(android.content.ComponentName, int); method public void setPasswordMinimumLength(android.content.ComponentName, int); @@ -6570,6 +6572,10 @@ package android.app.admin { field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1 field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 + field public static final int ID_TYPE_BASE_INFO = 1; // 0x1 + field public static final int ID_TYPE_IMEI = 4; // 0x4 + field public static final int ID_TYPE_MEID = 8; // 0x8 + field public static final int ID_TYPE_SERIAL = 2; // 0x2 field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0 field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20 @@ -6747,6 +6753,7 @@ package android.app.assist { method public java.lang.CharSequence getText(); method public int getTextBackgroundColor(); method public int getTextColor(); + method public java.lang.String getTextIdEntry(); method public int[] getTextLineBaselines(); method public int[] getTextLineCharOffsets(); method public int getTextSelectionEnd(); @@ -11092,6 +11099,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony"; field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; + field public static final java.lang.String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; field public static final deprecated java.lang.String FEATURE_TELEVISION = "android.hardware.type.television"; field public static final java.lang.String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"; field public static final java.lang.String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch"; @@ -11414,7 +11422,7 @@ package android.content.pm.crossprofile { method public android.graphics.drawable.Drawable getProfileSwitchingIcon(android.os.UserHandle); method public java.lang.CharSequence getProfileSwitchingLabel(android.os.UserHandle); method public java.util.List<android.os.UserHandle> getTargetUserProfiles(); - method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); + method public void startMainActivity(android.content.ComponentName, android.os.UserHandle); } } @@ -15485,6 +15493,7 @@ package android.hardware.camera2 { method public <T> T get(android.hardware.camera2.CameraCharacteristics.Key<T>); method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys(); method public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys(); + method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys(); method public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys(); field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES; @@ -15583,6 +15592,7 @@ package android.hardware.camera2 { method public abstract void close(); method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException; @@ -16126,6 +16136,20 @@ package android.hardware.camera2.params { field public static final int RED = 0; // 0x0 } + public final class SessionConfiguration { + ctor public SessionConfiguration(int, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler); + method public android.os.Handler getHandler(); + method public android.hardware.camera2.params.InputConfiguration getInputConfiguration(); + method public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations(); + method public android.hardware.camera2.CaptureRequest getSessionParameters(); + method public int getSessionType(); + method public android.hardware.camera2.CameraCaptureSession.StateCallback getStateCallback(); + method public void setInputConfiguration(android.hardware.camera2.params.InputConfiguration); + method public void setSessionParameters(android.hardware.camera2.CaptureRequest); + field public static final int SESSION_HIGH_SPEED = 1; // 0x1 + field public static final int SESSION_REGULAR = 0; // 0x0 + } + public final class StreamConfigurationMap { method public android.util.Size[] getHighResolutionOutputSizes(int); method public android.util.Range<java.lang.Integer>[] getHighSpeedVideoFpsRanges(); @@ -21357,6 +21381,8 @@ package android.location { method public void clearTestProviderStatus(java.lang.String); method public java.util.List<java.lang.String> getAllProviders(); method public java.lang.String getBestProvider(android.location.Criteria, boolean); + method public java.lang.String getGnssHardwareModelName(); + method public int getGnssYearOfHardware(); method public deprecated android.location.GpsStatus getGpsStatus(android.location.GpsStatus); method public android.location.Location getLastKnownLocation(java.lang.String); method public android.location.LocationProvider getProvider(java.lang.String); @@ -21392,6 +21418,7 @@ package android.location { method public void unregisterGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback); method public void unregisterGnssNavigationMessageCallback(android.location.GnssNavigationMessage.Callback); method public void unregisterGnssStatusCallback(android.location.GnssStatus.Callback); + field public static final java.lang.String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown"; field public static final java.lang.String GPS_PROVIDER = "gps"; field public static final java.lang.String KEY_LOCATION_CHANGED = "location"; field public static final java.lang.String KEY_PROVIDER_ENABLED = "providerEnabled"; @@ -21696,6 +21723,7 @@ package android.media { field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY"; field public static final java.lang.String ACTION_HDMI_AUDIO_PLUG = "android.media.action.HDMI_AUDIO_PLUG"; field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG"; + field public static final java.lang.String ACTION_MICROPHONE_MUTE_CHANGED = "android.media.action.MICROPHONE_MUTE_CHANGED"; field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED"; field public static final int ADJUST_LOWER = -1; // 0xffffffff @@ -31547,6 +31575,7 @@ package android.os { method public final boolean postAtTime(java.lang.Runnable, long); method public final boolean postAtTime(java.lang.Runnable, java.lang.Object, long); method public final boolean postDelayed(java.lang.Runnable, long); + method public final boolean postDelayed(java.lang.Runnable, java.lang.Object, long); method public final void removeCallbacks(java.lang.Runnable); method public final void removeCallbacks(java.lang.Runnable, java.lang.Object); method public final void removeCallbacksAndMessages(java.lang.Object); @@ -32272,6 +32301,7 @@ package android.os { field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; + field public static final java.lang.String DISALLOW_AIRPLANE_MODE = "no_airplane_mode"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill"; field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth"; @@ -32281,6 +32311,7 @@ package android.os { field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials"; field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time"; field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale"; + field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode"; field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn"; @@ -40677,19 +40708,19 @@ package android.telephony { } public class MbmsDownloadSession implements java.lang.AutoCloseable { - method public void cancelDownload(android.telephony.mbms.DownloadRequest); + method public int cancelDownload(android.telephony.mbms.DownloadRequest); method public void close(); method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler); method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler); - method public void download(android.telephony.mbms.DownloadRequest); + method public int download(android.telephony.mbms.DownloadRequest); method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo); method public java.io.File getTempFileRootDirectory(); method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads(); - method public void registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler); + method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler); method public void requestUpdateFileServices(java.util.List<java.lang.String>); method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest); method public void setTempFileRootDirectory(java.io.File); - method public void unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback); + method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback); field public static final java.lang.String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot"; field public static final java.lang.String EXTRA_MBMS_COMPLETED_FILE_URI = "android.telephony.extra.MBMS_COMPLETED_FILE_URI"; field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST"; @@ -40737,6 +40768,34 @@ package android.telephony { field public static final int UNKNOWN_RSSI = 99; // 0x63 } + public class NetworkScan { + method public void stop() throws android.os.RemoteException; + field public static final int ERROR_INTERRUPTED = 10002; // 0x2712 + field public static final int ERROR_INVALID_SCAN = 2; // 0x2 + field public static final int ERROR_INVALID_SCANID = 10001; // 0x2711 + field public static final int ERROR_MODEM_ERROR = 1; // 0x1 + field public static final int ERROR_MODEM_UNAVAILABLE = 3; // 0x3 + field public static final int ERROR_RADIO_INTERFACE_ERROR = 10000; // 0x2710 + field public static final int ERROR_UNSUPPORTED = 4; // 0x4 + field public static final int SUCCESS = 0; // 0x0 + } + + public final class NetworkScanRequest implements android.os.Parcelable { + ctor public NetworkScanRequest(int, android.telephony.RadioAccessSpecifier[], int, int, boolean, int, java.util.ArrayList<java.lang.String>); + method public int describeContents(); + method public boolean getIncrementalResults(); + method public int getIncrementalResultsPeriodicity(); + method public int getMaxSearchTime(); + method public java.util.ArrayList<java.lang.String> getPlmns(); + method public int getScanType(); + method public int getSearchPeriodicity(); + method public android.telephony.RadioAccessSpecifier[] getSpecifiers(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.NetworkScanRequest> CREATOR; + field public static final int SCAN_TYPE_ONE_SHOT = 0; // 0x0 + field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1 + } + public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher { ctor public PhoneNumberFormattingTextWatcher(); ctor public PhoneNumberFormattingTextWatcher(java.lang.String); @@ -40829,6 +40888,121 @@ package android.telephony { field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100 } + public final class RadioAccessSpecifier implements android.os.Parcelable { + ctor public RadioAccessSpecifier(int, int[], int[]); + method public int describeContents(); + method public int[] getBands(); + method public int[] getChannels(); + method public int getRadioAccessNetwork(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.RadioAccessSpecifier> CREATOR; + } + + public final class RadioNetworkConstants { + ctor public RadioNetworkConstants(); + } + + public static final class RadioNetworkConstants.EutranBands { + ctor public RadioNetworkConstants.EutranBands(); + field public static final int BAND_1 = 1; // 0x1 + field public static final int BAND_10 = 10; // 0xa + field public static final int BAND_11 = 11; // 0xb + field public static final int BAND_12 = 12; // 0xc + field public static final int BAND_13 = 13; // 0xd + field public static final int BAND_14 = 14; // 0xe + field public static final int BAND_17 = 17; // 0x11 + field public static final int BAND_18 = 18; // 0x12 + field public static final int BAND_19 = 19; // 0x13 + field public static final int BAND_2 = 2; // 0x2 + field public static final int BAND_20 = 20; // 0x14 + field public static final int BAND_21 = 21; // 0x15 + field public static final int BAND_22 = 22; // 0x16 + field public static final int BAND_23 = 23; // 0x17 + field public static final int BAND_24 = 24; // 0x18 + field public static final int BAND_25 = 25; // 0x19 + field public static final int BAND_26 = 26; // 0x1a + field public static final int BAND_27 = 27; // 0x1b + field public static final int BAND_28 = 28; // 0x1c + field public static final int BAND_3 = 3; // 0x3 + field public static final int BAND_30 = 30; // 0x1e + field public static final int BAND_31 = 31; // 0x1f + field public static final int BAND_33 = 33; // 0x21 + field public static final int BAND_34 = 34; // 0x22 + field public static final int BAND_35 = 35; // 0x23 + field public static final int BAND_36 = 36; // 0x24 + field public static final int BAND_37 = 37; // 0x25 + field public static final int BAND_38 = 38; // 0x26 + field public static final int BAND_39 = 39; // 0x27 + field public static final int BAND_4 = 4; // 0x4 + field public static final int BAND_40 = 40; // 0x28 + field public static final int BAND_41 = 41; // 0x29 + field public static final int BAND_42 = 42; // 0x2a + field public static final int BAND_43 = 43; // 0x2b + field public static final int BAND_44 = 44; // 0x2c + field public static final int BAND_45 = 45; // 0x2d + field public static final int BAND_46 = 46; // 0x2e + field public static final int BAND_47 = 47; // 0x2f + field public static final int BAND_48 = 48; // 0x30 + field public static final int BAND_5 = 5; // 0x5 + field public static final int BAND_6 = 6; // 0x6 + field public static final int BAND_65 = 65; // 0x41 + field public static final int BAND_66 = 66; // 0x42 + field public static final int BAND_68 = 68; // 0x44 + field public static final int BAND_7 = 7; // 0x7 + field public static final int BAND_70 = 70; // 0x46 + field public static final int BAND_8 = 8; // 0x8 + field public static final int BAND_9 = 9; // 0x9 + } + + public static final class RadioNetworkConstants.GeranBands { + ctor public RadioNetworkConstants.GeranBands(); + field public static final int BAND_450 = 3; // 0x3 + field public static final int BAND_480 = 4; // 0x4 + field public static final int BAND_710 = 5; // 0x5 + field public static final int BAND_750 = 6; // 0x6 + field public static final int BAND_850 = 8; // 0x8 + field public static final int BAND_DCS1800 = 12; // 0xc + field public static final int BAND_E900 = 10; // 0xa + field public static final int BAND_ER900 = 14; // 0xe + field public static final int BAND_P900 = 9; // 0x9 + field public static final int BAND_PCS1900 = 13; // 0xd + field public static final int BAND_R900 = 11; // 0xb + field public static final int BAND_T380 = 1; // 0x1 + field public static final int BAND_T410 = 2; // 0x2 + field public static final int BAND_T810 = 7; // 0x7 + } + + public static final class RadioNetworkConstants.RadioAccessNetworks { + ctor public RadioNetworkConstants.RadioAccessNetworks(); + field public static final int EUTRAN = 3; // 0x3 + field public static final int GERAN = 1; // 0x1 + field public static final int UTRAN = 2; // 0x2 + } + + public static final class RadioNetworkConstants.UtranBands { + ctor public RadioNetworkConstants.UtranBands(); + field public static final int BAND_1 = 1; // 0x1 + field public static final int BAND_10 = 10; // 0xa + field public static final int BAND_11 = 11; // 0xb + field public static final int BAND_12 = 12; // 0xc + field public static final int BAND_13 = 13; // 0xd + field public static final int BAND_14 = 14; // 0xe + field public static final int BAND_19 = 19; // 0x13 + field public static final int BAND_2 = 2; // 0x2 + field public static final int BAND_20 = 20; // 0x14 + field public static final int BAND_21 = 21; // 0x15 + field public static final int BAND_22 = 22; // 0x16 + field public static final int BAND_25 = 25; // 0x19 + field public static final int BAND_26 = 26; // 0x1a + field public static final int BAND_3 = 3; // 0x3 + field public static final int BAND_4 = 4; // 0x4 + field public static final int BAND_5 = 5; // 0x5 + field public static final int BAND_6 = 6; // 0x6 + field public static final int BAND_7 = 7; // 0x7 + field public static final int BAND_8 = 8; // 0x8 + field public static final int BAND_9 = 9; // 0x9 + } + public class ServiceState implements android.os.Parcelable { ctor public ServiceState(); ctor public ServiceState(android.telephony.ServiceState); @@ -41113,12 +41287,15 @@ package android.telephony { method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); + method public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback); method public void sendDialerSpecialCode(java.lang.String); method public java.lang.String sendEnvelopeWithStatus(java.lang.String); method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); method public void sendVisualVoicemailSms(java.lang.String, int, java.lang.String, android.app.PendingIntent); method public deprecated void setDataEnabled(boolean); method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String); + method public void setNetworkSelectionModeAutomatic(); + method public boolean setNetworkSelectionModeManual(java.lang.String, boolean); method public boolean setOperatorBrandOverride(java.lang.String); method public boolean setPreferredNetworkTypeToGlobal(); method public void setUserMobileDataEnabled(boolean); @@ -41216,6 +41393,17 @@ package android.telephony { method public void onReceiveUssdResponseFailed(android.telephony.TelephonyManager, java.lang.String, int); } + public final class TelephonyScanManager { + ctor public TelephonyScanManager(); + } + + public static abstract class TelephonyScanManager.NetworkScanCallback { + ctor public TelephonyScanManager.NetworkScanCallback(); + method public void onComplete(); + method public void onError(int); + method public void onResults(java.util.List<android.telephony.CellInfo>); + } + public abstract class VisualVoicemailService extends android.app.Service { ctor public VisualVoicemailService(); method public android.os.IBinder onBind(android.content.Intent); @@ -44092,6 +44280,7 @@ package android.util { method public int indexOfValue(boolean); method public int keyAt(int); method public void put(int, boolean); + method public void removeAt(int); method public int size(); method public boolean valueAt(int); } @@ -47067,6 +47256,7 @@ package android.view { method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); + method public void setTextIdEntry(java.lang.String); method public abstract void setTextLines(int[], int[]); method public abstract void setTextStyle(float, int, int, int); method public abstract void setTransformation(android.graphics.Matrix); @@ -47586,6 +47776,7 @@ package android.view.accessibility { method public java.lang.CharSequence getPackageName(); method public android.view.accessibility.AccessibilityRecord getRecord(int); method public int getRecordCount(); + method public int getWindowChanges(); method public void initFromParcel(android.os.Parcel); method public static android.view.accessibility.AccessibilityEvent obtain(int); method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent); @@ -47630,6 +47821,17 @@ package android.view.accessibility { field public static final int TYPE_WINDOWS_CHANGED = 4194304; // 0x400000 field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800 field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20 + field public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 128; // 0x80 + field public static final int WINDOWS_CHANGE_ACTIVE = 32; // 0x20 + field public static final int WINDOWS_CHANGE_ADDED = 1; // 0x1 + field public static final int WINDOWS_CHANGE_BOUNDS = 8; // 0x8 + field public static final int WINDOWS_CHANGE_CHILDREN = 512; // 0x200 + field public static final int WINDOWS_CHANGE_FOCUSED = 64; // 0x40 + field public static final int WINDOWS_CHANGE_LAYER = 16; // 0x10 + field public static final int WINDOWS_CHANGE_PARENT = 256; // 0x100 + field public static final int WINDOWS_CHANGE_PIP = 1024; // 0x400 + field public static final int WINDOWS_CHANGE_REMOVED = 2; // 0x2 + field public static final int WINDOWS_CHANGE_TITLE = 4; // 0x4 } public abstract interface AccessibilityEventSource { diff --git a/api/system-current.txt b/api/system-current.txt index a6f4314f794a..596474c2f59f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1340,9 +1340,30 @@ package android.hardware.hdmi { package android.hardware.location { + public class ContextHubClient implements java.io.Closeable { + method public void close(); + method public android.hardware.location.ContextHubInfo getAttachedHub(); + method public int sendMessageToNanoApp(android.hardware.location.NanoAppMessage); + } + + public class ContextHubClientCallback { + ctor public ContextHubClientCallback(); + method public void onHubReset(android.hardware.location.ContextHubClient); + method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage); + method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int); + method public void onNanoAppDisabled(android.hardware.location.ContextHubClient, long); + method public void onNanoAppEnabled(android.hardware.location.ContextHubClient, long); + method public void onNanoAppLoaded(android.hardware.location.ContextHubClient, long); + method public void onNanoAppUnloaded(android.hardware.location.ContextHubClient, long); + } + public class ContextHubInfo implements android.os.Parcelable { ctor public ContextHubInfo(); method public int describeContents(); + method public byte getChreApiMajorVersion(); + method public byte getChreApiMinorVersion(); + method public short getChrePatchVersion(); + method public long getChrePlatformId(); method public int getId(); method public int getMaxPacketLengthBytes(); method public android.hardware.location.MemoryRegion[] getMemoryRegions(); @@ -1362,19 +1383,27 @@ package android.hardware.location { } public final class ContextHubManager { - method public int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter); - method public int[] getContextHubHandles(); - method public android.hardware.location.ContextHubInfo getContextHubInfo(int); - method public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int); - method public int loadNanoApp(int, android.hardware.location.NanoApp); - method public int registerCallback(android.hardware.location.ContextHubManager.Callback); - method public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); - method public int sendMessage(int, int, android.hardware.location.ContextHubMessage); - method public int unloadNanoApp(int); - method public int unregisterCallback(android.hardware.location.ContextHubManager.Callback); - } - - public static abstract class ContextHubManager.Callback { + method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback, java.util.concurrent.Executor); + method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(android.hardware.location.ContextHubInfo, long); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(android.hardware.location.ContextHubInfo, long); + method public deprecated int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter); + method public deprecated int[] getContextHubHandles(); + method public deprecated android.hardware.location.ContextHubInfo getContextHubInfo(int); + method public java.util.List<android.hardware.location.ContextHubInfo> getContextHubs(); + method public deprecated android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int); + method public deprecated int loadNanoApp(int, android.hardware.location.NanoApp); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(android.hardware.location.ContextHubInfo, android.hardware.location.NanoAppBinary); + method public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(android.hardware.location.ContextHubInfo); + method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback); + method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); + method public deprecated int sendMessage(int, int, android.hardware.location.ContextHubMessage); + method public deprecated int unloadNanoApp(int); + method public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(android.hardware.location.ContextHubInfo, long); + method public deprecated int unregisterCallback(android.hardware.location.ContextHubManager.Callback); + } + + public static abstract deprecated class ContextHubManager.Callback { ctor protected ContextHubManager.Callback(); method public abstract void onMessageReceipt(int, int, android.hardware.location.ContextHubMessage); } @@ -1392,6 +1421,37 @@ package android.hardware.location { field public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubMessage> CREATOR; } + public class ContextHubTransaction<T> { + method public int getType(); + method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>, java.util.concurrent.Executor); + method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>); + method public static java.lang.String typeToString(int, boolean); + method public android.hardware.location.ContextHubTransaction.Response<T> waitForResponse(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException; + field public static final int RESULT_FAILED_AT_HUB = 5; // 0x5 + field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2 + field public static final int RESULT_FAILED_BUSY = 4; // 0x4 + field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8 + field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7 + field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6 + field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3 + field public static final int RESULT_FAILED_UNKNOWN = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public static final int TYPE_DISABLE_NANOAPP = 3; // 0x3 + field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2 + field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0 + field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4 + field public static final int TYPE_UNLOAD_NANOAPP = 1; // 0x1 + } + + public static abstract interface ContextHubTransaction.OnCompleteListener<L> { + method public abstract void onComplete(android.hardware.location.ContextHubTransaction<L>, android.hardware.location.ContextHubTransaction.Response<L>); + } + + public static class ContextHubTransaction.Response<R> { + method public R getContents(); + method public int getResult(); + } + public final class GeofenceHardware { method public boolean addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback); method public int[] getMonitoringTypes(); @@ -1508,6 +1568,25 @@ package android.hardware.location { field public static final android.os.Parcelable.Creator<android.hardware.location.NanoApp> CREATOR; } + public final class NanoAppBinary implements android.os.Parcelable { + ctor public NanoAppBinary(byte[]); + method public int describeContents(); + method public byte[] getBinary(); + method public byte[] getBinaryNoHeader(); + method public int getFlags(); + method public int getHeaderVersion(); + method public long getHwHubType(); + method public long getNanoAppId(); + method public int getNanoAppVersion(); + method public byte getTargetChreApiMajorVersion(); + method public byte getTargetChreApiMinorVersion(); + method public boolean hasValidHeader(); + method public boolean isEncrypted(); + method public boolean isSigned(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppBinary> CREATOR; + } + public class NanoAppFilter { ctor public NanoAppFilter(long, int, int, long); method public int describeContents(); @@ -1541,6 +1620,28 @@ package android.hardware.location { field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppInstanceInfo> CREATOR; } + public final class NanoAppMessage implements android.os.Parcelable { + method public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, byte[], boolean); + method public static android.hardware.location.NanoAppMessage createMessageToNanoApp(long, int, byte[]); + method public int describeContents(); + method public byte[] getMessageBody(); + method public int getMessageType(); + method public long getNanoAppId(); + method public boolean isBroadcastMessage(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR; + } + + public final class NanoAppState implements android.os.Parcelable { + ctor public NanoAppState(long, int, boolean); + method public int describeContents(); + method public long getNanoAppId(); + method public long getNanoAppVersion(); + method public boolean isEnabled(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppState> CREATOR; + } + } package android.hardware.radio { @@ -4144,11 +4245,13 @@ package android.telephony { method public java.lang.String getCdmaPrlVersion(); method public int getCurrentPhoneType(); method public int getCurrentPhoneType(int); + method public int getDataActivationState(); method public deprecated boolean getDataEnabled(); method public deprecated boolean getDataEnabled(int); method public boolean getEmergencyCallbackMode(); method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); method public android.os.Bundle getVisualVoicemailSettings(); + method public int getVoiceActivationState(); method public boolean handlePinMmi(java.lang.String); method public boolean handlePinMmiForSubscriber(int, java.lang.String); method public boolean isDataConnectivityPossible(); @@ -4160,10 +4263,12 @@ package android.telephony { method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method public boolean needsOtaServiceProvisioning(); method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); + method public void setDataActivationState(int); method public deprecated void setDataEnabled(int, boolean); method public boolean setRadio(boolean); method public boolean setRadioPower(boolean); method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean); + method public void setVoiceActivationState(int); method public deprecated void silenceRinger(); method public boolean supplyPin(java.lang.String); method public int[] supplyPinReportResult(java.lang.String); @@ -4177,6 +4282,11 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; + field public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; // 0x2 + field public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1; // 0x1 + field public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; // 0x3 + field public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; // 0x4 + field public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; // 0x0 } public abstract class VisualVoicemailService extends android.app.Service { @@ -4359,10 +4469,10 @@ package android.util { } public final class StatsManager { - method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String); - method public byte[] getData(java.lang.String); + method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); + method public byte[] getData(long); method public byte[] getMetadata(); - method public boolean removeConfiguration(java.lang.String); + method public boolean removeConfiguration(long); } } diff --git a/api/test-current.txt b/api/test-current.txt index 9a155e30d972..a0f981a7b5a9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -318,10 +318,6 @@ package android.location { method public void setType(int); } - public class LocationManager { - method public int getGnssYearOfHardware(); - } - } package android.net { diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index 716bc5f24631..f75678b7fa1e 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -32,12 +32,10 @@ import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import libcore.io.Streams; -import libcore.io.IoUtils; +import java.io.FileInputStream; +import java.io.FileOutputStream; /** * This class is a command line utility for manipulating content. A client @@ -122,13 +120,14 @@ public class Content { + "\n" + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n" + " Example:\n" - + " # cat default ringtone to a file, then pull to host\n" - + " adb shell 'content read --uri content://settings/system/ringtone >" - + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n" + + " adb shell 'content read --uri content://settings/system/ringtone_cache' > host.ogg\n" + + "\n" + + "usage: adb shell content write --uri <URI> [--user <USER_ID>]\n" + + " Example:\n" + + " adb shell 'content write --uri content://settings/system/ringtone_cache' < host.ogg\n" + "\n" + "usage: adb shell content gettype --uri <URI> [--user <USER_ID>]\n" + " Example:\n" - + " # Show the mime-type of the URI\n" + " adb shell content gettype --uri content://media/internal/audio/media/\n" + "\n"; @@ -139,6 +138,7 @@ public class Content { private static final String ARGUMENT_QUERY = "query"; private static final String ARGUMENT_CALL = "call"; private static final String ARGUMENT_READ = "read"; + private static final String ARGUMENT_WRITE = "write"; private static final String ARGUMENT_GET_TYPE = "gettype"; private static final String ARGUMENT_WHERE = "--where"; private static final String ARGUMENT_BIND = "--bind"; @@ -179,6 +179,8 @@ public class Content { return parseCallCommand(); } else if (ARGUMENT_READ.equals(operation)) { return parseReadCommand(); + } else if (ARGUMENT_WRITE.equals(operation)) { + return parseWriteCommand(); } else if (ARGUMENT_GET_TYPE.equals(operation)) { return parseGetTypeCommand(); } else { @@ -339,6 +341,25 @@ public class Content { return new ReadCommand(uri, userId); } + private WriteCommand parseWriteCommand() { + Uri uri = null; + int userId = UserHandle.USER_SYSTEM; + for (String argument; (argument = mTokenizer.nextArg())!= null;) { + if (ARGUMENT_URI.equals(argument)) { + uri = Uri.parse(argumentValueRequired(argument)); + } else if (ARGUMENT_USER.equals(argument)) { + userId = Integer.parseInt(argumentValueRequired(argument)); + } else { + throw new IllegalArgumentException("Unsupported argument: " + argument); + } + } + if (uri == null) { + throw new IllegalArgumentException("Content provider URI not specified." + + " Did you specify --uri argument?"); + } + return new WriteCommand(uri, userId); + } + public QueryCommand parseQueryCommand() { Uri uri = null; int userId = UserHandle.USER_SYSTEM; @@ -561,20 +582,21 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { - final ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null); - copy(new FileInputStream(fd.getFileDescriptor()), System.out); + try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) { + Streams.copy(new FileInputStream(fd.getFileDescriptor()), System.out); + } } + } - private static void copy(InputStream is, OutputStream os) throws IOException { - final byte[] buffer = new byte[8 * 1024]; - int read; - try { - while ((read = is.read(buffer)) > -1) { - os.write(buffer, 0, read); - } - } finally { - IoUtils.closeQuietly(is); - IoUtils.closeQuietly(os); + private static class WriteCommand extends Command { + public WriteCommand(Uri uri, int userId) { + super(uri, userId); + } + + @Override + public void onExecute(IContentProvider provider) throws Exception { + try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) { + Streams.copy(System.in, new FileOutputStream(fd.getFileDescriptor())); } } } diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp index 4bf956a9a03d..e23e80ae21e8 100644 --- a/cmds/incident_helper/src/ih_util.cpp +++ b/cmds/incident_helper/src/ih_util.cpp @@ -52,6 +52,12 @@ static inline std::string trimHeader(const std::string& s) { return toLowerStr(trimDefault(s)); } +static inline bool isNumber(const std::string& s) { + std::string::const_iterator it = s.begin(); + while (it != s.end() && std::isdigit(*it)) ++it; + return !s.empty() && it == s.end(); +} + // This is similiar to Split in android-base/file.h, but it won't add empty string static void split(const std::string& line, std::vector<std::string>& words, const trans_func& func, const std::string& delimiters) { @@ -86,24 +92,80 @@ record_t parseRecord(const std::string& line, const std::string& delimiters) { return record; } +bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) { + indices.clear(); + + size_t lastIndex = 0; + int i = 0; + while (headerNames[i] != NULL) { + string s = headerNames[i]; + lastIndex = line.find(s, lastIndex); + if (lastIndex == string::npos) { + fprintf(stderr, "Bad Task Header: %s\n", line.c_str()); + return false; + } + lastIndex += s.length(); + indices.push_back(lastIndex); + i++; + } + + return true; +} + record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) { record_t record; int lastIndex = 0; + int lastBeginning = 0; int lineSize = (int)line.size(); for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) { int idx = *it; - if (lastIndex > idx || idx > lineSize) { - record.clear(); // The indices is wrong, return empty; + if (idx <= lastIndex) { + // We saved up until lastIndex last time, so we should start at + // lastIndex + 1 this time. + idx = lastIndex + 1; + } + if (idx > lineSize) { + if (lastIndex < idx && lastIndex < lineSize) { + // There's a little bit more for us to save, which we'll do + // outside of the loop. + break; + } + // If we're past the end of the line AND we've already saved everything up to the end. + fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize); + record.clear(); // The indices are wrong, return empty. return record; } while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos); record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex))); + lastBeginning = lastIndex; lastIndex = idx; } - record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex))); + if (lineSize - lastIndex > 0) { + int beginning = lastIndex; + if (record.size() == indices.size()) { + // We've already encountered all of the columns...put whatever is + // left in the last column. + record.pop_back(); + beginning = lastBeginning; + } + record.push_back(trimDefault(line.substr(beginning, lineSize - beginning))); + } return record; } +void printRecord(const record_t& record) { + fprintf(stderr, "Record: { "); + if (record.size() == 0) { + fprintf(stderr, "}\n"); + return; + } + for(size_t i = 0; i < record.size(); ++i) { + if(i != 0) fprintf(stderr, "\", "); + fprintf(stderr, "\"%s", record[i].c_str()); + } + fprintf(stderr, "\" }\n"); +} + bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) { const auto head = line->find_first_not_of(DEFAULT_WHITESPACE); if (head == std::string::npos) return false; @@ -210,7 +272,10 @@ Table::~Table() void Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize) { - if (mFields.find(field) == mFields.end()) return; + if (mFields.find(field) == mFields.end()) { + fprintf(stderr, "Field '%s' not found", string(field).c_str()); + return; + } map<std::string, int> enu; for (int i = 0; i < enumSize; i++) { @@ -268,6 +333,8 @@ Table::insertField(ProtoOutputStream* proto, const std::string& name, const std: } } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) { proto->write(found, mEnumValuesByName[value]); + } else if (isNumber(value)) { + proto->write(found, toInt(value)); } else { return false; } diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h index 58ef29044048..b063b2fe0bba 100644 --- a/cmds/incident_helper/src/ih_util.h +++ b/cmds/incident_helper/src/ih_util.h @@ -56,12 +56,23 @@ header_t parseHeader(const std::string& line, const std::string& delimiters = DE record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE); /** + * Gets the list of end indices of each word in the line and places it in the given vector, + * clearing out the vector beforehand. These indices can be used with parseRecordByColumns. + * Will return false if there was a problem getting the indices. headerNames + * must be NULL terminated. + */ +bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line); + +/** * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters. * This function allows to parse record by its header's column position' indices, must in ascending order. * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters. */ record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE); +/** Prints record_t to stderr */ +void printRecord(const record_t& record); + /** * When the line starts/ends with the given key, the function returns true * as well as the line argument is changed to the rest trimmed part of the original. diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp index c8a0883d493c..8c6cd78d3bf2 100644 --- a/cmds/incident_helper/src/main.cpp +++ b/cmds/incident_helper/src/main.cpp @@ -16,11 +16,13 @@ #define LOG_TAG "incident_helper" +#include "parsers/BatteryTypeParser.h" #include "parsers/CpuFreqParser.h" #include "parsers/CpuInfoParser.h" #include "parsers/KernelWakesParser.h" #include "parsers/PageTypeInfoParser.h" #include "parsers/ProcrankParser.h" +#include "parsers/PsParser.h" #include "parsers/SystemPropertiesParser.h" #include <android-base/file.h> @@ -63,6 +65,10 @@ static TextParserBase* selectParser(int section) { return new CpuInfoParser(); case 2004: return new CpuFreqParser(); + case 2005: + return new PsParser(); + case 2006: + return new BatteryTypeParser(); default: return NULL; } diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp new file mode 100644 index 000000000000..ced6cf807e0d --- /dev/null +++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define LOG_TAG "incident_helper" + +#include <android/util/ProtoOutputStream.h> + +#include "frameworks/base/core/proto/android/os/batterytype.proto.h" +#include "ih_util.h" +#include "BatteryTypeParser.h" + +using namespace android::os; + +status_t +BatteryTypeParser::Parse(const int in, const int out) const +{ + Reader reader(in); + string line; + bool readLine = false; + + ProtoOutputStream proto; + + // parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + if (readLine) { + fprintf(stderr, "Multiple lines in file. Unsure what to do.\n"); + break; + } + + proto.write(BatteryTypeProto::TYPE, line); + + readLine = true; + } + + if (!reader.ok(&line)) { + fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str()); + return -1; + } + + if (!proto.flush(out)) { + fprintf(stderr, "[%s]Error writing proto back\n", this->name.string()); + return -1; + } + fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size()); + return NO_ERROR; +} diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.h b/cmds/incident_helper/src/parsers/BatteryTypeParser.h new file mode 100644 index 000000000000..ac0c098965d3 --- /dev/null +++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BATTERY_TYPE_PARSER_H +#define BATTERY_TYPE_PARSER_H + +#include "TextParserBase.h" + +using namespace android; + +/** + * Battery type parser, parses text in file + * /sys/class/power_supply/bms/battery_type. + */ +class BatteryTypeParser : public TextParserBase { +public: + BatteryTypeParser() : TextParserBase(String8("BatteryTypeParser")) {}; + ~BatteryTypeParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // BATTERY_TYPE_PARSER_H diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp index 3faca00c1b88..d73de54d8c5d 100644 --- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp +++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp @@ -49,6 +49,7 @@ CpuInfoParser::Parse(const int in, const int out) const vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions. record_t record; int nline = 0; + int diff = 0; bool nextToSwap = false; bool nextToUsage = false; @@ -107,18 +108,10 @@ CpuInfoParser::Parse(const int in, const int out) const header = parseHeader(line, "[ %]"); nextToUsage = false; - // NAME is not in the list since the last split index is default to the end of line. - const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" }; - size_t lastIndex = 0; - for (int i = 0; i < 11; i++) { - string s = headerNames[i]; - lastIndex = line.find(s, lastIndex); - if (lastIndex == string::npos) { - fprintf(stderr, "Bad Task Header: %s\n", line.c_str()); - return -1; - } - lastIndex += s.length(); - columnIndices.push_back(lastIndex); + // NAME is not in the list since we need to modify the end of the CMD index. + const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; } // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces. // for example: ... CMD NAME @@ -128,12 +121,20 @@ CpuInfoParser::Parse(const int in, const int out) const int endCMD = columnIndices.back(); columnIndices.pop_back(); columnIndices.push_back(line.find("NAME", endCMD) - 1); + // Add NAME index to complete the column list. + columnIndices.push_back(columnIndices.back() + 4); continue; } record = parseRecordByColumns(line, columnIndices); - if (record.size() != header.size()) { - fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str()); + diff = record.size() - header.size(); + if (diff < 0) { + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); continue; } diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp index ada4a5d0ffe2..cae51abbe57f 100644 --- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp +++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp @@ -47,10 +47,14 @@ KernelWakesParser::Parse(const int in, const int out) const // parse for each record, the line delimiter is \t only! record = parseRecord(line, TAB_DELIMITER); - if (record.size() != header.size()) { + if (record.size() < header.size()) { // TODO: log this to incident report! fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str()); continue; + } else if (record.size() > header.size()) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str()); + continue; } long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES); diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp new file mode 100644 index 000000000000..e9014cacfa0b --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define LOG_TAG "incident_helper" + +#include <android/util/ProtoOutputStream.h> + +#include "frameworks/base/core/proto/android/os/ps.proto.h" +#include "ih_util.h" +#include "PsParser.h" + +using namespace android::os; + +status_t PsParser::Parse(const int in, const int out) const { + Reader reader(in); + string line; + header_t header; // the header of /d/wakeup_sources + vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions. + record_t record; // retain each record + int nline = 0; + int diff = 0; + + ProtoOutputStream proto; + Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT); + const char* pcyNames[] = { "fg", "bg", "ta" }; + const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA}; + table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3); + const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" }; + const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z}; + table.addEnumTypeMap("s", sNames, sValues, 7); + + // Parse line by line + while (reader.readLine(&line)) { + if (line.empty()) continue; + + if (nline++ == 0) { + header = parseHeader(line, DEFAULT_WHITESPACE); + + const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL }; + if (!getColumnIndices(columnIndices, headerNames, line)) { + return -1; + } + + continue; + } + + record = parseRecordByColumns(line, columnIndices); + + diff = record.size() - header.size(); + if (diff < 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str()); + printRecord(record); + continue; + } else if (diff > 0) { + // TODO: log this to incident report! + fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str()); + printRecord(record); + continue; + } + + long long token = proto.start(PsDumpProto::PROCESSES); + for (int i=0; i<(int)record.size(); i++) { + if (!table.insertField(&proto, header[i], record[i])) { + fprintf(stderr, "[%s]Line %d has bad value %s of %s\n", + this->name.string(), nline, header[i].c_str(), record[i].c_str()); + } + } + proto.end(token); + } + + if (!reader.ok(&line)) { + fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str()); + return -1; + } + + if (!proto.flush(out)) { + fprintf(stderr, "[%s]Error writing proto back\n", this->name.string()); + return -1; + } + fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size()); + return NO_ERROR; +} diff --git a/cmds/incident_helper/src/parsers/PsParser.h b/cmds/incident_helper/src/parsers/PsParser.h new file mode 100644 index 000000000000..9488e40e88fe --- /dev/null +++ b/cmds/incident_helper/src/parsers/PsParser.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PS_PARSER_H +#define PS_PARSER_H + +#include "TextParserBase.h" + +/** + * PS parser, parses output of 'ps' command to protobuf. + */ +class PsParser : public TextParserBase { +public: + PsParser() : TextParserBase(String8("Ps")) {}; + ~PsParser() {}; + + virtual status_t Parse(const int in, const int out) const; +}; + +#endif // PS_PARSER_H diff --git a/cmds/incident_helper/testdata/batterytype.txt b/cmds/incident_helper/testdata/batterytype.txt new file mode 100644 index 000000000000..c763d36ad811 --- /dev/null +++ b/cmds/incident_helper/testdata/batterytype.txt @@ -0,0 +1 @@ +random_battery_type_string diff --git a/cmds/incident_helper/testdata/ps.txt b/cmds/incident_helper/testdata/ps.txt new file mode 100644 index 000000000000..72dafc2c4378 --- /dev/null +++ b/cmds/incident_helper/testdata/ps.txt @@ -0,0 +1,9 @@ +LABEL USER PID TID PPID VSZ RSS WCHAN ADDR S PRI NI RTPRIO SCH PCY TIME CMD +u:r:init:s0 root 1 1 0 15816 2636 SyS_epoll_wait 0 S 19 0 - 0 fg 00:00:01 init +u:r:kernel:s0 root 2 2 0 0 0 kthreadd 0 S 19 0 - 0 fg 00:00:00 kthreadd +u:r:surfaceflinger:s0 system 499 534 1 73940 22024 futex_wait_queue_me 0 S 42 -9 2 1 fg 00:00:00 EventThread +u:r:hal_gnss_default:s0 gps 670 2004 1 43064 7272 poll_schedule_timeout 0 S 19 0 - 0 fg 00:00:00 Loc_hal_worker +u:r:platform_app:s0:c512,c768 u0_a48 1660 1976 806 4468612 138328 binder_thread_read 0 S 35 -16 - 0 ta 00:00:00 HwBinder:1660_1 +u:r:perfd:s0 root 1939 1946 1 18132 2088 __skb_recv_datagram 7b9782fd14 S 19 0 - 0 00:00:00 perfd +u:r:perfd:s0 root 1939 1955 1 18132 2088 do_sigtimedwait 7b9782ff6c S 19 0 - 0 00:00:00 POSIX timer 0 +u:r:shell:s0 shell 2645 2645 802 11664 2972 0 7f67a2f8b4 R 19 0 - 0 fg 00:00:00 ps diff --git a/cmds/incident_helper/tests/BatteryTypeParser_test.cpp b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp new file mode 100644 index 000000000000..7fbe22df4b0a --- /dev/null +++ b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "BatteryTypeParser.h" + +#include "frameworks/base/core/proto/android/os/batterytype.pb.h" + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gmock/gmock.h> +#include <google/protobuf/message_lite.h> +#include <gtest/gtest.h> +#include <string.h> +#include <fcntl.h> + +using namespace android::base; +using namespace android::os; +using namespace std; +using ::testing::StrEq; +using ::testing::Test; +using ::testing::internal::CaptureStderr; +using ::testing::internal::CaptureStdout; +using ::testing::internal::GetCapturedStderr; +using ::testing::internal::GetCapturedStdout; + +class BatteryTypeParserTest : public Test { +public: + virtual void SetUp() override { + ASSERT_TRUE(tf.fd != -1); + } + +protected: + TemporaryFile tf; + + const string kTestPath = GetExecutableDirectory(); + const string kTestDataPath = kTestPath + "/testdata/"; +}; + +TEST_F(BatteryTypeParserTest, Success) { + const string testFile = kTestDataPath + "batterytype.txt"; + BatteryTypeParser parser; + BatteryTypeProto expected; + + expected.set_type("random_battery_type_string"); + + int fd = open(testFile.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1); + + CaptureStdout(); + ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO)); + EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString()); + close(fd); +} diff --git a/cmds/incident_helper/tests/PsParser_test.cpp b/cmds/incident_helper/tests/PsParser_test.cpp new file mode 100644 index 000000000000..1f03a7f3a332 --- /dev/null +++ b/cmds/incident_helper/tests/PsParser_test.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "PsParser.h" + +#include "frameworks/base/core/proto/android/os/ps.pb.h" + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gmock/gmock.h> +#include <google/protobuf/message_lite.h> +#include <gtest/gtest.h> +#include <string.h> +#include <fcntl.h> + +using namespace android::base; +using namespace android::os; +using namespace std; +using ::testing::StrEq; +using ::testing::Test; +using ::testing::internal::CaptureStderr; +using ::testing::internal::CaptureStdout; +using ::testing::internal::GetCapturedStderr; +using ::testing::internal::GetCapturedStdout; + +class PsParserTest : public Test { +public: + virtual void SetUp() override { + ASSERT_TRUE(tf.fd != -1); + } + +protected: + TemporaryFile tf; + + const string kTestPath = GetExecutableDirectory(); + const string kTestDataPath = kTestPath + "/testdata/"; +}; + +TEST_F(PsParserTest, Normal) { + const string testFile = kTestDataPath + "ps.txt"; + PsParser parser; + PsDumpProto expected; + PsDumpProto got; + + PsDumpProto::Process* record1 = expected.add_processes(); + record1->set_label("u:r:init:s0"); + record1->set_user("root"); + record1->set_pid(1); + record1->set_tid(1); + record1->set_ppid(0); + record1->set_vsz(15816); + record1->set_rss(2636); + record1->set_wchan("SyS_epoll_wait"); + record1->set_addr("0"); + record1->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record1->set_pri(19); + record1->set_ni(0); + record1->set_rtprio("-"); + record1->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record1->set_pcy(PsDumpProto::Process::POLICY_FG); + record1->set_time("00:00:01"); + record1->set_cmd("init"); + + PsDumpProto::Process* record2 = expected.add_processes(); + record2->set_label("u:r:kernel:s0"); + record2->set_user("root"); + record2->set_pid(2); + record2->set_tid(2); + record2->set_ppid(0); + record2->set_vsz(0); + record2->set_rss(0); + record2->set_wchan("kthreadd"); + record2->set_addr("0"); + record2->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record2->set_pri(19); + record2->set_ni(0); + record2->set_rtprio("-"); + record2->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record2->set_pcy(PsDumpProto::Process::POLICY_FG); + record2->set_time("00:00:00"); + record2->set_cmd("kthreadd"); + + PsDumpProto::Process* record3 = expected.add_processes(); + record3->set_label("u:r:surfaceflinger:s0"); + record3->set_user("system"); + record3->set_pid(499); + record3->set_tid(534); + record3->set_ppid(1); + record3->set_vsz(73940); + record3->set_rss(22024); + record3->set_wchan("futex_wait_queue_me"); + record3->set_addr("0"); + record3->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record3->set_pri(42); + record3->set_ni(-9); + record3->set_rtprio("2"); + record3->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_FIFO); + record3->set_pcy(PsDumpProto::Process::POLICY_FG); + record3->set_time("00:00:00"); + record3->set_cmd("EventThread"); + + PsDumpProto::Process* record4 = expected.add_processes(); + record4->set_label("u:r:hal_gnss_default:s0"); + record4->set_user("gps"); + record4->set_pid(670); + record4->set_tid(2004); + record4->set_ppid(1); + record4->set_vsz(43064); + record4->set_rss(7272); + record4->set_wchan("poll_schedule_timeout"); + record4->set_addr("0"); + record4->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record4->set_pri(19); + record4->set_ni(0); + record4->set_rtprio("-"); + record4->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record4->set_pcy(PsDumpProto::Process::POLICY_FG); + record4->set_time("00:00:00"); + record4->set_cmd("Loc_hal_worker"); + + PsDumpProto::Process* record5 = expected.add_processes(); + record5->set_label("u:r:platform_app:s0:c512,c768"); + record5->set_user("u0_a48"); + record5->set_pid(1660); + record5->set_tid(1976); + record5->set_ppid(806); + record5->set_vsz(4468612); + record5->set_rss(138328); + record5->set_wchan("binder_thread_read"); + record5->set_addr("0"); + record5->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record5->set_pri(35); + record5->set_ni(-16); + record5->set_rtprio("-"); + record5->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record5->set_pcy(PsDumpProto::Process::POLICY_TA); + record5->set_time("00:00:00"); + record5->set_cmd("HwBinder:1660_1"); + + PsDumpProto::Process* record6 = expected.add_processes(); + record6->set_label("u:r:perfd:s0"); + record6->set_user("root"); + record6->set_pid(1939); + record6->set_tid(1946); + record6->set_ppid(1); + record6->set_vsz(18132); + record6->set_rss(2088); + record6->set_wchan("__skb_recv_datagram"); + record6->set_addr("7b9782fd14"); + record6->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record6->set_pri(19); + record6->set_ni(0); + record6->set_rtprio("-"); + record6->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record6->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN); + record6->set_time("00:00:00"); + record6->set_cmd("perfd"); + + PsDumpProto::Process* record7 = expected.add_processes(); + record7->set_label("u:r:perfd:s0"); + record7->set_user("root"); + record7->set_pid(1939); + record7->set_tid(1955); + record7->set_ppid(1); + record7->set_vsz(18132); + record7->set_rss(2088); + record7->set_wchan("do_sigtimedwait"); + record7->set_addr("7b9782ff6c"); + record7->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S); + record7->set_pri(19); + record7->set_ni(0); + record7->set_rtprio("-"); + record7->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record7->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN); + record7->set_time("00:00:00"); + record7->set_cmd("POSIX timer 0"); + + PsDumpProto::Process* record8 = expected.add_processes(); + record8->set_label("u:r:shell:s0"); + record8->set_user("shell"); + record8->set_pid(2645); + record8->set_tid(2645); + record8->set_ppid(802); + record8->set_vsz(11664); + record8->set_rss(2972); + record8->set_wchan("0"); + record8->set_addr("7f67a2f8b4"); + record8->set_s(PsDumpProto_Process_ProcessStateCode_STATE_R); + record8->set_pri(19); + record8->set_ni(0); + record8->set_rtprio("-"); + record8->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL); + record8->set_pcy(PsDumpProto::Process::POLICY_FG); + record8->set_time("00:00:00"); + record8->set_cmd("ps"); + + int fd = open(testFile.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1); + + CaptureStdout(); + ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO)); + got.ParseFromString(GetCapturedStdout()); + bool matches = true; + + if (got.processes_size() != expected.processes_size()) { + fprintf(stderr, "Got %d processes, want %d\n", got.processes_size(), expected.processes_size()); + matches = false; + } else { + int n = got.processes_size(); + for (int i = 0; i < n; i++) { + PsDumpProto::Process g = got.processes(i); + PsDumpProto::Process e = expected.processes(i); + + if (g.label() != e.label()) { + fprintf(stderr, "prcs[%d]: Invalid label. Got %s, want %s\n", i, g.label().c_str(), e.label().c_str()); + matches = false; + } + if (g.user() != e.user()) { + fprintf(stderr, "prcs[%d]: Invalid user. Got %s, want %s\n", i, g.user().c_str(), e.user().c_str()); + matches = false; + } + if (g.pid() != e.pid()) { + fprintf(stderr, "prcs[%d]: Invalid pid. Got %d, want %d\n", i, g.pid(), e.pid()); + matches = false; + } + if (g.tid() != e.tid()) { + fprintf(stderr, "prcs[%d]: Invalid tid. Got %d, want %d\n", i, g.tid(), e.tid()); + matches = false; + } + if (g.ppid() != e.ppid()) { + fprintf(stderr, "prcs[%d]: Invalid ppid. Got %d, want %d\n", i, g.ppid(), e.ppid()); + matches = false; + } + if (g.vsz() != e.vsz()) { + fprintf(stderr, "prcs[%d]: Invalid vsz. Got %d, want %d\n", i, g.vsz(), e.vsz()); + matches = false; + } + if (g.rss() != e.rss()) { + fprintf(stderr, "prcs[%d]: Invalid rss. Got %d, want %d\n", i, g.rss(), e.rss()); + matches = false; + } + if (g.wchan() != e.wchan()) { + fprintf(stderr, "prcs[%d]: Invalid wchan. Got %s, want %s\n", i, g.wchan().c_str(), e.wchan().c_str()); + matches = false; + } + if (g.addr() != e.addr()) { + fprintf(stderr, "prcs[%d]: Invalid addr. Got %s, want %s\n", i, g.addr().c_str(), e.addr().c_str()); + matches = false; + } + if (g.s() != e.s()) { + fprintf(stderr, "prcs[%d]: Invalid s. Got %u, want %u\n", i, g.s(), e.s()); + matches = false; + } + if (g.pri() != e.pri()) { + fprintf(stderr, "prcs[%d]: Invalid pri. Got %d, want %d\n", i, g.pri(), e.pri()); + matches = false; + } + if (g.ni() != e.ni()) { + fprintf(stderr, "prcs[%d]: Invalid ni. Got %d, want %d\n", i, g.ni(), e.ni()); + matches = false; + } + if (g.rtprio() != e.rtprio()) { + fprintf(stderr, "prcs[%d]: Invalid rtprio. Got %s, want %s\n", i, g.rtprio().c_str(), e.rtprio().c_str()); + matches = false; + } + if (g.sch() != e.sch()) { + fprintf(stderr, "prcs[%d]: Invalid sch. Got %u, want %u\n", i, g.sch(), e.sch()); + matches = false; + } + if (g.pcy() != e.pcy()) { + fprintf(stderr, "prcs[%d]: Invalid pcy. Got %u, want %u\n", i, g.pcy(), e.pcy()); + matches = false; + } + if (g.time() != e.time()) { + fprintf(stderr, "prcs[%d]: Invalid time. Got %s, want %s\n", i, g.time().c_str(), e.time().c_str()); + matches = false; + } + if (g.cmd() != e.cmd()) { + fprintf(stderr, "prcs[%d]: Invalid cmd. Got %s, want %s\n", i, g.cmd().c_str(), e.cmd().c_str()); + matches = false; + } + } + } + + EXPECT_TRUE(matches); + close(fd); +} diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp index 5740b330d949..7b8cf52c8bee 100644 --- a/cmds/incident_helper/tests/ih_util_test.cpp +++ b/cmds/incident_helper/tests/ih_util_test.cpp @@ -71,11 +71,29 @@ TEST(IhUtilTest, ParseRecordByColumns) { EXPECT_EQ(expected, result); result = parseRecordByColumns("abc \t2345 6789 ", indices); - expected = { "abc", "2345", "6789" }; + expected = { "abc", "2345 6789" }; EXPECT_EQ(expected, result); - result = parseRecordByColumns("abc \t23456789 bob", indices); - expected = { "abc", "23456789", "bob" }; + std::string extraColumn1 = "abc \t23456789 bob"; + std::string emptyMidColm = "abc \t bob"; + std::string longFirstClm = "abcdefgt\t6789 bob"; + std::string lngFrstEmpty = "abcdefgt\t bob"; + + result = parseRecordByColumns(extraColumn1, indices); + expected = { "abc", "23456789 bob" }; + EXPECT_EQ(expected, result); + + // 2nd column should be treated as an empty entry. + result = parseRecordByColumns(emptyMidColm, indices); + expected = { "abc", "bob" }; + EXPECT_EQ(expected, result); + + result = parseRecordByColumns(longFirstClm, indices); + expected = { "abcdefgt", "6789 bob" }; + EXPECT_EQ(expected, result); + + result = parseRecordByColumns(lngFrstEmpty, indices); + expected = { "abcdefgt", "bob" }; EXPECT_EQ(expected, result); } diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk index fb8ef6338d90..11d3e4911761 100644 --- a/cmds/incidentd/Android.mk +++ b/cmds/incidentd/Android.mk @@ -72,9 +72,7 @@ LOCAL_GENERATED_SOURCES += $(GEN) gen_src_dir:= GEN:= -ifeq ($(BUILD_WITH_INCIDENTD_RC), true) LOCAL_INIT_RC := incidentd.rc -endif include $(BUILD_EXECUTABLE) diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 1bf795bb6557..22053ef3c53a 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -521,7 +521,7 @@ CommandSection::Execute(ReportRequestSet* requests) const ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno)); _exit(EXIT_FAILURE); } - execv(this->mCommand[0], (char *const *) this->mCommand); + execvp(this->mCommand[0], (char *const *) this->mCommand); int err = errno; // record command error code ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno)); _exit(err); // exit with command error code diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index f98ee3de2c95..a365f54bfdf0 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -20,8 +20,12 @@ statsd_common_src := \ src/stats_log.proto \ src/statsd_config.proto \ src/atoms.proto \ + src/field_util.cpp \ + src/stats_log_util.cpp \ + src/dimension.cpp \ src/anomaly/AnomalyMonitor.cpp \ src/anomaly/AnomalyTracker.cpp \ + src/anomaly/DurationAnomalyTracker.cpp \ src/condition/CombinationConditionTracker.cpp \ src/condition/condition_util.cpp \ src/condition/SimpleConditionTracker.cpp \ @@ -162,6 +166,7 @@ LOCAL_SRC_FILES := \ tests/indexed_priority_queue_test.cpp \ tests/LogEntryMatcher_test.cpp \ tests/LogReader_test.cpp \ + tests/LogEvent_test.cpp \ tests/MetricsManager_test.cpp \ tests/StatsLogProcessor_test.cpp \ tests/UidMap_test.cpp \ @@ -175,7 +180,10 @@ LOCAL_SRC_FILES := \ tests/metrics/ValueMetricProducer_test.cpp \ tests/metrics/GaugeMetricProducer_test.cpp \ tests/guardrail/StatsdStats_test.cpp \ - tests/metrics/metrics_test_helper.cpp + tests/metrics/metrics_test_helper.cpp \ + tests/statsd_test_util.cpp \ + tests/e2e/WakelockDuration_e2e_test.cpp \ + tests/e2e/MetricConditionLink_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ @@ -197,4 +205,4 @@ statsd_common_shared_libraries:= ############################## -include $(call all-makefiles-under,$(LOCAL_PATH)) +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 0b6f8f2b335d..288ebe9dbbe9 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -13,92 +13,108 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #include "HashableDimensionKey.h" +#include "dimension.h" namespace android { namespace os { namespace statsd { +android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value) { + android::hash_t hash = seed; + hash = android::JenkinsHashMix(hash, android::hash_type(value.field())); + + hash = android::JenkinsHashMix(hash, android::hash_type((int)value.value_case())); + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + hash = android::JenkinsHashMix( + hash, + static_cast<uint32_t>(std::hash<std::string>()(value.value_str()))); + break; + case DimensionsValue::ValueCase::kValueInt: + hash = android::JenkinsHashMix(hash, android::hash_type(value.value_int())); + break; + case DimensionsValue::ValueCase::kValueLong: + hash = android::JenkinsHashMix( + hash, android::hash_type(static_cast<int64_t>(value.value_long()))); + break; + case DimensionsValue::ValueCase::kValueBool: + hash = android::JenkinsHashMix(hash, android::hash_type(value.value_bool())); + break; + case DimensionsValue::ValueCase::kValueFloat: { + float floatVal = value.value_float(); + hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float)); + break; + } + case DimensionsValue::ValueCase::kValueTuple: { + hash = android::JenkinsHashMix(hash, android::hash_type( + value.value_tuple().dimensions_value_size())); + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + hash = android::JenkinsHashMix( + hash, + hashDimensionsValue(value.value_tuple().dimensions_value(i))); + } + break; + } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + break; + } + return JenkinsHashWhiten(hash); +} + +android::hash_t hashDimensionsValue(const DimensionsValue& value) { + return hashDimensionsValue(0, value); +} + using std::string; + string HashableDimensionKey::toString() const { string flattened; - for (const auto& pair : mKeyValuePairs) { - flattened += std::to_string(pair.key()); - flattened += ":"; - switch (pair.value_case()) { - case KeyValuePair::ValueCase::kValueStr: - flattened += pair.value_str(); - break; - case KeyValuePair::ValueCase::kValueInt: - flattened += std::to_string(pair.value_int()); - break; - case KeyValuePair::ValueCase::kValueLong: - flattened += std::to_string(pair.value_long()); - break; - case KeyValuePair::ValueCase::kValueBool: - flattened += std::to_string(pair.value_bool()); - break; - case KeyValuePair::ValueCase::kValueFloat: - flattened += std::to_string(pair.value_float()); - break; - default: - break; - } - flattened += "|"; - } + DimensionsValueToString(getDimensionsValue(), &flattened); return flattened; } -bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { - const auto& keyValue2 = that.getKeyValuePairs(); - if (mKeyValuePairs.size() != keyValue2.size()) { +bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) { + if (s1.field() != s2.field()) { return false; } - - for (size_t i = 0; i < keyValue2.size(); i++) { - const auto& kv1 = mKeyValuePairs[i]; - const auto& kv2 = keyValue2[i]; - if (kv1.key() != kv2.key()) { - return false; - } - - if (kv1.value_case() != kv2.value_case()) { - return false; - } - - switch (kv1.value_case()) { - case KeyValuePair::ValueCase::kValueStr: - if (kv1.value_str() != kv2.value_str()) { - return false; - } - break; - case KeyValuePair::ValueCase::kValueInt: - if (kv1.value_int() != kv2.value_int()) { - return false; - } - break; - case KeyValuePair::ValueCase::kValueLong: - if (kv1.value_long() != kv2.value_long()) { - return false; - } - break; - case KeyValuePair::ValueCase::kValueBool: - if (kv1.value_bool() != kv2.value_bool()) { + if (s1.value_case() != s1.value_case()) { + return false; + } + switch (s1.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return (s1.value_str() == s2.value_str()); + case DimensionsValue::ValueCase::kValueInt: + return s1.value_int() == s2.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return s1.value_long() == s2.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return s1.value_bool() == s2.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return s1.value_float() == s2.value_float(); + case DimensionsValue::ValueCase::kValueTuple: + { + if (s1.value_tuple().dimensions_value_size() != + s2.value_tuple().dimensions_value_size()) { return false; } - break; - case KeyValuePair::ValueCase::kValueFloat: { - if (kv1.value_float() != kv2.value_float()) { - return false; + bool allMatched = true; + for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) { + allMatched &= compareDimensionsValue(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); } - break; + return allMatched; } - case KeyValuePair::ValueCase::VALUE_NOT_SET: - break; - } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + default: + return true; } - return true; +} + +bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { + return compareDimensionsValue(getDimensionsValue(), that.getDimensionsValue()); }; bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 85215552b777..85c317f8cf1f 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -25,20 +25,20 @@ namespace statsd { class HashableDimensionKey { public: - explicit HashableDimensionKey(const std::vector<KeyValuePair>& keyValuePairs) - : mKeyValuePairs(keyValuePairs){}; + explicit HashableDimensionKey(const DimensionsValue& dimensionsValue) + : mDimensionsValue(dimensionsValue){}; HashableDimensionKey(){}; HashableDimensionKey(const HashableDimensionKey& that) - : mKeyValuePairs(that.getKeyValuePairs()){}; + : mDimensionsValue(that.getDimensionsValue()){}; HashableDimensionKey& operator=(const HashableDimensionKey& from) = default; std::string toString() const; - inline const std::vector<KeyValuePair>& getKeyValuePairs() const { - return mKeyValuePairs; + inline const DimensionsValue& getDimensionsValue() const { + return mDimensionsValue; } bool operator==(const HashableDimensionKey& that) const; @@ -50,9 +50,12 @@ public: } private: - std::vector<KeyValuePair> mKeyValuePairs; + DimensionsValue mDimensionsValue; }; +android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value); +android::hash_t hashDimensionsValue(const DimensionsValue& value); + } // namespace statsd } // namespace os } // namespace android @@ -60,42 +63,11 @@ private: namespace std { using android::os::statsd::HashableDimensionKey; -using android::os::statsd::KeyValuePair; template <> struct hash<HashableDimensionKey> { std::size_t operator()(const HashableDimensionKey& key) const { - android::hash_t hash = 0; - for (const auto& pair : key.getKeyValuePairs()) { - hash = android::JenkinsHashMix(hash, android::hash_type(pair.key())); - hash = android::JenkinsHashMix( - hash, android::hash_type(static_cast<int32_t>(pair.value_case()))); - switch (pair.value_case()) { - case KeyValuePair::ValueCase::kValueStr: - hash = android::JenkinsHashMix( - hash, - static_cast<uint32_t>(std::hash<std::string>()(pair.value_str()))); - break; - case KeyValuePair::ValueCase::kValueInt: - hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_int())); - break; - case KeyValuePair::ValueCase::kValueLong: - hash = android::JenkinsHashMix( - hash, android::hash_type(static_cast<int64_t>(pair.value_long()))); - break; - case KeyValuePair::ValueCase::kValueBool: - hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_bool())); - break; - case KeyValuePair::ValueCase::kValueFloat: { - float floatVal = pair.value_float(); - hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float)); - break; - } - case KeyValuePair::ValueCase::VALUE_NOT_SET: - break; - } - } - return hash; + return hashDimensionsValue(key.getDimensionsValue()); } }; diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 0c078d5db83c..9678014b14dc 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -54,7 +54,7 @@ const int FIELD_ID_CONFIG_KEY = 1; const int FIELD_ID_REPORTS = 2; // for ConfigKey const int FIELD_ID_UID = 1; -const int FIELD_ID_NAME = 2; +const int FIELD_ID_ID = 2; // for ConfigMetricsReport const int FIELD_ID_METRICS = 1; const int FIELD_ID_UID_MAP = 2; @@ -63,11 +63,12 @@ const int FIELD_ID_UID_MAP = 2; StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor, + const long timeBaseSec, const std::function<void(const ConfigKey&)>& sendBroadcast) : mUidMap(uidMap), mAnomalyMonitor(anomalyMonitor), mSendBroadcast(sendBroadcast), - mTimeBaseSec(time(nullptr)) { + mTimeBaseSec(timeBaseSec) { // On each initialization of StatsLogProcessor, check stats-data directory to see if there is // any left over data to be read. StorageManager::sendBroadcast(STATS_DATA_DIR, mSendBroadcast); @@ -81,6 +82,9 @@ StatsLogProcessor::~StatsLogProcessor() { void StatsLogProcessor::onAnomalyAlarmFired( const uint64_t timestampNs, unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) { + // TODO: This is a thread-safety issue. mMetricsManagers could change under our feet. + // TODO: Solution? Lock everything! :( + // TODO: Question: Can we replace the other lock (broadcast), or do we need to supplement it? for (const auto& itr : mMetricsManagers) { itr.second->onAnomalyAlarmFired(timestampNs, anomalySet); } @@ -94,7 +98,6 @@ void StatsLogProcessor::OnLogEvent(const LogEvent& msg) { pair.second->onLogEvent(msg); flushIfNecessary(msg.GetTimestampNs(), pair.first, *(pair.second)); } - // Hard-coded logic to update the isolated uid's in the uid-map. // The field numbers need to be currently updated by hand with atoms.proto if (msg.GetTagId() == android::util::ISOLATED_UID_CHANGED) { @@ -114,9 +117,7 @@ void StatsLogProcessor::OnLogEvent(const LogEvent& msg) { void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) { ALOGD("Updated configuration for key %s", key.ToString().c_str()); - sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap); - auto it = mMetricsManagers.find(key); if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) { ALOGE("Can't accept more configs!"); @@ -149,6 +150,19 @@ size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const { return it->second->byteSize(); } +void StatsLogProcessor::onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report) { + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + ALOGW("Config source %s does not exist", key.ToString().c_str()); + return; + } + report->mutable_config_key()->set_uid(key.GetUid()); + report->mutable_config_key()->set_id(key.GetId()); + ConfigMetricsReport* configMetricsReport = report->add_reports(); + it->second->onDumpReport(dumpTimeStampNs, configMetricsReport); + // TODO: dump uid mapping. +} + void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outData) { auto it = mMetricsManagers.find(key); if (it == mMetricsManagers.end()) { @@ -167,7 +181,7 @@ void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outD // Start of ConfigKey. long long configKeyToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY); proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid()); - proto.write(FIELD_TYPE_STRING | FIELD_ID_NAME, key.GetName()); + proto.write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)key.GetId()); proto.end(configKeyToken); // End of ConfigKey. @@ -264,8 +278,8 @@ void StatsLogProcessor::WriteDataToDisk() { vector<uint8_t> data; onDumpReport(key, &data); // TODO: Add a guardrail to prevent accumulation of file on disk. - string file_name = StringPrintf("%s/%d-%s-%ld", STATS_DATA_DIR, key.GetUid(), - key.GetName().c_str(), time(nullptr)); + string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(), + (long long)key.GetId(), time(nullptr)); StorageManager::writeFile(file_name.c_str(), &data[0], data.size()); } } diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 1e5c426efd02..f62fc4e31c0a 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef STATS_LOG_PROCESSOR_H -#define STATS_LOG_PROCESSOR_H +#pragma once + +#include <gtest/gtest_prod.h> #include "config/ConfigListener.h" #include "logd/LogReader.h" #include "metrics/MetricsManager.h" @@ -33,6 +34,7 @@ namespace statsd { class StatsLogProcessor : public ConfigListener { public: StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor, + const long timeBaseSec, const std::function<void(const ConfigKey&)>& sendBroadcast); virtual ~StatsLogProcessor(); @@ -44,6 +46,7 @@ public: size_t GetMetricsSize(const ConfigKey& key) const; void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData); + void onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report); /* Tells MetricsManager that the alarms in anomalySet have fired. Modifies anomalySet. */ void onAnomalyAlarmFired( @@ -81,10 +84,12 @@ private: FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); + FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); + }; } // namespace statsd } // namespace os } // namespace android - -#endif // STATS_LOG_PROCESSOR_H diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index dab3880287ee..45f1ea1bb183 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -75,7 +75,7 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) { mUidMap = new UidMap(); mConfigManager = new ConfigManager(); - mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) { + mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, time(nullptr), [this](const ConfigKey& key) { sp<IStatsCompanionService> sc = getStatsCompanionService(); auto receiver = mConfigManager->GetConfigReceiver(key); if (sc == nullptr) { @@ -335,7 +335,7 @@ status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) { print_cmd_help(out); return UNKNOWN_ERROR; } - auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, name)); + auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, StrToInt64(name))); sp<IStatsCompanionService> sc = getStatsCompanionService(); if (sc != nullptr) { sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str())); @@ -404,13 +404,13 @@ status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8 } // Add / update the config. - mConfigManager->UpdateConfig(ConfigKey(uid, name), config); + mConfigManager->UpdateConfig(ConfigKey(uid, StrToInt64(name)), config); } else { if (argCount == 2) { cmd_remove_all_configs(out); } else { // Remove the config. - mConfigManager->RemoveConfig(ConfigKey(uid, name)); + mConfigManager->RemoveConfig(ConfigKey(uid, StrToInt64(name))); } } @@ -459,7 +459,7 @@ status_t StatsService::cmd_dump_report(FILE* out, FILE* err, const Vector<String } if (good) { vector<uint8_t> data; - mProcessor->onDumpReport(ConfigKey(uid, name), &data); + mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), &data); // TODO: print the returned StatsLogReport to file instead of printing to logcat. if (proto) { for (size_t i = 0; i < data.size(); i ++) { @@ -699,12 +699,11 @@ void StatsService::OnLogEvent(const LogEvent& event) { mProcessor->OnLogEvent(event); } -Status StatsService::getData(const String16& key, vector<uint8_t>* output) { +Status StatsService::getData(int64_t key, vector<uint8_t>* output) { IPCThreadState* ipc = IPCThreadState::self(); VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); if (checkCallingPermission(String16(kPermissionDump))) { - string keyStr = string(String8(key).string()); - ConfigKey configKey(ipc->getCallingUid(), keyStr); + ConfigKey configKey(ipc->getCallingUid(), key); mProcessor->onDumpReport(configKey, output); return Status::ok(); } else { @@ -724,16 +723,18 @@ Status StatsService::getMetadata(vector<uint8_t>* output) { } } -Status StatsService::addConfiguration(const String16& key, +Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config, const String16& package, const String16& cls, bool* success) { IPCThreadState* ipc = IPCThreadState::self(); if (checkCallingPermission(String16(kPermissionDump))) { - string keyString = string(String8(key).string()); - ConfigKey configKey(ipc->getCallingUid(), keyString); + ConfigKey configKey(ipc->getCallingUid(), key); StatsdConfig cfg; - cfg.ParseFromArray(&config[0], config.size()); + if (!cfg.ParseFromArray(&config[0], config.size())) { + *success = false; + return Status::ok(); + } mConfigManager->UpdateConfig(configKey, cfg); mConfigManager->SetConfigReceiver(configKey, string(String8(package).string()), string(String8(cls).string())); @@ -745,11 +746,10 @@ Status StatsService::addConfiguration(const String16& key, } } -Status StatsService::removeConfiguration(const String16& key, bool* success) { +Status StatsService::removeConfiguration(int64_t key, bool* success) { IPCThreadState* ipc = IPCThreadState::self(); if (checkCallingPermission(String16(kPermissionDump))) { - string keyStr = string(String8(key).string()); - mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), keyStr)); + mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key)); *success = true; return Status::ok(); } else { diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 08fcdac27539..c0424f39a1fd 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -77,25 +77,27 @@ public: /** * Binder call for clients to request data for this configuration key. */ - virtual Status getData(const String16& key, vector<uint8_t>* output) override; + virtual Status getData(int64_t key, vector<uint8_t>* output) override; + /** * Binder call for clients to get metadata across all configs in statsd. */ virtual Status getMetadata(vector<uint8_t>* output) override; + /** * Binder call to let clients send a configuration and indicate they're interested when they * should requestData for this configuration. */ - virtual Status addConfiguration(const String16& key, const vector <uint8_t>& config, - const String16& package, const String16& cls, bool* success) + virtual Status addConfiguration(int64_t key, const vector <uint8_t>& config, + const String16& package, const String16& cls, bool* success) override; /** * Binder call to allow clients to remove the specified configuration. */ - virtual Status removeConfiguration(const String16& key, bool* success) override; + virtual Status removeConfiguration(int64_t key, bool* success) override; // TODO: public for testing since statsd doesn't run when system starts. Change to private // later. diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index 162a34b957f8..05c68e1fa471 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -30,17 +30,15 @@ namespace android { namespace os { namespace statsd { -// TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer -// decide and let which one it wants. // TODO: Get rid of bucketNumbers, and return to the original circular array method. AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) : mAlert(alert), mConfigKey(configKey), - mNumOfPastBuckets(mAlert.number_of_buckets() - 1) { + mNumOfPastBuckets(mAlert.num_buckets() - 1) { VLOG("AnomalyTracker() called"); - if (mAlert.number_of_buckets() <= 0) { + if (mAlert.num_buckets() <= 0) { ALOGE("Cannot create AnomalyTracker with %lld buckets", - (long long)mAlert.number_of_buckets()); + (long long)mAlert.num_buckets()); return; } if (!mAlert.has_trigger_if_sum_gt()) { @@ -52,7 +50,6 @@ AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey) AnomalyTracker::~AnomalyTracker() { VLOG("~AnomalyTracker() called"); - stopAllAlarms(); } void AnomalyTracker::resetStorage() { @@ -61,8 +58,6 @@ void AnomalyTracker::resetStorage() { // Excludes the current bucket. mPastBuckets.resize(mNumOfPastBuckets); mSumOverPastBuckets.clear(); - - if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!"); } size_t AnomalyTracker::index(int64_t bucketNum) const { @@ -205,43 +200,26 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) { return; } // TODO(guardrail): Consider guarding against too short refractory periods. - mLastAlarmTimestampNs = timestampNs; - + mLastAnomalyTimestampNs = timestampNs; // TODO: If we had access to the bucket_size_millis, consider calling resetStorage() // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); } - if (mAlert.has_incidentd_details()) { - if (mAlert.has_name()) { - ALOGW("An anomaly (%s) has occurred! Informing incidentd.", - mAlert.name().c_str()); + if (!mSubscriptions.empty()) { + if (mAlert.has_id()) { + ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id()); + informSubscribers(); } else { - // TODO: Can construct a name based on the criteria (and/or relay the criteria). - ALOGW("An anomaly (nameless) has occurred! Informing incidentd."); + ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers."); } - informIncidentd(); } else { - ALOGW("An anomaly has occurred! (But informing incidentd not requested.)"); + ALOGI("An anomaly has occurred! (But no subscriber for that alert.)"); } - StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name()); + StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(), - mConfigKey.GetName().c_str(), mAlert.name().c_str()); -} - -void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs) { - auto itr = mAlarms.find(dimensionKey); - if (itr == mAlarms.end()) { - return; - } - - if (itr->second != nullptr && - static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) { - declareAnomaly(timestampNs); - stopAlarm(dimensionKey); - } + mConfigKey.GetId(), mAlert.id()); } void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, @@ -261,91 +239,51 @@ void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs, } } -void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs) { - uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC); - if (isInRefractoryPeriod(timestampNs)) { - VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period"); - return; - } - - sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec}; - mAlarms.insert({dimensionKey, alarm}); - if (mAnomalyMonitor != nullptr) { - mAnomalyMonitor->add(alarm); - } -} - -void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) { - auto itr = mAlarms.find(dimensionKey); - if (itr != mAlarms.end()) { - mAlarms.erase(dimensionKey); - if (mAnomalyMonitor != nullptr) { - mAnomalyMonitor->remove(itr->second); - } - } +bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) const { + return mLastAnomalyTimestampNs >= 0 && + timestampNs - mLastAnomalyTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC; } -void AnomalyTracker::stopAllAlarms() { - std::set<HashableDimensionKey> keys; - for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) { - keys.insert(itr->first); - } - for (auto key : keys) { - stopAlarm(key); +void AnomalyTracker::informSubscribers() { + VLOG("informSubscribers called."); + if (mSubscriptions.empty()) { + ALOGE("Attempt to call with no subscribers."); + return; } -} - -bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) { - return mLastAlarmTimestampNs >= 0 && - timestampNs - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC; -} -void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, - unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { - - if (firedAlarms.empty() || mAlarms.empty()) return; - // Find the intersection of firedAlarms and mAlarms. - // The for loop is inefficient, since it loops over all keys, but that's okay since it is very - // seldomly called. The alternative would be having AnomalyAlarms store information about the - // AnomalyTracker and key, but that's a lot of data overhead to speed up something that is - // rarely ever called. - unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; - for (const auto& kv : mAlarms) { - if (firedAlarms.count(kv.second) > 0) { - matchedAlarms.insert({kv.first, kv.second}); + std::set<int> incidentdSections; + for (const Subscription& subscription : mSubscriptions) { + switch (subscription.subscriber_information_case()) { + case Subscription::SubscriberInformationCase::kIncidentdDetails: + for (int i = 0; i < subscription.incidentd_details().section_size(); i++) { + incidentdSections.insert(subscription.incidentd_details().section(i)); + } + break; + case Subscription::SubscriberInformationCase::kPerfettoDetails: + ALOGW("Perfetto reports not implemented."); + break; + default: + break; } } - - // Now declare each of these alarms to have fired. - for (const auto& kv : matchedAlarms) { - declareAnomaly(timestampNs /* TODO: , kv.first */); - mAlarms.erase(kv.first); - firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it. - } -} - -void AnomalyTracker::informIncidentd() { - VLOG("informIncidentd called."); - if (!mAlert.has_incidentd_details()) { - ALOGE("Attempted to call incidentd without any incidentd_details."); - return; - } - sp<IIncidentManager> service = interface_cast<IIncidentManager>( - defaultServiceManager()->getService(android::String16("incident"))); - if (service == NULL) { - ALOGW("Couldn't get the incident service."); - return; - } - - IncidentReportArgs incidentReport; - const Alert::IncidentdDetails& details = mAlert.incidentd_details(); - for (int i = 0; i < details.section_size(); i++) { - incidentReport.addSection(details.section(i)); + if (!incidentdSections.empty()) { + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service != NULL) { + IncidentReportArgs incidentReport; + for (const auto section : incidentdSections) { + incidentReport.addSection(section); + } + int64_t alertId = mAlert.id(); + std::vector<uint8_t> header; + uint8_t* src = static_cast<uint8_t*>(static_cast<void*>(&alertId)); + header.insert(header.end(), src, src + sizeof(int64_t)); + incidentReport.addHeader(header); + service->reportIncident(incidentReport); + } else { + ALOGW("Couldn't get the incident service."); + } } - // TODO: Pass in mAlert.name() into the addHeader? - - service->reportIncident(incidentReport); } } // namespace statsd diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index 874add2ba798..2d5ab867da00 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -40,6 +40,11 @@ public: virtual ~AnomalyTracker(); + // Add subscriptions that depend on this alert. + void addSubscription(const Subscription& subscription) { + mSubscriptions.push_back(subscription); + } + // Adds a bucket. // Bucket index starts from 0. void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum); @@ -61,23 +66,11 @@ public: const HashableDimensionKey& key, const int64_t& currentBucketValue); - // Starts the alarm at the given timestamp. - void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime); - // Stops the alarm. - void stopAlarm(const HashableDimensionKey& dimensionKey); - - // Stop all the alarms owned by this tracker. - void stopAllAlarms(); - - // Init the anmaly monitor which is shared across anomaly trackers. - inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) { - mAnomalyMonitor = anomalyMonitor; + // Init the AnomalyMonitor which is shared across anomaly trackers. + virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) { + return; // Base AnomalyTracker class has no need for the AnomalyMonitor. } - // Declares the anomaly when the alarm expired given the current timestamp. - void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, - const uint64_t& timestampNs); - // Helper function to return the sum value of past buckets at given dimension. int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const; @@ -89,9 +82,9 @@ public: return mAlert.trigger_if_sum_gt(); } - // Helper function to return the last alarm timestamp. - inline int64_t getLastAlarmTimestampNs() const { - return mLastAlarmTimestampNs; + // Helper function to return the timestamp of the last detected anomaly. + inline int64_t getLastAnomalyTimestampNs() const { + return mLastAnomalyTimestampNs; } inline int getNumOfPastBuckets() const { @@ -100,18 +93,18 @@ public: // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker, // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor. - // TODO: This will actually be called from a different thread, so make it thread-safe! - // TODO: Consider having AnomalyMonitor have a reference to each relevant MetricProducer - // instead of calling it from a chain starting at StatsLogProcessor. - void informAlarmsFired(const uint64_t& timestampNs, - unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms); + virtual void informAlarmsFired(const uint64_t& timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { + return; // The base AnomalyTracker class doesn't have alarms. + } protected: - void flushPastBuckets(const int64_t& currBucketNum); - // statsd_config.proto Alert message that defines this tracker. const Alert mAlert; + // The subscriptions that depend on this alert. + std::vector<Subscription> mSubscriptions; + // A reference to the Alert's config key. const ConfigKey& mConfigKey; @@ -119,14 +112,7 @@ protected: // for the anomaly detection (since the current bucket is not in the past). int mNumOfPastBuckets; - // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they - // are still active. - std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms; - - // Anomaly alarm monitor. - sp<AnomalyMonitor> mAnomalyMonitor; - - // The exisiting bucket list. + // The existing bucket list. std::vector<shared_ptr<DimToValMap>> mPastBuckets; // Sum over all existing buckets cached in mPastBuckets. @@ -136,7 +122,9 @@ protected: int64_t mMostRecentBucketNum = -1; // The timestamp when the last anomaly was declared. - int64_t mLastAlarmTimestampNs = -1; + int64_t mLastAnomalyTimestampNs = -1; + + void flushPastBuckets(const int64_t& currBucketNum); // Add the information in the given bucket to mSumOverPastBuckets. void addBucketToSum(const shared_ptr<DimToValMap>& bucket); @@ -145,26 +133,21 @@ protected: // and remove any items with value 0. void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket); - bool isInRefractoryPeriod(const uint64_t& timestampNs); + bool isInRefractoryPeriod(const uint64_t& timestampNs) const; // Calculates the corresponding bucket index within the circular array. size_t index(int64_t bucketNum) const; // Resets all bucket data. For use when all the data gets stale. - void resetStorage(); + virtual void resetStorage(); - // Informs the incident service that an anomaly has occurred. - void informIncidentd(); + // Informs the subscribers that an anomaly has occurred. + void informSubscribers(); FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection); - FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); - FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); }; } // namespace statsd diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp new file mode 100644 index 000000000000..d30810fc800d --- /dev/null +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include "DurationAnomalyTracker.h" +#include "guardrail/StatsdStats.h" + +namespace android { +namespace os { +namespace statsd { + +DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey) + : AnomalyTracker(alert, configKey) { +} + +DurationAnomalyTracker::~DurationAnomalyTracker() { + stopAllAlarms(); +} + +void DurationAnomalyTracker::resetStorage() { + AnomalyTracker::resetStorage(); + if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!"); +} + +void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, + const uint64_t& timestampNs) { + auto itr = mAlarms.find(dimensionKey); + if (itr == mAlarms.end()) { + return; + } + + if (itr->second != nullptr && + static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) { + declareAnomaly(timestampNs); + stopAlarm(dimensionKey); + } +} + +void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey, + const uint64_t& timestampNs) { + + uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC); + if (isInRefractoryPeriod(timestampNs)) { + VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period"); + return; + } + sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec}; + mAlarms.insert({dimensionKey, alarm}); + if (mAnomalyMonitor != nullptr) { + mAnomalyMonitor->add(alarm); + } +} + +void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) { + auto itr = mAlarms.find(dimensionKey); + if (itr != mAlarms.end()) { + mAlarms.erase(dimensionKey); + if (mAnomalyMonitor != nullptr) { + mAnomalyMonitor->remove(itr->second); + } + } +} + +void DurationAnomalyTracker::stopAllAlarms() { + std::set<HashableDimensionKey> keys; + for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) { + keys.insert(itr->first); + } + for (auto key : keys) { + stopAlarm(key); + } +} + +void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) { + + if (firedAlarms.empty() || mAlarms.empty()) return; + // Find the intersection of firedAlarms and mAlarms. + // The for loop is inefficient, since it loops over all keys, but that's okay since it is very + // seldomly called. The alternative would be having AnomalyAlarms store information about the + // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is + // rarely ever called. + unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms; + for (const auto& kv : mAlarms) { + if (firedAlarms.count(kv.second) > 0) { + matchedAlarms.insert({kv.first, kv.second}); + } + } + + // Now declare each of these alarms to have fired. + for (const auto& kv : matchedAlarms) { + declareAnomaly(timestampNs /* TODO: , kv.first */); + mAlarms.erase(kv.first); + firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it. + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h new file mode 100644 index 000000000000..182ce3ba74c0 --- /dev/null +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "AnomalyMonitor.h" +#include "AnomalyTracker.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unordered_map; + +class DurationAnomalyTracker : public virtual AnomalyTracker { +public: + DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey); + + virtual ~DurationAnomalyTracker(); + + // Starts the alarm at the given timestamp. + void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime); + + // Stops the alarm. + void stopAlarm(const HashableDimensionKey& dimensionKey); + + // Stop all the alarms owned by this tracker. + void stopAllAlarms(); + + // Init the AnomalyMonitor which is shared across anomaly trackers. + void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) override { + mAnomalyMonitor = anomalyMonitor; + } + + // Declares the anomaly when the alarm expired given the current timestamp. + void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, + const uint64_t& timestampNs); + + // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker + // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor. + // TODO: This will actually be called from a different thread, so make it thread-safe! + // This means that almost every function in DurationAnomalyTracker needs to be locked. + // But this should be done at the level of StatsLogProcessor, which needs to lock + // mMetricsMangers anyway. + void informAlarmsFired(const uint64_t& timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override; + +protected: + // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they + // are still active. + std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms; + + // Anomaly alarm monitor. + sp<AnomalyMonitor> mAnomalyMonitor; + + // Resets all bucket data. For use when all the data gets stale. + void resetStorage() override; + + FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection); + FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 1c6d9b09a839..221a55438f73 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -104,24 +104,18 @@ message Atom { } /** - * An attribution represents an application or module that is part of process where a particular bit - * of work is done. + * This proto represents a node of an attribution chain. + * Note: All attribution chains are represented as a repeated field of type + * AttributionNode. It is understood that in such arrays, the order is that + * of calls, that is [A, B, C] if A calls B that calls C. */ -message Attribution { - // The uid for an application or module. +message AttributionNode { + // The uid for a given element in the attribution chain. optional int32 uid = 1; - // The string tag for the attribution node. - optional string tag = 2; -} -/** - * An attribution chain represents the chained attributions of applications or modules that - * resulted in a particular bit of work being done. - * The ordering of the attributions is that of calls, that is uid = [A, B, C] if A calls B that - * calls C. - */ -message AttributionChain { - repeated Attribution attribution = 1; + // The (optional) string tag for an element in the attribution chain. If the + // element has no tag, it is encoded as an empty string. + optional string tag = 2; } /* @@ -151,7 +145,7 @@ message AttributionChain { */ message AttributionChainDummyAtom { - optional AttributionChain attribution_chain = 1; + repeated AttributionNode attribution_node = 1; optional int32 value = 2; } @@ -421,8 +415,7 @@ message CameraStateChanged { * TODO */ message WakelockStateChanged { - // TODO: Add attribution instead of uid. - optional int32 uid = 1; + repeated AttributionNode attribution_node = 1; // Type of wakelock. enum Type { @@ -780,13 +773,14 @@ message SettingChanged { * frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java */ message ActivityForegroundStateChanged { + optional int32 uid = 1; + optional string pkg_name = 2; + optional string class_name = 3; + enum Activity { MOVE_TO_BACKGROUND = 0; MOVE_TO_FOREGROUND = 1; } - optional int32 uid = 1; - optional string pkg_name = 2; - optional string class_name = 3; optional Activity activity = 4; } @@ -851,11 +845,11 @@ message AnomalyDetected { // Uid that owns the config whose anomaly detection alert fired. optional int32 config_uid = 1; - // Name of the config whose anomaly detection alert fired. - optional string config_name = 2; + // Id of the config whose anomaly detection alert fired. + optional int64 config_id = 2; - // Name of the alert (i.e. name of the anomaly that was detected). - optional string alert_name = 3; + // Id of the alert (i.e. name of the anomaly that was detected). + optional int64 alert_id = 3; } /** diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index bb4b817a820d..afa26f6da08a 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -30,20 +30,20 @@ using std::unique_ptr; using std::unordered_map; using std::vector; -CombinationConditionTracker::CombinationConditionTracker(const string& name, const int index) - : ConditionTracker(name, index) { - VLOG("creating CombinationConditionTracker %s", mName.c_str()); +CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index) + : ConditionTracker(id, index) { + VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId); } CombinationConditionTracker::~CombinationConditionTracker() { - VLOG("~CombinationConditionTracker() %s", mName.c_str()); + VLOG("~CombinationConditionTracker() %lld", (long long)mConditionId); } bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConfig, const vector<sp<ConditionTracker>>& allConditionTrackers, - const unordered_map<string, int>& conditionNameIndexMap, + const unordered_map<int64_t, int>& conditionIdIndexMap, vector<bool>& stack) { - VLOG("Combination predicate init() %s", mName.c_str()); + VLOG("Combination predicate init() %lld", (long long)mConditionId); if (mInitialized) { return true; } @@ -62,11 +62,11 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf return false; } - for (string child : combinationCondition.predicate()) { - auto it = conditionNameIndexMap.find(child); + for (auto child : combinationCondition.predicate()) { + auto it = conditionIdIndexMap.find(child); - if (it == conditionNameIndexMap.end()) { - ALOGW("Predicate %s not found in the config", child.c_str()); + if (it == conditionIdIndexMap.end()) { + ALOGW("Predicate %lld not found in the config", (long long)child); return false; } @@ -79,13 +79,13 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf } bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers, - conditionNameIndexMap, stack); + conditionIdIndexMap, stack); if (!initChildSucceeded) { - ALOGW("Child initialization failed %s ", child.c_str()); + ALOGW("Child initialization failed %lld ", (long long)child); return false; } else { - ALOGW("Child initialization success %s ", child.c_str()); + ALOGW("Child initialization success %lld ", (long long)child); } mChildren.push_back(childIndex); @@ -103,7 +103,7 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf } void CombinationConditionTracker::isConditionMet( - const map<string, HashableDimensionKey>& conditionParameters, + const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) const { for (const int childIndex : mChildren) { @@ -154,8 +154,8 @@ void CombinationConditionTracker::evaluateCondition( } } nonSlicedConditionCache[mIndex] = ConditionState::kUnknown; - ALOGD("CombinationPredicate %s sliced may changed? %d", mName.c_str(), - conditionChangedCache[mIndex] == true); + ALOGD("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId, + conditionChangedCache[mIndex] == true); } } diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h index 93369144ecd6..dfd3837f31f4 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -26,13 +26,13 @@ namespace statsd { class CombinationConditionTracker : public virtual ConditionTracker { public: - CombinationConditionTracker(const std::string& name, const int index); + CombinationConditionTracker(const int64_t& id, const int index); ~CombinationConditionTracker(); bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<std::string, int>& conditionNameIndexMap, + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack) override; void evaluateCondition(const LogEvent& event, @@ -41,7 +41,7 @@ public: std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) override; - void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters, + void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, std::vector<ConditionState>& conditionCache) const override; diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 6f66ad685ccf..773860f429b1 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -32,8 +32,8 @@ namespace statsd { class ConditionTracker : public virtual RefBase { public: - ConditionTracker(const std::string& name, const int index) - : mName(name), + ConditionTracker(const int64_t& id, const int index) + : mConditionId(id), mIndex(index), mInitialized(false), mTrackerIndex(), @@ -42,17 +42,19 @@ public: virtual ~ConditionTracker(){}; + inline const int64_t& getId() { return mConditionId; } + // Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also // be done in the constructor, but we do it separately because (1) easy to return a bool to // indicate whether the initialization is successful. (2) makes unit test easier. // allConditionConfig: the list of all Predicate config from statsd_config. // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also // need to call init() on children conditions) - // conditionNameIndexMap: the mapping from condition name to its index. + // conditionIdIndexMap: the mapping from condition id to its index. // stack: a bit map to keep track which nodes have been visited on the stack in the recursion. virtual bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<std::string, int>& conditionNameIndexMap, + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack) = 0; // evaluate current condition given the new event. @@ -83,7 +85,7 @@ public: // done recursively // [conditionCache]: the cache holding the condition evaluation values. virtual void isConditionMet( - const std::map<std::string, HashableDimensionKey>& conditionParameters, + const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, std::vector<ConditionState>& conditionCache) const = 0; @@ -97,9 +99,7 @@ public: } protected: - // We don't really need the string name, but having a name here makes log messages - // easy to debug. - const std::string mName; + const int64_t mConditionId; // the index of this condition in the manager's condition list. const int mIndex; diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp index 411f7e510c3b..d99c2ccd1fda 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -24,7 +24,7 @@ using std::string; using std::vector; ConditionState ConditionWizard::query(const int index, - const map<string, HashableDimensionKey>& parameters) { + const ConditionKey& parameters) { vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated); mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache); diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h index 30a368412c8e..4ff5c07e210f 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -19,6 +19,7 @@ #include "ConditionTracker.h" #include "condition_util.h" +#include "stats_util.h" namespace android { namespace os { @@ -40,7 +41,7 @@ public: // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. virtual ConditionState query( const int conditionIndex, - const std::map<std::string, HashableDimensionKey>& conditionParameters); + const ConditionKey& conditionParameters); private: std::vector<sp<ConditionTracker>> mAllConditions; diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index a63bc042284a..25257213a5d0 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -33,17 +33,17 @@ using std::unordered_map; using std::vector; SimpleConditionTracker::SimpleConditionTracker( - const ConfigKey& key, const string& name, const int index, + const ConfigKey& key, const int64_t& id, const int index, const SimplePredicate& simplePredicate, - const unordered_map<string, int>& trackerNameIndexMap) - : ConditionTracker(name, index), mConfigKey(key) { - VLOG("creating SimpleConditionTracker %s", mName.c_str()); + const unordered_map<int64_t, int>& trackerNameIndexMap) + : ConditionTracker(id, index), mConfigKey(key) { + VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId); mCountNesting = simplePredicate.count_nesting(); if (simplePredicate.has_start()) { auto pair = trackerNameIndexMap.find(simplePredicate.start()); if (pair == trackerNameIndexMap.end()) { - ALOGW("Start matcher %s not found in the config", simplePredicate.start().c_str()); + ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); return; } mStartLogMatcherIndex = pair->second; @@ -55,7 +55,7 @@ SimpleConditionTracker::SimpleConditionTracker( if (simplePredicate.has_stop()) { auto pair = trackerNameIndexMap.find(simplePredicate.stop()); if (pair == trackerNameIndexMap.end()) { - ALOGW("Stop matcher %s not found in the config", simplePredicate.stop().c_str()); + ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop()); return; } mStopLogMatcherIndex = pair->second; @@ -67,7 +67,7 @@ SimpleConditionTracker::SimpleConditionTracker( if (simplePredicate.has_stop_all()) { auto pair = trackerNameIndexMap.find(simplePredicate.stop_all()); if (pair == trackerNameIndexMap.end()) { - ALOGW("Stop all matcher %s not found in the config", simplePredicate.stop().c_str()); + ALOGW("Stop all matcher %lld found in the config", (long long)simplePredicate.stop_all()); return; } mStopAllLogMatcherIndex = pair->second; @@ -76,10 +76,9 @@ SimpleConditionTracker::SimpleConditionTracker( mStopAllLogMatcherIndex = -1; } - mOutputDimension.insert(mOutputDimension.begin(), simplePredicate.dimension().begin(), - simplePredicate.dimension().end()); + mOutputDimensions = simplePredicate.dimensions(); - if (mOutputDimension.size() > 0) { + if (mOutputDimensions.child_size() > 0) { mSliced = true; } @@ -100,15 +99,15 @@ SimpleConditionTracker::~SimpleConditionTracker() { bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig, const vector<sp<ConditionTracker>>& allConditionTrackers, - const unordered_map<string, int>& conditionNameIndexMap, + const unordered_map<int64_t, int>& conditionIdIndexMap, vector<bool>& stack) { // SimpleConditionTracker does not have dependency on other conditions, thus we just return // if the initialization was successful. return mInitialized; } -void print(map<HashableDimensionKey, int>& conditions, const string& name) { - VLOG("%s DUMP:", name.c_str()); +void print(map<HashableDimensionKey, int>& conditions, const int64_t& id) { + VLOG("%lld DUMP:", (long long)id); for (const auto& pair : conditions) { VLOG("\t%s : %d", pair.first.c_str(), pair.second); } @@ -136,10 +135,11 @@ bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 1. Report the tuple count if the tuple count > soft limit if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mSlicedConditionState.size() + 1; - StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("Predicate %s dropping data for dimension key %s", mName.c_str(), newKey.c_str()); + ALOGE("Predicate %lld dropping data for dimension key %s", + (long long)mConditionId, newKey.c_str()); return true; } } @@ -150,6 +150,14 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou bool matchStart, std::vector<ConditionState>& conditionCache, std::vector<bool>& conditionChangedCache) { + if ((int)conditionChangedCache.size() <= mIndex) { + ALOGE("handleConditionEvent: param conditionChangedCache not initialized."); + return; + } + if ((int)conditionCache.size() <= mIndex) { + ALOGE("handleConditionEvent: param conditionCache not initialized."); + return; + } bool changed = false; auto outputIt = mSlicedConditionState.find(outputKey); ConditionState newCondition; @@ -215,13 +223,13 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou // dump all dimensions for debugging if (DEBUG) { - print(mSlicedConditionState, mName); + print(mSlicedConditionState, mConditionId); } conditionChangedCache[mIndex] = changed; conditionCache[mIndex] = newCondition; - VLOG("SimplePredicate %s nonSlicedChange? %d", mName.c_str(), + VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId, conditionChangedCache[mIndex] == true); } @@ -232,7 +240,8 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, vector<bool>& conditionChangedCache) { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. - VLOG("Yes, already evaluated, %s %d", mName.c_str(), conditionCache[mIndex]); + VLOG("Yes, already evaluated, %lld %d", + (long long)mConditionId, conditionCache[mIndex]); return; } @@ -278,37 +287,65 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, return; } - // outputKey is the output key values. e.g, uid:1234 - const HashableDimensionKey outputKey(getDimensionKey(event, mOutputDimension)); - handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache); + // outputKey is the output values. e.g, uid:1234 + const std::vector<DimensionsValue> outputValues = getDimensionKeys(event, mOutputDimensions); + if (outputValues.size() == 0) { + // The original implementation would generate an empty string dimension hash when condition + // is not sliced. + handleConditionEvent( + DEFAULT_DIMENSION_KEY, matchedState == 1, conditionCache, conditionChangedCache); + } else if (outputValues.size() == 1) { + handleConditionEvent(HashableDimensionKey(outputValues[0]), matchedState == 1, + conditionCache, conditionChangedCache); + } else { + // If this event has multiple nodes in the attribution chain, this log event probably will + // generate multiple dimensions. If so, we will find if the condition changes for any + // dimension and ask the corresponding metric producer to verify whether the actual sliced + // condition has changed or not. + // A high level assumption is that a predicate is either sliced or unsliced. We will never + // have both sliced and unsliced version of a predicate. + for (const DimensionsValue& outputValue : outputValues) { + vector<ConditionState> dimensionalConditionCache(conditionCache.size(), + ConditionState::kNotEvaluated); + vector<bool> dimensionalConditionChangedCache(conditionChangedCache.size(), false); + + handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1, + dimensionalConditionCache, dimensionalConditionChangedCache); + + OrConditionState(dimensionalConditionCache, &conditionCache); + OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache); + } + } } void SimpleConditionTracker::isConditionMet( - const map<string, HashableDimensionKey>& conditionParameters, + const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) const { - const auto pair = conditionParameters.find(mName); - HashableDimensionKey key = - (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second; + const auto pair = conditionParameters.find(mConditionId); - if (pair == conditionParameters.end() && mOutputDimension.size() > 0) { - ALOGE("Predicate %s output has dimension, but it's not specified in the query!", - mName.c_str()); + if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) { + ALOGE("Predicate %lld output has dimension, but it's not specified in the query!", + (long long)mConditionId); conditionCache[mIndex] = mInitialValue; return; } - - VLOG("simplePredicate %s query key: %s", mName.c_str(), key.c_str()); - - auto startedCountIt = mSlicedConditionState.find(key); - if (startedCountIt == mSlicedConditionState.end()) { - conditionCache[mIndex] = mInitialValue; - } else { - conditionCache[mIndex] = - startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + std::vector<HashableDimensionKey> defaultKeys = {DEFAULT_DIMENSION_KEY}; + const std::vector<HashableDimensionKey> &keys = + (pair == conditionParameters.end()) ? defaultKeys : pair->second; + + ConditionState conditionState = ConditionState::kNotEvaluated; + for (const auto& key : keys) { + auto startedCountIt = mSlicedConditionState.find(key); + if (startedCountIt != mSlicedConditionState.end()) { + conditionState = conditionState | + (startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse); + } else { + conditionState = conditionState | mInitialValue; + } } - - VLOG("Predicate %s return %d", mName.c_str(), conditionCache[mIndex]); + conditionCache[mIndex] = conditionState; + VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); } } // namespace statsd diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h index 644d84c76591..815b445a8c5b 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -29,15 +29,15 @@ namespace statsd { class SimpleConditionTracker : public virtual ConditionTracker { public: - SimpleConditionTracker(const ConfigKey& key, const std::string& name, const int index, + SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const int index, const SimplePredicate& simplePredicate, - const std::unordered_map<std::string, int>& trackerNameIndexMap); + const std::unordered_map<int64_t, int>& trackerNameIndexMap); ~SimpleConditionTracker(); bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<std::string, int>& conditionNameIndexMap, + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack) override; void evaluateCondition(const LogEvent& event, @@ -46,7 +46,7 @@ public: std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) override; - void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters, + void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, std::vector<ConditionState>& conditionCache) const override; @@ -66,7 +66,7 @@ private: ConditionState mInitialValue; - std::vector<KeyMatcher> mOutputDimension; + FieldMatcher mOutputDimensions; std::map<HashableDimensionKey, int> mSlicedConditionState; diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp index 53ef9d58b889..a5aee73ce84f 100644 --- a/cmds/statsd/src/condition/condition_util.cpp +++ b/cmds/statsd/src/condition/condition_util.cpp @@ -27,6 +27,7 @@ #include "ConditionTracker.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "stats_util.h" +#include "dimension.h" namespace android { namespace os { @@ -93,23 +94,106 @@ ConditionState evaluateCombinationCondition(const std::vector<int>& children, return newCondition; } -HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event, - const MetricConditionLink& link) { - vector<KeyMatcher> eventKey; - eventKey.reserve(link.key_in_what().size()); +ConditionState operator|(ConditionState l, ConditionState r) { + return l >= r ? l : r; +} + +void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored) { + if (ref.size() != ored->size()) { + return; + } + for (size_t i = 0; i < ored->size(); ++i) { + ored->at(i) = ored->at(i) | ref.at(i); + } +} - for (const auto& key : link.key_in_what()) { - eventKey.push_back(key); +void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored) { + if (ref.size() != ored->size()) { + return; + } + for (size_t i = 0; i < ored->size(); ++i) { + ored->at(i) = ored->at(i) | ref.at(i); + } +} + +void getFieldsFromFieldMatcher(const FieldMatcher& matcher, const Field& parentField, + std::vector<Field> *allFields) { + Field newParent = parentField; + Field* leaf = getSingleLeaf(&newParent); + leaf->set_field(matcher.field()); + if (matcher.child_size() == 0) { + allFields->push_back(newParent); + return; + } + for (int i = 0; i < matcher.child_size(); ++i) { + leaf->add_child(); + getFieldsFromFieldMatcher(matcher.child(i), newParent, allFields); } +} + +void getFieldsFromFieldMatcher(const FieldMatcher& matcher, std::vector<Field> *allFields) { + Field parentField; + getFieldsFromFieldMatcher(matcher, parentField, allFields); +} - vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey); +void flattenValueLeaves(const DimensionsValue& value, + std::vector<DimensionsValue> *allLaves) { + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::kValueInt: + case DimensionsValue::ValueCase::kValueLong: + case DimensionsValue::ValueCase::kValueBool: + case DimensionsValue::ValueCase::kValueFloat: + case DimensionsValue::ValueCase::VALUE_NOT_SET: + allLaves->push_back(value); + break; + case DimensionsValue::ValueCase::kValueTuple: + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + flattenValueLeaves(value.value_tuple().dimensions_value(i), allLaves); + } + break; + } +} - for (int i = 0; i < link.key_in_what_size(); i++) { - auto& kv = dimensionKey[i]; - kv.set_key(link.key_in_condition(i).key()); +std::vector<HashableDimensionKey> getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link) { + std::vector<Field> whatFields; + getFieldsFromFieldMatcher(link.dimensions_in_what(), &whatFields); + std::vector<Field> conditionFields; + getFieldsFromFieldMatcher(link.dimensions_in_condition(), &conditionFields); + + std::vector<HashableDimensionKey> hashableDimensionKeys; + + // TODO(yanglu): here we could simplify the logic to get the leaf value node in what and + // directly construct the full condition value tree. + std::vector<DimensionsValue> whatValues = getDimensionKeys(event, link.dimensions_in_what()); + + for (size_t i = 0; i < whatValues.size(); ++i) { + std::vector<DimensionsValue> whatLeaves; + flattenValueLeaves(whatValues[i], &whatLeaves); + if (whatLeaves.size() != whatFields.size() || + whatLeaves.size() != conditionFields.size()) { + ALOGE("Dimensions between what and condition not equal."); + return hashableDimensionKeys; + } + FieldValueMap conditionValueMap; + for (size_t j = 0; j < whatLeaves.size(); ++j) { + if (!setFieldInLeafValueProto(conditionFields[j], &whatLeaves[j])) { + ALOGE("Not able to reset the field for condition leaf value."); + return hashableDimensionKeys; + } + conditionValueMap.insert(std::make_pair(conditionFields[j], whatLeaves[j])); + } + std::vector<DimensionsValue> conditionValues; + findDimensionsValues(conditionValueMap, link.dimensions_in_condition(), &conditionValues); + if (conditionValues.size() != 1) { + ALOGE("Not able to find unambiguous field value in condition atom."); + continue; + } + hashableDimensionKeys.push_back(HashableDimensionKey(conditionValues[0])); } - return HashableDimensionKey(dimensionKey); + return hashableDimensionKeys; } } // namespace statsd diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h index 934c2076ddfa..598027b7e366 100644 --- a/cmds/statsd/src/condition/condition_util.h +++ b/cmds/statsd/src/condition/condition_util.h @@ -32,12 +32,16 @@ enum ConditionState { kTrue = 1, }; +ConditionState operator|(ConditionState l, ConditionState r); +void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored); +void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored); + ConditionState evaluateCombinationCondition(const std::vector<int>& children, const LogicalOperation& operation, const std::vector<ConditionState>& conditionCache); -HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event, - const MetricConditionLink& link); +std::vector<HashableDimensionKey> getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/config/ConfigKey.cpp b/cmds/statsd/src/config/ConfigKey.cpp index a365dc0b9189..d791f8632f3c 100644 --- a/cmds/statsd/src/config/ConfigKey.cpp +++ b/cmds/statsd/src/config/ConfigKey.cpp @@ -27,10 +27,10 @@ using std::ostringstream; ConfigKey::ConfigKey() { } -ConfigKey::ConfigKey(const ConfigKey& that) : mName(that.mName), mUid(that.mUid) { +ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) { } -ConfigKey::ConfigKey(int uid, const string& name) : mName(name), mUid(uid) { +ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) { } ConfigKey::~ConfigKey() { @@ -38,10 +38,21 @@ ConfigKey::~ConfigKey() { string ConfigKey::ToString() const { ostringstream out; - out << '(' << mUid << ',' << mName << ')'; + out << '(' << mUid << ',' << mId << ')'; return out.str(); } + +int64_t StrToInt64(const string& str) { + char* endp; + int64_t value; + value = strtoll(str.c_str(), &endp, 0); + if (endp == str.c_str() || *endp != '\0') { + value = 0; + } + return value; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/config/ConfigKey.h b/cmds/statsd/src/config/ConfigKey.h index 3489c43c8052..3ad0eed3f2b9 100644 --- a/cmds/statsd/src/config/ConfigKey.h +++ b/cmds/statsd/src/config/ConfigKey.h @@ -37,14 +37,14 @@ class ConfigKey { public: ConfigKey(); explicit ConfigKey(const ConfigKey& that); - ConfigKey(int uid, const string& name); + ConfigKey(int uid, const int64_t& id); ~ConfigKey(); inline int GetUid() const { return mUid; } - inline const string& GetName() const { - return mName; + inline const int64_t& GetId() const { + return mId; } inline bool operator<(const ConfigKey& that) const { @@ -54,17 +54,17 @@ public: if (mUid > that.mUid) { return false; } - return mName < that.mName; + return mId < that.mId; }; inline bool operator==(const ConfigKey& that) const { - return mUid == that.mUid && mName == that.mName; + return mUid == that.mUid && mId == that.mId; }; string ToString() const; private: - string mName; + int64_t mId; int mUid; }; @@ -72,6 +72,8 @@ inline ostream& operator<<(ostream& os, const ConfigKey& config) { return os << config.ToString(); } +int64_t StrToInt64(const string& str); + } // namespace statsd } // namespace os } // namespace android @@ -87,7 +89,7 @@ using android::os::statsd::ConfigKey; template <> struct hash<ConfigKey> { std::size_t operator()(const ConfigKey& key) const { - return (7 * key.GetUid()) ^ ((hash<string>()(key.GetName()))); + return (7 * key.GetUid()) ^ ((hash<long long>()(key.GetId()))); } }; diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index cb3f3d634695..de75c71d36c2 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -103,7 +103,7 @@ void ConfigManager::RemoveConfig(const ConfigKey& key) { } void ConfigManager::remove_saved_configs(const ConfigKey& key) { - string prefix = StringPrintf("%d-%s", key.GetUid(), key.GetName().c_str()); + string prefix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId()); StorageManager::deletePrefixedFiles(STATS_SERVICE_DIR, prefix.c_str()); } @@ -173,7 +173,7 @@ void ConfigManager::Dump(FILE* out) { fprintf(out, "CONFIGURATIONS (%d)\n", (int)mConfigs.size()); fprintf(out, " uid name\n"); for (const auto& key : mConfigs) { - fprintf(out, " %6d %s\n", key.GetUid(), key.GetName().c_str()); + fprintf(out, " %6d %lld\n", key.GetUid(), (long long)key.GetId()); auto receiverIt = mConfigReceivers.find(key); if (receiverIt != mConfigReceivers.end()) { fprintf(out, " -> received by %s, %s\n", receiverIt->second.first.c_str(), @@ -189,8 +189,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-%s-%ld", STATS_SERVICE_DIR, key.GetUid(), - key.GetName().c_str(), time(nullptr)); + string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_SERVICE_DIR, key.GetUid(), + (long long)key.GetId(), time(nullptr)); const int numBytes = config.ByteSize(); vector<uint8_t> buffer(numBytes); config.SerializeToArray(&buffer[0], numBytes); @@ -200,7 +200,7 @@ void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfi StatsdConfig build_fake_config() { // HACK: Hard code a test metric for counting screen on events... StatsdConfig config; - config.set_name("CONFIG_12345"); + config.set_id(12345); int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier. int WAKE_LOCK_UID_KEY_ID = 1; @@ -209,7 +209,7 @@ StatsdConfig build_fake_config() { int WAKE_LOCK_ACQUIRE_VALUE = 1; int WAKE_LOCK_RELEASE_VALUE = 0; - int APP_USAGE_ID = 12345; + int APP_USAGE_TAG_ID = 12345; int APP_USAGE_UID_KEY_ID = 1; int APP_USAGE_STATE_KEY = 2; int APP_USAGE_FOREGROUND = 1; @@ -232,14 +232,14 @@ StatsdConfig build_fake_config() { // Count Screen ON events. CountMetric* metric = config.add_count_metric(); - metric->set_name("METRIC_1"); - metric->set_what("SCREEN_TURNED_ON"); + metric->set_id(1); // METRIC_1 + metric->set_what(102); // "SCREEN_TURNED_ON" metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); // Anomaly threshold for screen-on count. // TODO(b/70627390): Uncomment once the bug is fixed. /*Alert* alert = config.add_alert(); - alert->set_name("ALERT_1"); + alert->set_id("ALERT_1"); alert->set_metric_name("METRIC_1"); alert->set_number_of_buckets(6); alert->set_trigger_if_sum_gt(10); @@ -256,17 +256,18 @@ StatsdConfig build_fake_config() { // Count process state changes, slice by uid. metric = config.add_count_metric(); - metric->set_name("METRIC_2"); - metric->set_what("PROCESS_STATE_CHANGE"); + metric->set_id(2); // "METRIC_2" + metric->set_what(104); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY); + FieldMatcher* dimensions = metric->mutable_dimensions(); + dimensions->set_field(UID_PROCESS_STATE_TAG_ID); + dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY); // Anomaly threshold for background count. // TODO(b/70627390): Uncomment once the bug is fixed. /* alert = config.add_alert(); - alert->set_name("ALERT_2"); + alert->set_id("ALERT_2"); alert->set_metric_name("METRIC_2"); alert->set_number_of_buckets(4); alert->set_trigger_if_sum_gt(30); @@ -277,79 +278,95 @@ StatsdConfig build_fake_config() { // Count process state changes, slice by uid, while SCREEN_IS_OFF metric = config.add_count_metric(); - metric->set_name("METRIC_3"); - metric->set_what("PROCESS_STATE_CHANGE"); + metric->set_id(3); + metric->set_what(104); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - keyMatcher = metric->add_dimension(); - keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY); - metric->set_condition("SCREEN_IS_OFF"); + + dimensions = metric->mutable_dimensions(); + dimensions->set_field(UID_PROCESS_STATE_TAG_ID); + dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY); + metric->set_condition(202); // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background metric = config.add_count_metric(); - metric->set_name("METRIC_4"); - metric->set_what("APP_GET_WL"); + metric->set_id(4); + metric->set_what(107); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - keyMatcher = metric->add_dimension(); - keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID); - metric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + dimensions = metric->mutable_dimensions(); + dimensions->set_field(WAKE_LOCK_TAG_ID); + dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + + + metric->set_condition(204); MetricConditionLink* link = metric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // Duration of an app holding any wl, while screen on and app in background, slice by uid DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_5"); + durationMetric->set_id(5); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - keyMatcher = durationMetric->add_dimension(); - keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID); - durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); - durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + dimensions = durationMetric->mutable_dimensions(); + dimensions->set_field(WAKE_LOCK_TAG_ID); + dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + durationMetric->set_what(205); + durationMetric->set_condition(204); link = durationMetric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // max Duration of an app holding any wl, while screen on and app in background, slice by uid durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_6"); + durationMetric->set_id(6); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - keyMatcher = durationMetric->add_dimension(); - keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID); - durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); - durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + dimensions = durationMetric->mutable_dimensions(); + dimensions->set_field(WAKE_LOCK_TAG_ID); + dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + durationMetric->set_what(205); + durationMetric->set_condition(204); link = durationMetric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); // Duration of an app holding any wl, while screen on and app in background durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_7"); + durationMetric->set_id(7); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); - durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON"); + durationMetric->set_what(205); + durationMetric->set_condition(204); link = durationMetric->add_links(); - link->set_condition("APP_IS_BACKGROUND"); - link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID); - link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + link->set_condition(203); + link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID); + link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID); + link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID); + // Duration of screen on time. durationMetric = config.add_duration_metric(); - durationMetric->set_name("METRIC_8"); + durationMetric->set_id(8); durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - durationMetric->set_what("SCREEN_IS_ON"); + durationMetric->set_what(201); // Anomaly threshold for background count. // TODO(b/70627390): Uncomment once the bug is fixed. /* alert = config.add_alert(); - alert->set_name("ALERT_8"); - alert->set_metric_name("METRIC_8"); + alert->set_id(308); + alert->set_metric_id(8); alert->set_number_of_buckets(4); alert->set_trigger_if_sum_gt(2000000000); // 2 seconds alert->set_refractory_period_secs(120); @@ -358,142 +375,148 @@ StatsdConfig build_fake_config() { // Value metric to count KERNEL_WAKELOCK when screen turned on ValueMetric* valueMetric = config.add_value_metric(); - valueMetric->set_name("METRIC_6"); - valueMetric->set_what("KERNEL_WAKELOCK"); - valueMetric->set_value_field(KERNEL_WAKELOCK_COUNT_KEY); - valueMetric->set_condition("SCREEN_IS_ON"); - keyMatcher = valueMetric->add_dimension(); - keyMatcher->set_key(KERNEL_WAKELOCK_NAME_KEY); + valueMetric->set_id(11); + valueMetric->set_what(109); + valueMetric->mutable_value_field()->set_field(KERNEL_WAKELOCK_TAG_ID); + valueMetric->mutable_value_field()->add_child()->set_field(KERNEL_WAKELOCK_COUNT_KEY); + valueMetric->set_condition(201); + dimensions = valueMetric->mutable_dimensions(); + dimensions->set_field(KERNEL_WAKELOCK_TAG_ID); + dimensions->add_child()->set_field(KERNEL_WAKELOCK_NAME_KEY); // This is for testing easier. We should never set bucket size this small. valueMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L); // Add an EventMetric to log process state change events. EventMetric* eventMetric = config.add_event_metric(); - eventMetric->set_name("METRIC_9"); - eventMetric->set_what("SCREEN_TURNED_ON"); + eventMetric->set_id(9); + eventMetric->set_what(102); // "SCREEN_TURNED_ON" // Add an GaugeMetric. GaugeMetric* gaugeMetric = config.add_gauge_metric(); - gaugeMetric->set_name("METRIC_10"); - gaugeMetric->set_what("DEVICE_TEMPERATURE"); - gaugeMetric->mutable_gauge_fields()->add_field_num(DEVICE_TEMPERATURE_KEY); + gaugeMetric->set_id(10); + gaugeMetric->set_what(101); + auto gaugeFieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(DEVICE_TEMPERATURE_TAG_ID); + gaugeFieldMatcher->add_child()->set_field(DEVICE_TEMPERATURE_KEY); gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L); - // Event matchers............ + // Event matchers. AtomMatcher* temperatureAtomMatcher = config.add_atom_matcher(); - temperatureAtomMatcher->set_name("DEVICE_TEMPERATURE"); - temperatureAtomMatcher->mutable_simple_atom_matcher()->set_tag( + temperatureAtomMatcher->set_id(101); // "DEVICE_TEMPERATURE" + temperatureAtomMatcher->mutable_simple_atom_matcher()->set_atom_id( DEVICE_TEMPERATURE_TAG_ID); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_TURNED_ON"); + eventMatcher->set_id(102); // "SCREEN_TURNED_ON" SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID); - KeyValueMatcher* keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY); - keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE); + simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID); + FieldValueMatcher* fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY); + fieldValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_TURNED_OFF"); + eventMatcher->set_id(103); // "SCREEN_TURNED_OFF" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY); - keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE); + simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY); + fieldValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("PROCESS_STATE_CHANGE"); + eventMatcher->set_id(104); // "PROCESS_STATE_CHANGE" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(UID_PROCESS_STATE_TAG_ID); + simpleAtomMatcher->set_atom_id(UID_PROCESS_STATE_TAG_ID); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_GOES_BACKGROUND"); + eventMatcher->set_id(105); // "APP_GOES_BACKGROUND" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(APP_USAGE_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY); - keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND); + simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(APP_USAGE_STATE_KEY); + fieldValueMatcher->set_eq_int(APP_USAGE_BACKGROUND); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_GOES_FOREGROUND"); + eventMatcher->set_id(106); // "APP_GOES_FOREGROUND" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(APP_USAGE_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY); - keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND); + simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(APP_USAGE_STATE_KEY); + fieldValueMatcher->set_eq_int(APP_USAGE_FOREGROUND); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_GET_WL"); + eventMatcher->set_id(107); // "APP_GET_WL" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY); - keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE); + simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY); + fieldValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("APP_RELEASE_WL"); + eventMatcher->set_id(108); //"APP_RELEASE_WL" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID); - keyValueMatcher = simpleAtomMatcher->add_key_value_matcher(); - keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY); - keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE); + simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID); + fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY); + fieldValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE); // pulled events eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("KERNEL_WAKELOCK"); + eventMatcher->set_id(109); // "KERNEL_WAKELOCK" simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(KERNEL_WAKELOCK_TAG_ID); + simpleAtomMatcher->set_atom_id(KERNEL_WAKELOCK_TAG_ID); // Predicates............. Predicate* predicate = config.add_predicate(); - predicate->set_name("SCREEN_IS_ON"); + predicate->set_id(201); // "SCREEN_IS_ON" SimplePredicate* simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("SCREEN_TURNED_ON"); - simplePredicate->set_stop("SCREEN_TURNED_OFF"); + simplePredicate->set_start(102); // "SCREEN_TURNED_ON" + simplePredicate->set_stop(103); simplePredicate->set_count_nesting(false); predicate = config.add_predicate(); - predicate->set_name("SCREEN_IS_OFF"); + predicate->set_id(202); // "SCREEN_IS_OFF" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("SCREEN_TURNED_OFF"); - simplePredicate->set_stop("SCREEN_TURNED_ON"); + simplePredicate->set_start(103); + simplePredicate->set_stop(102); // "SCREEN_TURNED_ON" simplePredicate->set_count_nesting(false); predicate = config.add_predicate(); - predicate->set_name("APP_IS_BACKGROUND"); + predicate->set_id(203); // "APP_IS_BACKGROUND" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("APP_GOES_BACKGROUND"); - simplePredicate->set_stop("APP_GOES_FOREGROUND"); - KeyMatcher* predicate_dimension1 = simplePredicate->add_dimension(); - predicate_dimension1->set_key(APP_USAGE_UID_KEY_ID); + simplePredicate->set_start(105); + simplePredicate->set_stop(106); + FieldMatcher* predicate_dimension1 = simplePredicate->mutable_dimensions(); + predicate_dimension1->set_field(APP_USAGE_TAG_ID); + predicate_dimension1->add_child()->set_field(APP_USAGE_UID_KEY_ID); simplePredicate->set_count_nesting(false); predicate = config.add_predicate(); - predicate->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON"); + predicate->set_id(204); // "APP_IS_BACKGROUND_AND_SCREEN_ON" Predicate_Combination* combination_predicate = predicate->mutable_combination(); combination_predicate->set_operation(LogicalOperation::AND); - combination_predicate->add_predicate("APP_IS_BACKGROUND"); - combination_predicate->add_predicate("SCREEN_IS_ON"); + combination_predicate->add_predicate(203); + combination_predicate->add_predicate(201); predicate = config.add_predicate(); - predicate->set_name("WL_HELD_PER_APP_PER_NAME"); + predicate->set_id(205); // "WL_HELD_PER_APP_PER_NAME" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("APP_GET_WL"); - simplePredicate->set_stop("APP_RELEASE_WL"); - KeyMatcher* predicate_dimension = simplePredicate->add_dimension(); - predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID); - predicate_dimension = simplePredicate->add_dimension(); - predicate_dimension->set_key(WAKE_LOCK_NAME_KEY); + simplePredicate->set_start(107); + simplePredicate->set_stop(108); + FieldMatcher* predicate_dimension = simplePredicate->mutable_dimensions(); + predicate_dimension1->set_field(WAKE_LOCK_TAG_ID); + predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); + predicate_dimension->add_child()->set_field(WAKE_LOCK_NAME_KEY); simplePredicate->set_count_nesting(true); predicate = config.add_predicate(); - predicate->set_name("WL_HELD_PER_APP"); + predicate->set_id(206); // "WL_HELD_PER_APP" simplePredicate = predicate->mutable_simple_predicate(); - simplePredicate->set_start("APP_GET_WL"); - simplePredicate->set_stop("APP_RELEASE_WL"); + simplePredicate->set_start(107); + simplePredicate->set_stop(108); simplePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); - predicate_dimension = simplePredicate->add_dimension(); - predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID); + predicate_dimension = simplePredicate->mutable_dimensions(); + predicate_dimension->set_field(WAKE_LOCK_TAG_ID); + predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID); simplePredicate->set_count_nesting(true); return config; diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp new file mode 100644 index 000000000000..8c0ae978fcb8 --- /dev/null +++ b/cmds/statsd/src/dimension.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Log.h" + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "dimension.h" +#include "field_util.h" + + +namespace android { +namespace os { +namespace statsd { + +const DimensionsValue* getSingleLeafValue(const DimensionsValue* value) { + if (value->value_case() == DimensionsValue::ValueCase::kValueTuple) { + return getSingleLeafValue(&value->value_tuple().dimensions_value(0)); + } else { + return value; + } +} + +DimensionsValue getSingleLeafValue(const DimensionsValue& value) { + const DimensionsValue* leafValue = getSingleLeafValue(&value); + return *leafValue; +} + +void appendLeafNodeToParent(const Field& field, + const DimensionsValue& value, + DimensionsValue* parentValue) { + if (field.child_size() <= 0) { + *parentValue = value; + parentValue->set_field(field.field()); + return; + } + parentValue->set_field(field.field()); + int idx = -1; + for (int i = 0; i < parentValue->mutable_value_tuple()->dimensions_value_size(); ++i) { + if (parentValue->mutable_value_tuple()->dimensions_value(i).field() == + field.child(0).field()) { + idx = i; + } + } + if (idx < 0) { + parentValue->mutable_value_tuple()->add_dimensions_value(); + idx = parentValue->mutable_value_tuple()->dimensions_value_size() - 1; + } + appendLeafNodeToParent( + field.child(0), value, + parentValue->mutable_value_tuple()->mutable_dimensions_value(idx)); +} + +void addNodeToRootDimensionsValues(const Field& field, + const DimensionsValue& node, + std::vector<DimensionsValue>* rootValues) { + if (rootValues == nullptr) { + return; + } + if (rootValues->empty()) { + DimensionsValue rootValue; + appendLeafNodeToParent(field, node, &rootValue); + rootValues->push_back(rootValue); + } else { + for (size_t i = 0; i < rootValues->size(); ++i) { + appendLeafNodeToParent(field, node, &rootValues->at(i)); + } + } +} + +namespace { + +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootDimensionsValues); + +void findNonRepeatedDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootValues) { + if (matcher.child_size() > 0) { + for (const auto& childMatcher : matcher.child()) { + Field childField = field; + appendLeaf(&childField, childMatcher.field()); + findDimensionsValues(fieldValueMap, childMatcher, childField, rootValues); + } + } else { + auto ret = fieldValueMap.equal_range(field); + int found = 0; + for (auto it = ret.first; it != ret.second; ++it) { + found++; + } + // Not found. + if (found <= 0) { + return; + } + if (found > 1) { + ALOGE("Found multiple values for optional field."); + return; + } + addNodeToRootDimensionsValues(field, ret.first->second, rootValues); + } +} + +void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootValues) { + if (matcher.position() == Position::FIRST) { + Field first_field = field; + setPositionForLeaf(&first_field, 0); + findNonRepeatedDimensionsValues(fieldValueMap, matcher, first_field, rootValues); + } else { + auto itLower = fieldValueMap.lower_bound(field); + if (itLower == fieldValueMap.end()) { + return; + } + Field next_field = field; + getNextField(&next_field); + auto itUpper = fieldValueMap.lower_bound(next_field); + + switch (matcher.position()) { + case Position::LAST: + { + itUpper--; + if (itUpper != fieldValueMap.end()) { + Field last_field = field; + int last_index = getPositionByReferenceField(field, itUpper->first); + if (last_index < 0) { + return; + } + setPositionForLeaf(&last_field, last_index); + findNonRepeatedDimensionsValues( + fieldValueMap, matcher, last_field, rootValues); + } + } + break; + case Position::ANY: + { + std::set<int> indexes; + for (auto it = itLower; it != itUpper; ++it) { + int index = getPositionByReferenceField(field, it->first); + if (index >= 0) { + indexes.insert(index); + } + } + if (!indexes.empty()) { + Field any_field = field; + std::vector<DimensionsValue> allValues; + for (const int index : indexes) { + setPositionForLeaf(&any_field, index); + std::vector<DimensionsValue> newValues = *rootValues; + findNonRepeatedDimensionsValues( + fieldValueMap, matcher, any_field, &newValues); + allValues.insert(allValues.end(), newValues.begin(), newValues.end()); + } + rootValues->clear(); + rootValues->insert(rootValues->end(), allValues.begin(), allValues.end()); + } + } + break; + default: + break; + } + } +} + +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<DimensionsValue>* rootDimensionsValues) { + if (!matcher.has_position()) { + findNonRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + } else { + findRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + } +} + +} // namespace + +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + std::vector<DimensionsValue>* rootDimensionsValues) { + findDimensionsValues(fieldValueMap, matcher, + buildSimpleAtomField(matcher.field()), rootDimensionsValues); +} + +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId) { + FieldMatcher matcher; + matcher.set_field(tagId); + return matcher; +} + +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum) { + FieldMatcher matcher; + matcher.set_field(tagId); + matcher.add_child()->set_field(atomFieldNum); + return matcher; +} + +constexpr int ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO = 1; +constexpr int UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 1; +constexpr int TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 2; + +void buildAttributionUidFieldMatcher(const int tagId, const Position position, + FieldMatcher *matcher) { + matcher->set_field(tagId); + matcher->add_child()->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); + FieldMatcher* child = matcher->mutable_child(0)->add_child(); + child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); +} + +void buildAttributionTagFieldMatcher(const int tagId, const Position position, + FieldMatcher *matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); + child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); + child->set_position(position); + child = child->add_child(); + child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); +} + +void buildAttributionFieldMatcher(const int tagId, const Position position, + FieldMatcher *matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); + child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); + child->set_position(position); + child = child->add_child(); + child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); + child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); +} + +void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) { + *flattened += std::to_string(value.field()); + *flattened += ":"; + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + *flattened += value.value_str(); + break; + case DimensionsValue::ValueCase::kValueInt: + *flattened += std::to_string(value.value_int()); + break; + case DimensionsValue::ValueCase::kValueLong: + *flattened += std::to_string(value.value_long()); + break; + case DimensionsValue::ValueCase::kValueBool: + *flattened += std::to_string(value.value_bool()); + break; + case DimensionsValue::ValueCase::kValueFloat: + *flattened += std::to_string(value.value_float()); + break; + case DimensionsValue::ValueCase::kValueTuple: + { + *flattened += "{"; + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + DimensionsValueToString(value.value_tuple().dimensions_value(i), flattened); + *flattened += "|"; + } + *flattened += "}"; + } + break; + case DimensionsValue::ValueCase::VALUE_NOT_SET: + break; + } +} + +void getDimensionsValueLeafNodes( + const DimensionsValue& value, std::vector<DimensionsValue> *leafNodes) { + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::kValueInt: + case DimensionsValue::ValueCase::kValueLong: + case DimensionsValue::ValueCase::kValueBool: + case DimensionsValue::ValueCase::kValueFloat: + leafNodes->push_back(value); + break; + case DimensionsValue::ValueCase::kValueTuple: + for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { + getDimensionsValueLeafNodes(value.value_tuple().dimensions_value(i), leafNodes); + } + break; + case DimensionsValue::ValueCase::VALUE_NOT_SET: + break; + default: + break; + } +} + +std::string DimensionsValueToString(const DimensionsValue& value) { + std::string flatten; + DimensionsValueToString(value, &flatten); + return flatten; +} + +bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub) { + if (dimension.field() != sub.field()) { + return false; + } + if (dimension.value_case() != sub.value_case()) { + return false; + } + switch (dimension.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return dimension.value_str() == sub.value_str(); + case DimensionsValue::ValueCase::kValueInt: + return dimension.value_int() == sub.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return dimension.value_long() == sub.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return dimension.value_bool() == sub.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return dimension.value_float() == sub.value_float(); + case DimensionsValue::ValueCase::kValueTuple: { + if (dimension.value_tuple().dimensions_value_size() < sub.value_tuple().dimensions_value_size()) { + return false; + } + bool allSub = true; + for (int i = 0; i < sub.value_tuple().dimensions_value_size(); ++i) { + bool isSub = false; + for (int j = 0; !isSub && j < dimension.value_tuple().dimensions_value_size(); ++j) { + isSub |= IsSubDimension(dimension.value_tuple().dimensions_value(j), + sub.value_tuple().dimensions_value(i)); + } + allSub &= isSub; + } + return allSub; + } + break; + case DimensionsValue::ValueCase::VALUE_NOT_SET: + return false; + default: + return false; + } +} + +long getLongFromDimenValue(const DimensionsValue& dimensionValue) { + switch (dimensionValue.value_case()) { + case DimensionsValue::ValueCase::kValueInt: + return dimensionValue.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return dimensionValue.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return dimensionValue.value_bool() ? 1 : 0; + case DimensionsValue::ValueCase::kValueFloat: + return (int64_t)dimensionValue.value_float(); + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::VALUE_NOT_SET: + return 0; + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h new file mode 100644 index 000000000000..5bb64a9cf5ee --- /dev/null +++ b/cmds/statsd/src/dimension.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <log/logprint.h> +#include <set> +#include <vector> +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "field_util.h" + +namespace android { +namespace os { +namespace statsd { + + +// Returns the leaf node from the DimensionsValue proto. It assume that the input has only one +// leaf node at most. +const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); +DimensionsValue getSingleLeafValue(const DimensionsValue& value); + +// Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto +// represents a tree. When the input proto has repeated fields and the input "dimensions" wants +// "ANY" locations, it will return multiple trees. +void findDimensionsValues( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + std::vector<DimensionsValue>* rootDimensionsValues); + +// Utils to build FieldMatcher proto for simple one-depth atoms. +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum); +FieldMatcher buildSimpleAtomFieldMatcher(const int tagId); + +// Utils to build FieldMatcher proto for attribution nodes. +FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position); +FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position); +FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position); + +// Utils to print pretty string for DimensionsValue proto. +std::string DimensionsValueToString(const DimensionsValue& value); +void DimensionsValueToString(const DimensionsValue& value, std::string *flattened); + +bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub); + +// Helper function to get long value from the DimensionsValue proto. +long getLongFromDimenValue(const DimensionsValue& dimensionValue); +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/field_util.cpp b/cmds/statsd/src/field_util.cpp new file mode 100644 index 000000000000..d10e1670ddf6 --- /dev/null +++ b/cmds/statsd/src/field_util.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Log.h" +#include "field_util.h" + +#include <set> +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +// This function is to compare two Field trees where each node has at most one child. +bool CompareField(const Field& a, const Field& b) { + if (a.field() < b.field()) { + return true; + } + if (a.field() > b.field()) { + return false; + } + if (a.position_index() < b.position_index()) { + return true; + } + if (a.position_index() > b.position_index()) { + return false; + } + if (a.child_size() < b.child_size()) { + return true; + } + if (a.child_size() > b.child_size()) { + return false; + } + if (a.child_size() == 0 && b.child_size() == 0) { + return false; + } + return CompareField(a.child(0), b.child(0)); +} + +const Field* getSingleLeaf(const Field* field) { + if (field->child_size() <= 0) { + return field; + } else { + return getSingleLeaf(&field->child(0)); + } +} + +Field* getSingleLeaf(Field* field) { + if (field->child_size() <= 0) { + return field; + } else { + return getSingleLeaf(field->mutable_child(0)); + } +} + +void FieldToString(const Field& field, std::string *flattened) { + *flattened += std::to_string(field.field()); + if (field.has_position_index()) { + *flattened += "["; + *flattened += std::to_string(field.position_index()); + *flattened += "]"; + } + if (field.child_size() <= 0) { + return; + } + *flattened += "."; + *flattened += "{"; + for (int i = 0 ; i < field.child_size(); ++i) { + *flattened += FieldToString(field.child(i)); + } + *flattened += "},"; +} + +std::string FieldToString(const Field& field) { + std::string flatten; + FieldToString(field, &flatten); + return flatten; +} + +bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue) { + if (field.child_size() <= 0) { + leafValue->set_field(field.field()); + return true; + } else if (field.child_size() == 1) { + return setFieldInLeafValueProto(field.child(0), leafValue); + } else { + ALOGE("Not able to set the 'field' in leaf value for multiple children."); + return false; + } +} + +Field buildAtomField(const int tagId, const Field &atomField) { + Field field; + *field.add_child() = atomField; + field.set_field(tagId); + return field; +} + +Field buildSimpleAtomField(const int tagId, const int atomFieldNum) { + Field field; + field.set_field(tagId); + field.add_child()->set_field(atomFieldNum); + return field; +} + +Field buildSimpleAtomField(const int tagId) { + Field field; + field.set_field(tagId); + return field; +} + +void appendLeaf(Field *parent, int node_field_num) { + if (!parent->has_field()) { + parent->set_field(node_field_num); + } else if (parent->child_size() <= 0) { + parent->add_child()->set_field(node_field_num); + } else { + appendLeaf(parent->mutable_child(0), node_field_num); + } +} + +void appendLeaf(Field *parent, int node_field_num, int position) { + if (!parent->has_field()) { + parent->set_field(node_field_num); + parent->set_position_index(position); + } else if (parent->child_size() <= 0) { + auto child = parent->add_child(); + child->set_field(node_field_num); + child->set_position_index(position); + } else { + appendLeaf(parent->mutable_child(0), node_field_num, position); + } +} + + +void getNextField(Field* field) { + if (field->child_size() <= 0) { + field->set_field(field->field() + 1); + return; + } + if (field->child_size() != 1) { + return; + } + getNextField(field->mutable_child(0)); +} + +void increasePosition(Field *field) { + if (!field->has_position_index()) { + field->set_position_index(0); + } else { + field->set_position_index(field->position_index() + 1); + } +} + +int getPositionByReferenceField(const Field& ref, const Field& field_with_index) { + if (ref.child_size() <= 0) { + return field_with_index.position_index(); + } + if (ref.child_size() != 1 || + field_with_index.child_size() != 1) { + return -1; + } + return getPositionByReferenceField(ref.child(0), field_with_index.child(0)); +} + +void setPositionForLeaf(Field *field, int index) { + if (field->child_size() <= 0) { + field->set_position_index(index); + } else { + setPositionForLeaf(field->mutable_child(0), index); + } +} + +void findFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<Field>* rootFields); + +void findNonRepeatedFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<Field>* rootFields) { + if (matcher.child_size() > 0) { + for (const auto& childMatcher : matcher.child()) { + Field childField = field; + appendLeaf(&childField, childMatcher.field()); + findFields(fieldValueMap, childMatcher, childField, rootFields); + } + } else { + auto ret = fieldValueMap.equal_range(field); + int found = 0; + for (auto it = ret.first; it != ret.second; ++it) { + found++; + } + // Not found. + if (found <= 0) { + return; + } + if (found > 1) { + ALOGE("Found multiple values for optional field."); + return; + } + rootFields->push_back(ret.first->first); + } +} + +void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, + const Field& field, std::vector<Field>* rootFields) { + if (matcher.position() == Position::FIRST) { + Field first_field = field; + setPositionForLeaf(&first_field, 0); + findNonRepeatedFields(fieldValueMap, matcher, first_field, rootFields); + } else { + auto itLower = fieldValueMap.lower_bound(field); + if (itLower == fieldValueMap.end()) { + return; + } + Field next_field = field; + getNextField(&next_field); + auto itUpper = fieldValueMap.lower_bound(next_field); + + switch (matcher.position()) { + case Position::LAST: + { + itUpper--; + if (itUpper != fieldValueMap.end()) { + Field last_field = field; + int last_index = getPositionByReferenceField(field, itUpper->first); + if (last_index < 0) { + return; + } + setPositionForLeaf(&last_field, last_index); + findNonRepeatedFields( + fieldValueMap, matcher, last_field, rootFields); + } + } + break; + case Position::ANY: + { + std::set<int> indexes; + for (auto it = itLower; it != itUpper; ++it) { + int index = getPositionByReferenceField(field, it->first); + if (index >= 0) { + indexes.insert(index); + } + } + if (!indexes.empty()) { + Field any_field = field; + for (const int index : indexes) { + setPositionForLeaf(&any_field, index); + findNonRepeatedFields( + fieldValueMap, matcher, any_field, rootFields); + } + } + } + break; + default: + break; + } + } +} + +void findFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + const Field& field, + std::vector<Field>* rootFields) { + if (!matcher.has_position()) { + findNonRepeatedFields(fieldValueMap, matcher, field, rootFields); + } else { + findRepeatedFields(fieldValueMap, matcher, field, rootFields); + } +} + +void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap) { + std::vector<Field> rootFields; + findFields(*fieldValueMap, matcher, buildSimpleAtomField(matcher.field()), &rootFields); + std::set<Field, FieldCmp> rootFieldSet(rootFields.begin(), rootFields.end()); + auto it = fieldValueMap->begin(); + while (it != fieldValueMap->end()) { + if (rootFieldSet.find(it->first) == rootFieldSet.end()) { + it = fieldValueMap->erase(it); + } else { + it++; + } + } +} + +bool hasLeafNode(const FieldMatcher& matcher) { + if (!matcher.has_field()) { + return false; + } + for (int i = 0; i < matcher.child_size(); ++i) { + if (hasLeafNode(matcher.child(i))) { + return true; + } + } + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/field_util.h b/cmds/statsd/src/field_util.h new file mode 100644 index 000000000000..5907e17d666b --- /dev/null +++ b/cmds/statsd/src/field_util.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" + +#include <unordered_map> + +namespace android { +namespace os { +namespace statsd { + +// Function to sort the Field protos. +bool CompareField(const Field& a, const Field& b); +struct FieldCmp { + bool operator()(const Field& a, const Field& b) const { + return CompareField(a, b); + } +}; + +// Flattened dimensions value map. To save space, usually the key contains the tree structure info +// and value field is only leaf node. +typedef std::map<Field, DimensionsValue, FieldCmp> FieldValueMap; + +// Util function to print the Field proto. +std::string FieldToString(const Field& field); + +// Util function to find the leaf node from the input Field proto and set it in the corresponding +// value proto. +bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue); + +// Returns the leaf node from the Field proto. It assume that the input has only one +// leaf node at most. +const Field* getSingleLeaf(const Field* field); +Field* getSingleLeaf(Field* field); + +// Append a node to the current leaf. It assumes that the input "parent" has one leaf node at most. +void appendLeaf(Field *parent, int node_field_num); +void appendLeaf(Field *parent, int node_field_num, int position); + +// Given the field sorting logic, this function is to increase the "field" at the leaf node. +void getNextField(Field* field); + +// Increase the position index for the node. If the "position_index" is not set, set it as 0. +void increasePosition(Field *field); + +// Finds the leaf node and set the index there. +void setPositionForLeaf(Field *field, int index); + +// Returns true if the matcher has specified at least one leaf node. +bool hasLeafNode(const FieldMatcher& matcher); + +// The two input Field proto are describing the same tree structure. Both contain one leaf node at +// most. This is find the position index info for the leaf node at "reference" stored in the +// "field_with_index" tree. +int getPositionByReferenceField(const Field& reference, const Field& field_with_index); + +// Utils to build the Field proto for simple atom fields. +Field buildAtomField(const int tagId, const Field &atomField); +Field buildSimpleAtomField(const int tagId, const int atomFieldNum); +Field buildSimpleAtomField(const int tagId); + +// Find out all the fields specified by the matcher. +void findFields( + const FieldValueMap& fieldValueMap, + const FieldMatcher& matcher, + std::vector<Field>* rootFields); + +// Filter out the fields not in the field matcher. +void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index bf277f0a383d..33927aa9b44c 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -80,7 +80,7 @@ void StatsdStats::noteConfigReceived(const ConfigKey& key, int metricsCount, int StatsdStatsReport_ConfigStats configStats; configStats.set_uid(key.GetUid()); - configStats.set_name(key.GetName()); + configStats.set_id(key.GetId()); configStats.set_creation_time_sec(nowTimeSec); configStats.set_metric_count(metricsCount); configStats.set_condition_count(conditionsCount); @@ -196,34 +196,34 @@ void StatsdStats::setCurrentUidMapMemory(int bytes) { mUidMapStats.set_bytes_used(bytes); } -void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const string& name, int size) { +void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size) { lock_guard<std::mutex> lock(mLock); // if name doesn't exist before, it will create the key with count 0. auto& conditionSizeMap = mConditionStats[key]; - if (size > conditionSizeMap[name]) { - conditionSizeMap[name] = size; + if (size > conditionSizeMap[id]) { + conditionSizeMap[id] = size; } } -void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const string& name, int size) { +void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size) { lock_guard<std::mutex> lock(mLock); // if name doesn't exist before, it will create the key with count 0. auto& metricsDimensionMap = mMetricsStats[key]; - if (size > metricsDimensionMap[name]) { - metricsDimensionMap[name] = size; + if (size > metricsDimensionMap[id]) { + metricsDimensionMap[id] = size; } } -void StatsdStats::noteMatcherMatched(const ConfigKey& key, const string& name) { +void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) { lock_guard<std::mutex> lock(mLock); auto& matcherStats = mMatcherStats[key]; - matcherStats[name]++; + matcherStats[id]++; } -void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const string& name) { +void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t& id) { lock_guard<std::mutex> lock(mLock); auto& alertStats = mAlertStats[key]; - alertStats[name]++; + alertStats[id]++; } void StatsdStats::noteRegisteredAnomalyAlarmChanged() { @@ -279,9 +279,10 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& matcherStats = mMatcherStats[key]; for (const auto& stats : matcherStats) { auto output = configStats.add_matcher_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_matched_times(stats.second); - VLOG("matcher %s matched %d times", stats.first.c_str(), stats.second); + VLOG("matcher %lld matched %d times", + (long long)stats.first, stats.second); } } // Add condition stats @@ -289,9 +290,10 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& conditionStats = mConditionStats[key]; for (const auto& stats : conditionStats) { auto output = configStats.add_condition_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_max_tuple_counts(stats.second); - VLOG("condition %s max output tuple size %d", stats.first.c_str(), stats.second); + VLOG("condition %lld max output tuple size %d", + (long long)stats.first, stats.second); } } // Add metrics stats @@ -299,9 +301,10 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& conditionStats = mMetricsStats[key]; for (const auto& stats : conditionStats) { auto output = configStats.add_metric_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_max_tuple_counts(stats.second); - VLOG("metrics %s max output tuple size %d", stats.first.c_str(), stats.second); + VLOG("metrics %lld max output tuple size %d", + (long long)stats.first, stats.second); } } // Add anomaly detection alert stats @@ -309,9 +312,9 @@ void StatsdStats::addSubStatsToConfigLocked(const ConfigKey& key, const auto& alertStats = mAlertStats[key]; for (const auto& stats : alertStats) { auto output = configStats.add_alert_stats(); - output->set_name(stats.first); + output->set_id(stats.first); output->set_alerted_times(stats.second); - VLOG("alert %s declared %d times", stats.first.c_str(), stats.second); + VLOG("alert %lld declared %d times", (long long)stats.first, stats.second); } } } @@ -343,9 +346,9 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { // in production. if (DEBUG) { VLOG("*****ICEBOX*****"); - VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " + VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " "#matcher=%d, #alert=%d, #valid=%d", - configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(), + configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(), configStats.deletion_time_sec(), configStats.metric_count(), configStats.condition_count(), configStats.matcher_count(), configStats.alert_count(), configStats.is_valid()); @@ -364,9 +367,9 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { auto& configStats = pair.second; if (DEBUG) { VLOG("********Active Configs***********"); - VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " + VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, " "#matcher=%d, #alert=%d, #valid=%d", - configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(), + configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(), configStats.deletion_time_sec(), configStats.metric_count(), configStats.condition_count(), configStats.matcher_count(), configStats.alert_count(), configStats.is_valid()); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index cb868e1fd8c6..45aa192318a4 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -99,10 +99,10 @@ public: * count > kDimensionKeySizeSoftLimit. * * [key]: The config key that this condition belongs to. - * [name]: The name of the condition. + * [id]: The id of the condition. * [size]: The output tuple size. */ - void noteConditionDimensionSize(const ConfigKey& key, const std::string& name, int size); + void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size); /** * Report the size of output tuple of a metric. @@ -111,26 +111,26 @@ public: * count > kDimensionKeySizeSoftLimit. * * [key]: The config key that this metric belongs to. - * [name]: The name of the metric. + * [id]: The id of the metric. * [size]: The output tuple size. */ - void noteMetricDimensionSize(const ConfigKey& key, const std::string& name, int size); + void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size); /** * Report a matcher has been matched. * * [key]: The config key that this matcher belongs to. - * [name]: The name of the matcher. + * [id]: The id of the matcher. */ - void noteMatcherMatched(const ConfigKey& key, const std::string& name); + void noteMatcherMatched(const ConfigKey& key, const int64_t& id); /** * Report that an anomaly detection alert has been declared. * * [key]: The config key that this alert belongs to. - * [name]: The name of the alert. + * [id]: The id of the alert. */ - void noteAnomalyDeclared(const ConfigKey& key, const std::string& name); + void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id); /** * Report an atom event has been logged. @@ -187,12 +187,12 @@ private: // Stores the number of output tuple of condition trackers when it's bigger than // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, // it means some data has been dropped. - std::map<const ConfigKey, std::map<const std::string, int>> mConditionStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mConditionStats; // Stores the number of output tuple of metric producers when it's bigger than // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1, // it means some data has been dropped. - std::map<const ConfigKey, std::map<const std::string, int>> mMetricsStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mMetricsStats; // Stores the number of times a pushed atom is logged. // The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms @@ -206,10 +206,10 @@ private: // Stores the number of times an anomaly detection alert has been declared // (per config, per alert name). - std::map<const ConfigKey, std::map<const std::string, int>> mAlertStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mAlertStats; // Stores how many times a matcher have been matched. - std::map<const ConfigKey, std::map<const std::string, int>> mMatcherStats; + std::map<const ConfigKey, std::map<const int64_t, int>> mMatcherStats; void noteConfigRemovedInternalLocked(const ConfigKey& key); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index d660b5f9fe02..49a6e330c590 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -17,8 +17,14 @@ #define DEBUG true // STOPSHIP if true #include "logd/LogEvent.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" + +#include <set> #include <sstream> -#include "stats_util.h" + +#include "field_util.h" +#include "dimension.h" +#include "stats_log_util.h" namespace android { namespace os { @@ -30,16 +36,20 @@ using std::string; using android::util::ProtoOutputStream; LogEvent::LogEvent(log_msg& msg) { - mContext = + android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t)); mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec; mLogUid = msg.entry_v4.uid; - init(mContext); + init(context); + if (context) { + android_log_destroy(&context); + } } LogEvent::LogEvent(int32_t tagId, uint64_t timestampNs) { mTimestampNs = timestampNs; mTagId = tagId; + mLogUid = 0; mContext = create_android_logger(1937006964); // the event tag shared by all stats logs if (mContext) { android_log_write_int32(mContext, tagId); @@ -53,6 +63,14 @@ void LogEvent::init() { // turns to reader mode mContext = create_android_log_parser(buffer, len); init(mContext); + // destroy the context to save memory. + android_log_destroy(&mContext); + } +} + +LogEvent::~LogEvent() { + if (mContext) { + android_log_destroy(&mContext); } } @@ -98,19 +116,72 @@ bool LogEvent::write(float value) { return false; } -LogEvent::~LogEvent() { +bool LogEvent::write(const std::vector<AttributionNode>& nodes) { if (mContext) { - android_log_destroy(&mContext); + if (android_log_write_list_begin(mContext) < 0) { + return false; + } + for (size_t i = 0; i < nodes.size(); ++i) { + if (!write(nodes[i])) { + return false; + } + } + if (android_log_write_list_end(mContext) < 0) { + return false; + } + return true; + } + return false; +} + +bool LogEvent::write(const AttributionNode& node) { + if (mContext) { + if (android_log_write_list_begin(mContext) < 0) { + return false; + } + if (android_log_write_int32(mContext, node.uid()) < 0) { + return false; + } + if (android_log_write_string8(mContext, node.tag().c_str()) < 0) { + return false; + } + if (android_log_write_int32(mContext, node.uid()) < 0) { + return false; + } + if (android_log_write_list_end(mContext) < 0) { + return false; + } + return true; } + return false; } +namespace { + +void increaseField(Field *field, bool is_child) { + if (is_child) { + if (field->child_size() <= 0) { + field->add_child(); + } + } else { + field->clear_child(); + } + Field* curr = is_child ? field->mutable_child(0) : field; + if (!curr->has_field()) { + curr->set_field(1); + } else { + curr->set_field(curr->field() + 1); + } +} + +} // namespace + /** * The elements of each log event are stored as a vector of android_log_list_elements. * The goal is to do as little preprocessing as possible, because we read a tiny fraction * of the elements that are written to the log. */ void LogEvent::init(android_log_context context) { - mElements.clear(); android_log_list_element elem; // TODO: The log is actually structured inside one list. This is convenient // because we'll be able to use it to put the attribution (WorkSource) block first @@ -118,24 +189,79 @@ void LogEvent::init(android_log_context context) { // list-related log elements and the order we get there is our index-keyed data // structure. int i = 0; + + int seenListStart = 0; + + Field field; do { elem = android_log_read_next(context); switch ((int)elem.type) { case EVENT_TYPE_INT: - // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. If we add WorkSource, it would - // be the list starting at [2]. + // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. if (i == 1) { mTagId = elem.data.int32; - break; + } else { + increaseField(&field, seenListStart > 0/* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_int(elem.data.int32); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); } + break; case EVENT_TYPE_FLOAT: + { + increaseField(&field, seenListStart > 0/* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_float(elem.data.float32); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + } + break; case EVENT_TYPE_STRING: + { + increaseField(&field, seenListStart > 0/* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_str(string(elem.data.string, elem.len).c_str()); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + } + break; case EVENT_TYPE_LONG: - mElements.push_back(elem); + { + increaseField(&field, seenListStart > 0 /* is_child */); + DimensionsValue dimensionsValue; + dimensionsValue.set_value_long(elem.data.int64); + setFieldInLeafValueProto(field, &dimensionsValue); + mFieldValueMap.insert( + std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + } break; case EVENT_TYPE_LIST: + if (i >= 1) { + if (seenListStart > 0) { + increasePosition(&field); + } else { + increaseField(&field, false /* is_child */); + } + seenListStart++; + if (seenListStart >= 3) { + ALOGE("Depth > 2. Not supported!"); + return; + } + } break; case EVENT_TYPE_LIST_STOP: + seenListStart--; + if (seenListStart == 0) { + field.clear_position_index(); + } else { + if (field.child_size() > 0) { + field.mutable_child(0)->clear_field(); + } + } break; case EVENT_TYPE_UNKNOWN: break; @@ -147,142 +273,145 @@ void LogEvent::init(android_log_context context) { } int64_t LogEvent::GetLong(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - return elem.data.int32; - } else if (elem.type == EVENT_TYPE_LONG) { - return elem.data.int64; - } else if (elem.type == EVENT_TYPE_FLOAT) { - return (int64_t)elem.data.float32; - } else { - *err = BAD_TYPE; - return 0; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueInt: + return (int64_t)leafValue->value_int(); + case DimensionsValue::ValueCase::kValueLong: + return leafValue->value_long(); + case DimensionsValue::ValueCase::kValueBool: + return leafValue->value_bool() ? 1 : 0; + case DimensionsValue::ValueCase::kValueFloat: + return (int64_t)leafValue->value_float(); + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } } const char* LogEvent::GetString(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; - return NULL; + return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type != EVENT_TYPE_STRING) { - *err = BAD_TYPE; - return NULL; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return leafValue->value_str().c_str(); + case DimensionsValue::ValueCase::kValueInt: + case DimensionsValue::ValueCase::kValueLong: + case DimensionsValue::ValueCase::kValueBool: + case DimensionsValue::ValueCase::kValueFloat: + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } - // Need to add the '/0' at the end by specifying the length of the string. - return string(elem.data.string, elem.len).c_str(); } bool LogEvent::GetBool(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - return elem.data.int32 != 0; - } else if (elem.type == EVENT_TYPE_LONG) { - return elem.data.int64 != 0; - } else if (elem.type == EVENT_TYPE_FLOAT) { - return elem.data.float32 != 0; - } else { - *err = BAD_TYPE; - return 0; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueInt: + return leafValue->value_int() != 0; + case DimensionsValue::ValueCase::kValueLong: + return leafValue->value_long() != 0; + case DimensionsValue::ValueCase::kValueBool: + return leafValue->value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return leafValue->value_float() != 0; + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } } float LogEvent::GetFloat(size_t key, status_t* err) const { - if (key < 1 || (key - 1) >= mElements.size()) { + DimensionsValue value; + if (!GetSimpleAtomDimensionsValueProto(key, &value)) { *err = BAD_INDEX; return 0; } - key--; - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - return (float)elem.data.int32; - } else if (elem.type == EVENT_TYPE_LONG) { - return (float)elem.data.int64; - } else if (elem.type == EVENT_TYPE_FLOAT) { - return elem.data.float32; - } else { - *err = BAD_TYPE; - return 0; + const DimensionsValue* leafValue = getSingleLeafValue(&value); + switch (leafValue->value_case()) { + case DimensionsValue::ValueCase::kValueInt: + return (float)leafValue->value_int(); + case DimensionsValue::ValueCase::kValueLong: + return (float)leafValue->value_long(); + case DimensionsValue::ValueCase::kValueBool: + return leafValue->value_bool() ? 1.0f : 0.0f; + case DimensionsValue::ValueCase::kValueFloat: + return leafValue->value_float(); + case DimensionsValue::ValueCase::kValueTuple: + case DimensionsValue::ValueCase::kValueStr: + case DimensionsValue::ValueCase::VALUE_NOT_SET: { + *err = BAD_TYPE; + return 0; + } } } -KeyValuePair LogEvent::GetKeyValueProto(size_t key) const { - KeyValuePair pair; - pair.set_key(key); - // If the value is not valid, return the KeyValuePair without assigning the value. - // Caller can detect the error by checking the enum for "one of" proto type. - if (key < 1 || (key - 1) >= mElements.size()) { - return pair; - } - key--; - - const android_log_list_element& elem = mElements[key]; - if (elem.type == EVENT_TYPE_INT) { - pair.set_value_int(elem.data.int32); - } else if (elem.type == EVENT_TYPE_LONG) { - pair.set_value_long(elem.data.int64); - } else if (elem.type == EVENT_TYPE_STRING) { - pair.set_value_str(elem.data.string); - } else if (elem.type == EVENT_TYPE_FLOAT) { - pair.set_value_float(elem.data.float32); +void LogEvent::GetAtomDimensionsValueProtos(const FieldMatcher& matcher, + std::vector<DimensionsValue> *dimensionsValues) const { + findDimensionsValues(mFieldValueMap, matcher, dimensionsValues); +} + +bool LogEvent::GetAtomDimensionsValueProto(const FieldMatcher& matcher, + DimensionsValue* dimensionsValue) const { + std::vector<DimensionsValue> rootDimensionsValues; + findDimensionsValues(mFieldValueMap, matcher, &rootDimensionsValues); + if (rootDimensionsValues.size() != 1) { + return false; } - return pair; + *dimensionsValue = rootDimensionsValues.front(); + return true; +} + +bool LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField, + DimensionsValue* dimensionsValue) const { + return GetAtomDimensionsValueProto( + buildSimpleAtomFieldMatcher(mTagId, atomField), dimensionsValue); +} + +DimensionsValue LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField) const { + DimensionsValue dimensionsValue; + GetSimpleAtomDimensionsValueProto(atomField, &dimensionsValue); + return dimensionsValue; } string LogEvent::ToString() const { ostringstream result; result << "{ " << mTimestampNs << " (" << mTagId << ")"; - const size_t N = mElements.size(); - for (size_t i=0; i<N; i++) { - result << " "; - result << (i + 1); + for (const auto& itr : mFieldValueMap) { + result << FieldToString(itr.first); result << "->"; - const android_log_list_element& elem = mElements[i]; - if (elem.type == EVENT_TYPE_INT) { - result << elem.data.int32; - } else if (elem.type == EVENT_TYPE_LONG) { - result << elem.data.int64; - } else if (elem.type == EVENT_TYPE_FLOAT) { - result << elem.data.float32; - } else if (elem.type == EVENT_TYPE_STRING) { - // Need to add the '/0' at the end by specifying the length of the string. - result << string(elem.data.string, elem.len).c_str(); - } + result << DimensionsValueToString(itr.second); + result << " "; } result << " }"; return result.str(); } -void LogEvent::ToProto(ProtoOutputStream& proto) const { - long long atomToken = proto.start(FIELD_TYPE_MESSAGE | mTagId); - const size_t N = mElements.size(); - for (size_t i=0; i<N; i++) { - const int key = i + 1; - - const android_log_list_element& elem = mElements[i]; - if (elem.type == EVENT_TYPE_INT) { - proto.write(FIELD_TYPE_INT32 | key, elem.data.int32); - } else if (elem.type == EVENT_TYPE_LONG) { - proto.write(FIELD_TYPE_INT64 | key, (long long)elem.data.int64); - } else if (elem.type == EVENT_TYPE_FLOAT) { - proto.write(FIELD_TYPE_FLOAT | key, elem.data.float32); - } else if (elem.type == EVENT_TYPE_STRING) { - proto.write(FIELD_TYPE_STRING | key, elem.data.string); - } - } - proto.end(atomToken); +void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { + writeFieldValueTreeToStream(getFieldValueMap(), &protoOutput); } } // namespace statsd diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index d3f38deef886..8f3dedfd1825 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -16,6 +16,7 @@ #pragma once +#include "field_util.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" #include <android/util/ProtoOutputStream.h> @@ -23,9 +24,11 @@ #include <log/log_read.h> #include <private/android_logger.h> #include <utils/Errors.h> +#include <utils/JenkinsHash.h> #include <memory> #include <string> +#include <map> #include <vector> namespace android { @@ -77,6 +80,20 @@ public: bool GetBool(size_t key, status_t* err) const; float GetFloat(size_t key, status_t* err) const; + /* + * Get DimensionsValue proto objects from FieldMatcher. + */ + void GetAtomDimensionsValueProtos( + const FieldMatcher& matcher, std::vector<DimensionsValue> *dimensionsValues) const; + bool GetAtomDimensionsValueProto( + const FieldMatcher& matcher, DimensionsValue* dimensionsValue) const; + + /* + * Get a DimensionsValue proto objects from Field. + */ + bool GetSimpleAtomDimensionsValueProto(size_t field, DimensionsValue* dimensionsValue) const; + DimensionsValue GetSimpleAtomDimensionsValueProto(size_t atomField) const; + /** * Write test data to the LogEvent. This can only be used when the LogEvent is constructed * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it. @@ -87,6 +104,8 @@ public: bool write(int64_t value); bool write(const string& value); bool write(float value); + bool write(const std::vector<AttributionNode>& nodes); + bool write(const AttributionNode& node); /** * Return a string representation of this event. @@ -98,11 +117,6 @@ public: */ void ToProto(android::util::ProtoOutputStream& out) const; - /* - * Get a KeyValuePair proto object. - */ - KeyValuePair GetKeyValueProto(size_t key) const; - /** * Used with the constructor where tag is passed in. Converts the log_event_list to read mode * and prepares the list for reading. @@ -114,10 +128,12 @@ public: */ void setTimestampNs(uint64_t timestampNs) {mTimestampNs = timestampNs;} - int size() const { - return mElements.size(); + inline int size() const { + return mFieldValueMap.size(); } + inline const FieldValueMap& getFieldValueMap() const { return mFieldValueMap; } + private: /** * Don't copy, it's slower. If we really need this we can add it but let's try to @@ -130,8 +146,11 @@ private: */ void init(android_log_context context); - vector<android_log_list_element> mElements; + FieldValueMap mFieldValueMap; + // This field is used when statsD wants to create log event object and write fields to it. After + // calling init() function, this object would be destroyed to save memory usage. + // When the log event is created from log msg, this field is never initiated. android_log_context mContext; uint64_t mTimestampNs; diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp index 51a38b61ccf2..15c067ee936d 100644 --- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp +++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp @@ -29,8 +29,8 @@ using std::unique_ptr; using std::unordered_map; using std::vector; -CombinationLogMatchingTracker::CombinationLogMatchingTracker(const string& name, const int index) - : LogMatchingTracker(name, index) { +CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index) + : LogMatchingTracker(id, index) { } CombinationLogMatchingTracker::~CombinationLogMatchingTracker() { @@ -38,7 +38,7 @@ CombinationLogMatchingTracker::~CombinationLogMatchingTracker() { bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers, const vector<sp<LogMatchingTracker>>& allTrackers, - const unordered_map<string, int>& matcherMap, + const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) { if (mInitialized) { return true; @@ -60,10 +60,10 @@ bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatche return false; } - for (const string& child : matcher.matcher()) { + for (const auto& child : matcher.matcher()) { auto pair = matcherMap.find(child); if (pair == matcherMap.end()) { - ALOGW("Matcher %s not found in the config", child.c_str()); + ALOGW("Matcher %lld not found in the config", (long long)child); return false; } @@ -76,14 +76,14 @@ bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatche } if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) { - ALOGW("child matcher init failed %s", child.c_str()); + ALOGW("child matcher init failed %lld", (long long)child); return false; } mChildren.push_back(childIndex); - const set<int>& childTagIds = allTrackers[childIndex]->getTagIds(); - mTagIds.insert(childTagIds.begin(), childTagIds.end()); + const set<int>& childTagIds = allTrackers[childIndex]->getAtomIds(); + mAtomIds.insert(childTagIds.begin(), childTagIds.end()); } mInitialized = true; @@ -100,7 +100,7 @@ void CombinationLogMatchingTracker::onLogEvent(const LogEvent& event, return; } - if (mTagIds.find(event.GetTagId()) == mTagIds.end()) { + if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { matcherResults[mIndex] = MatchingState::kNotMatched; return; } diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h index 81f6e8052ebc..2a3f08da7b96 100644 --- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h +++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h @@ -31,11 +31,11 @@ namespace statsd { // Represents a AtomMatcher_Combination in the StatsdConfig. class CombinationLogMatchingTracker : public virtual LogMatchingTracker { public: - CombinationLogMatchingTracker(const std::string& name, const int index); + CombinationLogMatchingTracker(const int64_t& id, const int index); bool init(const std::vector<AtomMatcher>& allLogMatchers, const std::vector<sp<LogMatchingTracker>>& allTrackers, - const std::unordered_map<std::string, int>& matcherMap, + const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack); ~CombinationLogMatchingTracker(); diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h index 8162c443fbd3..4f30a047e256 100644 --- a/cmds/statsd/src/matchers/LogMatchingTracker.h +++ b/cmds/statsd/src/matchers/LogMatchingTracker.h @@ -33,8 +33,8 @@ namespace statsd { class LogMatchingTracker : public virtual RefBase { public: - LogMatchingTracker(const std::string& name, const int index) - : mName(name), mIndex(index), mInitialized(false){}; + LogMatchingTracker(const int64_t& id, const int index) + : mId(id), mIndex(index), mInitialized(false){}; virtual ~LogMatchingTracker(){}; @@ -48,7 +48,7 @@ public: // circle dependency. virtual bool init(const std::vector<AtomMatcher>& allLogMatchers, const std::vector<sp<LogMatchingTracker>>& allTrackers, - const std::unordered_map<std::string, int>& matcherMap, + const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack) = 0; // Called when a log event comes. @@ -65,17 +65,17 @@ public: // Get the tagIds that this matcher cares about. The combined collection is stored // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses // some memory but hopefully it can save us much CPU time when there is flood of events. - virtual const std::set<int>& getTagIds() const { - return mTagIds; + virtual const std::set<int>& getAtomIds() const { + return mAtomIds; } - const std::string& getName() const { - return mName; + const int64_t& getId() const { + return mId; } protected: // Name of this matching. We don't really need the name, but it makes log message easy to debug. - const std::string mName; + const int64_t mId; // Index of this LogMatchingTracker in MetricsManager's container. const int mIndex; @@ -88,7 +88,7 @@ protected: // useful when we have a complex CombinationLogMatcherTracker. // TODO: Consider use an array instead of stl set. In reality, the number of the tag ids a // LogMatchingTracker cares is only a few. - std::set<int> mTagIds; + std::set<int> mAtomIds; }; } // namespace statsd diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp index ac217abecba3..31b3db524e80 100644 --- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp +++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp @@ -29,13 +29,14 @@ using std::unordered_map; using std::vector; -SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index, - const SimpleAtomMatcher& matcher) - : LogMatchingTracker(name, index), mMatcher(matcher) { - if (!matcher.has_tag()) { +SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index, + const SimpleAtomMatcher& matcher, + const UidMap& uidMap) + : LogMatchingTracker(id, index), mMatcher(matcher), mUidMap(uidMap) { + if (!matcher.has_atom_id()) { mInitialized = false; } else { - mTagIds.insert(matcher.tag()); + mAtomIds.insert(matcher.atom_id()); mInitialized = true; } } @@ -45,7 +46,7 @@ SimpleLogMatchingTracker::~SimpleLogMatchingTracker() { bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers, const vector<sp<LogMatchingTracker>>& allTrackers, - const unordered_map<string, int>& matcherMap, + const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) { // no need to do anything. return mInitialized; @@ -55,18 +56,18 @@ void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event, const vector<sp<LogMatchingTracker>>& allTrackers, vector<MatchingState>& matcherResults) { if (matcherResults[mIndex] != MatchingState::kNotComputed) { - VLOG("Matcher %s already evaluated ", mName.c_str()); + VLOG("Matcher %lld already evaluated ", (long long)mId); return; } - if (mTagIds.find(event.GetTagId()) == mTagIds.end()) { + if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) { matcherResults[mIndex] = MatchingState::kNotMatched; return; } - bool matched = matchesSimple(mMatcher, event); + bool matched = matchesSimple(mUidMap, mMatcher, event); matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched; - VLOG("Stats SimpleLogMatcher %s matched? %d", mName.c_str(), matched); + VLOG("Stats SimpleLogMatcher %lld matched? %d", (long long)mId, matched); } } // namespace statsd diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h index 2c188c18c95b..28b339caa466 100644 --- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h +++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h @@ -24,6 +24,7 @@ #include <vector> #include "LogMatchingTracker.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "packages/UidMap.h" namespace android { namespace os { @@ -31,14 +32,15 @@ namespace statsd { class SimpleLogMatchingTracker : public virtual LogMatchingTracker { public: - SimpleLogMatchingTracker(const std::string& name, const int index, - const SimpleAtomMatcher& matcher); + SimpleLogMatchingTracker(const int64_t& id, const int index, + const SimpleAtomMatcher& matcher, + const UidMap& uidMap); ~SimpleLogMatchingTracker(); bool init(const std::vector<AtomMatcher>& allLogMatchers, const std::vector<sp<LogMatchingTracker>>& allTrackers, - const std::unordered_map<std::string, int>& matcherMap, + const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack) override; void onLogEvent(const LogEvent& event, @@ -47,6 +49,7 @@ public: private: const SimpleAtomMatcher mMatcher; + const UidMap& mUidMap; }; } // namespace statsd diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp index 9e88e5d0d8e3..46d9b92ec94c 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -19,7 +19,9 @@ #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "matchers/LogMatchingTracker.h" #include "matchers/matcher_util.h" +#include "dimension.h" #include "stats_util.h" +#include "field_util.h" #include <log/event_tag_map.h> #include <log/log_event_list.h> @@ -91,127 +93,173 @@ bool combinationMatch(const vector<int>& children, const LogicalOperation& opera return matched; } -bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) { - const int tagId = event.GetTagId(); - - if (simpleMatcher.tag() != tagId) { - return false; - } - // now see if this event is interesting to us -- matches ALL the matchers - // defined in the metrics. - bool allMatched = true; - for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) { - auto cur = simpleMatcher.key_value_matcher(j); - - // TODO: Check if this key is a magic key (eg package name). - // TODO: Maybe make packages a different type in the config? - int key = cur.key_matcher().key(); +bool IsAttributionUidField(const Field& field) { + return field.child_size() == 1 && field.child(0).field() == 1 + && field.child(0).child_size() == 1 && field.child(0).child(0).field() == 1; +} - const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case(); - if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) { - // String fields - status_t err = NO_ERROR; - const char* val = event.GetString(key, &err); - if (err == NO_ERROR && val != NULL) { - if (!(cur.eq_string() == val)) { - allMatched = false; - break; - } - } else { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt || - matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) { - // Integer fields - status_t err = NO_ERROR; - int64_t val = event.GetLong(key, &err); - if (err == NO_ERROR) { - if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) { - if (!(val == cur.eq_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) { - if (!(val < cur.lt_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) { - if (!(val > cur.gt_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) { - if (!(val <= cur.lte_int())) { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) { - if (!(val >= cur.gte_int())) { - allMatched = false; - break; +bool matchesNonRepeatedField( + const UidMap& uidMap, + const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, + const Field& field) { + if (matcher.value_matcher_case() == + FieldValueMatcher::ValueMatcherCase::VALUE_MATCHER_NOT_SET) { + return !fieldMap.empty() && fieldMap.begin()->first.field() == matcher.field(); + } else if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::kMatchesTuple) { + bool allMatched = true; + for (int i = 0; allMatched && i < matcher.matches_tuple().field_value_matcher_size(); ++i) { + const auto& childMatcher = matcher.matches_tuple().field_value_matcher(i); + Field childField = field; + appendLeaf(&childField, childMatcher.field()); + allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, childField); + } + return allMatched; + } else { + auto ret = fieldMap.equal_range(field); + int found = 0; + for (auto it = ret.first; it != ret.second; ++it) { + found++; + } + // Not found. + if (found <= 0) { + return false; + } + if (found > 1) { + ALOGE("Found multiple values for optional field."); + return false; + } + bool matched = false; + switch (matcher.value_matcher_case()) { + case FieldValueMatcher::ValueMatcherCase::kEqBool: + // Logd does not support bool, it is int instead. + matched = ((ret.first->second.value_int() > 0) == matcher.eq_bool()); + break; + case FieldValueMatcher::ValueMatcherCase::kEqString: + { + if (IsAttributionUidField(field)) { + const int uid = ret.first->second.value_int(); + std::set<string> packageNames = + uidMap.getAppNamesFromUid(uid, true /* normalize*/); + matched = packageNames.find(matcher.eq_string()) != packageNames.end(); + } else { + matched = (ret.first->second.value_str() == matcher.eq_string()); } - } - } else { - allMatched = false; + } + break; + case FieldValueMatcher::ValueMatcherCase::kEqInt: + matched = (ret.first->second.value_int() == matcher.eq_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kLtInt: + matched = (ret.first->second.value_int() < matcher.lt_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kGtInt: + matched = (ret.first->second.value_int() > matcher.gt_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kLtFloat: + matched = (ret.first->second.value_float() < matcher.lt_float()); + break; + case FieldValueMatcher::ValueMatcherCase::kGtFloat: + matched = (ret.first->second.value_float() > matcher.gt_float()); + break; + case FieldValueMatcher::ValueMatcherCase::kLteInt: + matched = (ret.first->second.value_int() <= matcher.lte_int()); + break; + case FieldValueMatcher::ValueMatcherCase::kGteInt: + matched = (ret.first->second.value_int() >= matcher.gte_int()); + break; + default: break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) { - // Boolean fields - status_t err = NO_ERROR; - bool val = event.GetBool(key, &err); - if (err == NO_ERROR) { - if (!(cur.eq_bool() == val)) { - allMatched = false; - break; - } - } else { - allMatched = false; - break; - } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat || - matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) { - // Float fields - status_t err = NO_ERROR; - float val = event.GetFloat(key, &err); - if (err == NO_ERROR) { - if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) { - if (!(val < cur.lt_float())) { - allMatched = false; - break; + } + return matched; + } +} + +bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, const Field& field) { + if (matcher.position() == Position::FIRST) { + Field first_field = field; + setPositionForLeaf(&first_field, 0); + return matchesNonRepeatedField(uidMap, fieldMap, matcher, first_field); + } else { + auto itLower = fieldMap.lower_bound(field); + if (itLower == fieldMap.end()) { + return false; + } + Field next_field = field; + getNextField(&next_field); + auto itUpper = fieldMap.lower_bound(next_field); + switch (matcher.position()) { + case Position::LAST: + { + itUpper--; + if (itUpper == fieldMap.end()) { + return false; + } else { + Field last_field = field; + int last_index = getPositionByReferenceField(field, itUpper->first); + if (last_index < 0) { + return false; + } + setPositionForLeaf(&last_field, last_index); + return matchesNonRepeatedField(uidMap, fieldMap, matcher, last_field); + } + } + break; + case Position::ANY: + { + std::set<int> indexes; + for (auto it = itLower; it != itUpper; ++it) { + int index = getPositionByReferenceField(field, it->first); + if (index >= 0) { + indexes.insert(index); + } } - } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) { - if (!(val > cur.gt_float())) { - allMatched = false; - break; + bool matched = false; + for (const int index : indexes) { + Field any_field = field; + setPositionForLeaf(&any_field, index); + matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, any_field); } - } - } else { - allMatched = false; - break; - } - } else { - // If value matcher is not present, assume that we match. - } + return matched; + } + default: + return false; + } } - return allMatched; + } -vector<KeyValuePair> getDimensionKey(const LogEvent& event, - const std::vector<KeyMatcher>& dimensions) { - vector<KeyValuePair> key; - key.reserve(dimensions.size()); - for (const KeyMatcher& dimension : dimensions) { - KeyValuePair k = event.GetKeyValueProto(dimension.key()); - key.push_back(k); +bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, const Field& field) { + if (!matcher.has_position()) { + return matchesNonRepeatedField(uidMap, fieldMap, matcher, field); + } else { + return matchesRepeatedField(uidMap, fieldMap, matcher, field); } - return key; } +bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, + const LogEvent& event) { + if (simpleMatcher.field_value_matcher_size() <= 0) { + return event.GetTagId() == simpleMatcher.atom_id(); + } + Field root_field; + root_field.set_field(simpleMatcher.atom_id()); + FieldValueMatcher root_field_matcher; + root_field_matcher.set_field(simpleMatcher.atom_id()); + for (int i = 0; i < simpleMatcher.field_value_matcher_size(); i++) { + *root_field_matcher.mutable_matches_tuple()->add_field_value_matcher() = + simpleMatcher.field_value_matcher(i); + } + return matchFieldSimple(uidMap, event.getFieldValueMap(), root_field_matcher, root_field); +} + +vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher) { + vector<DimensionsValue> values; + findDimensionsValues(event.getFieldValueMap(), matcher, &values); + return values; +} } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h index f54ab3648a19..704cb4c22e00 100644 --- a/cmds/statsd/src/matchers/matcher_util.h +++ b/cmds/statsd/src/matchers/matcher_util.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef MATCHER_UTIL_H -#define MATCHER_UTIL_H +#pragma once #include "logd/LogEvent.h" @@ -28,6 +27,7 @@ #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "stats_util.h" +#include "packages/UidMap.h" namespace android { namespace os { @@ -42,12 +42,14 @@ enum MatchingState { bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation, const std::vector<MatchingState>& matcherResults); -bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper); +bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& dimensionsMap, + const FieldValueMatcher& matcher, const Field& field); -std::vector<KeyValuePair> getDimensionKey(const LogEvent& event, - const std::vector<KeyMatcher>& dimensions); +bool matchesSimple(const UidMap& uidMap, + const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper); + +std::vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher); } // namespace statsd } // namespace os } // namespace android -#endif // MATCHER_UTIL_H diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 9031ed018df2..a24364df0fb2 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -20,6 +20,7 @@ #include "CountMetricProducer.h" #include "guardrail/StatsdStats.h" #include "stats_util.h" +#include "stats_log_util.h" #include <limits.h> #include <stdlib.h> @@ -42,7 +43,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_COUNT_METRICS = 5; @@ -51,12 +52,6 @@ const int FIELD_ID_DATA = 1; // for CountMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for CountBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -66,7 +61,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric const int conditionIndex, const sp<ConditionWizard>& wizard, const uint64_t startTimeNs) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) { + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) { // TODO: evaluate initial conditions. and set mConditionMet. if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) { mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000; @@ -75,7 +70,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric } // TODO: use UidMap if uid->pkg_name is required - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -83,7 +78,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mConditionSliced = true; } - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -92,43 +87,49 @@ CountMetricProducer::~CountMetricProducer() { } void CountMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); +} + +void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); + report->set_metric_id(mMetricId); + report->set_start_report_nanos(mStartTimeNs); + + auto count_metrics = report->mutable_count_metrics(); + for (const auto& counter : mPastBuckets) { + CountMetricData* metricData = count_metrics->add_data(); + *metricData->mutable_dimension() = counter.first.getDimensionsValue(); + for (const auto& bucket : counter.second) { + CountBucketInfo* bucketInfo = metricData->add_bucket_info(); + bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); + bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs); + bucketInfo->set_count(bucket.mCount); + } + } } void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS); - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...",(long long)mMetricId); for (const auto& counter : mPastBuckets) { const HashableDimensionKey& hashableKey = counter.first; - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); VLOG(" dimension key %s", hashableKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (CountBucketInfo). for (const auto& bucket : counter.second) { @@ -157,7 +158,7 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, void CountMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); mCondition = conditionMet; } @@ -169,11 +170,11 @@ bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedCounter->size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("CountMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("CountMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -183,7 +184,7 @@ bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) void CountMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); @@ -214,7 +215,7 @@ void CountMetricProducer::onMatchedLogEventInternalLocked( mCurrentSlicedCounter->find(eventKey)->second); } - VLOG("metric %s %s->%lld", mName.c_str(), eventKey.c_str(), + VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(), (long long)(*mCurrentSlicedCounter)[eventKey]); } @@ -233,8 +234,8 @@ void CountMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { info.mCount = counter.second; auto& bucketList = mPastBuckets[counter.first]; bucketList.push_back(info); - VLOG("metric %s, dump key value: %s -> %lld", mName.c_str(), counter.first.c_str(), - (long long)counter.second); + VLOG("metric %lld, dump key value: %s -> %lld", + (long long)mMetricId, counter.first.c_str(), (long long)counter.second); } for (auto& tracker : mAnomalyTrackers) { @@ -246,7 +247,7 @@ void CountMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; - VLOG("metric %s: new bucket start time: %lld", mName.c_str(), + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); } diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index e32fc06de353..6087ae502e7b 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -51,12 +51,13 @@ public: protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 1c8f422eca61..0117b6d7faa5 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -20,6 +20,7 @@ #include "DurationMetricProducer.h" #include "guardrail/StatsdStats.h" #include "stats_util.h" +#include "stats_log_util.h" #include <limits.h> #include <stdlib.h> @@ -41,7 +42,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_DURATION_METRICS = 6; @@ -50,12 +51,6 @@ const int FIELD_ID_DATA = 1; // for DurationMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for DurationBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -66,15 +61,15 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat const size_t stopIndex, const size_t stopAllIndex, const bool nesting, const sp<ConditionWizard>& wizard, - const vector<KeyMatcher>& internalDimension, + const FieldMatcher& internalDimensions, const uint64_t startTimeNs) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard), + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mAggregationType(metric.aggregation_type()), mStartIndex(startIndex), mStopIndex(stopIndex), mStopAllIndex(stopAllIndex), mNested(nesting), - mInternalDimension(internalDimension) { + mInternalDimensions(internalDimensions) { // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract // them in the base class, because the proto generated CountMetric, and DurationMetric are // not related. Maybe we should add a template in the future?? @@ -85,7 +80,7 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat } // TODO: use UidMap if uid->pkg_name is required - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -93,7 +88,7 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat mConditionSliced = true; } - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -101,15 +96,19 @@ DurationMetricProducer::~DurationMetricProducer() { VLOG("~DurationMetric() called"); } -sp<AnomalyTracker> DurationMetricProducer::createAnomalyTracker(const Alert &alert) { - if (alert.trigger_if_sum_gt() > alert.number_of_buckets() * mBucketSizeNs) { - ALOGW("invalid alert: threshold (%lld) > possible recordable value (%d x %lld)", - alert.trigger_if_sum_gt(), alert.number_of_buckets(), +sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(const Alert &alert) { + std::lock_guard<std::mutex> lock(mMutex); + if (alert.trigger_if_sum_gt() > alert.num_buckets() * mBucketSizeNs) { + ALOGW("invalid alert: threshold (%f) > possible recordable value (%d x %lld)", + alert.trigger_if_sum_gt(), alert.num_buckets(), (long long)mBucketSizeNs); return nullptr; } - // TODO: return a DurationAnomalyTracker (which should sublclass AnomalyTracker) - return new AnomalyTracker(alert, mConfigKey); + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, mConfigKey); + if (anomalyTracker != nullptr) { + mAnomalyTrackers.push_back(anomalyTracker); + } + return anomalyTracker; } unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( @@ -117,17 +116,17 @@ unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( - mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( - mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested, + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers); } } void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); flushIfNeededLocked(eventTime); // Now for each of the on-going event, check if the condition has changed for them. for (auto& pair : mCurrentSlicedDuration) { @@ -137,7 +136,7 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eve void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); mCondition = conditionMet; flushIfNeededLocked(eventTime); // TODO: need to populate the condition change time from the event which triggers the condition @@ -147,40 +146,46 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, } } +void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); + report->set_metric_id(mMetricId); + report->set_start_report_nanos(mStartTimeNs); + + auto duration_metrics = report->mutable_duration_metrics(); + for (const auto& pair : mPastBuckets) { + DurationMetricData* metricData = duration_metrics->add_data(); + *metricData->mutable_dimension() = pair.first.getDimensionsValue(); + for (const auto& bucket : pair.second) { + auto bucketInfo = metricData->add_bucket_info(); + bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); + bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs); + bucketInfo->set_duration_nanos(bucket.mDuration); + } + } +} + void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS); - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...", (long long)mMetricId); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); VLOG(" dimension key %s", hashableKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { @@ -232,11 +237,11 @@ bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newK // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedDuration.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("DurationMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("DurationMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -245,7 +250,7 @@ bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newK void DurationMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKeys, bool condition, + const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { flushIfNeededLocked(event.GetTimestampNs()); @@ -256,7 +261,6 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( return; } - HashableDimensionKey atomKey(getDimensionKey(event, mInternalDimension)); if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) { if (hitGuardRailLocked(eventKey)) { @@ -267,11 +271,25 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( auto it = mCurrentSlicedDuration.find(eventKey); - if (matcherIndex == mStartIndex) { - it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys); - } else if (matcherIndex == mStopIndex) { - it->second->noteStop(atomKey, event.GetTimestampNs(), false); + std::vector<DimensionsValue> values = getDimensionKeys(event, mInternalDimensions); + if (values.empty()) { + if (matcherIndex == mStartIndex) { + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, + event.GetTimestampNs(), conditionKeys); + } else if (matcherIndex == mStopIndex) { + it->second->noteStop(DEFAULT_DIMENSION_KEY, event.GetTimestampNs(), false); + } + } else { + for (const DimensionsValue& value : values) { + if (matcherIndex == mStartIndex) { + it->second->noteStart(HashableDimensionKey(value), condition, + event.GetTimestampNs(), conditionKeys); + } else if (matcherIndex == mStopIndex) { + it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false); + } + } } + } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 7044b4b56258..e06b9a14563d 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -20,6 +20,7 @@ #include <unordered_map> #include <android/util/ProtoOutputStream.h> +#include "../anomaly/DurationAnomalyTracker.h" #include "../condition/ConditionTracker.h" #include "../matchers/matcher_util.h" #include "MetricProducer.h" @@ -41,21 +42,22 @@ public: const int conditionIndex, const size_t startIndex, const size_t stopIndex, const size_t stopAllIndex, const bool nesting, const sp<ConditionWizard>& wizard, - const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs); + const FieldMatcher& internalDimensions, const uint64_t startTimeNs); virtual ~DurationMetricProducer(); - virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) override; + sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) override; protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKeys, bool condition, + const ConditionKey& conditionKeys, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; @@ -84,7 +86,7 @@ private: const bool mNested; // The dimension from the atom predicate. e.g., uid, wakelock name. - const vector<KeyMatcher> mInternalDimension; + const FieldMatcher mInternalDimensions; // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. @@ -98,6 +100,9 @@ private: std::unique_ptr<DurationTracker> createDurationTracker( const HashableDimensionKey& eventKey) const; + // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers + std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; + // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const HashableDimensionKey& newKey); @@ -105,6 +110,7 @@ private: FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 6a072b076bef..821d8ea48aef 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -41,7 +41,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_EVENT_METRICS = 4; @@ -55,7 +55,7 @@ EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric const int conditionIndex, const sp<ConditionWizard>& wizard, const uint64_t startTimeNs) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) { + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) { if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), metric.links().end()); @@ -64,7 +64,7 @@ EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric startNewProtoOutputStreamLocked(); - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -96,14 +96,19 @@ std::unique_ptr<std::vector<uint8_t>> serializeProtoLocked(ProtoOutputStream& pr return buffer; } +void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + +} + void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); size_t bufferSize = mProto->size(); - VLOG("metric %s dump report now... proto size: %zu ", mName.c_str(), bufferSize); + VLOG("metric %lld dump report now... proto size: %zu ", + (long long)mMetricId, bufferSize); std::unique_ptr<std::vector<uint8_t>> buffer = serializeProtoLocked(*mProto); protoOutput->write(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS, @@ -115,13 +120,13 @@ void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); mCondition = conditionMet; } void EventMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (!condition) { return; diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h index 6120ad8fd6da..a57b07d6648e 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -46,11 +46,12 @@ protected: private: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 47cca0e23892..eaf1de238e8d 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -19,6 +19,8 @@ #include "GaugeMetricProducer.h" #include "guardrail/StatsdStats.h" +#include "dimension.h" +#include "stats_log_util.h" #include <cutils/log.h> @@ -42,7 +44,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_GAUGE_METRICS = 8; @@ -51,12 +53,6 @@ const int FIELD_ID_DATA = 1; // for GaugeMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for GaugeBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -67,22 +63,22 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric const sp<ConditionWizard>& wizard, const int atomTagId, const int pullTagId, const uint64_t startTimeNs, shared_ptr<StatsPullerManager> statsPullerManager) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard), + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mStatsPullerManager(statsPullerManager), mPullTagId(pullTagId), mAtomTagId(atomTagId) { + mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>(); + mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) { mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000; } else { mBucketSizeNs = kDefaultGaugemBucketSizeNs; } - for (int i = 0; i < metric.gauge_fields().field_num_size(); i++) { - mGaugeFields.push_back(metric.gauge_fields().field_num(i)); - } + mFieldFilter = metric.gauge_fields_filter(); // TODO: use UidMap if uid->pkg_name is required - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -93,10 +89,10 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric // Kicks off the puller immediately. if (mPullTagId != -1) { mStatsPullerManager->RegisterReceiver(mPullTagId, this, - metric.bucket().bucket_size_millis()); + metric.bucket().bucket_size_millis()); } - VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } @@ -116,40 +112,32 @@ GaugeMetricProducer::~GaugeMetricProducer() { } } +void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); +} + void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { - VLOG("gauge metric %s dump report now...", mName.c_str()); + VLOG("gauge metric %lld report now...", (long long)mMetricId); flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); VLOG(" dimension key %s", hashableKey.c_str()); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (GaugeBucketInfo). for (const auto& bucket : pair.second) { @@ -160,25 +148,11 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS, (long long)bucket.mBucketEndNs); long long atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM); - long long eventToken = protoOutput->start(FIELD_TYPE_MESSAGE | mAtomTagId); - for (const auto& pair : bucket.mEvent->kv) { - if (pair.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT32 | pair.key(), pair.value_int()); - } else if (pair.has_value_long()) { - protoOutput->write(FIELD_TYPE_INT64 | pair.key(), pair.value_long()); - } else if (pair.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | pair.key(), pair.value_str()); - } else if (pair.has_value_long()) { - protoOutput->write(FIELD_TYPE_FLOAT | pair.key(), pair.value_float()); - } else if (pair.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | pair.key(), pair.value_bool()); - } - } - protoOutput->end(eventToken); + writeFieldValueTreeToStream(*bucket.mGaugeFields, protoOutput); protoOutput->end(atomToken); protoOutput->end(bucketInfoToken); - VLOG("\t bucket [%lld - %lld] content: %s", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, bucket.mEvent->ToString().c_str()); + VLOG("\t bucket [%lld - %lld] includes %d gauge fields.", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, (int)bucket.mGaugeFields->size()); } protoOutput->end(wrapperToken); } @@ -192,7 +166,7 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) { - VLOG("Metric %s onConditionChanged", mName.c_str()); + VLOG("Metric %lld onConditionChanged", (long long)mMetricId); flushIfNeededLocked(eventTime); mCondition = conditionMet; @@ -220,21 +194,16 @@ void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, } void GaugeMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); } -shared_ptr<EventKV> GaugeMetricProducer::getGauge(const LogEvent& event) { - shared_ptr<EventKV> ret = make_shared<EventKV>(); - if (mGaugeFields.size() == 0) { - for (int i = 1; i <= event.size(); i++) { - ret->kv.push_back(event.GetKeyValueProto(i)); - } - } else { - for (int i = 0; i < (int)mGaugeFields.size(); i++) { - ret->kv.push_back(event.GetKeyValueProto(mGaugeFields[i])); - } +std::shared_ptr<FieldValueMap> GaugeMetricProducer::getGaugeFields(const LogEvent& event) { + std::shared_ptr<FieldValueMap> gaugeFields = + std::make_shared<FieldValueMap>(event.getFieldValueMap()); + if (!mFieldFilter.include_all()) { + filterFields(mFieldFilter.fields(), gaugeFields.get()); } - return ret; + return gaugeFields; } void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) { @@ -254,11 +223,11 @@ bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedBucket->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedBucket->size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("GaugeMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("GaugeMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -268,7 +237,7 @@ bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) void GaugeMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { if (condition == false) { return; @@ -285,21 +254,21 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end()) { return; } - shared_ptr<EventKV> gauge = getGauge(event); + std::shared_ptr<FieldValueMap> gaugeFields = getGaugeFields(event); if (hitGuardRailLocked(eventKey)) { return; } - (*mCurrentSlicedBucket)[eventKey] = gauge; + (*mCurrentSlicedBucket)[eventKey] = gaugeFields; // Anomaly detection on gauge metric only works when there is one numeric // field specified. if (mAnomalyTrackers.size() > 0) { - if (gauge->kv.size() == 1) { - KeyValuePair pair = gauge->kv[0]; + if (gaugeFields->size() == 1) { + const DimensionsValue& dimensionsValue = gaugeFields->begin()->second; long gaugeVal = 0; - if (pair.has_value_int()) { - gaugeVal = (long)pair.value_int(); - } else if (pair.has_value_long()) { - gaugeVal = pair.value_long(); + if (dimensionsValue.has_value_int()) { + gaugeVal = (long)dimensionsValue.value_int(); + } else if (dimensionsValue.has_value_long()) { + gaugeVal = dimensionsValue.value_long(); } for (auto& tracker : mAnomalyTrackers) { tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, @@ -313,12 +282,12 @@ void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { mCurrentSlicedBucketForAnomaly->clear(); status_t err = NO_ERROR; for (const auto& slice : *mCurrentSlicedBucket) { - KeyValuePair pair = slice.second->kv[0]; + const DimensionsValue& dimensionsValue = slice.second->begin()->second; long gaugeVal = 0; - if (pair.has_value_int()) { - gaugeVal = (long)pair.value_int(); - } else if (pair.has_value_long()) { - gaugeVal = pair.value_long(); + if (dimensionsValue.has_value_int()) { + gaugeVal = (long)dimensionsValue.value_int(); + } else if (dimensionsValue.has_value_long()) { + gaugeVal = dimensionsValue.value_long(); } (*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal; } @@ -342,11 +311,11 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { info.mBucketNum = mCurrentBucketNum; for (const auto& slice : *mCurrentSlicedBucket) { - info.mEvent = slice.second; + info.mGaugeFields = slice.second; auto& bucketList = mPastBuckets[slice.first]; bucketList.push_back(info); - VLOG("gauge metric %s, dump key value: %s -> %s", mName.c_str(), - slice.first.c_str(), slice.second->ToString().c_str()); + VLOG("gauge metric %lld, dump key value: %s", + (long long)mMetricId, slice.first.c_str()); } // Reset counters @@ -357,13 +326,13 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { } } - mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>(); + mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>(); // Adjusts the bucket start time int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; - VLOG("metric %s: new bucket start time: %lld", mName.c_str(), + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); } diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 19d51e8c517e..2a6401d6dcac 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -35,10 +35,13 @@ namespace statsd { struct GaugeBucket { int64_t mBucketStartNs; int64_t mBucketEndNs; - std::shared_ptr<EventKV> mEvent; + std::shared_ptr<FieldValueMap> mGaugeFields; uint64_t mBucketNum; }; +typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<FieldValueMap>> + DimToGaugeFieldsMap; + // This gauge metric producer first register the puller to automatically pull the gauge at the // beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise // proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric @@ -57,12 +60,13 @@ public: protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // for testing GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric, @@ -94,10 +98,10 @@ private: std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets; // The current bucket. - std::shared_ptr<DimToEventKVMap> mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>(); + std::shared_ptr<DimToGaugeFieldsMap> mCurrentSlicedBucket; // The current bucket for anomaly detection. - std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); + std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly; // Translate Atom based bucket to single numeric value bucket for anomaly void updateCurrentSlicedBucketForAnomaly(); @@ -105,10 +109,10 @@ private: int mAtomTagId; // Whitelist of fields to report. Empty means all are reported. - std::vector<int> mGaugeFields; + FieldFilter mFieldFilter; // apply a whitelist on the original input - std::shared_ptr<EventKV> getGauge(const LogEvent& event); + std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const HashableDimensionKey& newKey); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 528690832f03..d620a7ebee00 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -28,24 +28,14 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo return; } - HashableDimensionKey eventKey; - - if (mDimension.size() > 0) { - vector<KeyValuePair> key = getDimensionKey(event, mDimension); - eventKey = HashableDimensionKey(key); - } else { - eventKey = DEFAULT_DIMENSION_KEY; - } - bool condition; - - map<string, HashableDimensionKey> conditionKeys; + ConditionKey conditionKey; if (mConditionSliced) { for (const auto& link : mConditionLinks) { - HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link); - conditionKeys[link.condition()] = conditionKey; + conditionKey.insert(std::make_pair(link.condition(), + getDimensionKeysForCondition(event, link))); } - if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) { + if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) { condition = false; } else { condition = true; @@ -53,7 +43,17 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo } else { condition = mCondition; } - onMatchedLogEventInternalLocked(matcherIndex, eventKey, conditionKeys, condition, event); + + if (mDimensions.child_size() > 0) { + vector<DimensionsValue> dimensionValues = getDimensionKeys(event, mDimensions); + for (const DimensionsValue& dimensionValue : dimensionValues) { + onMatchedLogEventInternalLocked( + matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event); + } + } else { + onMatchedLogEventInternalLocked( + matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event); + } } } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 85ef4ada07d8..3779c4487d23 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -39,9 +39,9 @@ namespace statsd { // be a no-op. class MetricProducer : public virtual PackageInfoListener { public: - MetricProducer(const std::string& name, const ConfigKey& key, const int64_t startTimeNs, + MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t startTimeNs, const int conditionIndex, const sp<ConditionWizard>& wizard) - : mName(name), + : mMetricId(metricId), mConfigKey(key), mStartTimeNs(startTimeNs), mCurrentBucketStartTimeNs(startTimeNs), @@ -50,6 +50,7 @@ public: mConditionSliced(false), mWizard(wizard), mConditionTrackerIndex(conditionIndex){}; + virtual ~MetricProducer(){}; void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{ @@ -90,6 +91,10 @@ public: std::lock_guard<std::mutex> lock(mMutex); return onDumpReportLocked(dumpTimeNs, protoOutput); } + void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) { + std::lock_guard<std::mutex> lock(mMutex); + return onDumpReportLocked(dumpTimeNs, report); + } // Returns the memory in bytes currently used to store this metric's data. Does not change // state. @@ -98,13 +103,13 @@ public: return byteSizeLocked(); } - virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) { - return new AnomalyTracker(alert, mConfigKey); - } - - void addAnomalyTracker(sp<AnomalyTracker> tracker) { + virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) { std::lock_guard<std::mutex> lock(mMutex); - mAnomalyTrackers.push_back(tracker); + sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey); + if (anomalyTracker != nullptr) { + mAnomalyTrackers.push_back(anomalyTracker); + } + return anomalyTracker; } int64_t getBuckeSizeInNs() const { @@ -112,14 +117,19 @@ public: return mBucketSizeNs; } + inline const int64_t& getMetricId() { + return mMetricId; + } + protected: virtual void onConditionChangedLocked(const bool condition, const uint64_t eventTime) = 0; virtual void onSlicedConditionMayChangeLocked(const uint64_t eventTime) = 0; virtual void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) = 0; + virtual void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) = 0; virtual size_t byteSizeLocked() const = 0; - const std::string mName; + const int64_t mMetricId; const ConfigKey mConfigKey; @@ -140,7 +150,7 @@ protected: int mConditionTrackerIndex; - std::vector<KeyMatcher> mDimension; // The dimension defined in statsd_config + FieldMatcher mDimensions; // The dimension defined in statsd_config std::vector<MetricConditionLink> mConditionLinks; @@ -163,7 +173,7 @@ protected: */ virtual void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) = 0; // Consume the parsed stats log entry that already matched the "what" of the metric. diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 231bd8e4df99..7f0239fd334e 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -49,9 +49,9 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, sp<UidMap> uidMap) : mConfigKey(key), mUidMap(uidMap) { mConfigValid = - initStatsdConfig(key, config, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers, + initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap, - mTrackerToMetricMap, mTrackerToConditionMap); + mTrackerToMetricMap, mTrackerToConditionMap, mNoReportMetricIds); if (!config.has_log_source()) { // TODO(b/70794411): uncomment the following line and remove the hard coded log source @@ -142,15 +142,25 @@ void MetricsManager::onUidMapReceived() { initLogSourceWhiteList(); } +void MetricsManager::onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report) { + for (const auto& producer : mAllMetricProducers) { + if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) { + producer->onDumpReport(dumpTimeStampNs, report->add_metrics()); + } + } +} + void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) { VLOG("=========================Metric Reports Start=========================="); uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC; // one StatsLogReport per MetricProduer - for (auto& metric : mAllMetricProducers) { - long long token = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS); - metric->onDumpReport(dumpTimeStampNs, protoOutput); - protoOutput->end(token); + for (const auto& producer : mAllMetricProducers) { + if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) { + long long token = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS); + producer->onDumpReport(dumpTimeStampNs, protoOutput); + protoOutput->end(token); + } } VLOG("=========================Metric Reports End=========================="); } @@ -167,6 +177,22 @@ void MetricsManager::onLogEvent(const LogEvent& event) { VLOG("log source %d not on the whitelist", event.GetUid()); return; } + } else { // Check that app hook fields are valid. + // TODO: Find a way to make these checks easier to maintain if the app hooks get changed. + + // Label is 2nd from last field and must be from [0, 15]. + status_t err = NO_ERROR; + long label = event.GetLong(event.size()-1, &err); + if (err != NO_ERROR || label < 0 || label > 15) { + VLOG("App hook does not have valid label %ld", label); + return; + } + // The state must be from 0,3. This part of code must be manually updated. + long apphookState = event.GetLong(event.size(), &err); + if (err != NO_ERROR || apphookState < 0 || apphookState > 3) { + VLOG("App hook does not have valid state %ld", apphookState); + return; + } } int tagId = event.GetTagId(); @@ -234,7 +260,7 @@ void MetricsManager::onLogEvent(const LogEvent& event) { for (size_t i = 0; i < mAllAtomMatchers.size(); i++) { if (matcherCache[i] == MatchingState::kMatched) { StatsdStats::getInstance().noteMatcherMatched(mConfigKey, - mAllAtomMatchers[i]->getName()); + mAllAtomMatchers[i]->getId()); auto pair = mTrackerToMetricMap.find(i); if (pair != mTrackerToMetricMap.end()) { auto& metricList = pair->second; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 8faa75d34be9..8175cd2c9889 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -45,8 +45,9 @@ public: void onLogEvent(const LogEvent& event); - void onAnomalyAlarmFired(const uint64_t timestampNs, - unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet); + void onAnomalyAlarmFired( + const uint64_t timestampNs, + unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet); void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor); @@ -58,6 +59,7 @@ public: // Config source owner can call onDumpReport() to get all the metrics collected. virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput); + virtual void onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report); // Computes the total byte size of all metrics managed by a single config source. // Does not change the state. @@ -128,6 +130,12 @@ private: std::unordered_map<int, std::vector<int>> mConditionToMetricMap; void initLogSourceWhiteList(); + + // The metrics that don't need to be uploaded or even reported. + std::set<int64_t> mNoReportMetricIds; + + FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); + FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 40aed7bcf934..5f7d76140397 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -17,8 +17,10 @@ #define DEBUG false // STOPSHIP if true #include "Log.h" +#include "dimension.h" #include "ValueMetricProducer.h" #include "guardrail/StatsdStats.h" +#include "stats_log_util.h" #include <cutils/log.h> #include <limits.h> @@ -45,7 +47,7 @@ namespace os { namespace statsd { // for StatsLogReport -const int FIELD_ID_NAME = 1; +const int FIELD_ID_ID = 1; const int FIELD_ID_START_REPORT_NANOS = 2; const int FIELD_ID_END_REPORT_NANOS = 3; const int FIELD_ID_VALUE_METRICS = 7; @@ -54,12 +56,6 @@ const int FIELD_ID_DATA = 1; // for ValueMetricData const int FIELD_ID_DIMENSION = 1; const int FIELD_ID_BUCKET_INFO = 2; -// for KeyValuePair -const int FIELD_ID_KEY = 1; -const int FIELD_ID_VALUE_STR = 2; -const int FIELD_ID_VALUE_INT = 3; -const int FIELD_ID_VALUE_BOOL = 4; -const int FIELD_ID_VALUE_FLOAT = 5; // for ValueBucketInfo const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; @@ -73,7 +69,7 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric const sp<ConditionWizard>& wizard, const int pullTagId, const uint64_t startTimeNs, shared_ptr<StatsPullerManager> statsPullerManager) - : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard), + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mValueField(metric.value_field()), mStatsPullerManager(statsPullerManager), mPullTagId(pullTagId) { @@ -84,7 +80,7 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric mBucketSizeNs = kDefaultBucketSizeMillis * 1000 * 1000; } - mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); + mDimensions = metric.dimensions(); if (metric.links().size() > 0) { mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), @@ -97,8 +93,8 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric mStatsPullerManager->RegisterReceiver(mPullTagId, this, metric.bucket().bucket_size_millis()); } - VLOG("value metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(), - (long long)mBucketSizeNs, (long long)mStartTimeNs); + VLOG("value metric %lld created. bucket size %lld start_time: %lld", + (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs); } // for testing @@ -118,40 +114,45 @@ ValueMetricProducer::~ValueMetricProducer() { } void ValueMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) { - VLOG("Metric %s onSlicedConditionMayChange", mName.c_str()); + VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId); +} + +void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { + flushIfNeededLocked(dumpTimeNs); + report->set_metric_id(mMetricId); + report->set_start_report_nanos(mStartTimeNs); + auto value_metrics = report->mutable_value_metrics(); + for (const auto& pair : mPastBuckets) { + ValueMetricData* metricData = value_metrics->add_data(); + *metricData->mutable_dimension() = pair.first.getDimensionsValue(); + for (const auto& bucket : pair.second) { + ValueBucketInfo* bucketInfo = metricData->add_bucket_info(); + bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs); + bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs); + bucketInfo->set_value(bucket.mValue); + } + } } void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, ProtoOutputStream* protoOutput) { - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...", (long long)mMetricId); flushIfNeededLocked(dumpTimeNs); - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs); long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; VLOG(" dimension key %s", hashableKey.c_str()); - const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs(); long long wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - // First fill dimension (KeyValuePairs). - for (const auto& kv : kvs) { - long long dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); - protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); - if (kv.has_value_str()) { - protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); - } else if (kv.has_value_int()) { - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); - } else if (kv.has_value_bool()) { - protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool()); - } else if (kv.has_value_float()) { - protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float()); - } - protoOutput->end(dimensionToken); - } + // First fill dimension. + long long dimensionToken = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); + writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput); + protoOutput->end(dimensionToken); // Then fill bucket_info (ValueBucketInfo). for (const auto& bucket : pair.second) { @@ -171,7 +172,7 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, protoOutput->end(protoToken); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs); - VLOG("metric %s dump report now...", mName.c_str()); + VLOG("metric %lld dump report now...", (long long)mMetricId); mPastBuckets.clear(); mStartTimeNs = mCurrentBucketStartTimeNs; // TODO: Clear mDimensionKeyMap once the report is dumped. @@ -218,7 +219,8 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven // For scheduled pulled data, the effective event time is snap to the nearest // bucket boundary to make bucket finalize. uint64_t realEventTime = allData.at(0)->GetTimestampNs(); - uint64_t eventTime = mStartTimeNs + ((realEventTime - mStartTimeNs)/mBucketSizeNs) * mBucketSizeNs; + uint64_t eventTime = mStartTimeNs + + ((realEventTime - mStartTimeNs)/mBucketSizeNs) * mBucketSizeNs; mCondition = false; for (const auto& data : allData) { @@ -242,11 +244,11 @@ bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) } if (mCurrentSlicedBucket.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedBucket.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("ValueMetric %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("ValueMetric %lld dropping data for dimension key %s", + (long long)mMetricId, newKey.c_str()); return true; } } @@ -256,7 +258,7 @@ bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) void ValueMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); if (eventTimeNs < mCurrentBucketStartTimeNs) { @@ -272,7 +274,11 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( } Interval& interval = mCurrentSlicedBucket[eventKey]; - long value = get_value(event); + std::shared_ptr<FieldValueMap> valueFieldMap = getValueFields(event); + if (valueFieldMap->empty() || valueFieldMap->size() > 1) { + return; + } + const long value = getLongFromDimenValue(valueFieldMap->begin()->second); if (mPullTagId != -1) { // for pulled events if (mCondition == true) { @@ -296,15 +302,11 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( } } -long ValueMetricProducer::get_value(const LogEvent& event) { - status_t err = NO_ERROR; - long val = event.GetLong(mValueField, &err); - if (err == NO_ERROR) { - return val; - } else { - VLOG("Can't find value in message. %s", event.ToString().c_str()); - return 0; - } +std::shared_ptr<FieldValueMap> ValueMetricProducer::getValueFields(const LogEvent& event) { + std::shared_ptr<FieldValueMap> valueFields = + std::make_shared<FieldValueMap>(event.getFieldValueMap()); + filterFields(mValueField, valueFields.get()); + return valueFields; } void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { @@ -346,7 +348,7 @@ void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { if (numBucketsForward > 1) { VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); } - VLOG("metric %s: new bucket start time: %lld", mName.c_str(), + VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 2f27e4e52aef..3e7032d8cf2d 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -50,12 +50,13 @@ public: protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const HashableDimensionKey& eventKey, - const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const ConditionKey& conditionKey, bool condition, const LogEvent& event) override; private: void onDumpReportLocked(const uint64_t dumpTimeNs, android::util::ProtoOutputStream* protoOutput) override; + void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override; // Internal interface to handle condition change. void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override; @@ -69,7 +70,7 @@ private: // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); - const int32_t mValueField; + const FieldMatcher mValueField; std::shared_ptr<StatsPullerManager> mStatsPullerManager; @@ -102,7 +103,7 @@ private: // TODO: Add a lock to mPastBuckets. std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets; - long get_value(const LogEvent& event); + std::shared_ptr<FieldValueMap> getValueFields(const LogEvent& event); // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const HashableDimensionKey& newKey); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 3c714b3923e8..842581ed1a9f 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -17,7 +17,7 @@ #ifndef DURATION_TRACKER_H #define DURATION_TRACKER_H -#include "anomaly/AnomalyTracker.h" +#include "anomaly/DurationAnomalyTracker.h" #include "condition/ConditionWizard.h" #include "config/ConfigKey.h" #include "stats_util.h" @@ -60,12 +60,12 @@ struct DurationBucket { class DurationTracker { public: - DurationTracker(const ConfigKey& key, const string& name, const HashableDimensionKey& eventKey, + DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers) + const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) : mConfigKey(key), - mName(name), + mTrackerId(id), mEventKey(eventKey), mWizard(wizard), mConditionTrackerIndex(conditionIndex), @@ -94,7 +94,7 @@ public: std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) = 0; // Predict the anomaly timestamp given the current status. - virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const = 0; protected: @@ -145,7 +145,7 @@ protected: // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; - const std::string mName; + const int64_t mTrackerId; HashableDimensionKey mEventKey; @@ -163,7 +163,7 @@ protected: uint64_t mCurrentBucketNum; - std::vector<sp<AnomalyTracker>> mAnomalyTrackers; + std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp); FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection); diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 6050f4395a5f..94f98ada7014 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -24,12 +24,12 @@ namespace android { namespace os { namespace statsd { -MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const string& name, +MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, anomalyTrackers) { } @@ -42,12 +42,13 @@ bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 1. Report the tuple count if the tuple count > soft limit if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mInfos.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(), - newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize( + mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("MaxDurTracker %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("MaxDurTracker %lld dropping data for dimension key %s", + (long long)mTrackerId, newKey.c_str()); return true; } } @@ -281,7 +282,7 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b } } -int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, +int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const { ALOGE("Max duration producer does not support anomaly timestamp prediction!!!"); return currentTimestamp; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 10eddb8bee6e..68c48cb10a4d 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -28,11 +28,11 @@ namespace statsd { // they stop or bucket expires. class MaxDurationTracker : public DurationTracker { public: - MaxDurationTracker(const ConfigKey& key, const string& name, + MaxDurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers); + const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const uint64_t eventTime, @@ -46,7 +46,7 @@ public: void onSlicedConditionMayChange(const uint64_t timestamp) override; void onConditionChanged(bool condition, const uint64_t timestamp) override; - int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; private: diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 5c430961036d..c77d0b70507b 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -24,13 +24,12 @@ namespace statsd { using std::pair; -OringDurationTracker::OringDurationTracker(const ConfigKey& key, const string& name, - const HashableDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - bool nesting, uint64_t currentBucketStartNs, - uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, +OringDurationTracker::OringDurationTracker( + const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, + uint64_t bucketSizeNs, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, anomalyTrackers), mStarted(), mPaused() { @@ -45,12 +44,13 @@ bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { } if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mConditionKeyMap.size() + 1; - StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(), - newTupleCount); + StatsdStats::getInstance().noteMetricDimensionSize( + mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()), + newTupleCount); // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("OringDurTracker %s dropping data for dimension key %s", mName.c_str(), - newKey.c_str()); + ALOGE("OringDurTracker %lld dropping data for dimension key %s", + (long long)mTrackerId, newKey.c_str()); return true; } } @@ -264,8 +264,8 @@ void OringDurationTracker::onConditionChanged(bool condition, const uint64_t tim } } -int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, - const uint64_t eventTimestampNs) const { +int64_t OringDurationTracker::predictAnomalyTimestampNs( + const DurationAnomalyTracker& anomalyTracker, const uint64_t eventTimestampNs) const { // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32). // All variables below represent durations (not timestamps). diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index b7d3cba7055e..7fe649c436e2 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -27,11 +27,11 @@ namespace statsd { // Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted. class OringDurationTracker : public DurationTracker { public: - OringDurationTracker(const ConfigKey& key, const string& name, + OringDurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs, uint64_t bucketSizeNs, - const std::vector<sp<AnomalyTracker>>& anomalyTrackers); + const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime, const ConditionKey& conditionKey) override; @@ -46,7 +46,7 @@ public: uint64_t timestampNs, std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override; - int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker, + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; private: diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 5d0e97e24f79..89d54c7bc7ee 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -38,21 +38,21 @@ namespace android { namespace os { namespace statsd { -bool handleMetricWithLogTrackers(const string what, const int metricIndex, +bool handleMetricWithLogTrackers(const int64_t what, const int metricIndex, const bool usedForDimension, const vector<sp<LogMatchingTracker>>& allAtomMatchers, - const unordered_map<string, int>& logTrackerMap, + const unordered_map<int64_t, int>& logTrackerMap, unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) { auto logTrackerIt = logTrackerMap.find(what); if (logTrackerIt == logTrackerMap.end()) { - ALOGW("cannot find the AtomMatcher \"%s\" in config", what.c_str()); + ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)what); return false; } - if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getTagIds().size() > 1) { - ALOGE("AtomMatcher \"%s\" has more than one tag ids. When a metric has dimension, " + if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) { + ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, " "the \"what\" can only about one atom type.", - what.c_str()); + (long long)what); return false; } logTrackerIndex = logTrackerIt->second; @@ -62,22 +62,22 @@ bool handleMetricWithLogTrackers(const string what, const int metricIndex, } bool handleMetricWithConditions( - const string condition, const int metricIndex, - const unordered_map<string, int>& conditionTrackerMap, + const int64_t condition, const int metricIndex, + const unordered_map<int64_t, int>& conditionTrackerMap, const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>& links, vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex, unordered_map<int, std::vector<int>>& conditionToMetricMap) { auto condition_it = conditionTrackerMap.find(condition); if (condition_it == conditionTrackerMap.end()) { - ALOGW("cannot find Predicate \"%s\" in the config", condition.c_str()); + ALOGW("cannot find Predicate \"%lld\" in the config", (long long)condition); return false; } for (const auto& link : links) { auto it = conditionTrackerMap.find(link.condition()); if (it == conditionTrackerMap.end()) { - ALOGW("cannot find Predicate \"%s\" in the config", link.condition().c_str()); + ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition()); return false; } allConditionTrackers[condition_it->second]->setSliced(true); @@ -92,7 +92,8 @@ bool handleMetricWithConditions( return true; } -bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap, +bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap, + unordered_map<int64_t, int>& logTrackerMap, vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) { vector<AtomMatcher> matcherConfigs; const int atomMatcherCount = config.atom_matcher_size(); @@ -106,22 +107,22 @@ bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& log switch (logMatcher.contents_case()) { case AtomMatcher::ContentsCase::kSimpleAtomMatcher: allAtomMatchers.push_back(new SimpleLogMatchingTracker( - logMatcher.name(), index, logMatcher.simple_atom_matcher())); + logMatcher.id(), index, logMatcher.simple_atom_matcher(), uidMap)); break; case AtomMatcher::ContentsCase::kCombination: allAtomMatchers.push_back( - new CombinationLogMatchingTracker(logMatcher.name(), index)); + new CombinationLogMatchingTracker(logMatcher.id(), index)); break; default: - ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str()); + ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id()); return false; // continue; } - if (logTrackerMap.find(logMatcher.name()) != logTrackerMap.end()) { + if (logTrackerMap.find(logMatcher.id()) != logTrackerMap.end()) { ALOGE("Duplicate AtomMatcher found!"); return false; } - logTrackerMap[logMatcher.name()] = index; + logTrackerMap[logMatcher.id()] = index; matcherConfigs.push_back(logMatcher); } @@ -131,15 +132,15 @@ bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& log return false; } // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only. - const set<int>& tagIds = matcher->getTagIds(); + const set<int>& tagIds = matcher->getAtomIds(); allTagIds.insert(tagIds.begin(), tagIds.end()); } return true; } bool initConditions(const ConfigKey& key, const StatsdConfig& config, - const unordered_map<string, int>& logTrackerMap, - unordered_map<string, int>& conditionTrackerMap, + const unordered_map<int64_t, int>& logTrackerMap, + unordered_map<int64_t, int>& conditionTrackerMap, vector<sp<ConditionTracker>>& allConditionTrackers, unordered_map<int, std::vector<int>>& trackerToConditionMap) { vector<Predicate> conditionConfigs; @@ -153,23 +154,23 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, switch (condition.contents_case()) { case Predicate::ContentsCase::kSimplePredicate: { allConditionTrackers.push_back(new SimpleConditionTracker( - key, condition.name(), index, condition.simple_predicate(), logTrackerMap)); + key, condition.id(), index, condition.simple_predicate(), logTrackerMap)); break; } case Predicate::ContentsCase::kCombination: { allConditionTrackers.push_back( - new CombinationConditionTracker(condition.name(), index)); + new CombinationConditionTracker(condition.id(), index)); break; } default: - ALOGE("Predicate \"%s\" malformed", condition.name().c_str()); + ALOGE("Predicate \"%lld\" malformed", (long long)condition.id()); return false; } - if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) { + if (conditionTrackerMap.find(condition.id()) != conditionTrackerMap.end()) { ALOGE("Duplicate Predicate found!"); return false; } - conditionTrackerMap[condition.name()] = index; + conditionTrackerMap[condition.id()] = index; conditionConfigs.push_back(condition); } @@ -189,14 +190,15 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, } bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, - const unordered_map<string, int>& logTrackerMap, - const unordered_map<string, int>& conditionTrackerMap, + const unordered_map<int64_t, int>& logTrackerMap, + const unordered_map<int64_t, int>& conditionTrackerMap, const vector<sp<LogMatchingTracker>>& allAtomMatchers, vector<sp<ConditionTracker>>& allConditionTrackers, vector<sp<MetricProducer>>& allMetricProducers, unordered_map<int, std::vector<int>>& conditionToMetricMap, unordered_map<int, std::vector<int>>& trackerToMetricMap, - unordered_map<string, int>& metricMap) { + unordered_map<int64_t, int>& metricMap, + std::set<int64_t> &noReportMetricIds) { sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers); const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + config.event_metric_size() + config.value_metric_size(); @@ -205,24 +207,27 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti // Align all buckets to same instant in MIN_BUCKET_SIZE_SEC, so that avoid alarm // clock will not grow very aggressive. New metrics will be delayed up to // MIN_BUCKET_SIZE_SEC before starting. - long currentTimeSec = time(nullptr); - uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec - - (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) * - NS_PER_SEC; + // Why not use timeBaseSec directly? +// long currentTimeSec = time(nullptr); +// uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec - +// (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) * +// NS_PER_SEC; + + uint64_t startTimeNs = timeBaseSec * NS_PER_SEC; // Build MetricProducers for each metric defined in config. // build CountMetricProducer for (int i = 0; i < config.count_metric_size(); i++) { const CountMetric& metric = config.count_metric(i); if (!metric.has_what()) { - ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str()); + ALOGW("cannot find \"what\" in CountMetric \"%lld\"", (long long)metric.id()); return false; } int metricIndex = allMetricProducers.size(); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0, + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -252,7 +257,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.duration_metric_size(); i++) { int metricIndex = allMetricProducers.size(); const DurationMetric& metric = config.duration_metric(i); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); auto what_it = conditionTrackerMap.find(metric.what()); if (what_it == conditionTrackerMap.end()) { @@ -274,7 +279,7 @@ 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.dimension_size() > 0, allAtomMatchers, + metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[0])) { ALOGE("Duration metrics must specify a valid the start event matcher"); return false; @@ -282,21 +287,19 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti if (simplePredicate.has_stop() && !handleMetricWithLogTrackers(simplePredicate.stop(), metricIndex, - metric.dimension_size() > 0, allAtomMatchers, + metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[1])) { return false; } if (simplePredicate.has_stop_all() && !handleMetricWithLogTrackers(simplePredicate.stop_all(), metricIndex, - metric.dimension_size() > 0, allAtomMatchers, + metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndices[2])) { return false; } - vector<KeyMatcher> internalDimension; - internalDimension.insert(internalDimension.begin(), simplePredicate.dimension().begin(), - simplePredicate.dimension().end()); + FieldMatcher internalDimensions = simplePredicate.dimensions(); int conditionIndex = -1; @@ -316,7 +319,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti sp<MetricProducer> durationMetric = new DurationMetricProducer( key, metric, conditionIndex, trackerIndices[0], trackerIndices[1], - trackerIndices[2], nesting, wizard, internalDimension, startTimeNs); + trackerIndices[2], nesting, wizard, internalDimensions, startTimeNs); allMetricProducers.push_back(durationMetric); } @@ -325,8 +328,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.event_metric_size(); i++) { int metricIndex = allMetricProducers.size(); const EventMetric& metric = config.event_metric(i); - metricMap.insert({metric.name(), metricIndex}); - if (!metric.has_name() || !metric.has_what()) { + metricMap.insert({metric.id(), metricIndex}); + if (!metric.has_id() || !metric.has_what()) { ALOGW("cannot find the metric name or what in config"); return false; } @@ -361,14 +364,14 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.value_metric_size(); i++) { const ValueMetric& metric = config.value_metric(i); if (!metric.has_what()) { - ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str()); + ALOGW("cannot find \"what\" in ValueMetric \"%lld\"", (long long)metric.id()); return false; } int metricIndex = allMetricProducers.size(); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0, + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -376,10 +379,10 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex); // If it is pulled atom, it should be simple matcher with one tagId. - if (atomMatcher->getTagIds().size() != 1) { + if (atomMatcher->getAtomIds().size() != 1) { return false; } - int atomTagId = *(atomMatcher->getTagIds().begin()); + int atomTagId = *(atomMatcher->getAtomIds().begin()); int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1; int conditionIndex = -1; @@ -406,27 +409,27 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti for (int i = 0; i < config.gauge_metric_size(); i++) { const GaugeMetric& metric = config.gauge_metric(i); if (!metric.has_what()) { - ALOGW("cannot find \"what\" in GaugeMetric \"%s\"", metric.name().c_str()); + ALOGW("cannot find \"what\" in GaugeMetric \"%lld\"", (long long)metric.id()); return false; } - if ((!metric.gauge_fields().has_include_all() || - (metric.gauge_fields().include_all() == false)) && - metric.gauge_fields().field_num_size() == 0) { - ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str()); + if ((!metric.gauge_fields_filter().has_include_all() || + (metric.gauge_fields_filter().include_all() == false)) && + !hasLeafNode(metric.gauge_fields_filter().fields())) { + ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); return false; } - if ((metric.gauge_fields().has_include_all() && - metric.gauge_fields().include_all() == true) && - metric.gauge_fields().field_num_size() > 0) { - ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str()); + if ((metric.gauge_fields_filter().has_include_all() && + metric.gauge_fields_filter().include_all() == true) && + hasLeafNode(metric.gauge_fields_filter().fields())) { + ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id()); return false; } int metricIndex = allMetricProducers.size(); - metricMap.insert({metric.name(), metricIndex}); + metricMap.insert({metric.id(), metricIndex}); int trackerIndex; - if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0, + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(), allAtomMatchers, logTrackerMap, trackerToMetricMap, trackerIndex)) { return false; @@ -434,10 +437,10 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex); // If it is pulled atom, it should be simple matcher with one tagId. - if (atomMatcher->getTagIds().size() != 1) { + if (atomMatcher->getAtomIds().size() != 1) { return false; } - int atomTagId = *(atomMatcher->getTagIds().begin()); + int atomTagId = *(atomMatcher->getAtomIds().begin()); int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1; int conditionIndex = -1; @@ -459,49 +462,79 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti key, metric, conditionIndex, wizard, pullTagId, atomTagId, startTimeNs); allMetricProducers.push_back(gaugeProducer); } + for (int i = 0; i < config.no_report_metric_size(); ++i) { + const auto no_report_metric = config.no_report_metric(i); + if (metricMap.find(no_report_metric) == metricMap.end()) { + ALOGW("no_report_metric %lld not exist", no_report_metric); + return false; + } + noReportMetricIds.insert(no_report_metric); + } return true; } -bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap, +bool initAlerts(const StatsdConfig& config, + const unordered_map<int64_t, int>& metricProducerMap, vector<sp<MetricProducer>>& allMetricProducers, vector<sp<AnomalyTracker>>& allAnomalyTrackers) { + unordered_map<int64_t, int> anomalyTrackerMap; for (int i = 0; i < config.alert_size(); i++) { const Alert& alert = config.alert(i); - const auto& itr = metricProducerMap.find(alert.metric_name()); + const auto& itr = metricProducerMap.find(alert.metric_id()); if (itr == metricProducerMap.end()) { - ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(), - alert.metric_name().c_str()); + ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(), + (long long)alert.metric_id()); return false; } - if (alert.trigger_if_sum_gt() < 0 || alert.number_of_buckets() <= 0) { - ALOGW("invalid alert: threshold=%lld num_buckets= %d", - alert.trigger_if_sum_gt(), alert.number_of_buckets()); + if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) { + ALOGW("invalid alert: threshold=%f num_buckets= %d", + alert.trigger_if_sum_gt(), alert.num_buckets()); return false; } const int metricIndex = itr->second; sp<MetricProducer> metric = allMetricProducers[metricIndex]; - sp<AnomalyTracker> anomalyTracker = metric->createAnomalyTracker(alert); + sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert); if (anomalyTracker != nullptr) { - metric->addAnomalyTracker(anomalyTracker); + anomalyTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size())); allAnomalyTrackers.push_back(anomalyTracker); } } + for (int i = 0; i < config.subscription_size(); ++i) { + const Subscription& subscription = config.subscription(i); + if (subscription.subscriber_information_case() == + Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) { + ALOGW("subscription \"%lld\" has no subscriber info.\"", + (long long)subscription.id()); + return false; + } + const auto& itr = anomalyTrackerMap.find(subscription.rule_id()); + if (itr == anomalyTrackerMap.end()) { + ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"", + (long long)subscription.id(), (long long)subscription.rule_id()); + return false; + } + const int anomalyTrackerIndex = itr->second; + allAnomalyTrackers[anomalyTrackerIndex]->addSubscription(subscription); + } return true; } -bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, set<int>& allTagIds, +bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, + const UidMap& uidMap, + const long timeBaseSec, set<int>& allTagIds, vector<sp<LogMatchingTracker>>& allAtomMatchers, vector<sp<ConditionTracker>>& allConditionTrackers, vector<sp<MetricProducer>>& allMetricProducers, vector<sp<AnomalyTracker>>& allAnomalyTrackers, unordered_map<int, std::vector<int>>& conditionToMetricMap, unordered_map<int, std::vector<int>>& trackerToMetricMap, - unordered_map<int, std::vector<int>>& trackerToConditionMap) { - unordered_map<string, int> logTrackerMap; - unordered_map<string, int> conditionTrackerMap; - unordered_map<string, int> metricProducerMap; + unordered_map<int, std::vector<int>>& trackerToConditionMap, + std::set<int64_t> &noReportMetricIds) { + unordered_map<int64_t, int> logTrackerMap; + unordered_map<int64_t, int> conditionTrackerMap; + unordered_map<int64_t, int> metricProducerMap; - if (!initLogTrackers(config, logTrackerMap, allAtomMatchers, allTagIds)) { + if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) { ALOGE("initLogMatchingTrackers failed"); return false; } @@ -515,7 +548,7 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const lo if (!initMetrics(key, config, timeBaseSec, logTrackerMap, conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers, conditionToMetricMap, - trackerToMetricMap, metricProducerMap)) { + trackerToMetricMap, metricProducerMap, noReportMetricIds)) { ALOGE("initMetricProducers failed"); return false; } diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index 333733234699..4f19ada5b022 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -43,7 +43,8 @@ namespace statsd { // [allAtomMatchers]: should store the sp to all the LogMatchingTracker // [allTagIds]: contains the set of all interesting tag ids to this config. bool initLogTrackers(const StatsdConfig& config, - std::unordered_map<std::string, int>& logTrackerMap, + const UidMap& uidMap, + std::unordered_map<int64_t, int>& logTrackerMap, std::vector<sp<LogMatchingTracker>>& allAtomMatchers, std::set<int>& allTagIds); @@ -58,8 +59,8 @@ bool initLogTrackers(const StatsdConfig& config, // [trackerToConditionMap]: contain the mapping from index of // log tracker to condition trackers that use the log tracker bool initConditions(const ConfigKey& key, const StatsdConfig& config, - const std::unordered_map<std::string, int>& logTrackerMap, - std::unordered_map<std::string, int>& conditionTrackerMap, + const std::unordered_map<int64_t, int>& logTrackerMap, + std::unordered_map<int64_t, int>& conditionTrackerMap, std::vector<sp<ConditionTracker>>& allConditionTrackers, std::unordered_map<int, std::vector<int>>& trackerToConditionMap, std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks); @@ -78,25 +79,29 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index. bool initMetrics( const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, - const std::unordered_map<std::string, int>& logTrackerMap, - const std::unordered_map<std::string, int>& conditionTrackerMap, + const std::unordered_map<int64_t, int>& logTrackerMap, + const std::unordered_map<int64_t, int>& conditionTrackerMap, const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks, const vector<sp<LogMatchingTracker>>& allAtomMatchers, vector<sp<ConditionTracker>>& allConditionTrackers, std::vector<sp<MetricProducer>>& allMetricProducers, std::unordered_map<int, std::vector<int>>& conditionToMetricMap, - std::unordered_map<int, std::vector<int>>& trackerToMetricMap); + std::unordered_map<int, std::vector<int>>& trackerToMetricMap, + std::set<int64_t> &noReportMetricIds); // Initialize MetricsManager from StatsdConfig. // Parameters are the members of MetricsManager. See MetricsManager for declaration. -bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, std::set<int>& allTagIds, +bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, + const UidMap& uidMap, + const long timeBaseSec, std::set<int>& allTagIds, std::vector<sp<LogMatchingTracker>>& allAtomMatchers, std::vector<sp<ConditionTracker>>& allConditionTrackers, std::vector<sp<MetricProducer>>& allMetricProducers, vector<sp<AnomalyTracker>>& allAnomalyTrackers, std::unordered_map<int, std::vector<int>>& conditionToMetricMap, std::unordered_map<int, std::vector<int>>& trackerToMetricMap, - std::unordered_map<int, std::vector<int>>& trackerToConditionMap); + std::unordered_map<int, std::vector<int>>& trackerToConditionMap, + std::set<int64_t> &noReportMetricIds); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 21a9cf319375..416b87b36c54 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -404,4 +404,4 @@ set<int32_t> UidMap::getAppUid(const string& package) const { } // namespace statsd } // namespace os -} // namespace android +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h index a9aec94a932e..02dea54f1b50 100644 --- a/cmds/statsd/src/packages/UidMap.h +++ b/cmds/statsd/src/packages/UidMap.h @@ -168,4 +168,3 @@ private: } // namespace statsd } // namespace os } // namespace android - diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 3c85c57ab037..0b369bb2fdbc 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -24,8 +24,14 @@ option java_outer_classname = "StatsLog"; import "frameworks/base/cmds/statsd/src/atoms.proto"; -message KeyValuePair { - optional int32 key = 1; +message Field { + optional int32 field = 1; + optional int32 position_index = 2 [default = -1]; + repeated Field child = 3; +} + +message DimensionsValue { + optional int32 field = 1; oneof value { string value_str = 2; @@ -33,9 +39,14 @@ message KeyValuePair { int64 value_long = 4; bool value_bool = 5; float value_float = 6; + DimensionsValueTuple value_tuple = 7; } } +message DimensionsValueTuple { + repeated DimensionsValue dimensions_value = 1; +} + message EventMetricData { optional int64 timestamp_nanos = 1; @@ -51,7 +62,7 @@ message CountBucketInfo { } message CountMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated CountBucketInfo bucket_info = 2; } @@ -65,7 +76,7 @@ message DurationBucketInfo { } message DurationMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated DurationBucketInfo bucket_info = 2; } @@ -79,7 +90,7 @@ message ValueBucketInfo { } message ValueMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated ValueBucketInfo bucket_info = 2; } @@ -93,7 +104,7 @@ message GaugeBucketInfo { } message GaugeMetricData { - repeated KeyValuePair dimension = 1; + optional DimensionsValue dimension = 1; repeated GaugeBucketInfo bucket_info = 2; } @@ -126,7 +137,7 @@ message UidMapping { } message StatsLogReport { - optional string metric_name = 1; + optional int64 metric_id = 1; optional int64 start_report_nanos = 2; @@ -167,7 +178,7 @@ message ConfigMetricsReport { message ConfigMetricsReportList { message ConfigKey { optional int32 uid = 1; - optional string name = 2; + optional int64 id = 2; } optional ConfigKey config_key = 1; @@ -180,28 +191,28 @@ message StatsdStatsReport { optional int32 stats_end_time_sec = 2; message MatcherStats { - optional string name = 1; + optional int64 id = 1; optional int32 matched_times = 2; } message ConditionStats { - optional string name = 1; + optional int64 id = 1; optional int32 max_tuple_counts = 2; } message MetricStats { - optional string name = 1; + optional int64 id = 1; optional int32 max_tuple_counts = 2; } message AlertStats { - optional string name = 1; + optional int64 id = 1; optional int32 alerted_times = 2; } message ConfigStats { optional int32 uid = 1; - optional string name = 2; + optional int64 id = 2; optional int32 creation_time_sec = 3; optional int32 deletion_time_sec = 4; optional int32 metric_count = 5; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp new file mode 100644 index 000000000000..476e11724318 --- /dev/null +++ b/cmds/statsd/src/stats_log_util.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stats_log_util.h" + +#include <set> +#include <stack> +#include <utils/Log.h> + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_BOOL; +using android::util::FIELD_TYPE_FLOAT; +using android::util::FIELD_TYPE_INT32; +using android::util::FIELD_TYPE_INT64; +using android::util::FIELD_TYPE_MESSAGE; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; + +namespace android { +namespace os { +namespace statsd { + +// for DimensionsValue Proto +const int DIMENSIONS_VALUE_FIELD = 1; +const int DIMENSIONS_VALUE_VALUE_STR = 2; +const int DIMENSIONS_VALUE_VALUE_INT = 3; +const int DIMENSIONS_VALUE_VALUE_LONG = 4; +const int DIMENSIONS_VALUE_VALUE_BOOL = 5; +const int DIMENSIONS_VALUE_VALUE_FLOAT = 6; +const int DIMENSIONS_VALUE_VALUE_TUPLE = 7; + +// for MessageValue Proto +const int FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO = 1; + +void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue, + ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field()); + switch (dimensionsValue.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR, + dimensionsValue.value_str()); + break; + case DimensionsValue::ValueCase::kValueInt: + protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT, + dimensionsValue.value_int()); + break; + case DimensionsValue::ValueCase::kValueLong: + protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG, + dimensionsValue.value_long()); + break; + case DimensionsValue::ValueCase::kValueBool: + protoOutput->write(FIELD_TYPE_BOOL | DIMENSIONS_VALUE_VALUE_BOOL, + dimensionsValue.value_bool()); + break; + case DimensionsValue::ValueCase::kValueFloat: + protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT, + dimensionsValue.value_float()); + break; + case DimensionsValue::ValueCase::kValueTuple: + { + long long tupleToken = protoOutput->start( + FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); + for (int i = 0; i < dimensionsValue.value_tuple().dimensions_value_size(); ++i) { + long long token = protoOutput->start( + FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED + | FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO); + writeDimensionsValueProtoToStream( + dimensionsValue.value_tuple().dimensions_value(i), protoOutput); + protoOutput->end(token); + } + protoOutput->end(tupleToken); + } + break; + default: + break; + } +} + +// for Field Proto +const int FIELD_FIELD = 1; +const int FIELD_POSITION_INDEX = 2; +const int FIELD_CHILD = 3; + +void writeFieldProtoToStream( + const Field& field, util::ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field()); + if (field.has_position_index()) { + protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index()); + } + for (int i = 0; i < field.child_size(); ++i) { + long long childToken = protoOutput->start( + FIELD_TYPE_MESSAGE| FIELD_COUNT_REPEATED | FIELD_CHILD); + writeFieldProtoToStream(field.child(i), protoOutput); + protoOutput->end(childToken); + } +} + +namespace { + +void addOrUpdateChildrenMap( + const Field& root, + const Field& node, + std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) { + Field parentNode = root; + if (node.has_position_index()) { + appendLeaf(&parentNode, node.field(), node.position_index()); + } else { + appendLeaf(&parentNode, node.field()); + } + if (childrenMap->find(parentNode) == childrenMap->end()) { + childrenMap->insert(std::make_pair(parentNode, std::set<Field, FieldCmp>{})); + } + auto it = childrenMap->find(parentNode); + for (int i = 0; i < node.child_size(); ++i) { + auto child = node.child(i); + Field childNode = parentNode; + if (child.has_position_index()) { + appendLeaf(&childNode, child.field(), child.position_index()); + } else { + appendLeaf(&childNode, child.field()); + } + it->second.insert(childNode); + addOrUpdateChildrenMap(parentNode, child, childrenMap); + } +} + +void addOrUpdateChildrenMap( + const Field& field, + std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) { + Field root; + addOrUpdateChildrenMap(root, field, childrenMap); +} + +} // namespace + +void writeFieldValueTreeToStream(const FieldValueMap &fieldValueMap, + util::ProtoOutputStream* protoOutput) { + std::map<Field, std::set<Field, FieldCmp>, FieldCmp> childrenMap; + // Rebuild the field tree. + for (auto it = fieldValueMap.begin(); it != fieldValueMap.end(); ++it) { + addOrUpdateChildrenMap(it->first, &childrenMap); + } + std::stack<std::pair<long long, Field>> tokenStack; + // Iterate over the node tree to fill the Atom proto. + for (auto it = childrenMap.begin(); it != childrenMap.end(); ++it) { + const Field* nodeLeaf = getSingleLeaf(&it->first); + const int fieldNum = nodeLeaf->field(); + while (!tokenStack.empty()) { + auto currentMsgNode = tokenStack.top().second; + auto currentMsgNodeChildrenIt = childrenMap.find(currentMsgNode); + if (currentMsgNodeChildrenIt->second.find(it->first) == + currentMsgNodeChildrenIt->second.end()) { + protoOutput->end(tokenStack.top().first); + tokenStack.pop(); + } else { + break; + } + } + if (it->second.size() == 0) { + auto itValue = fieldValueMap.find(it->first); + if (itValue != fieldValueMap.end()) { + const DimensionsValue& value = itValue->second; + switch (value.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + protoOutput->write(FIELD_TYPE_STRING | fieldNum, + value.value_str()); + break; + case DimensionsValue::ValueCase::kValueInt: + protoOutput->write(FIELD_TYPE_INT32 | fieldNum, + value.value_int()); + break; + case DimensionsValue::ValueCase::kValueLong: + protoOutput->write(FIELD_TYPE_INT64 | fieldNum, + value.value_long()); + break; + case DimensionsValue::ValueCase::kValueBool: + protoOutput->write(FIELD_TYPE_BOOL | fieldNum, + value.value_bool()); + break; + case DimensionsValue::ValueCase::kValueFloat: + protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, + value.value_float()); + break; + // This would not happen as the node has no child. + case DimensionsValue::ValueCase::kValueTuple: + break; + default: + break; + } + } else { + ALOGE("Leaf node value not found. This should never happen."); + } + } else { + long long token; + if (nodeLeaf->has_position_index()) { + token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum); + } else { + token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum); + } + tokenStack.push(std::make_pair(token, it->first)); + } + } + + while (!tokenStack.empty()) { + protoOutput->end(tokenStack.top().first); + tokenStack.pop(); + } + + +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h new file mode 100644 index 000000000000..1f8186083d12 --- /dev/null +++ b/cmds/statsd/src/stats_log_util.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <android/util/ProtoOutputStream.h> +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" +#include "field_util.h" + +namespace android { +namespace os { +namespace statsd { + +// Helper function to write DimensionsValue proto to ProtoOutputStream. +void writeDimensionsValueProtoToStream( + const DimensionsValue& fieldValue, util::ProtoOutputStream* protoOutput); + +// Helper function to write Field proto to ProtoOutputStream. +void writeFieldProtoToStream( + const Field& field, util::ProtoOutputStream* protoOutput); + +// Helper function to construct the field value tree and write to ProtoOutputStream +void writeFieldValueTreeToStream(const FieldValueMap &fieldValueMap, + util::ProtoOutputStream* protoOutput); + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h index 1cdf031c7858..160b1f40243f 100644 --- a/cmds/statsd/src/stats_util.h +++ b/cmds/statsd/src/stats_util.h @@ -27,45 +27,15 @@ namespace android { namespace os { namespace statsd { -const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>()); +const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(); // Minimum bucket size in seconds const long kMinBucketSizeSec = 5 * 60; -typedef std::map<std::string, HashableDimensionKey> ConditionKey; +typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey; typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap; -/* - * In memory rep for LogEvent. Uses much less memory than LogEvent - */ -typedef struct EventKV { - std::vector<KeyValuePair> kv; - string ToString() const { - std::ostringstream result; - result << "{ "; - const size_t N = kv.size(); - for (size_t i = 0; i < N; i++) { - result << " "; - result << (i + 1); - result << "->"; - const auto& pair = kv[i]; - if (pair.has_value_int()) { - result << pair.value_int(); - } else if (pair.has_value_long()) { - result << pair.value_long(); - } else if (pair.has_value_float()) { - result << pair.value_float(); - } else if (pair.has_value_str()) { - result << pair.value_str().c_str(); - } - } - result << " }"; - return result.str(); - } -} EventKV; - -typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap; } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 4729f6acc621..a5057daf1eda 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -22,30 +22,52 @@ package android.os.statsd; option java_package = "com.android.internal.os"; option java_outer_classname = "StatsdConfigProto"; -message KeyMatcher { - optional int32 key = 1; +enum Position { + POSITION_UNKNOWN = 0; + FIRST = 1; + LAST = 2; + ANY = 3; +} + +message FieldMatcher { + optional int32 field = 1; - optional bool as_package_name = 2 [default = false]; + optional Position position = 2; + + repeated FieldMatcher child = 3; } -message KeyValueMatcher { - optional KeyMatcher key_matcher = 1; +message FieldValueMatcher { + // Field id, as specified in the atom proto message. + optional int32 field = 1; + + // For repeated fields, specifies the position in the array. + // FIRST and LAST mean that if the values are found at the first + // or last position, it's a match. ANY means that if the values are found + // anywhere in the array, then it's a match. + optional Position position = 2; oneof value_matcher { - bool eq_bool = 2; - string eq_string = 3; - int32 eq_int = 4; + bool eq_bool = 3; + string eq_string = 4; + int32 eq_int = 5; - int64 lt_int = 5; - int64 gt_int = 6; - float lt_float = 7; - float gt_float = 8; + int64 lt_int = 6; + int64 gt_int = 7; + float lt_float = 8; + float gt_float = 9; - int64 lte_int = 9; - int64 gte_int = 10; + int64 lte_int = 10; + int64 gte_int = 11; + + MessageMatcher matches_tuple = 12; } } +message MessageMatcher { + repeated FieldValueMatcher field_value_matcher = 1; +} + enum LogicalOperation { LOGICAL_OPERATION_UNSPECIFIED = 0; AND = 1; @@ -56,18 +78,18 @@ enum LogicalOperation { } message SimpleAtomMatcher { - optional int32 tag = 1; + optional int32 atom_id = 1; - repeated KeyValueMatcher key_value_matcher = 2; + repeated FieldValueMatcher field_value_matcher = 2; } message AtomMatcher { - optional string name = 1; + optional int64 id = 1; message Combination { optional LogicalOperation operation = 1; - repeated string matcher = 2; + repeated int64 matcher = 2; } oneof contents { SimpleAtomMatcher simple_atom_matcher = 2; @@ -76,13 +98,13 @@ message AtomMatcher { } message SimplePredicate { - optional string start = 1; + optional int64 start = 1; - optional string stop = 2; + optional int64 stop = 2; optional bool count_nesting = 3 [default = true]; - optional string stop_all = 4; + optional int64 stop_all = 4; enum InitialValue { UNKNOWN = 0; @@ -90,16 +112,16 @@ message SimplePredicate { } optional InitialValue initial_value = 5 [default = FALSE]; - repeated KeyMatcher dimension = 6; + optional FieldMatcher dimensions = 6; } message Predicate { - optional string name = 1; + optional int64 id = 1; message Combination { optional LogicalOperation operation = 1; - repeated string predicate = 2; + repeated int64 predicate = 2; } oneof contents { @@ -113,36 +135,36 @@ message Bucket { } message MetricConditionLink { - optional string condition = 1; + optional int64 condition = 1; - repeated KeyMatcher key_in_what = 2; + optional FieldMatcher dimensions_in_what = 2; - repeated KeyMatcher key_in_condition = 3; + optional FieldMatcher dimensions_in_condition = 3; } message FieldFilter { - optional bool include_all = 1; - repeated int32 field_num = 2; + optional bool include_all = 1 [default = false]; + optional FieldMatcher fields = 2; } message EventMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional string condition = 3; + optional int64 condition = 3; repeated MetricConditionLink links = 4; } message CountMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional string condition = 3; + optional int64 condition = 3; - repeated KeyMatcher dimension = 4; + optional FieldMatcher dimensions = 4; optional Bucket bucket = 5; @@ -150,11 +172,11 @@ message CountMetric { } message DurationMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional string condition = 3; + optional int64 condition = 3; repeated MetricConditionLink links = 4; @@ -165,21 +187,21 @@ message DurationMetric { } optional AggregationType aggregation_type = 5 [default = SUM]; - repeated KeyMatcher dimension = 6; + optional FieldMatcher dimensions = 6; optional Bucket bucket = 7; } message GaugeMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional FieldFilter gauge_fields = 3; + optional FieldFilter gauge_fields_filter = 3; - optional string condition = 4; + optional int64 condition = 4; - repeated KeyMatcher dimension = 5; + optional FieldMatcher dimensions = 5; optional Bucket bucket = 6; @@ -187,15 +209,15 @@ message GaugeMetric { } message ValueMetric { - optional string name = 1; + optional int64 id = 1; - optional string what = 2; + optional int64 what = 2; - optional int32 value_field = 3; + optional FieldMatcher value_field = 3; - optional string condition = 4; + optional int64 condition = 4; - repeated KeyMatcher dimension = 5; + optional FieldMatcher dimensions = 5; optional Bucket bucket = 6; @@ -206,20 +228,15 @@ message ValueMetric { } message Alert { - optional string name = 1; + optional int64 id = 1; - optional string metric_name = 2; + optional int64 metric_id = 2; - message IncidentdDetails { - repeated int32 section = 1; - } - optional IncidentdDetails incidentd_details = 3; - - optional int32 number_of_buckets = 4; + optional int32 num_buckets = 3; - optional int32 refractory_period_secs = 5; + optional int32 refractory_period_secs = 4; - optional int64 trigger_if_sum_gt = 6; + optional double trigger_if_sum_gt = 5; } message AllowedLogSource { @@ -227,8 +244,40 @@ message AllowedLogSource { repeated string package = 2; } +message Alarm { + optional int64 id = 1; + optional int64 offset_millis = 2; + optional int64 period_millis = 3; +} + +message IncidentdDetails { + repeated int32 section = 1; +} + +message PerfettoDetails { + optional int32 perfetto_stuff = 1; +} + +message Subscription { + optional int64 id = 1; + + enum RuleType { + RULE_TYPE_UNSPECIFIED = 0; + ALARM = 1; + ALERT = 2; + } + optional RuleType rule_type = 2; + + optional int64 rule_id = 3; + + oneof subscriber_information { + IncidentdDetails incidentd_details = 4; + PerfettoDetails perfetto_details = 5; + } +} + message StatsdConfig { - optional string name = 1; + optional int64 id = 1; repeated EventMetric event_metric = 2; @@ -246,5 +295,11 @@ message StatsdConfig { repeated Alert alert = 9; - optional AllowedLogSource log_source = 10; + repeated Alarm alarm = 10; + + repeated Subscription subscription = 11; + + optional AllowedLogSource log_source = 12; + + repeated int64 no_report_metric = 13; } diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 9919abf7532a..c542db2312ea 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -124,7 +124,7 @@ void StorageManager::sendBroadcast(const char* path, } if (index < 2) continue; - sendBroadcast(ConfigKey(uid, configName)); + sendBroadcast(ConfigKey(uid, StrToInt64(configName))); } } @@ -198,6 +198,7 @@ void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap index++; } if (index < 2) continue; + string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name); VLOG("full file %s", file_name.c_str()); int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); @@ -206,7 +207,7 @@ void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap if (android::base::ReadFdToString(fd, &content)) { StatsdConfig config; if (config.ParseFromString(content)) { - configsMap[ConfigKey(uid, configName)] = config; + configsMap[ConfigKey(uid, StrToInt64(configName))] = config; VLOG("map key uid=%d|name=%s", uid, name); } } diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp index 3d923e246e71..3eac5d213c22 100644 --- a/cmds/statsd/tests/ConfigManager_test.cpp +++ b/cmds/statsd/tests/ConfigManager_test.cpp @@ -14,6 +14,7 @@ #include "src/config/ConfigManager.h" #include "src/metrics/MetricsManager.h" +#include "statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -31,7 +32,7 @@ namespace os { namespace statsd { static ostream& operator<<(ostream& os, const StatsdConfig& config) { - return os << "StatsdConfig{name=" << config.name().c_str() << "}"; + return os << "StatsdConfig{id=" << config.id() << "}"; } } // namespace statsd @@ -50,19 +51,21 @@ public: /** * Validate that the ConfigKey is the one we wanted. */ -MATCHER_P2(ConfigKeyEq, uid, name, "") { - return arg.GetUid() == uid && arg.GetName() == name; +MATCHER_P2(ConfigKeyEq, uid, id, "") { + return arg.GetUid() == uid && (long long)arg.GetId() == (long long)id; } /** * Validate that the StatsdConfig is the one we wanted. */ -MATCHER_P(StatsdConfigEq, name, "") { - return arg.name() == name; +MATCHER_P(StatsdConfigEq, id, 0) { + return (long long)arg.id() == (long long)id; } +const int64_t testConfigId = 12345; + TEST(ConfigManagerTest, TestFakeConfig) { - auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, "test"), + auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, testConfigId), build_fake_config(), 1000, new UidMap()); EXPECT_TRUE(metricsManager->isConfigValid()); } @@ -77,13 +80,13 @@ TEST(ConfigManagerTest, TestAddUpdateRemove) { manager->AddListener(listener); StatsdConfig config91; - config91.set_name("91"); + config91.set_id(91); StatsdConfig config92; - config92.set_name("92"); + config92.set_id(92); StatsdConfig config93; - config93.set_name("93"); + config93.set_id(93); StatsdConfig config94; - config94.set_name("94"); + config94.set_id(94); { InSequence s; @@ -91,42 +94,46 @@ TEST(ConfigManagerTest, TestAddUpdateRemove) { manager->Startup(); // Add another one - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("91"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")), + StatsdConfigEq(91))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, "zzz"), config91); + manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91); // Update It - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("92"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")), + StatsdConfigEq(92))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, "zzz"), config92); + manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92); // Add one with the same uid but a different name - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "yyy"), StatsdConfigEq("93"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("yyy")), + StatsdConfigEq(93))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(1, "yyy"), config93); + manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93); // Add one with the same name but a different uid - EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, "zzz"), StatsdConfigEq("94"))) + EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, StringToId("zzz")), + StatsdConfigEq(94))) .RetiresOnSaturation(); - manager->UpdateConfig(ConfigKey(2, "zzz"), config94); + manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94); // Remove (1,yyy) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "yyy"))) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("yyy")))) .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(1, "yyy")); + manager->RemoveConfig(ConfigKey(1, StringToId("yyy"))); // Remove (2,zzz) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz"))) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))) .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(2, "zzz")); + manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); // Remove (1,zzz) - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "zzz"))) + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("zzz")))) .RetiresOnSaturation(); - manager->RemoveConfig(ConfigKey(1, "zzz")); + manager->RemoveConfig(ConfigKey(1, StringToId("zzz"))); // Remove (2,zzz) again and we shouldn't get the callback - manager->RemoveConfig(ConfigKey(2, "zzz")); + manager->RemoveConfig(ConfigKey(2, StringToId("zzz"))); } } @@ -142,16 +149,16 @@ TEST(ConfigManagerTest, TestRemoveUid) { StatsdConfig config; EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _)).Times(5); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "xxx"))); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "yyy"))); - EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz"))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx")))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy")))); + EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz")))); manager->Startup(); - manager->UpdateConfig(ConfigKey(1, "aaa"), config); - manager->UpdateConfig(ConfigKey(2, "xxx"), config); - manager->UpdateConfig(ConfigKey(2, "yyy"), config); - manager->UpdateConfig(ConfigKey(2, "zzz"), config); - manager->UpdateConfig(ConfigKey(3, "bbb"), config); + manager->UpdateConfig(ConfigKey(1, StringToId("aaa")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("xxx")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("yyy")), config); + manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config); + manager->UpdateConfig(ConfigKey(3, StringToId("bbb")), config); manager->RemoveConfigs(2); } diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp index 1ec91558f790..111b4ba7a32c 100644 --- a/cmds/statsd/tests/LogEntryMatcher_test.cpp +++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp @@ -32,65 +32,313 @@ const int FIELD_ID_1 = 1; const int FIELD_ID_2 = 2; const int FIELD_ID_3 = 2; +const int ATTRIBUTION_UID_FIELD_ID = 1; +const int ATTRIBUTION_TAG_FIELD_ID = 2; + // Private API from liblog. extern "C" void android_log_rewind(android_log_context ctx); #ifdef __ANDROID__ TEST(AtomMatcherTest, TestSimpleMatcher) { + UidMap uidMap; + // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); + simpleMatcher->set_atom_id(TAG_ID); LogEvent event(TAG_ID, 0); + EXPECT_TRUE(event.write(11)); event.init(); // Test - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Wrong tag id. + simpleMatcher->set_atom_id(TAG_ID + 1); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestAttributionMatcher) { + UidMap uidMap; + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("location1"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("location2"); + + AttributionNode attribution_node3; + attribution_node3.set_uid(3333); + attribution_node3.set_tag("location3"); + std::vector<AttributionNode> attribution_nodes = + { attribution_node1, attribution_node2, attribution_node3 }; + + // Set up the event + LogEvent event(TAG_ID, 0); + event.write(attribution_nodes); + event.write("some value"); + // Convert to a LogEvent + event.init(); + + // Set up the matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first node. + auto attributionMatcher = simpleMatcher->add_field_value_matcher(); + attributionMatcher->set_field(FIELD_ID_1); + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("tag"); + + auto fieldMatcher = simpleMatcher->add_field_value_matcher(); + fieldMatcher->set_field(FIELD_ID_2); + fieldMatcher->set_eq_string("some value"); + + // Tag not matched. + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match last node. + attributionMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match any node. + attributionMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location4"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Attribution match but primitive field not match. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("location2"); + fieldMatcher->set_eq_string("wrong value"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldMatcher->set_eq_string("some value"); + + // Uid match. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field( + ATTRIBUTION_UID_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("pkg0"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + uidMap.updateMap({1111, 1111, 2222, 3333, 3333} /* uid list */, + {1, 1, 2, 1, 2} /* version list */, + {android::String16("pkg0"), android::String16("pkg1"), + android::String16("pkg1"), android::String16("Pkg2"), + android::String16("PkG3")} /* package name list */); + + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::LAST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Uid + tag. + attributionMatcher->set_position(Position::ANY); + attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::FIRST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + attributionMatcher->set_position(Position::LAST); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) + ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) + ->set_eq_string("location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestBoolMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue1 = simpleMatcher->add_key_value_matcher(); - keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1); - auto keyValue2 = simpleMatcher->add_key_value_matcher(); - keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue1 = simpleMatcher->add_field_value_matcher(); + keyValue1->set_field(FIELD_ID_1); + auto keyValue2 = simpleMatcher->add_field_value_matcher(); + keyValue2->set_field(FIELD_ID_2); // Set up the event LogEvent event(TAG_ID, 0); - event.write(true); - event.write(false); + EXPECT_TRUE(event.write(true)); + EXPECT_TRUE(event.write(false)); // Convert to a LogEvent event.init(); // Test keyValue1->set_eq_bool(true); keyValue2->set_eq_bool(false); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_bool(false); keyValue2->set_eq_bool(false); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - keyValue1->set_eq_bool(true); - keyValue2->set_eq_bool(false); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + keyValue1->set_eq_bool(false); + keyValue2->set_eq_bool(true); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_bool(true); keyValue2->set_eq_bool(true); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestStringMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(FIELD_ID_1); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); keyValue->set_eq_string("some value"); // Set up the event @@ -100,18 +348,19 @@ TEST(AtomMatcherTest, TestStringMatcher) { event.init(); // Test - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestMultiFieldsMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue1 = simpleMatcher->add_key_value_matcher(); - keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1); - auto keyValue2 = simpleMatcher->add_key_value_matcher(); - keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue1 = simpleMatcher->add_field_value_matcher(); + keyValue1->set_field(FIELD_ID_1); + auto keyValue2 = simpleMatcher->add_field_value_matcher(); + keyValue2->set_field(FIELD_ID_2); // Set up the event LogEvent event(TAG_ID, 0); @@ -124,25 +373,26 @@ TEST(AtomMatcherTest, TestMultiFieldsMatcher) { // Test keyValue1->set_eq_int(2); keyValue2->set_eq_int(3); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_int(2); keyValue2->set_eq_int(4); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue1->set_eq_int(4); keyValue2->set_eq_int(3); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestIntComparisonMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(FIELD_ID_1); + simpleMatcher->set_atom_id(TAG_ID); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); // Set up the event LogEvent event(TAG_ID, 0); @@ -153,82 +403,83 @@ TEST(AtomMatcherTest, TestIntComparisonMatcher) { // eq_int keyValue->set_eq_int(10); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_eq_int(11); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_eq_int(12); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); // lt_int keyValue->set_lt_int(10); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lt_int(11); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lt_int(12); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); // lte_int keyValue->set_lte_int(10); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lte_int(11); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_lte_int(12); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); // gt_int keyValue->set_gt_int(10); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gt_int(11); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gt_int(12); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); // gte_int keyValue->set_gte_int(10); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gte_int(11); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); keyValue->set_gte_int(12); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } TEST(AtomMatcherTest, TestFloatComparisonMatcher) { + UidMap uidMap; // Set up the matcher AtomMatcher matcher; auto simpleMatcher = matcher.mutable_simple_atom_matcher(); - simpleMatcher->set_tag(TAG_ID); + simpleMatcher->set_atom_id(TAG_ID); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(FIELD_ID_1); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(FIELD_ID_1); LogEvent event1(TAG_ID, 0); keyValue->set_lt_float(10.0); event1.write(10.1f); event1.init(); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event1)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); LogEvent event2(TAG_ID, 0); event2.write(9.9f); event2.init(); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event2)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); LogEvent event3(TAG_ID, 0); event3.write(10.1f); event3.init(); keyValue->set_gt_float(10.0); - EXPECT_TRUE(matchesSimple(*simpleMatcher, event3)); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3)); LogEvent event4(TAG_ID, 0); event4.write(9.9f); event4.init(); - EXPECT_FALSE(matchesSimple(*simpleMatcher, event4)); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4)); } // Helper for the composite matchers. void addSimpleMatcher(SimpleAtomMatcher* simpleMatcher, int tag, int key, int val) { - simpleMatcher->set_tag(tag); - auto keyValue = simpleMatcher->add_key_value_matcher(); - keyValue->mutable_key_matcher()->set_key(key); + simpleMatcher->set_atom_id(tag); + auto keyValue = simpleMatcher->add_field_value_matcher(); + keyValue->set_field(key); keyValue->set_eq_int(val); } diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp new file mode 100644 index 000000000000..fd28460e8e01 --- /dev/null +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -0,0 +1,573 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <gtest/gtest.h> +#include <log/log_event_list.h> +#include "src/logd/LogEvent.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +TEST(LogEventTest, testEmptyEvent) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + event.init(); + + DimensionsValue dimensionsValue; + EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(234, &dimensionsValue)); + FieldMatcher dimensions; + dimensions.set_field(event.GetTagId()); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); + + dimensions.add_child()->set_field(3); + dimensions.mutable_child(0)->set_position(Position::FIRST); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); + + dimensions.mutable_child(0)->set_position(Position::ANY); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); + + dimensions.mutable_child(0)->set_position(Position::LAST); + EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue)); +} + +TEST(LogEventTest, testRepeatedAttributionNode) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("locationService"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("locationService2"); + + AttributionNode attribution_node3; + attribution_node3.set_uid(3333); + attribution_node3.set_tag("locationService3"); + std::vector<AttributionNode> attribution_nodes = + {attribution_node1, attribution_node2, attribution_node3}; + + // 1nd field: int32. + EXPECT_TRUE(event.write(int32_t(11))); + // 2rd field: float. + EXPECT_TRUE(event.write(3.45f)); + // Here it assume that the atom proto contains a repeated AttributionNode field. + // 3rd field: attribution node. This is repeated field. + EXPECT_TRUE(event.write(attribution_nodes)); + // 4th field: bool. + EXPECT_TRUE(event.write(true)); + // 5th field: long. + EXPECT_TRUE(event.write(uint64_t(1234))); + + event.init(); + + DimensionsValue dimensionsValue; + // Query single primitive fields. + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234)); + + // First attribution. + FieldMatcher first_uid_dimensions; + first_uid_dimensions.set_field(event.GetTagId()); + first_uid_dimensions.add_child()->set_field(3); + first_uid_dimensions.mutable_child(0)->set_position(Position::FIRST); + first_uid_dimensions.mutable_child(0)->add_child()->set_field(1); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_uid_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + + FieldMatcher first_tag_dimensions = first_uid_dimensions; + first_tag_dimensions.mutable_child(0)->mutable_child(0)->set_field(2); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_tag_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_str(), "locationService"); + + FieldMatcher first_attribution_dimensions = first_uid_dimensions; + first_attribution_dimensions.mutable_child(0)->add_child()->set_field(2); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_attribution_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + + FieldMatcher last_attribution_dimensions = first_attribution_dimensions; + last_attribution_dimensions.mutable_child(0)->set_position(Position::LAST); + EXPECT_TRUE(event.GetAtomDimensionsValueProto(last_attribution_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 3333); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService3"); + + FieldMatcher any_attribution_dimensions = first_attribution_dimensions; + any_attribution_dimensions.mutable_child(0)->set_position(Position::ANY); + std::vector<DimensionsValue> dimensionsValues; + event.GetAtomDimensionsValueProtos(any_attribution_dimensions, &dimensionsValues); + EXPECT_EQ(dimensionsValues.size(), 3u); + EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 2222); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService2"); + EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 3333); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService3"); + + FieldMatcher mixed_dimensions = any_attribution_dimensions; + mixed_dimensions.add_child()->set_field(1000); + mixed_dimensions.add_child()->set_field(6); // missing field. + mixed_dimensions.add_child()->set_field(3); // position not set. + mixed_dimensions.add_child()->set_field(5); + mixed_dimensions.add_child()->set_field(1); + dimensionsValues.clear(); + event.GetAtomDimensionsValueProtos(mixed_dimensions, &dimensionsValues); + EXPECT_EQ(dimensionsValues.size(), 3u); + EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 3); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + 1111); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(), + "locationService"); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).field(), 5); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).value_long(), long(1234)); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).field(), 1); + EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).value_int(), 11); + + EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 3); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + 2222); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(), + "locationService2"); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).field(), 5); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).value_long(), long(1234)); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).field(), 1); + EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).value_int(), 11); + + EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId()); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 3); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + 3333); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(), + "locationService3"); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).field(), 5); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).value_long(), long(1234)); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).field(), 1); + EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).value_int(), 11); + + FieldMatcher wrong_dimensions = mixed_dimensions; + // Wrong tagId. + wrong_dimensions.set_field(event.GetTagId() + 100); + dimensionsValues.clear(); + event.GetAtomDimensionsValueProtos(wrong_dimensions, &dimensionsValues); + EXPECT_TRUE(dimensionsValues.empty()); +} + +TEST(LogEventTest, testMessageField) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("locationService"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("locationService2"); + + // 1nd field: int32. + EXPECT_TRUE(event.write(int32_t(11))); + // 2rd field: float. + EXPECT_TRUE(event.write(3.45f)); + // Here it assume that the atom proto contains two optional AttributionNode fields. + // 3rd field: attribution node. This is not repeated field. + EXPECT_TRUE(event.write(attribution_node1)); + // 4th field: another attribution field. This is not repeated field. + EXPECT_TRUE(event.write(attribution_node2)); + // 5th field: bool. + EXPECT_TRUE(event.write(true)); + // 6th field: long. + EXPECT_TRUE(event.write(uint64_t(1234))); + + event.init(); + + FieldMatcher uid_dimensions1; + uid_dimensions1.set_field(event.GetTagId()); + uid_dimensions1.add_child()->set_field(3); + uid_dimensions1.mutable_child(0)->add_child()->set_field(1); + + FieldMatcher tag_dimensions1; + tag_dimensions1.set_field(event.GetTagId()); + tag_dimensions1.add_child()->set_field(3); + tag_dimensions1.mutable_child(0)->add_child()->set_field(2); + + FieldMatcher attribution_dimensions1; + attribution_dimensions1.set_field(event.GetTagId()); + attribution_dimensions1.add_child()->set_field(3); + attribution_dimensions1.mutable_child(0)->add_child()->set_field(1); + attribution_dimensions1.mutable_child(0)->add_child()->set_field(2); + + FieldMatcher uid_dimensions2 = uid_dimensions1; + uid_dimensions2.mutable_child(0)->set_field(4); + + FieldMatcher tag_dimensions2 = tag_dimensions1; + tag_dimensions2.mutable_child(0)->set_field(4); + + FieldMatcher attribution_dimensions2 = attribution_dimensions1; + attribution_dimensions2.mutable_child(0)->set_field(4); + + FieldMatcher mixed_dimensions = attribution_dimensions1; + mixed_dimensions.add_child()->set_field(4); + mixed_dimensions.mutable_child(1)->add_child()->set_field(1); + mixed_dimensions.add_child()->set_field(1000); + mixed_dimensions.add_child()->set_field(5); + mixed_dimensions.add_child()->set_field(1); + + DimensionsValue dimensionsValue; + + // Query single primitive fields. + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234)); + + // Query atom field 3: attribution node uid field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + + // Query atom field 3: attribution node tag field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_str(), "locationService"); + + // Query atom field 3: attribution node uid + tag fields. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + + // Query atom field 4: attribution node uid field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 2222); + + // Query atom field 4: attribution node tag field only. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_str(), "locationService2"); + + // Query atom field 4: attribution node uid + tag fields. + EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 2222); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService2"); + + // Query multiple fields: + // 1/ Field 3: attribution uid + tag. + // 2/ Field 4: attribution uid only. + // 3/ Field not exist. + // 4/ Primitive fields #5 + // 5/ Primitive fields #1 + EXPECT_TRUE(event.GetAtomDimensionsValueProto(mixed_dimensions, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value_size(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(0).value_int(), 1111); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple() + .dimensions_value(1).value_str(), "locationService"); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).field(), 4); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple() + .dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple() + .dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple() + .dimensions_value(0).value_int(), 2222); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).field(), 5); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).value_int(), true); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).value_int(), 11); +} + +TEST(LogEventTest, testAllPrimitiveFields) { + const int32_t TAG_ID = 123; + LogEvent event(TAG_ID, 0); + + // 1nd field: int32. + EXPECT_TRUE(event.write(int32_t(11))); + // 2rd field: float. + EXPECT_TRUE(event.write(3.45f)); + // 3th field: string. + EXPECT_TRUE(event.write("test")); + // 4th field: bool. + EXPECT_TRUE(event.write(true)); + // 5th field: bool. + EXPECT_TRUE(event.write(false)); + // 6th field: long. + EXPECT_TRUE(event.write(uint64_t(1234))); + + event.init(); + + DimensionsValue dimensionsValue; + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(3, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_str(), "test"); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5); + // The bool value is stored in value_int field as logD does not support bool. + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), false); + + EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue)); + EXPECT_EQ(dimensionsValue.field(), event.GetTagId()); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6); + EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234)); + + // Field not exist. + EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(7, &dimensionsValue)); +} + +TEST(LogEventTest, testWriteAtomProtoToStream) { + AttributionNode attribution_node1; + attribution_node1.set_uid(1111); + attribution_node1.set_tag("locationService"); + + AttributionNode attribution_node2; + attribution_node2.set_uid(2222); + attribution_node2.set_tag("locationService2"); + + AttributionNode attribution_node3; + attribution_node3.set_uid(3333); + attribution_node3.set_tag("locationService3"); + std::vector<AttributionNode> attribution_nodes = + {attribution_node1, attribution_node2, attribution_node3}; + + LogEvent event(1, 0); + EXPECT_TRUE(event.write("222")); + EXPECT_TRUE(event.write(attribution_nodes)); + EXPECT_TRUE(event.write(345)); + EXPECT_TRUE(event.write(attribution_node3)); + EXPECT_TRUE(event.write("hello")); + event.init(); + + util::ProtoOutputStream protoOutput; + // For now only see whether it will crash. + // TODO(yanglu): test parsing from stream. + event.ToProto(protoOutput); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif
\ No newline at end of file diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp index 3c8ccabed3ae..cb212a76a1dd 100644 --- a/cmds/statsd/tests/MetricsManager_test.cpp +++ b/cmds/statsd/tests/MetricsManager_test.cpp @@ -21,6 +21,7 @@ #include "src/metrics/MetricProducer.h" #include "src/metrics/ValueMetricProducer.h" #include "src/metrics/metrics_manager_util.h" +#include "statsd_test_util.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" @@ -40,53 +41,55 @@ using android::os::statsd::Predicate; // TODO: ADD MORE TEST CASES. -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); const long timeBaseSec = 1000; StatsdConfig buildGoodConfig() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_OFF"); + eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_ON_OR_OFF"); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("SCREEN_IS_ON"); - combination->add_matcher("SCREEN_IS_OFF"); + combination->add_matcher(StringToId("SCREEN_IS_ON")); + combination->add_matcher(StringToId("SCREEN_IS_OFF")); CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("SCREEN_IS_ON"); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_IS_ON")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(1); + metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions()->add_child()->set_field(1); + + config.add_no_report_metric(3); auto alert = config.add_alert(); - alert->set_name("3"); - alert->set_metric_name("3"); - alert->set_number_of_buckets(10); + alert->set_id(3); + alert->set_metric_id(3); + alert->set_num_buckets(10); alert->set_refractory_period_secs(100); alert->set_trigger_if_sum_gt(100); return config; @@ -94,48 +97,48 @@ StatsdConfig buildGoodConfig() { StatsdConfig buildCircleMatchers() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_ON_OR_OFF"); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("SCREEN_IS_ON"); + combination->add_matcher(StringToId("SCREEN_IS_ON")); // Circle dependency - combination->add_matcher("SCREEN_ON_OR_OFF"); + combination->add_matcher(StringToId("SCREEN_ON_OR_OFF")); return config; } StatsdConfig buildAlertWithUnknownMetric() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("SCREEN_IS_ON"); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_IS_ON")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(1); + metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/); + metric->mutable_dimensions()->add_child()->set_field(1); auto alert = config.add_alert(); - alert->set_name("3"); - alert->set_metric_name("2"); - alert->set_number_of_buckets(10); + alert->set_id(3); + alert->set_metric_id(2); + alert->set_num_buckets(10); alert->set_refractory_period_secs(100); alert->set_trigger_if_sum_gt(100); return config; @@ -143,83 +146,83 @@ StatsdConfig buildAlertWithUnknownMetric() { StatsdConfig buildMissingMatchers() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_ON_OR_OFF"); + eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("SCREEN_IS_ON"); + combination->add_matcher(StringToId("SCREEN_IS_ON")); // undefined matcher - combination->add_matcher("ABC"); + combination->add_matcher(StringToId("ABC")); return config; } StatsdConfig buildMissingPredicate() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("SCREEN_EVENT"); + metric->set_id(3); + metric->set_what(StringToId("SCREEN_EVENT")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - metric->set_condition("SOME_CONDITION"); + metric->set_condition(StringToId("SOME_CONDITION")); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_EVENT"); + eventMatcher->set_id(StringToId("SCREEN_EVENT")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2); + simpleAtomMatcher->set_atom_id(2); return config; } StatsdConfig buildDimensionMetricsWithMultiTags() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("BATTERY_VERY_LOW"); + eventMatcher->set_id(StringToId("BATTERY_VERY_LOW")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2); + simpleAtomMatcher->set_atom_id(2); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("BATTERY_VERY_VERY_LOW"); + eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW")); simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(3); + simpleAtomMatcher->set_atom_id(3); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("BATTERY_LOW"); + eventMatcher->set_id(StringToId("BATTERY_LOW")); AtomMatcher_Combination* combination = eventMatcher->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_matcher("BATTERY_VERY_LOW"); - combination->add_matcher("BATTERY_VERY_VERY_LOW"); + combination->add_matcher(StringToId("BATTERY_VERY_LOW")); + combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW")); // Count process state changes, slice by uid, while SCREEN_IS_OFF CountMetric* metric = config.add_count_metric(); - metric->set_name("3"); - metric->set_what("BATTERY_LOW"); + metric->set_id(3); + metric->set_what(StringToId("BATTERY_LOW")); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); - KeyMatcher* keyMatcher = metric->add_dimension(); - keyMatcher->set_key(1); + // This case is interesting. We want to dimension across two atoms. + metric->mutable_dimensions()->add_child()->set_field(1); auto alert = config.add_alert(); - alert->set_name("3"); - alert->set_metric_name("3"); - alert->set_number_of_buckets(10); + alert->set_id(103); + alert->set_metric_id(3); + alert->set_num_buckets(10); alert->set_refractory_period_secs(100); alert->set_trigger_if_sum_gt(100); return config; @@ -227,46 +230,47 @@ StatsdConfig buildDimensionMetricsWithMultiTags() { StatsdConfig buildCirclePredicates() { StatsdConfig config; - config.set_name("12345"); + config.set_id(12345); AtomMatcher* eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_ON"); + eventMatcher->set_id(StringToId("SCREEN_IS_ON")); SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/); eventMatcher = config.add_atom_matcher(); - eventMatcher->set_name("SCREEN_IS_OFF"); + eventMatcher->set_id(StringToId("SCREEN_IS_OFF")); simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher(); - simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/); - simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key( + simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/); + simpleAtomMatcher->add_field_value_matcher()->set_field( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/); - simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int( + simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int( 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/); auto condition = config.add_predicate(); - condition->set_name("SCREEN_IS_ON"); + condition->set_id(StringToId("SCREEN_IS_ON")); SimplePredicate* simplePredicate = condition->mutable_simple_predicate(); - simplePredicate->set_start("SCREEN_IS_ON"); - simplePredicate->set_stop("SCREEN_IS_OFF"); + simplePredicate->set_start(StringToId("SCREEN_IS_ON")); + simplePredicate->set_stop(StringToId("SCREEN_IS_OFF")); condition = config.add_predicate(); - condition->set_name("SCREEN_IS_EITHER_ON_OFF"); + condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF")); Predicate_Combination* combination = condition->mutable_combination(); combination->set_operation(LogicalOperation::OR); - combination->add_predicate("SCREEN_IS_ON"); - combination->add_predicate("SCREEN_IS_EITHER_ON_OFF"); + combination->add_predicate(StringToId("SCREEN_IS_ON")); + combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF")); return config; } TEST(MetricsManagerTest, TestGoodConfig) { + UidMap uidMap; StatsdConfig config = buildGoodConfig(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -276,15 +280,19 @@ TEST(MetricsManagerTest, TestGoodConfig) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_TRUE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); EXPECT_EQ(1u, allMetricProducers.size()); EXPECT_EQ(1u, allAnomalyTrackers.size()); + EXPECT_EQ(1u, noReportMetricIds.size()); } TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { + UidMap uidMap; StatsdConfig config = buildDimensionMetricsWithMultiTags(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -294,13 +302,16 @@ TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { + UidMap uidMap; StatsdConfig config = buildCircleMatchers(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -310,13 +321,16 @@ TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingMatchers) { + UidMap uidMap; StatsdConfig config = buildMissingMatchers(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -326,12 +340,15 @@ TEST(MetricsManagerTest, TestMissingMatchers) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + std::set<int64_t> noReportMetricIds; + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingPredicate) { + UidMap uidMap; StatsdConfig config = buildMissingPredicate(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -341,12 +358,15 @@ TEST(MetricsManagerTest, TestMissingPredicate) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + std::set<int64_t> noReportMetricIds; + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, TestCirclePredicateDependency) { + UidMap uidMap; StatsdConfig config = buildCirclePredicates(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -356,13 +376,16 @@ TEST(MetricsManagerTest, TestCirclePredicateDependency) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } TEST(MetricsManagerTest, testAlertWithUnknownMetric) { + UidMap uidMap; StatsdConfig config = buildAlertWithUnknownMetric(); set<int> allTagIds; vector<sp<LogMatchingTracker>> allAtomMatchers; @@ -372,10 +395,12 @@ TEST(MetricsManagerTest, testAlertWithUnknownMetric) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; + std::set<int64_t> noReportMetricIds; - EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers, + EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, - conditionToMetricMap, trackerToMetricMap, trackerToConditionMap)); + conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, + noReportMetricIds)); } #else diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 9b96bb75743a..5d053e25003d 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -41,7 +41,7 @@ using android::util::ProtoOutputStream; */ class MockMetricsManager : public MetricsManager { public: - MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000, new UidMap()) { + MockMetricsManager() : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, new UidMap()) { } MOCK_METHOD0(byteSize, size_t()); @@ -52,11 +52,11 @@ TEST(StatsLogProcessorTest, TestRateLimitByteSize) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. - StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {}); + StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {}); MockMetricsManager mockMetricsManager; - ConfigKey key(100, "key"); + ConfigKey key(100, 12345); // Expect only the first flush to trigger a check for byte size since the last two are // rate-limited. EXPECT_CALL(mockMetricsManager, byteSize()).Times(1); @@ -69,12 +69,12 @@ TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; int broadcastCount = 0; - StatsLogProcessor p(m, anomalyMonitor, + StatsLogProcessor p(m, anomalyMonitor, 0, [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); MockMetricsManager mockMetricsManager; - ConfigKey key(100, "key"); + ConfigKey key(100, 12345); EXPECT_CALL(mockMetricsManager, byteSize()) .Times(2) .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * .95))); @@ -93,12 +93,12 @@ TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; int broadcastCount = 0; - StatsLogProcessor p(m, anomalyMonitor, + StatsLogProcessor p(m, anomalyMonitor, 0, [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); MockMetricsManager mockMetricsManager; - ConfigKey key(100, "key"); + ConfigKey key(100, 12345); EXPECT_CALL(mockMetricsManager, byteSize()) .Times(1) .WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * 1.2))); diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index 3fa96d392927..945af2746eae 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -18,6 +18,7 @@ #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" #include "statslog.h" +#include "statsd_test_util.h" #include <gtest/gtest.h> @@ -37,7 +38,7 @@ TEST(UidMapTest, TestIsolatedUID) { sp<UidMap> m = new UidMap(); sp<AnomalyMonitor> anomalyMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. - StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {}); + StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {}); LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1); addEvent.write(100); // parent UID addEvent.write(101); // isolated UID @@ -156,8 +157,8 @@ TEST(UidMapTest, TestUpdateApp) { TEST(UidMapTest, TestClearingOutput) { UidMap m; - ConfigKey config1(1, "config1"); - ConfigKey config2(1, "config2"); + ConfigKey config1(1, StringToId("config1")); + ConfigKey config2(1, StringToId("config2")); m.OnConfigUpdated(config1); @@ -211,7 +212,7 @@ TEST(UidMapTest, TestClearingOutput) { TEST(UidMapTest, TestMemoryComputed) { UidMap m; - ConfigKey config1(1, "config1"); + ConfigKey config1(1, StringToId("config1")); m.OnConfigUpdated(config1); size_t startBytes = m.mBytesUsed; @@ -241,7 +242,7 @@ TEST(UidMapTest, TestMemoryGuardrail) { UidMap m; string buf; - ConfigKey config1(1, "config1"); + ConfigKey config1(1, StringToId("config1")); m.OnConfigUpdated(config1); size_t startBytes = m.mBytesUsed; @@ -273,4 +274,4 @@ GTEST_LOG_(INFO) << "This test does nothing.\n"; } // namespace statsd } // namespace os -} // namespace android +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp index f62171d57a5a..5842bc889f93 100644 --- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp @@ -31,17 +31,13 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); HashableDimensionKey getMockDimensionKey(int key, string value) { - KeyValuePair pair; - pair.set_key(key); - pair.set_value_str(value); - - vector<KeyValuePair> pairs; - pairs.push_back(pair); - - return HashableDimensionKey(pairs); + DimensionsValue dimensionsValue; + dimensionsValue.set_field(key); + dimensionsValue.set_value_str(value); + return HashableDimensionKey(dimensionsValue); } void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list, @@ -61,7 +57,7 @@ std::shared_ptr<DimToValMap> MockBucket( TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { const int64_t bucketSizeNs = 30 * NS_PER_SEC; Alert alert; - alert.set_number_of_buckets(3); + alert.set_num_buckets(3); alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC); alert.set_trigger_if_sum_gt(2); @@ -89,7 +85,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L); // Adds past bucket #0 anomalyTracker.addPastBucket(bucket0, 0); @@ -100,7 +96,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L); // Adds past bucket #0 again. The sum does not change. anomalyTracker.addPastBucket(bucket0, 0); @@ -111,7 +107,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L); // Adds past bucket #1. anomalyTracker.addPastBucket(bucket1, 1); @@ -122,7 +118,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds past bucket #1 again. Nothing changes. anomalyTracker.addPastBucket(bucket1, 1); @@ -133,7 +129,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds past bucket #2. anomalyTracker.addPastBucket(bucket2, 2); @@ -144,7 +140,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3); // Within refractory period. - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds bucket #3. anomalyTracker.addPastBucket(bucket3, 3L); @@ -154,7 +150,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); // Adds bucket #4. anomalyTracker.addPastBucket(bucket4, 4); @@ -164,7 +160,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5); // Adds bucket #5. anomalyTracker.addPastBucket(bucket5, 5); @@ -175,13 +171,13 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6)); // Within refractory period. anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5); } TEST(AnomalyTrackerTest, TestSparseBuckets) { const int64_t bucketSizeNs = 30 * NS_PER_SEC; Alert alert; - alert.set_number_of_buckets(3); + alert.set_num_buckets(3); alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC); alert.set_trigger_if_sum_gt(2); @@ -210,7 +206,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9)); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1); // Add past bucket #9 anomalyTracker.addPastBucket(bucket9, 9); @@ -224,7 +220,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); // Add past bucket #16 @@ -237,7 +233,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); // Within refractory period. anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); @@ -253,7 +249,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Add bucket #18 again. Nothing changes. anomalyTracker.addPastBucket(bucket18, 18); @@ -267,7 +263,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20); // Within refractory period. - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Add past bucket #20 anomalyTracker.addPastBucket(bucket20, 20); @@ -279,7 +275,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Add past bucket #25 anomalyTracker.addPastBucket(bucket25, 25); @@ -291,7 +287,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4); // Updates current bucket #28. (*bucket28)[keyE] = 5; @@ -300,7 +296,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28); EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); - EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7); + EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp6 + 7); } } // namespace statsd diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index eb0fafe781c6..819f2bebf327 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -11,12 +11,15 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + #include "src/condition/SimpleConditionTracker.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <stdio.h> #include <vector> +#include <numeric> using std::map; using std::unordered_map; @@ -28,17 +31,24 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); + +const int ATTRIBUTION_NODE_FIELD_ID = 1; +const int ATTRIBUTION_UID_FIELD_ID = 1; +const int TAG_ID = 1; SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse, - bool outputSlicedUid) { + bool outputSlicedUid, Position position) { SimplePredicate simplePredicate; - simplePredicate.set_start("WAKE_LOCK_ACQUIRE"); - simplePredicate.set_stop("WAKE_LOCK_RELEASE"); - simplePredicate.set_stop_all("RELEASE_ALL"); + simplePredicate.set_start(StringToId("WAKE_LOCK_ACQUIRE")); + simplePredicate.set_stop(StringToId("WAKE_LOCK_RELEASE")); + simplePredicate.set_stop_all(StringToId("RELEASE_ALL")); if (outputSlicedUid) { - KeyMatcher* keyMatcher = simplePredicate.add_dimension(); - keyMatcher->set_key(1); + simplePredicate.mutable_dimensions()->set_field(TAG_ID); + simplePredicate.mutable_dimensions()->add_child()->set_field(ATTRIBUTION_NODE_FIELD_ID); + simplePredicate.mutable_dimensions()->mutable_child(0)->set_position(position); + simplePredicate.mutable_dimensions()->mutable_child(0)->add_child()->set_field( + ATTRIBUTION_UID_FIELD_ID); } simplePredicate.set_count_nesting(countNesting); @@ -47,38 +57,70 @@ SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse, return simplePredicate; } -void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) { - event->write(uid); // uid +void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) { + std::vector<AttributionNode> nodes; + for (size_t i = 0; i < uids.size(); ++i) { + AttributionNode node; + node.set_uid(uids[i]); + nodes.push_back(node); + } + event->write(nodes); // attribution chain. +} + +void makeWakeLockEvent( + LogEvent* event, const std::vector<int> &uids, const string& wl, int acquire) { + writeAttributionNodesToEvent(event, uids); event->write(wl); event->write(acquire); event->init(); } -map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid, - const string& conditionName) { - // test query - KeyValuePair kv1; - kv1.set_key(key); - kv1.set_value_int(uid); - vector<KeyValuePair> kv_list; - kv_list.push_back(kv1); - map<string, HashableDimensionKey> queryKey; - queryKey[conditionName] = HashableDimensionKey(kv_list); - return queryKey; +std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey( + const Position position, + const std::vector<int> &uids, const string& conditionName) { + std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap; + std::vector<int> uid_indexes; + switch(position) { + case Position::FIRST: + uid_indexes.push_back(0); + break; + case Position::LAST: + uid_indexes.push_back(uids.size() - 1); + break; + case Position::ANY: + uid_indexes.resize(uids.size()); + std::iota(uid_indexes.begin(), uid_indexes.end(), 0); + break; + default: + break; + } + + for (const int idx : uid_indexes) { + DimensionsValue dimensionsValue; + dimensionsValue.set_field(TAG_ID); + dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field( + ATTRIBUTION_NODE_FIELD_ID); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0) + ->mutable_value_tuple()->add_dimensions_value()->set_field(ATTRIBUTION_NODE_FIELD_ID); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0) + ->mutable_value_tuple()->mutable_dimensions_value(0)->set_value_int(uids[idx]); + outputKeyMap[StringToId(conditionName)].push_back(HashableDimensionKey(dimensionsValue)); + } + return outputKeyMap; } TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { SimplePredicate simplePredicate; - simplePredicate.set_start("SCREEN_TURNED_ON"); - simplePredicate.set_stop("SCREEN_TURNED_OFF"); + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); simplePredicate.set_count_nesting(false); simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN); - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["SCREEN_TURNED_ON"] = 0; - trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1; + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON", 0 /*tracker index*/, + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*tracker index*/, simplePredicate, trackerNameIndexMap); LogEvent event(1 /*tagId*/, 0 /*timestamp*/); @@ -152,15 +194,15 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { SimplePredicate simplePredicate; - simplePredicate.set_start("SCREEN_TURNED_ON"); - simplePredicate.set_stop("SCREEN_TURNED_OFF"); + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); simplePredicate.set_count_nesting(true); - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["SCREEN_TURNED_ON"] = 0; - trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1; + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; - SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON", + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*condition tracker index*/, simplePredicate, trackerNameIndexMap); @@ -221,108 +263,133 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { } TEST(SimpleConditionTrackerTest, TestSlicedCondition) { - SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, true /*output slice by uid*/); - string conditionName = "WL_HELD_BY_UID2"; - - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0; - trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1; - trackerNameIndexMap["RELEASE_ALL"] = 2; - - SimpleConditionTracker conditionTracker(kConfigKey, conditionName, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - int uid = 111; - - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid, "wl1", 1); - - // one matched start - vector<MatchingState> matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector<sp<ConditionTracker>> allPredicates; - vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); - vector<bool> changedCache(1, false); - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // Now test query - const auto queryKey = getWakeLockQueryKey(1, uid, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // another wake lock acquired by this uid - LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid, "wl2", 1); - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_FALSE(changedCache[0]); + for (Position position : + { Position::ANY, Position::FIRST, Position::LAST}) { + SimplePredicate simplePredicate = getWakeLockHeldCondition( + true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, + position); + string conditionName = "WL_HELD_BY_UID2"; + + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + std::vector<int> uids = {111, 222, 333}; + + LogEvent event(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event, uids, "wl1", 1); + + // one matched start + vector<MatchingState> matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector<sp<ConditionTracker>> allPredicates; + vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); + vector<bool> changedCache(1, false); + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + + // Now test query + const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // another wake lock acquired by this uid + LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event2, uids, "wl2", 1); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_FALSE(changedCache[0]); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + + // wake lock 1 release + LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event3, uids, "wl1", 0); // now release it. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, + changedCache); + // nothing changes, because wake lock 2 is still held for this uid + EXPECT_FALSE(changedCache[0]); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + } + + LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event4, uids, "wl2", 0); // now release it. + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + EXPECT_TRUE(changedCache[0]); + + // query again + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - // wake lock 1 release - LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event3, uid, "wl1", 0); // now release it. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, - changedCache); - // nothing changes, because wake lock 2 is still held for this uid - EXPECT_FALSE(changedCache[0]); - - LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event4, uid, "wl2", 0); // now release it. - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); + } - // query again - conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, false /*slice output by uid*/); + true /*nesting*/, true /*default to false*/, false /*slice output by uid*/, + Position::ANY /* position */); string conditionName = "WL_HELD"; - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0; - trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1; - trackerNameIndexMap["RELEASE_ALL"] = 2; + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; - SimpleConditionTracker conditionTracker(kConfigKey, conditionName, + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), 0 /*condition tracker index*/, simplePredicate, trackerNameIndexMap); - int uid1 = 111; + + std::vector<int> uid_list1 = {111, 1111, 11111}; string uid1_wl1 = "wl1_1"; - int uid2 = 222; + std::vector<int> uid_list2 = {222, 2222, 22222}; string uid2_wl1 = "wl2_1"; LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid1, uid1_wl1, 1); + makeWakeLockEvent(&event, uid_list1, uid1_wl1, 1); // one matched start for uid1 vector<MatchingState> matcherState; @@ -340,7 +407,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { EXPECT_TRUE(changedCache[0]); // Now test query - map<string, HashableDimensionKey> queryKey; + ConditionKey queryKey; conditionCache[0] = ConditionState::kNotEvaluated; conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); @@ -348,7 +415,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // another wake lock acquired by this uid LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid2, uid2_wl1, 1); + makeWakeLockEvent(&event2, uid_list2, uid2_wl1, 1); matcherState.clear(); matcherState.push_back(MatchingState::kMatched); matcherState.push_back(MatchingState::kNotMatched); @@ -360,7 +427,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // uid1 wake lock 1 release LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event3, uid1, uid1_wl1, 0); // now release it. + makeWakeLockEvent(&event3, uid_list1, uid1_wl1, 0); // now release it. matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -372,7 +439,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { EXPECT_FALSE(changedCache[0]); LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event4, uid2, uid2_wl1, 0); // now release it. + makeWakeLockEvent(&event4, uid_list2, uid2_wl1, 0); // now release it. matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -390,95 +457,111 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { } TEST(SimpleConditionTrackerTest, TestStopAll) { - SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, true /*output slice by uid*/); - string conditionName = "WL_HELD_BY_UID3"; - - unordered_map<string, int> trackerNameIndexMap; - trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0; - trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1; - trackerNameIndexMap["RELEASE_ALL"] = 2; - - SimpleConditionTracker conditionTracker(kConfigKey, conditionName, - 0 /*condition tracker index*/, simplePredicate, - trackerNameIndexMap); - int uid1 = 111; - int uid2 = 222; - - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid1, "wl1", 1); - - // one matched start - vector<MatchingState> matcherState; - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - vector<sp<ConditionTracker>> allPredicates; - vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); - vector<bool> changedCache(1, false); - - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, - changedCache); - - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // Now test query - const auto queryKey = getWakeLockQueryKey(1, uid1, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - // another wake lock acquired by uid2 - LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid2, "wl2", 1); - matcherState.clear(); - matcherState.push_back(MatchingState::kMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); - EXPECT_TRUE(changedCache[0]); - - // TEST QUERY - const auto queryKey2 = getWakeLockQueryKey(1, uid2, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - - - // stop all event - LogEvent event3(2 /*tagId*/, 0 /*timestamp*/); - matcherState.clear(); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kNotMatched); - matcherState.push_back(MatchingState::kMatched); - - conditionCache[0] = ConditionState::kNotEvaluated; - changedCache[0] = false; - conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, - changedCache); - EXPECT_TRUE(changedCache[0]); - EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); - - // TEST QUERY - const auto queryKey3 = getWakeLockQueryKey(1, uid1, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; - - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); - - // TEST QUERY - const auto queryKey4 = getWakeLockQueryKey(1, uid2, conditionName); - conditionCache[0] = ConditionState::kNotEvaluated; + for (Position position : + {Position::ANY, Position::FIRST, Position::LAST}) { + SimplePredicate simplePredicate = getWakeLockHeldCondition( + true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, + position); + string conditionName = "WL_HELD_BY_UID3"; + + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0; + trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1; + trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), + 0 /*condition tracker index*/, simplePredicate, + trackerNameIndexMap); + + std::vector<int> uid_list1 = {111, 1111, 11111}; + std::vector<int> uid_list2 = {222, 2222, 22222}; + + LogEvent event(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event, uid_list1, "wl1", 1); + + // one matched start + vector<MatchingState> matcherState; + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + vector<sp<ConditionTracker>> allPredicates; + vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); + vector<bool> changedCache(1, false); + + conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + changedCache); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + + // Now test query + const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + // another wake lock acquired by uid2 + LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); + makeWakeLockEvent(&event2, uid_list2, "wl2", 1); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, + changedCache); + if (position == Position::FIRST || + position == Position::LAST) { + EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); + } else { + EXPECT_EQ(uid_list1.size() + uid_list2.size(), + conditionTracker.mSlicedConditionState.size()); + } + EXPECT_TRUE(changedCache[0]); + + // TEST QUERY + const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + + + // stop all event + LogEvent event3(2 /*tagId*/, 0 /*timestamp*/); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kNotMatched); + matcherState.push_back(MatchingState::kMatched); + + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_TRUE(changedCache[0]); + EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + + // TEST QUERY + const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + + // TEST QUERY + const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName); + conditionCache[0] = ConditionState::kNotEvaluated; + + conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + } - conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache); - EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } } // namespace statsd diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp new file mode 100644 index 000000000000..b56b8176cceb --- /dev/null +++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp @@ -0,0 +1,228 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +StatsdConfig CreateStatsdConfig() { + StatsdConfig config; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + + *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); + + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto appCrashMatcher = CreateProcessCrashAtomMatcher(); + *config.add_atom_matcher() = appCrashMatcher; + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + + auto isSyncingPredicate = CreateIsSyncingPredicate(); + *isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions( + android::util::SYNC_STATE_CHANGED, {1 /* uid field */}); + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + + *config.add_predicate() = screenIsOffPredicate; + *config.add_predicate() = isSyncingPredicate; + *config.add_predicate() = isInBackgroundPredicate; + + auto combinationPredicate = config.add_predicate(); + combinationPredicate->set_id(StringToId("combinationPredicate")); + combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); + addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); + addPredicateToPredicateCombination(isInBackgroundPredicate, combinationPredicate); + + auto countMetric = config.add_count_metric(); + countMetric->set_id(StringToId("AppCrashes")); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_condition(combinationPredicate->id()); + // The metric is dimensioning by uid only. + *countMetric->mutable_dimensions() = + CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); + countMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000LL); + + // Links between crash atom and condition of app is in syncing. + auto links = countMetric->add_links(); + links->set_condition(isSyncingPredicate.id()); + auto dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + + // Links between crash atom and condition of app is in background. + links = countMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} + +TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) { + auto config = CreateStatsdConfig(); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = config.count_metric(0).bucket().bucket_size_millis() * 1000 * 1000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid = 123; + auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1); + auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201); + auto crashEvent3= CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101); + + auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51); + auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299); + auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001); + + auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16); + auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249); + + auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351); + auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2); + + auto screenTurnedOnEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 2); + auto screenTurnedOffEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, + bucketStartTimeNs + 2 * bucketSizeNs - 100); + + auto syncOnEvent1 = + CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50); + auto syncOffEvent1 = + CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); + auto syncOnEvent2 = + CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); + + auto moveToBackgroundEvent1 = + CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15); + auto moveToForegroundEvent1 = + CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250); + + auto moveToBackgroundEvent2 = + CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350); + auto moveToForegroundEvent2 = + CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1); + + /* + bucket #1 bucket #2 + + + | | | | | | | | | | (crashEvents) + |-------------------------------------|-----------------------------------|--------- + + | | (MoveToBkground) + + | | (MoveToForeground) + + | | (SyncIsOn) + | (SyncIsOff) + | | (ScreenIsOn) + | (ScreenIsOff) + */ + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(std::move(crashEvent1)); + events.push_back(std::move(crashEvent2)); + events.push_back(std::move(crashEvent3)); + events.push_back(std::move(crashEvent4)); + events.push_back(std::move(crashEvent5)); + events.push_back(std::move(crashEvent6)); + events.push_back(std::move(crashEvent7)); + events.push_back(std::move(crashEvent8)); + events.push_back(std::move(crashEvent9)); + events.push_back(std::move(crashEvent10)); + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(syncOnEvent1)); + events.push_back(std::move(syncOffEvent1)); + events.push_back(std::move(syncOnEvent2)); + events.push_back(std::move(moveToBackgroundEvent1)); + events.push_back(std::move(moveToForegroundEvent1)); + events.push_back(std::move(moveToBackgroundEvent2)); + events.push_back(std::move(moveToForegroundEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(*event); + } + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); + auto data = reports.reports(0).metrics(0).count_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Uid field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid); + + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3); + data = reports.reports(0).metrics(0).count_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Uid field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp new file mode 100644 index 000000000000..ecdb002c1863 --- /dev/null +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -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. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { + StatsdConfig config; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto screenIsOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenIsOffPredicate; + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + // The predicate is dimensioning by any attribution node and both by uid and tag. + *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateAttributionUidAndTagDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST}); + *config.add_predicate() = holdingWakelockPredicate; + + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("WakelockDuration")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->set_condition(screenIsOffPredicate.id()); + durationMetric->set_aggregation_type(aggregationType); + // The metric is dimensioning by first attribution node and only by uid. + *durationMetric->mutable_dimensions() = + CreateAttributionUidDimensions( + android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000LL); + return config; +} + +TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { + ConfigKey cfgKey; + for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) { + auto config = CreateStatsdConfig(aggregationType); + uint64_t bucketStartTimeNs = 10000000000; + uint64_t bucketSizeNs = + config.duration_metric(0).bucket().bucket_size_millis() * 1000 * 1000; + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + auto screenTurnedOnEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 1); + auto screenTurnedOffEvent = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200); + auto screenTurnedOnEvent2 = + CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, + bucketStartTimeNs + bucketSizeNs + 500); + + std::vector<AttributionNode> attributions1 = + {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector<AttributionNode> attributions2 = + {CreateAttribution(111, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + auto acquireEvent1 = CreateAcquireWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + 2); + auto acquireEvent2 = CreateAcquireWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10); + + auto releaseEvent1 = CreateReleaseWakelockEvent( + attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2); + auto releaseEvent2 = CreateReleaseWakelockEvent( + attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15); + + std::vector<std::unique_ptr<LogEvent>> events; + + events.push_back(std::move(screenTurnedOnEvent)); + events.push_back(std::move(screenTurnedOffEvent)); + events.push_back(std::move(screenTurnedOnEvent2)); + events.push_back(std::move(acquireEvent1)); + events.push_back(std::move(acquireEvent2)); + events.push_back(std::move(releaseEvent1)); + events.push_back(std::move(releaseEvent2)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(*event); + } + + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + // Only 1 dimension output. The tag dimension in the predicate has been aggregated. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + + auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::WAKELOCK_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Attribution field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + // Uid only. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).value_int(), 111); + // Validate bucket info. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + // The wakelock holding interval starts from the screen off event and to the end of the 1st + // bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200); + + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + // Dump the report after the end of 2nd bucket. + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + // Validate dimension value. + EXPECT_EQ(data.dimension().field(), + android::util::WAKELOCK_STATE_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + // Attribution field. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); + // Uid only. + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) + .value_tuple().dimensions_value(0).value_int(), 111); + // Two output buckets. + // The wakelock holding interval in the 1st bucket starts from the screen off event and to + // the end of the 1st bucket. + EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), + bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200)); + // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and + // ends at the second screen on event. + EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); + } +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 765804475eef..a1343002405b 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -14,6 +14,7 @@ #include "src/guardrail/StatsdStats.h" #include "statslog.h" +#include "tests/statsd_test_util.h" #include <gtest/gtest.h> #include <vector> @@ -28,8 +29,7 @@ using std::vector; TEST(StatsdStatsTest, TestValidConfigAdd) { StatsdStats stats; - string name = "StatsdTest"; - ConfigKey key(0, name); + ConfigKey key(0, 12345); const int metricsCount = 10; const int conditionsCount = 20; const int matchersCount = 30; @@ -45,7 +45,7 @@ TEST(StatsdStatsTest, TestValidConfigAdd) { EXPECT_EQ(1, report.config_stats_size()); const auto& configReport = report.config_stats(0); EXPECT_EQ(0, configReport.uid()); - EXPECT_EQ(name, configReport.name()); + EXPECT_EQ(12345, configReport.id()); EXPECT_EQ(metricsCount, configReport.metric_count()); EXPECT_EQ(conditionsCount, configReport.condition_count()); EXPECT_EQ(matchersCount, configReport.matcher_count()); @@ -56,8 +56,7 @@ TEST(StatsdStatsTest, TestValidConfigAdd) { TEST(StatsdStatsTest, TestInvalidConfigAdd) { StatsdStats stats; - string name = "StatsdTest"; - ConfigKey key(0, name); + ConfigKey key(0, 12345); const int metricsCount = 10; const int conditionsCount = 20; const int matchersCount = 30; @@ -78,8 +77,7 @@ TEST(StatsdStatsTest, TestInvalidConfigAdd) { TEST(StatsdStatsTest, TestConfigRemove) { StatsdStats stats; - string name = "StatsdTest"; - ConfigKey key(0, name); + ConfigKey key(0, 12345); const int metricsCount = 10; const int conditionsCount = 20; const int matchersCount = 30; @@ -105,22 +103,22 @@ TEST(StatsdStatsTest, TestConfigRemove) { TEST(StatsdStatsTest, TestSubStats) { StatsdStats stats; - ConfigKey key(0, "test"); + ConfigKey key(0, 12345); stats.noteConfigReceived(key, 2, 3, 4, 5, true); - stats.noteMatcherMatched(key, "matcher1"); - stats.noteMatcherMatched(key, "matcher1"); - stats.noteMatcherMatched(key, "matcher2"); + stats.noteMatcherMatched(key, StringToId("matcher1")); + stats.noteMatcherMatched(key, StringToId("matcher1")); + stats.noteMatcherMatched(key, StringToId("matcher2")); - stats.noteConditionDimensionSize(key, "condition1", 250); - stats.noteConditionDimensionSize(key, "condition1", 240); + stats.noteConditionDimensionSize(key, StringToId("condition1"), 250); + stats.noteConditionDimensionSize(key, StringToId("condition1"), 240); - stats.noteMetricDimensionSize(key, "metric1", 201); - stats.noteMetricDimensionSize(key, "metric1", 202); + stats.noteMetricDimensionSize(key, StringToId("metric1"), 201); + stats.noteMetricDimensionSize(key, StringToId("metric1"), 202); - stats.noteAnomalyDeclared(key, "alert1"); - stats.noteAnomalyDeclared(key, "alert1"); - stats.noteAnomalyDeclared(key, "alert2"); + stats.noteAnomalyDeclared(key, StringToId("alert1")); + stats.noteAnomalyDeclared(key, StringToId("alert1")); + stats.noteAnomalyDeclared(key, StringToId("alert2")); // broadcast-> 2 stats.noteBroadcastSent(key); @@ -147,39 +145,39 @@ TEST(StatsdStatsTest, TestSubStats) { EXPECT_EQ(2, configReport.matcher_stats_size()); // matcher1 is the first in the list - if (!configReport.matcher_stats(0).name().compare("matcher1")) { + if (configReport.matcher_stats(0).id() == StringToId("matcher1")) { EXPECT_EQ(2, configReport.matcher_stats(0).matched_times()); EXPECT_EQ(1, configReport.matcher_stats(1).matched_times()); - EXPECT_EQ("matcher2", configReport.matcher_stats(1).name()); + EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id()); } else { // matcher1 is the second in the list. EXPECT_EQ(1, configReport.matcher_stats(0).matched_times()); - EXPECT_EQ("matcher2", configReport.matcher_stats(0).name()); + EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id()); EXPECT_EQ(2, configReport.matcher_stats(1).matched_times()); - EXPECT_EQ("matcher1", configReport.matcher_stats(1).name()); + EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id()); } EXPECT_EQ(2, configReport.alert_stats_size()); - bool alert1first = !configReport.alert_stats(0).name().compare("alert1"); - EXPECT_EQ("alert1", configReport.alert_stats(alert1first ? 0 : 1).name()); + bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1"); + EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id()); EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times()); - EXPECT_EQ("alert2", configReport.alert_stats(alert1first ? 1 : 0).name()); + EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id()); EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times()); EXPECT_EQ(1, configReport.condition_stats_size()); - EXPECT_EQ("condition1", configReport.condition_stats(0).name()); + EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id()); EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts()); EXPECT_EQ(1, configReport.metric_stats_size()); - EXPECT_EQ("metric1", configReport.metric_stats(0).name()); + EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id()); EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts()); // after resetting the stats, some new events come - stats.noteMatcherMatched(key, "matcher99"); - stats.noteConditionDimensionSize(key, "condition99", 300); - stats.noteMetricDimensionSize(key, "metric99", 270); - stats.noteAnomalyDeclared(key, "alert99"); + stats.noteMatcherMatched(key, StringToId("matcher99")); + stats.noteConditionDimensionSize(key, StringToId("condition99"), 300); + stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270); + stats.noteAnomalyDeclared(key, StringToId("alert99")); // now the config stats should only contain the stats about the new event. stats.dumpStats(&output, false); @@ -188,19 +186,19 @@ TEST(StatsdStatsTest, TestSubStats) { EXPECT_EQ(1, report.config_stats_size()); const auto& configReport2 = report.config_stats(0); EXPECT_EQ(1, configReport2.matcher_stats_size()); - EXPECT_EQ("matcher99", configReport2.matcher_stats(0).name()); + EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id()); EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times()); EXPECT_EQ(1, configReport2.condition_stats_size()); - EXPECT_EQ("condition99", configReport2.condition_stats(0).name()); + EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id()); EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts()); EXPECT_EQ(1, configReport2.metric_stats_size()); - EXPECT_EQ("metric99", configReport2.metric_stats(0).name()); + EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id()); EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts()); EXPECT_EQ(1, configReport2.alert_stats_size()); - EXPECT_EQ("alert99", configReport2.alert_stats(0).name()); + EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id()); EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times()); } @@ -260,7 +258,7 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { timestamps.push_back(i); } - ConfigKey key(0, "test"); + ConfigKey key(0, 12345); stats.noteConfigReceived(key, 2, 3, 4, 5, true); for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index eec9453940dc..4cb242aa3bcc 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -13,7 +13,9 @@ // limitations under the License. #include "src/metrics/CountMetricProducer.h" +#include "src/dimension.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -32,7 +34,7 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); TEST(CountMetricProducerTest, TestNonDimensionalEvents) { int64_t bucketStartTimeNs = 10000000000; @@ -42,7 +44,7 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { int tagId = 1; CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); LogEvent event1(tagId, bucketStartTimeNs + 1); @@ -99,9 +101,9 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_condition("SCREEN_ON"); + metric.set_condition(StringToId("SCREEN_ON")); LogEvent event1(1, bucketStartTimeNs + 1); LogEvent event2(1, bucketStartTimeNs + 10); @@ -137,26 +139,31 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int tagId = 1; + int conditionTagId = 2; + CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"); + metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); - link->set_condition("APP_IN_BACKGROUND_PER_UID"); - link->add_key_in_what()->set_key(1); - link->add_key_in_condition()->set_key(2); + link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); + *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); + *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); - LogEvent event1(1, bucketStartTimeNs + 1); + LogEvent event1(tagId, bucketStartTimeNs + 1); event1.write("111"); // uid event1.init(); ConditionKey key1; - key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111"); + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = + {getMockedDimensionKey(conditionTagId, 2, "111")}; - LogEvent event2(1, bucketStartTimeNs + 10); + LogEvent event2(tagId, bucketStartTimeNs + 10); event2.write("222"); // uid event2.init(); ConditionKey key2; - key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222"); + key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = + {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); @@ -185,10 +192,10 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { TEST(CountMetricProducerTest, TestAnomalyDetection) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name("1"); + alert.set_id(11); + alert.set_metric_id(1); alert.set_trigger_if_sum_gt(2); - alert.set_number_of_buckets(2); + alert.set_num_buckets(2); alert.set_refractory_period_secs(1); int64_t bucketStartTimeNs = 10000000000; @@ -196,16 +203,14 @@ TEST(CountMetricProducerTest, TestAnomalyDetection) { int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - CountMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, bucketStartTimeNs); - countProducer.addAnomalyTracker(anomalyTracker); + sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert); int tagId = 1; LogEvent event1(tagId, bucketStartTimeNs + 1); @@ -222,13 +227,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // One event in bucket #2. No alarm as bucket #0 is trashed out. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Two events in bucket #3. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); @@ -237,12 +242,12 @@ TEST(CountMetricProducerTest, TestAnomalyDetection) { EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second); // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6 - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event5.GetTimestampNs()); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event7.GetTimestampNs()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index 58a4ac6afd51..a4213dec7c44 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -36,7 +36,7 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); TEST(DurationMetricTrackerTest, TestNoCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -44,7 +44,7 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; DurationMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); @@ -52,9 +52,10 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { LogEvent event1(tagId, bucketStartTimeNs + 1); LogEvent event2(tagId, bucketStartTimeNs + bucketSizeNs + 2); + FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); @@ -78,7 +79,7 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; DurationMetric metric; - metric.set_name("1"); + metric.set_id(1); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); @@ -88,9 +89,10 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1); LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3); + FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); EXPECT_FALSE(durationProducer.mCondition); EXPECT_FALSE(durationProducer.isConditionSliced()); diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index baaac67f395b..7171de939c62 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -13,7 +13,9 @@ // limitations under the License. #include "src/metrics/EventMetricProducer.h" +#include "src/dimension.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -32,7 +34,7 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); TEST(EventMetricProducerTest, TestNoCondition) { uint64_t bucketStartTimeNs = 10000000000; @@ -40,7 +42,7 @@ TEST(EventMetricProducerTest, TestNoCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; EventMetric metric; - metric.set_name("1"); + metric.set_id(1); LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1); LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2); @@ -63,8 +65,8 @@ TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; EventMetric metric; - metric.set_name("1"); - metric.set_condition("SCREEN_ON"); + metric.set_id(1); + metric.set_condition(StringToId("SCREEN_ON")); LogEvent event1(1, bucketStartTimeNs + 1); LogEvent event2(1, bucketStartTimeNs + 10); @@ -88,25 +90,28 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; + int tagId = 1; + int conditionTagId = 2; + EventMetric metric; - metric.set_name("1"); - metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"); + metric.set_id(1); + metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); - link->set_condition("APP_IN_BACKGROUND_PER_UID"); - link->add_key_in_what()->set_key(1); - link->add_key_in_condition()->set_key(2); + link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); + *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); + *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); - LogEvent event1(1, bucketStartTimeNs + 1); - event1.write("111"); // uid + LogEvent event1(tagId, bucketStartTimeNs + 1); + EXPECT_TRUE(event1.write("111")); event1.init(); ConditionKey key1; - key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111"); + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "111")}; - LogEvent event2(1, bucketStartTimeNs + 10); - event2.write("222"); // uid + LogEvent event2(tagId, bucketStartTimeNs + 10); + EXPECT_TRUE(event2.write("222")); event2.init(); ConditionKey key2; - key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222"); + key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse)); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 5204834634ce..749cf26e4630 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -15,6 +15,7 @@ #include "src/metrics/GaugeMetricProducer.h" #include "logd/LogEvent.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,9 +35,9 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); const int tagId = 1; -const string metricName = "test_metric"; +const int64_t metricId = 123; const int64_t bucketStartTimeNs = 10000000000; const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL; const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; @@ -45,9 +46,13 @@ const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; TEST(GaugeMetricProducerTest, TestNoCondition) { GaugeMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.mutable_gauge_fields()->add_field_num(2); + metric.mutable_gauge_fields_filter()->set_include_all(false); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(1); + gaugeFieldMatcher->add_child()->set_field(3); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -64,30 +69,41 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { vector<shared_ptr<LogEvent>> allData; allData.clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); + event->write(10); + event->write("some value"); event->write(11); event->init(); allData.push_back(event); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(11, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + EXPECT_EQ(10, it->second.value_int()); + it++; + EXPECT_EQ(11, it->second.value_int()); EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); allData.clear(); std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10); - event2->write(tagId); + event2->write(24); + event2->write("some value"); event2->write(25); event2->init(); allData.push_back(event2); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(25, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + EXPECT_EQ(24, it->second.value_int()); + it++; + EXPECT_EQ(25, it->second.value_int()); // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(11L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + EXPECT_EQ(10L, it->second.value_int()); + it++; + EXPECT_EQ(11L, it->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs); @@ -95,16 +111,21 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + EXPECT_EQ(24L, it->second.value_int()); + it++; + EXPECT_EQ(25L, it->second.value_int()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); } TEST(GaugeMetricProducerTest, TestWithCondition) { GaugeMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.mutable_gauge_fields()->add_field_num(2); - metric.set_condition("SCREEN_ON"); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -116,7 +137,7 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { data->clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(tagId); + event->write("some value"); event->write(100); event->init(); data->push_back(event); @@ -128,28 +149,32 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + EXPECT_EQ(100, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(1); + event->write("some value"); event->write(110); event->init(); allData.push_back(event); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); + EXPECT_EQ(110, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back() + .mGaugeFields->begin()->second.value_int()); gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10); gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int()); + EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back() + .mGaugeFields->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); } @@ -162,61 +187,66 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); GaugeMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.mutable_gauge_fields()->add_field_num(2); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(2); GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, tagId, bucketStartTimeNs, pullerManager); Alert alert; - alert.set_name("alert"); - alert.set_metric_name(metricName); + alert.set_id(101); + alert.set_metric_id(metricId); alert.set_trigger_if_sum_gt(25); - alert.set_number_of_buckets(2); - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - gaugeProducer.addAnomalyTracker(anomalyTracker); + alert.set_num_buckets(2); + sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert); - std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1); - event1->write(1); + int tagId = 1; + std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); + event1->write("some value"); event1->write(13); event1->init(); gaugeProducer.onDataPulled({event1}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); + EXPECT_EQ(13L, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); std::shared_ptr<LogEvent> event2 = - std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10); - event2->write(1); + std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 10); + event2->write("some value"); event2->write(15); event2->init(); gaugeProducer.onDataPulled({event2}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs()); + EXPECT_EQ(15L, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event2->GetTimestampNs()); std::shared_ptr<LogEvent> event3 = - std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10); - event3->write(1); + std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10); + event3->write("some value"); event3->write(24); event3->init(); gaugeProducer.onDataPulled({event3}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs()); + EXPECT_EQ(24L, + gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs()); // The event4 does not have the gauge field. Thus the current bucket value is 0. std::shared_ptr<LogEvent> event4 = - std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10); - event4->write(1); + std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10); + event4->write("some value"); event4->init(); gaugeProducer.onDataPulled({event4}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(0, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int()); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs()); + EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second->empty()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index 7dac0fbb38a0..704a46691328 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -13,8 +13,9 @@ // limitations under the License. #include "src/metrics/duration_helper/MaxDurationTracker.h" -#include "metrics_test_helper.h" #include "src/condition/ConditionWizard.h" +#include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -36,24 +37,27 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); + +const int TagId = 1; -const HashableDimensionKey eventKey = getMockedDimensionKey(0, "1"); -const HashableDimensionKey conditionKey = getMockedDimensionKey(4, "1"); -const HashableDimensionKey key1 = getMockedDimensionKey(1, "1"); -const HashableDimensionKey key2 = getMockedDimensionKey(1, "2"); +const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1"); +const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; +const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); +const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(key1, true, bucketStartTimeNs, conditionKey1); @@ -77,12 +81,13 @@ TEST(MaxDurationTrackerTest, TestStopAll) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, conditionKey1); @@ -108,12 +113,13 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {}); // The event starts. @@ -139,12 +145,13 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; ConditionKey conditionKey1; - conditionKey1["condition"] = conditionKey; + conditionKey1[StringToId("condition")] = conditionKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {}); // 2 starts @@ -174,8 +181,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; - HashableDimensionKey eventKey = getMockedDimensionKey(2, "maps"); - conditionKey1["APP_BACKGROUND"] = conditionKey; + HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps"); + conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4 .WillOnce(Return(ConditionState::kFalse)); @@ -187,7 +194,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; int64_t durationTimeNs = 2 * 1000; - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, bucketStartTimeNs, + int64_t metricId = 1; + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); @@ -204,35 +212,36 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { } TEST(MaxDurationTrackerTest, TestAnomalyDetection) { + int64_t metricId = 1; Alert alert; - alert.set_name("alert"); - alert.set_metric_name("metric"); + alert.set_id(101); + alert.set_metric_id(metricId); alert.set_trigger_if_sum_gt(32 * NS_PER_SEC); - alert.set_number_of_buckets(2); + alert.set_num_buckets(2); alert.set_refractory_period_secs(1); unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; - conditionKey1["APP_BACKGROUND"] = conditionKey; + conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey; uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; uint64_t bucketSizeNs = 30 * NS_PER_SEC; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs, + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); tracker.noteStop(key1, eventStartTimeNs + 10, false); - EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1); + EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1); EXPECT_EQ(10LL, tracker.mDuration); tracker.noteStart(key2, true, eventStartTimeNs + 20, conditionKey1); tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets); tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false); EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration); - EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, + EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC)); } diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 9ec302fbfd7d..36cdaae01b4f 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -13,8 +13,9 @@ // limitations under the License. #include "src/metrics/duration_helper/OringDurationTracker.h" -#include "metrics_test_helper.h" #include "src/condition/ConditionWizard.h" +#include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,18 +35,20 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); -const HashableDimensionKey eventKey = getMockedDimensionKey(0, "event"); +const ConfigKey kConfigKey(0, 12345); +const int TagId = 1; +const int64_t metricId = 123; +const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); -const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(1, "maps"); -const HashableDimensionKey kEventKey1 = getMockedDimensionKey(2, "maps"); -const HashableDimensionKey kEventKey2 = getMockedDimensionKey(3, "maps"); +const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; +const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); +const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); TEST(OringDurationTrackerTest, TestDurationOverlap) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -54,7 +57,7 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -74,7 +77,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -82,7 +85,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -101,7 +104,7 @@ TEST(OringDurationTrackerTest, TestStopAll) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -109,7 +112,7 @@ TEST(OringDurationTrackerTest, TestStopAll) { uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -127,7 +130,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; @@ -136,7 +139,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -162,7 +165,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; EXPECT_CALL(*wizard, query(_, key1)) // #4 .WillOnce(Return(ConditionState::kFalse)); @@ -174,7 +177,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -193,7 +196,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; EXPECT_CALL(*wizard, query(_, key1)) .Times(2) @@ -207,7 +210,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; uint64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -228,7 +231,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; EXPECT_CALL(*wizard, query(_, key1)) // #4 .WillOnce(Return(ConditionState::kFalse)); @@ -239,7 +242,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { uint64_t eventStartTimeNs = bucketStartTimeNs + 1; uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -259,22 +262,22 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name("1"); + alert.set_id(101); + alert.set_metric_id(1); alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_number_of_buckets(2); + alert.set_num_buckets(2); alert.set_refractory_period_secs(1); unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; uint64_t bucketSizeNs = 30 * NS_PER_SEC; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs, + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {anomalyTracker}); // Nothing in the past bucket. @@ -321,27 +324,27 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { TEST(OringDurationTrackerTest, TestAnomalyDetection) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name("1"); + alert.set_id(101); + alert.set_metric_id(1); alert.set_trigger_if_sum_gt(40 * NS_PER_SEC); - alert.set_number_of_buckets(2); + alert.set_num_buckets(2); alert.set_refractory_period_secs(1); unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; - key1["APP_BACKGROUND"] = kConditionKey1; + key1[StringToId("APP_BACKGROUND")] = kConditionKey1; uint64_t bucketStartTimeNs = 10 * NS_PER_SEC; uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1; uint64_t bucketSizeNs = 30 * NS_PER_SEC; - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); - OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/, + sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, bucketStartTimeNs, bucketSizeNs, {anomalyTracker}); tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1); tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false); - EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1); + EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1); EXPECT_TRUE(tracker.mStarted.empty()); EXPECT_EQ(10LL, tracker.mDuration); @@ -355,7 +358,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetection) { tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false); EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs)); EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25), - anomalyTracker->mLastAlarmTimestampNs); + anomalyTracker->mLastAnomalyTimestampNs); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 6f117d3d76d3..15acca4bd2a1 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -14,6 +14,7 @@ #include "src/metrics/ValueMetricProducer.h" #include "metrics_test_helper.h" +#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,9 +35,9 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, "test"); +const ConfigKey kConfigKey(0, 12345); const int tagId = 1; -const string metricName = "test_metric"; +const int64_t metricId = 123; const int64_t bucketStartTimeNs = 10000000000; const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL; const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; @@ -48,9 +49,10 @@ const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; */ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_value_field(2); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); // TODO: pending refactor of StatsPullerManager @@ -124,10 +126,11 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { */ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_value_field(2); - metric.set_condition("SCREEN_ON"); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); shared_ptr<MockStatsPullerManager> pullerManager = @@ -200,9 +203,10 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_value_field(2); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); shared_ptr<MockStatsPullerManager> pullerManager = @@ -240,22 +244,22 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { TEST(ValueMetricProducerTest, TestAnomalyDetection) { Alert alert; - alert.set_name("alert"); - alert.set_metric_name(metricName); + alert.set_id(101); + alert.set_metric_id(metricId); alert.set_trigger_if_sum_gt(130); - alert.set_number_of_buckets(2); + alert.set_num_buckets(2); alert.set_refractory_period_secs(3); - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey); ValueMetric metric; - metric.set_name(metricName); + metric.set_id(metricId); metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000); - metric.set_value_field(2); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, -1 /*not pulled*/, bucketStartTimeNs); - valueProducer.addAnomalyTracker(anomalyTracker); + sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert); shared_ptr<LogEvent> event1 @@ -292,23 +296,23 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { // Two events in bucket #0. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 30 <= 130. + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 30 <= 130. // One event in bucket #2. No alarm as bucket #0 is trashed out. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 130 <= 130. + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 130 <= 130. // Three events in bucket #3. valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); // Anomaly at event 4 since Value sum == 131 > 130! - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs()); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5); // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4. - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs()); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6); // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period. - EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event6->GetTimestampNs()); + EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event6->GetTimestampNs()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp index a0a854a28d36..fc7245ca54e3 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp +++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp @@ -18,15 +18,12 @@ namespace android { namespace os { namespace statsd { -HashableDimensionKey getMockedDimensionKey(int key, string value) { - KeyValuePair pair; - pair.set_key(key); - pair.set_value_str(value); - - vector<KeyValuePair> pairs; - pairs.push_back(pair); - - return HashableDimensionKey(pairs); +HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) { + DimensionsValue dimensionsValue; + dimensionsValue.set_field(tagId); + dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key); + dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value); + return HashableDimensionKey(dimensionsValue); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index 7cb332953016..23e86f92f844 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -28,7 +28,7 @@ public: MOCK_METHOD2( query, ConditionState(const int conditionIndex, - const std::map<std::string, HashableDimensionKey>& conditionParameters)); + const ConditionKey& conditionParameters)); }; class MockStatsPullerManager : public StatsPullerManager { @@ -38,7 +38,7 @@ public: MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data)); }; -HashableDimensionKey getMockedDimensionKey(int key, std::string value); +HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value); } // namespace statsd } // namespace os diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp new file mode 100644 index 000000000000..939dc1f7c66c --- /dev/null +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -0,0 +1,324 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <gtest/gtest.h> +#include "statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, + WakelockStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateAcquireWakelockAtomMatcher() { + return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE); +} + +AtomMatcher CreateReleaseWakelockAtomMatcher() { + return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE); +} + +AtomMatcher CreateScreenStateChangedAtomMatcher( + const string& name, ScreenStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(1); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateScreenTurnedOnAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", ScreenStateChanged::STATE_ON); +} + +AtomMatcher CreateScreenTurnedOffAtomMatcher() { + return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", ScreenStateChanged::STATE_OFF); +} + +AtomMatcher CreateSyncStateChangedAtomMatcher( + const string& name, SyncStateChanged::State state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateSyncStartAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON); +} + +AtomMatcher CreateSyncEndAtomMatcher() { + return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF); +} + +AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( + const string& name, ActivityForegroundStateChanged::Activity activity) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(4); // Activity field. + field_value_matcher->set_eq_int(activity); + return atom_matcher; +} + +AtomMatcher CreateMoveToBackgroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND); +} + +AtomMatcher CreateMoveToForegroundAtomMatcher() { + return CreateActivityForegroundStateChangedAtomMatcher( + "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND); +} + +AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher( + const string& name, ProcessLifeCycleStateChanged::Event event) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(3); // Process state field. + field_value_matcher->set_eq_int(event); + return atom_matcher; +} + +AtomMatcher CreateProcessCrashAtomMatcher() { + return CreateProcessLifeCycleStateChangedAtomMatcher( + "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED); +} + + +Predicate CreateScreenIsOnPredicate() { + Predicate predicate; + predicate.set_id(StringToId("ScreenIsOn")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff")); + return predicate; +} + +Predicate CreateScreenIsOffPredicate() { + Predicate predicate; + predicate.set_id(StringToId("ScreenIsOff")); + predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn")); + return predicate; +} + +Predicate CreateHoldingWakelockPredicate() { + Predicate predicate; + predicate.set_id(StringToId("HoldingWakelock")); + predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock")); + predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock")); + return predicate; +} + +Predicate CreateIsSyncingPredicate() { + Predicate predicate; + predicate.set_id(StringToId("IsSyncing")); + predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart")); + predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd")); + return predicate; +} + +Predicate CreateIsInBackgroundPredicate() { + Predicate predicate; + predicate.set_id(StringToId("IsInBackground")); + predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground")); + predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground")); + return predicate; +} + +void addPredicateToPredicateCombination(const Predicate& predicate, + Predicate* combinationPredicate) { + combinationPredicate->mutable_combination()->add_predicate(predicate.id()); +} + +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector<Position>& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + } + return dimensions; +} + +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector<Position>& positions) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const auto position : positions) { + auto child = dimensions.add_child(); + child->set_field(1); + child->set_position(position); + child->add_child()->set_field(1); + child->add_child()->set_field(2); + } + return dimensions; +} + +FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( + const ScreenStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs); + EXPECT_TRUE(event->write(state)); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent( + const std::vector<AttributionNode>& attributions, const string& wakelockName, + const WakelockStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs); + event->write(attributions); + event->write(WakelockStateChanged::PARTIAL); + event->write(wakelockName); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs) { + return CreateWakelockStateChangedEvent( + attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs); +} + +std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs) { + return CreateWakelockStateChangedEvent( + attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs); +} + +std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( + const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>( + android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs); + event->write(uid); + event->write("pkg_name"); + event->write("class_name"); + event->write(activity); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) { + return CreateActivityForegroundStateChangedEvent( + uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs); +} + +std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) { + return CreateActivityForegroundStateChangedEvent( + uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs); +} + +std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( + const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs); + event->write(uid); + event->write(name); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr<LogEvent> CreateSyncStartEvent( + const int uid, const string& name, uint64_t timestampNs){ + return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs); +} + +std::unique_ptr<LogEvent> CreateSyncEndEvent( + const int uid, const string& name, uint64_t timestampNs) { + return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs); +} + +std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent( + const int uid, const ProcessLifeCycleStateChanged::Event event, uint64_t timestampNs) { + auto logEvent = std::make_unique<LogEvent>( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, timestampNs); + logEvent->write(uid); + logEvent->write(""); + logEvent->write(event); + logEvent->init(); + return logEvent; +} + +std::unique_ptr<LogEvent> CreateAppCrashEvent(const int uid, uint64_t timestampNs) { + return CreateProcessLifeCycleStateChangedEvent( + uid, ProcessLifeCycleStateChanged::PROCESS_CRASHED, timestampNs); +} + +sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key) { + sp<UidMap> uidMap = new UidMap(); + sp<AnomalyMonitor> anomalyMonitor = new AnomalyMonitor(10); // 10 seconds + sp<StatsLogProcessor> processor = new StatsLogProcessor( + uidMap, anomalyMonitor, timeBaseSec, [](const ConfigKey&){}); + processor->OnConfigUpdated(key, config); + return processor; +} + +AttributionNode CreateAttribution(const int& uid, const string& tag) { + AttributionNode attribution; + attribution.set_uid(uid); + attribution.set_tag(tag); + return attribution; +} + +void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { + std::sort(events->begin(), events->end(), + [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { + return a->GetTimestampNs() < b->GetTimestampNs(); + }); +} + +int64_t StringToId(const string& str) { + return static_cast<int64_t>(std::hash<std::string>()(str)); +} +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h new file mode 100644 index 000000000000..5e19da032e07 --- /dev/null +++ b/cmds/statsd/tests/statsd_test_util.h @@ -0,0 +1,128 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "statslog.h" +#include "src/logd/LogEvent.h" +#include "src/StatsLogProcessor.h" + +namespace android { +namespace os { +namespace statsd { + +// Create AtomMatcher proto for acquiring wakelock. +AtomMatcher CreateAcquireWakelockAtomMatcher(); + +// Create AtomMatcher proto for releasing wakelock. +AtomMatcher CreateReleaseWakelockAtomMatcher() ; + +// Create AtomMatcher proto for screen turned on. +AtomMatcher CreateScreenTurnedOnAtomMatcher(); + +// Create AtomMatcher proto for screen turned off. +AtomMatcher CreateScreenTurnedOffAtomMatcher(); + +// Create AtomMatcher proto for app sync turned on. +AtomMatcher CreateSyncStartAtomMatcher(); + +// Create AtomMatcher proto for app sync turned off. +AtomMatcher CreateSyncEndAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to background. +AtomMatcher CreateMoveToBackgroundAtomMatcher(); + +// Create AtomMatcher proto for app sync moves to foreground. +AtomMatcher CreateMoveToForegroundAtomMatcher(); + +// Create AtomMatcher proto for process crashes +AtomMatcher CreateProcessCrashAtomMatcher() ; + +// Create Predicate proto for screen is on. +Predicate CreateScreenIsOnPredicate(); + +// Create Predicate proto for screen is off. +Predicate CreateScreenIsOffPredicate(); + +// Create Predicate proto for holding wakelock. +Predicate CreateHoldingWakelockPredicate(); + +// Create a Predicate proto for app syncing. +Predicate CreateIsSyncingPredicate(); + +// Create a Predicate proto for app is in background. +Predicate CreateIsInBackgroundPredicate(); + +// Add a predicate to the predicate combination. +void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); + +// Create dimensions from primitive fields. +FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields); + +// Create dimensions by attribution uid and tag. +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector<Position>& positions); + +// Create dimensions by attribution uid only. +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector<Position>& positions); + +// Create log event for screen state changed. +std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( + const ScreenStateChanged::State state, uint64_t timestampNs); + +// Create log event for app moving to background. +std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); + +// Create log event for app moving to foreground. +std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs); + +// Create log event when the app sync starts. +std::unique_ptr<LogEvent> CreateSyncStartEvent( + const int uid, const string& name, uint64_t timestampNs); + +// Create log event when the app sync ends. +std::unique_ptr<LogEvent> CreateSyncEndEvent( + const int uid, const string& name, uint64_t timestampNs); + +// Create log event when the app sync ends. +std::unique_ptr<LogEvent> CreateAppCrashEvent( + const int uid, uint64_t timestampNs); + +// Create log event for acquiring wakelock. +std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs); + +// Create log event for releasing wakelock. +std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( + const std::vector<AttributionNode>& attributions, + const string& wakelockName, uint64_t timestampNs); + +// Helper function to create an AttributionNode proto. +AttributionNode CreateAttribution(const int& uid, const string& tag); + +// Create a statsd log event processor upon the start time in seconds, config and key. +sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key); + +// Util function to sort the log events by timestamp. +void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events); + +int64_t StringToId(const string& str); + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file 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 fe3d86d4cd6a..93dba71716a5 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java @@ -26,7 +26,7 @@ public class DisplayProtoUtils { sb.append("ConfigKey: "); if (reports.hasConfigKey()) { com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey(); - sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName()) + sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getId()) .append("\n"); } @@ -34,7 +34,7 @@ public class DisplayProtoUtils { sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); for (StatsLog.StatsLogReport log : report.getMetricsList()) { sb.append("\n\n"); - sb.append("metric id: ").append(log.getMetricName()).append("\n"); + sb.append("metric id: ").append(log.getMetricId()).append("\n"); sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n"); sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n"); @@ -71,20 +71,25 @@ public class DisplayProtoUtils { return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); } - private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) { - for (com.android.os.StatsLog.KeyValuePair kv : pairs) { - sb.append(kv.getKey()).append(":"); - if (kv.hasValueBool()) { - sb.append(kv.getValueBool()); - } else if (kv.hasValueFloat()) { - sb.append(kv.getValueFloat()); - } else if (kv.hasValueInt()) { - sb.append(kv.getValueInt()); - } else if (kv.hasValueStr()) { - sb.append(kv.getValueStr()); + private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) { + sb.append(dimensionValue.getField()).append(":"); + if (dimensionValue.hasValueBool()) { + sb.append(dimensionValue.getValueBool()); + } else if (dimensionValue.hasValueFloat()) { + sb.append(dimensionValue.getValueFloat()); + } else if (dimensionValue.hasValueInt()) { + sb.append(dimensionValue.getValueInt()); + } else if (dimensionValue.hasValueStr()) { + sb.append(dimensionValue.getValueStr()); + } else if (dimensionValue.hasValueTuple()) { + sb.append("{"); + for (StatsLog.DimensionsValue child : + dimensionValue.getValueTuple().getDimensionsValueList()) { + displayDimension(sb, child); } - sb.append(" "); + sb.append("}"); } + sb.append(" "); } public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { @@ -93,7 +98,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, duration.getDimensionList()); + displayDimension(sb, duration.getDimension()); sb.append("\n"); for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { @@ -120,7 +125,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, count.getDimensionList()); + displayDimension(sb, count.getDimension()); sb.append("\n"); for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java index 70dd6347e1a4..119d6f52266e 100644 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java @@ -34,6 +34,8 @@ import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport; public class MainActivity extends Activity { private final static String TAG = "StatsdDogfood"; + private final static long CONFIG_ID = 987654321; + final int[] mUids = {11111111, 2222222}; StatsManager mStatsManager; @@ -163,7 +165,7 @@ public class MainActivity extends Activity { return; } if (mStatsManager != null) { - byte[] data = mStatsManager.getData("fake"); + byte[] data = mStatsManager.getData(CONFIG_ID); if (data != null) { displayData(data); } else { @@ -186,7 +188,7 @@ public class MainActivity extends Activity { byte[] config = new byte[inputStream.available()]; inputStream.read(config); if (mStatsManager != null) { - if (mStatsManager.addConfiguration("fake", + if (mStatsManager.addConfiguration(CONFIG_ID, config, getPackageName(), MainActivity.this.getClass().getName())) { Toast.makeText( MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show(); @@ -252,7 +254,9 @@ public class MainActivity extends Activity { Log.d(TAG, "invalid pkg id"); return; } - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1); + int[] uids = new int[] {mUids[id]}; + String[] tags = new String[] {"acquire"}; + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, 0, name, 1); StringBuilder sb = new StringBuilder(); sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) .append(", ").append(name).append(", 1);"); @@ -264,7 +268,9 @@ public class MainActivity extends Activity { Log.d(TAG, "invalid pkg id"); return; } - StatsLog.write(10, mUids[id], 0, name, 0); + int[] uids = new int[] {mUids[id]}; + String[] tags = new String[] {"release"}; + StatsLog.write(10, uids, tags, 0, name, 0); StringBuilder sb = new StringBuilder(); sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) .append(", ").append(name).append(", 0);"); diff --git a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml index 2a254df2302a..f10b69dc3e69 100644 --- a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml +++ b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml @@ -137,13 +137,54 @@ android:text="@integer/duration_default" android:textSize="30dp"/> </LinearLayout> - <CheckBox + <CheckBox android:id="@+id/placebo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/placebo" android:checked="false" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <CheckBox + android:id="@+id/include_count" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/count" + android:checked="true"/> + <CheckBox + android:id="@+id/include_duration" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/duration" + android:checked="true"/> + <CheckBox + android:id="@+id/include_event" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/event" + android:checked="true"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <CheckBox + android:id="@+id/include_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/value" + android:checked="true"/> + <CheckBox + android:id="@+id/include_gauge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/gauge" + android:checked="true"/> + </LinearLayout> + <Space android:layout_width="1dp" android:layout_height="30dp"/> diff --git a/cmds/statsd/tools/loadtest/res/values/strings.xml b/cmds/statsd/tools/loadtest/res/values/strings.xml index 522337ee656d..d0f77c65f660 100644 --- a/cmds/statsd/tools/loadtest/res/values/strings.xml +++ b/cmds/statsd/tools/loadtest/res/values/strings.xml @@ -26,5 +26,10 @@ <string name="duration_label">test duration (mins): </string> <string name="start">  Start  </string> <string name="stop">  Stop  </string> + <string name="count"> count </string> + <string name="duration"> duration </string> + <string name="event"> event </string> + <string name="value"> value </string> + <string name="gauge"> gauge </string> </resources> diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java index 0d890fbb6e56..4bd284448446 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java @@ -27,8 +27,7 @@ import com.android.internal.os.StatsdConfigProto.MetricConditionLink; import com.android.internal.os.StatsdConfigProto.EventMetric; import com.android.internal.os.StatsdConfigProto.GaugeMetric; import com.android.internal.os.StatsdConfigProto.ValueMetric; -import com.android.internal.os.StatsdConfigProto.KeyMatcher; -import com.android.internal.os.StatsdConfigProto.KeyValueMatcher; +import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; import com.android.internal.os.StatsdConfigProto.AtomMatcher; import com.android.internal.os.StatsdConfigProto.SimplePredicate; import com.android.internal.os.StatsdConfigProto.StatsdConfig; @@ -42,7 +41,7 @@ import java.util.List; * Creates StatsdConfig protos for loadtesting. */ public class ConfigFactory { - public static final String CONFIG_NAME = "LOADTEST"; + public static final long CONFIG_ID = 123456789; private static final String TAG = "loadtest.ConfigFactory"; @@ -83,34 +82,46 @@ public class ConfigFactory { * @param placebo If true, only return an empty config * @return The serialized config */ - public byte[] getConfig(int replication, long bucketMillis, boolean placebo) { + public byte[] getConfig(int replication, long bucketMillis, boolean placebo, boolean includeCount, + boolean includeDuration, boolean includeEvent, boolean includeValue, + boolean includeGauge) { StatsdConfig.Builder config = StatsdConfig.newBuilder() - .setName(CONFIG_NAME); + .setId(CONFIG_ID); if (placebo) { replication = 0; // Config will be empty, aside from a name. } int numMetrics = 0; for (int i = 0; i < replication; i++) { // metrics - for (EventMetric metric : mTemplate.getEventMetricList()) { - addEventMetric(metric, i, config); - numMetrics++; + if (includeEvent) { + for (EventMetric metric : mTemplate.getEventMetricList()) { + addEventMetric(metric, i, config); + numMetrics++; + } } - for (CountMetric metric : mTemplate.getCountMetricList()) { - addCountMetric(metric, i, bucketMillis, config); - numMetrics++; + if (includeCount) { + for (CountMetric metric : mTemplate.getCountMetricList()) { + addCountMetric(metric, i, bucketMillis, config); + numMetrics++; + } } - for (DurationMetric metric : mTemplate.getDurationMetricList()) { - addDurationMetric(metric, i, bucketMillis, config); - numMetrics++; + if (includeDuration) { + for (DurationMetric metric : mTemplate.getDurationMetricList()) { + addDurationMetric(metric, i, bucketMillis, config); + numMetrics++; + } } - for (GaugeMetric metric : mTemplate.getGaugeMetricList()) { - addGaugeMetric(metric, i, bucketMillis, config); - numMetrics++; + if (includeGauge) { + for (GaugeMetric metric : mTemplate.getGaugeMetricList()) { + addGaugeMetric(metric, i, bucketMillis, config); + numMetrics++; + } } - for (ValueMetric metric : mTemplate.getValueMetricList()) { - addValueMetric(metric, i, bucketMillis, config); - numMetrics++; + if (includeValue) { + for (ValueMetric metric : mTemplate.getValueMetricList()) { + addValueMetric(metric, i, bucketMillis, config); + numMetrics++; + } } // predicates for (Predicate predicate : mTemplate.getPredicateList()) { @@ -149,7 +160,7 @@ public class ConfigFactory { */ private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) { EventMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -175,7 +186,7 @@ public class ConfigFactory { private void addCountMetric(CountMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { CountMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -196,7 +207,7 @@ public class ConfigFactory { private void addDurationMetric(DurationMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { DurationMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -217,7 +228,7 @@ public class ConfigFactory { private void addGaugeMetric(GaugeMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { GaugeMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -238,7 +249,7 @@ public class ConfigFactory { private void addValueMetric(ValueMetric template, int suffix, long bucketMillis, StatsdConfig.Builder config) { ValueMetric.Builder metric = template.toBuilder() - .setName(template.getName() + suffix) + .setId(template.getId() + suffix) .setWhat(template.getWhat() + suffix); if (template.hasCondition()) { metric.setCondition(template.getCondition() + suffix); @@ -258,11 +269,11 @@ public class ConfigFactory { */ private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) { Predicate.Builder predicate = template.toBuilder() - .setName(template.getName() + suffix); + .setId(template.getId() + suffix); if (template.hasCombination()) { Predicate.Combination.Builder cb = template.getCombination().toBuilder() .clearPredicate(); - for (String child : template.getCombination().getPredicateList()) { + for (long child : template.getCombination().getPredicateList()) { cb.addPredicate(child + suffix); } predicate.setCombination(cb.build()); @@ -285,11 +296,11 @@ public class ConfigFactory { */ private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) { AtomMatcher.Builder matcher = template.toBuilder() - .setName(template.getName() + suffix); + .setId(template.getId() + suffix); if (template.hasCombination()) { AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder() .clearMatcher(); - for (String child : template.getCombination().getMatcherList()) { + for (long child : template.getCombination().getMatcherList()) { cb.addMatcher(child + suffix); } matcher.setCombination(cb); diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java index 735a32729a0f..19087d86c4a6 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java @@ -26,7 +26,7 @@ public class DisplayProtoUtils { sb.append("ConfigKey: "); if (reports.hasConfigKey()) { com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey(); - sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName()) + sb.append("\tuid: ").append(key.getUid()).append(" id: ").append(key.getId()) .append("\n"); } @@ -34,7 +34,7 @@ public class DisplayProtoUtils { sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); for (StatsLog.StatsLogReport log : report.getMetricsList()) { sb.append("\n\n"); - sb.append("metric id: ").append(log.getMetricName()).append("\n"); + sb.append("metric id: ").append(log.getMetricId()).append("\n"); sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n"); sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n"); @@ -71,20 +71,25 @@ public class DisplayProtoUtils { return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); } - private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) { - for (com.android.os.StatsLog.KeyValuePair kv : pairs) { - sb.append(kv.getKey()).append(":"); - if (kv.hasValueBool()) { - sb.append(kv.getValueBool()); - } else if (kv.hasValueFloat()) { - sb.append(kv.getValueFloat()); - } else if (kv.hasValueInt()) { - sb.append(kv.getValueInt()); - } else if (kv.hasValueStr()) { - sb.append(kv.getValueStr()); + private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) { + sb.append(dimensionValue.getField()).append(":"); + if (dimensionValue.hasValueBool()) { + sb.append(dimensionValue.getValueBool()); + } else if (dimensionValue.hasValueFloat()) { + sb.append(dimensionValue.getValueFloat()); + } else if (dimensionValue.hasValueInt()) { + sb.append(dimensionValue.getValueInt()); + } else if (dimensionValue.hasValueStr()) { + sb.append(dimensionValue.getValueStr()); + } else if (dimensionValue.hasValueTuple()) { + sb.append("{"); + for (StatsLog.DimensionsValue child : + dimensionValue.getValueTuple().getDimensionsValueList()) { + displayDimension(sb, child); } - sb.append(" "); + sb.append("}"); } + sb.append(" "); } public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { @@ -93,7 +98,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, duration.getDimensionList()); + displayDimension(sb, duration.getDimension()); sb.append("\n"); for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { @@ -120,7 +125,7 @@ public class DisplayProtoUtils { sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { sb.append("dimension: "); - displayDimension(sb, count.getDimensionList()); + displayDimension(sb, count.getDimension()); sb.append("\n"); for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java index 0a30ff8cf311..86da16c82e9b 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 @@ -110,6 +110,11 @@ public class LoadtestActivity extends Activity { private EditText mDurationText; private TextView mReportText; private CheckBox mPlaceboCheckBox; + private CheckBox mCountMetricCheckBox; + private CheckBox mDurationMetricCheckBox; + private CheckBox mEventMetricCheckBox; + private CheckBox mValueMetricCheckBox; + private CheckBox mGaugeMetricCheckBox; /** When the load test started. */ private long mStartedTimeMillis; @@ -129,6 +134,31 @@ public class LoadtestActivity extends Activity { */ private boolean mPlacebo; + /** + * Whether to include CountMetric in the config. + */ + private boolean mIncludeCountMetric; + + /** + * Whether to include DurationMetric in the config. + */ + private boolean mIncludeDurationMetric; + + /** + * Whether to include EventMetric in the config. + */ + private boolean mIncludeEventMetric; + + /** + * Whether to include ValueMetric in the config. + */ + private boolean mIncludeValueMetric; + + /** + * Whether to include GaugeMetric in the config. + */ + private boolean mIncludeGaugeMetric; + /** The burst size. */ private int mBurst; @@ -170,6 +200,7 @@ public class LoadtestActivity extends Activity { initPeriod(); initDuration(); initPlacebo(); + initMetricWhitelist(); // Hide the keyboard outside edit texts. findViewById(R.id.outside).setOnTouchListener(new View.OnTouchListener() { @@ -263,7 +294,7 @@ public class LoadtestActivity extends Activity { return null; } if (mStatsManager != null) { - byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_NAME); + byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_ID); if (data != null) { ConfigMetricsReportList reports = null; try { @@ -329,7 +360,9 @@ public class LoadtestActivity extends Activity { getData(); // Create a config and push it to statsd. - if (!setConfig(mFactory.getConfig(mReplication, mBucketMins * 60 * 1000, mPlacebo))) { + if (!setConfig(mFactory.getConfig(mReplication, mBucketMins * 60 * 1000, mPlacebo, + mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric, + mIncludeValueMetric, mIncludeGaugeMetric))) { return; } @@ -420,7 +453,7 @@ public class LoadtestActivity extends Activity { // TODO: Clear all configs instead of specific ones. if (mStatsManager != null) { if (mStarted) { - if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_NAME)) { + if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_ID)) { Log.d(TAG, "Removed loadtest statsd configs."); } else { Log.d(TAG, "Failed to remove loadtest configs."); @@ -431,7 +464,7 @@ public class LoadtestActivity extends Activity { private boolean setConfig(byte[] config) { if (mStatsManager != null) { - if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_NAME, + if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_ID, config, getPackageName(), LoadtestActivity.this.getClass().getName())) { Log.d(TAG, "Config pushed to statsd"); return true; @@ -548,4 +581,48 @@ public class LoadtestActivity extends Activity { } }); } + + private void initMetricWhitelist() { + mCountMetricCheckBox = findViewById(R.id.include_count); + mCountMetricCheckBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mIncludeCountMetric = mCountMetricCheckBox.isChecked(); + } + }); + mDurationMetricCheckBox = findViewById(R.id.include_duration); + mDurationMetricCheckBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mIncludeDurationMetric = mDurationMetricCheckBox.isChecked(); + } + }); + mEventMetricCheckBox = findViewById(R.id.include_event); + mEventMetricCheckBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mIncludeEventMetric = mEventMetricCheckBox.isChecked(); + } + }); + mValueMetricCheckBox = findViewById(R.id.include_value); + mValueMetricCheckBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mIncludeValueMetric = mValueMetricCheckBox.isChecked(); + } + }); + mGaugeMetricCheckBox = findViewById(R.id.include_gauge); + mGaugeMetricCheckBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked(); + } + }); + + mIncludeCountMetric = mCountMetricCheckBox.isChecked(); + mIncludeDurationMetric = mDurationMetricCheckBox.isChecked(); + mIncludeEventMetric = mEventMetricCheckBox.isChecked(); + mIncludeValueMetric = mValueMetricCheckBox.isChecked(); + mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked(); + } } diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java index 4b614aa19492..d122654ec8a1 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java +++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java @@ -59,18 +59,8 @@ public class ValidationRecorder extends PerfDataRecorder { Log.d(TAG, "GOT DATA"); for (ConfigMetricsReport report : reports) { for (StatsLogReport logReport : report.getMetricsList()) { - if (!logReport.hasMetricName()) { + if (!logReport.hasMetricId()) { Log.e(TAG, "Metric missing name."); - continue; - } - String metricName = logReport.getMetricName(); - if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_WHILE_SCREEN_IS_ON_")) { - validateEventBatteryLevelChangesWhileScreenIsOn(logReport); - continue; - } - if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_")) { - validateEventBatteryLevelChanges(logReport); - continue; } } } @@ -78,7 +68,7 @@ public class ValidationRecorder extends PerfDataRecorder { } private void validateEventBatteryLevelChanges(StatsLogReport logReport) { - Log.d(TAG, "Validating " + logReport.getMetricName()); + Log.d(TAG, "Validating " + logReport.getMetricId()); if (logReport.hasEventMetrics()) { Log.d(TAG, "Num events captured: " + logReport.getEventMetrics().getDataCount()); for (EventMetricData data : logReport.getEventMetrics().getDataList()) { @@ -90,6 +80,6 @@ public class ValidationRecorder extends PerfDataRecorder { } private void validateEventBatteryLevelChangesWhileScreenIsOn(StatsLogReport logReport) { - Log.d(TAG, "Validating " + logReport.getMetricName()); + Log.d(TAG, "Validating " + logReport.getMetricId()); } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 1adae7a84fcc..847f91bdc59a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -60,6 +60,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -3911,10 +3912,10 @@ public class ActivityManager { /** * @hide */ - public static void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg, - String tag) { + public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid, + String sourcePkg, String tag) { try { - getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, + getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource, sourceUid, sourcePkg, tag); } catch (RemoteException ex) { } @@ -3923,19 +3924,24 @@ public class ActivityManager { /** * @hide */ - public static void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) { + public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid, + String tag) { try { - getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag); + getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource, + sourceUid, tag); } catch (RemoteException ex) { } } + /** * @hide */ - public static void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { + public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid, + String tag) { try { - getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag); + getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource, + sourceUid, tag); } catch (RemoteException ex) { } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index c09403c29943..4c558f374f91 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -75,20 +75,20 @@ public abstract class ActivityManagerNative { */ static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg, String tag) { - ActivityManager.noteWakeupAlarm(ps, sourceUid, sourcePkg, tag); + ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag); } /** * @deprecated use ActivityManager.noteAlarmStart instead. */ static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) { - ActivityManager.noteAlarmStart(ps, sourceUid, tag); + ActivityManager.noteAlarmStart(ps, null, sourceUid, tag); } /** * @deprecated use ActivityManager.noteAlarmFinish instead. */ static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) { - ActivityManager.noteAlarmFinish(ps, sourceUid, tag); + ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag); } } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4a21f5c424d5..e61c5b7c78a1 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -36,6 +36,7 @@ import android.os.IRemoteCallback; import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.UserHandle; import android.transition.Transition; import android.transition.TransitionListenerAdapter; import android.transition.TransitionManager; @@ -265,6 +266,8 @@ public class ActivityOptions { public static final int ANIM_CUSTOM_IN_PLACE = 10; /** @hide */ public static final int ANIM_CLIP_REVEAL = 11; + /** @hide */ + public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12; private String mPackageName; private Rect mLaunchBounds; @@ -486,6 +489,19 @@ public class ActivityOptions { } /** + * Creates an {@link ActivityOptions} object specifying an animation where the new activity + * is started in another user profile by calling {@link + * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle) + * }. + * @hide + */ + public static ActivityOptions makeOpenCrossProfileAppsAnimation() { + ActivityOptions options = new ActivityOptions(); + options.mAnimationType = ANIM_OPEN_CROSS_PROFILE_APPS; + return options; + } + + /** * Create an ActivityOptions specifying an animation where a thumbnail * is scaled from a given position to the new activity window that is * being started. diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index a4e221ac9733..a9e633ff392d 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -63,6 +63,7 @@ import android.os.IProgressListener; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.StrictMode; +import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; @@ -86,6 +87,9 @@ interface IActivityManager { // the ones in frameworks/native/libs/binder/include/binder/IActivityManager.h // =============== Beginning of transactions used on native side as well ====================== ParcelFileDescriptor openContentUri(in String uriString); + void registerUidObserver(in IUidObserver observer, int which, int cutpoint, + String callingPackage); + void unregisterUidObserver(in IUidObserver observer); // =============== End of transactions used on native side as well ============================ // Special low-level communication with activity manager. @@ -195,7 +199,7 @@ interface IActivityManager { void enterSafeMode(); boolean startNextMatchingActivity(in IBinder callingActivity, in Intent intent, in Bundle options); - void noteWakeupAlarm(in IIntentSender sender, int sourceUid, + void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String sourcePkg, in String tag); void removeContentProvider(in IBinder connection, boolean stable); void setRequestedOrientation(in IBinder token, int requestedOrientation); @@ -465,8 +469,8 @@ interface IActivityManager { void dumpHeapFinished(in String path); void setVoiceKeepAwake(in IVoiceInteractionSession session, boolean keepAwake); void updateLockTaskPackages(int userId, in String[] packages); - void noteAlarmStart(in IIntentSender sender, int sourceUid, in String tag); - void noteAlarmFinish(in IIntentSender sender, int sourceUid, in String tag); + void noteAlarmStart(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag); + void noteAlarmFinish(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag); int getPackageProcessState(in String packageName, in String callingPackage); oneway void showLockTaskEscapeMessage(in IBinder token); void updateDeviceOwner(in String packageName); @@ -478,9 +482,6 @@ interface IActivityManager { */ void keyguardGoingAway(int flags); int getUidProcessState(int uid, in String callingPackage); - void registerUidObserver(in IUidObserver observer, int which, int cutpoint, - String callingPackage); - void unregisterUidObserver(in IUidObserver observer); boolean isAssistDataAllowedOnCurrentActivity(); boolean showAssistFromActivity(in IBinder token, in Bundle args); boolean isRootVoiceInteraction(in IBinder token); @@ -626,9 +627,6 @@ interface IActivityManager { /** Cancels the window transitions for the given task. */ void cancelTaskWindowTransition(int taskId); - /** Cancels the thumbnail transitions for the given task. */ - void cancelTaskThumbnailTransition(int taskId); - /** * @param taskId the id of the task to retrieve the sAutoapshots for * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl index 01a9cbf56c35..ce8880940c78 100644 --- a/core/java/android/app/IUidObserver.aidl +++ b/core/java/android/app/IUidObserver.aidl @@ -18,15 +18,14 @@ package android.app; /** {@hide} */ oneway interface IUidObserver { - /** - * General report of a state change of an uid. - * - * @param uid The uid for which the state change is being reported. - * @param procState The updated process state for the uid. - * @param procStateSeq The sequence no. associated with process state change of the uid, - * see UidRecord.procStateSeq for details. - */ - void onUidStateChanged(int uid, int procState, long procStateSeq); + // WARNING: when these transactions are updated, check if they are any callers on the native + // side. If so, make sure they are using the correct transaction ids and arguments. + // If a transaction which will also be used on the native side is being inserted, add it to + // below block of transactions. + + // Since these transactions are also called from native code, these must be kept in sync with + // the ones in frameworks/native/include/binder/IActivityManager.h + // =============== Beginning of transactions used on native side as well ====================== /** * Report that there are no longer any processes running for a uid. @@ -44,6 +43,18 @@ oneway interface IUidObserver { */ void onUidIdle(int uid, boolean disabled); + // =============== End of transactions used on native side as well ============================ + + /** + * General report of a state change of an uid. + * + * @param uid The uid for which the state change is being reported. + * @param procState The updated process state for the uid. + * @param procStateSeq The sequence no. associated with process state change of the uid, + * see UidRecord.procStateSeq for details. + */ + void onUidStateChanged(int uid, int procState, long procStateSeq); + /** * Report when the cached state of a uid has changed. * If true, a uid has become cached -- that is, it has some active processes that are diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 495fd3c4682e..66cf99158e00 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -552,8 +552,16 @@ final class SystemServiceRegistry { registerService(Context.WALLPAPER_SERVICE, WallpaperManager.class, new CachedServiceFetcher<WallpaperManager>() { @Override - public WallpaperManager createService(ContextImpl ctx) { - return new WallpaperManager(ctx.getOuterContext(), + public WallpaperManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + final IBinder b; + if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) { + b = ServiceManager.getServiceOrThrow(Context.WALLPAPER_SERVICE); + } else { + b = ServiceManager.getService(Context.WALLPAPER_SERVICE); + } + IWallpaperManager service = IWallpaperManager.Stub.asInterface(b); + return new WallpaperManager(service, ctx.getOuterContext(), ctx.mMainThread.getHandler()); }}); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 3829afb04eaa..f21746cdd275 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -286,9 +286,8 @@ public class WallpaperManager { private Bitmap mDefaultWallpaper; private Handler mMainLooperHandler; - Globals(Looper looper) { - IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); - mService = IWallpaperManager.Stub.asInterface(b); + Globals(IWallpaperManager service, Looper looper) { + mService = service; mMainLooperHandler = new Handler(looper); forgetLoadedWallpaper(); } @@ -497,17 +496,17 @@ public class WallpaperManager { private static final Object sSync = new Object[0]; private static Globals sGlobals; - static void initGlobals(Looper looper) { + static void initGlobals(IWallpaperManager service, Looper looper) { synchronized (sSync) { if (sGlobals == null) { - sGlobals = new Globals(looper); + sGlobals = new Globals(service, looper); } } } - /*package*/ WallpaperManager(Context context, Handler handler) { + /*package*/ WallpaperManager(IWallpaperManager service, Context context, Handler handler) { mContext = context; - initGlobals(context.getMainLooper()); + initGlobals(service, context.getMainLooper()); } /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 70e1a96e4a2e..0b747416bbbf 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1668,6 +1668,46 @@ public class DevicePolicyManager { public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"ID_TYPE_"}, value = { + ID_TYPE_BASE_INFO, + ID_TYPE_SERIAL, + ID_TYPE_IMEI, + ID_TYPE_MEID + }) + public @interface AttestationIdType {} + + /** + * Specifies that the device should attest its manufacturer details. For use with + * {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_BASE_INFO = 1; + + /** + * Specifies that the device should attest its serial number. For use with + * {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_SERIAL = 2; + + /** + * Specifies that the device should attest its IMEI. For use with {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_IMEI = 4; + + /** + * Specifies that the device should attest its MEID. For use with {@link #generateKeyPair}. + * + * @see #generateKeyPair + */ + public static final int ID_TYPE_MEID = 8; + /** * Return true if the given administrator component is currently active (enabled) in the system. * @@ -2690,9 +2730,6 @@ public class DevicePolicyManager { * @see #getPasswordBlacklistName * @see #isActivePasswordSufficient * @see #resetPasswordWithToken - * - * TODO(63578054): unhide for P - * @hide */ public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name, @Nullable List<String> blacklist) { @@ -2712,9 +2749,6 @@ public class DevicePolicyManager { * @return the name of the blacklist or {@code null} if no blacklist is set * * @see #setPasswordBlacklist - * - * TODO(63578054): unhide for P - * @hide */ public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) { try { @@ -4112,22 +4146,46 @@ public class DevicePolicyManager { * @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}. * @param keySpec Specification of the key to generate, see * {@link java.security.KeyPairGenerator}. + * @param idAttestationFlags A bitmask of all the identifiers that should be included in the + * attestation record ({@code ID_TYPE_BASE_INFO}, {@code ID_TYPE_SERIAL}, + * {@code ID_TYPE_IMEI} and {@code ID_TYPE_MEID}), or {@code 0} if no device + * identification is required in the attestation record. + * Device owner, profile owner and their delegated certificate installer can use + * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information + * including manufacturer, model, brand, device and product in the attestation record. + * Only device owner and their delegated certificate installer can use + * {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request + * unique device identifiers to be attested. + * <p> + * If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} + * is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set. + * <p> + * If any flag is specified, then an attestation challenge must be included in the + * {@code keySpec}. * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise. * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile - * owner. - * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, or if the + * owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL}, + * {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner + * or the Certificate Installer delegate. + * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, if the * algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec} - * or {@code ECGenParameterSpec}. + * or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the + * {@code keySpec} does not contain an attestation challenge. + * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[]) */ public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin, - @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec) { + @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec, + @AttestationIdType int idAttestationFlags) { throwIfParentInstance("generateKeyPair"); try { final ParcelableKeyGenParameterSpec parcelableSpec = new ParcelableKeyGenParameterSpec(keySpec); KeymasterCertificateChain attestationChain = new KeymasterCertificateChain(); + + // Translate ID attestation flags to values used by AttestationUtils final boolean success = mService.generateKeyPair( - admin, mContext.getPackageName(), algorithm, parcelableSpec, attestationChain); + admin, mContext.getPackageName(), algorithm, parcelableSpec, + idAttestationFlags, attestationChain); if (!success) { Log.e(TAG, "Error generating key via DevicePolicyManagerService."); return null; diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 7cf19eeb881d..5916a629f2e6 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -173,7 +173,7 @@ interface IDevicePolicyManager { boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias); boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm, in ParcelableKeyGenParameterSpec keySpec, - out KeymasterCertificateChain attestationChain); + in int idAttestationFlags, out KeymasterCertificateChain attestationChain); boolean setKeyPairCertificate(in ComponentName who, in String callerPackage, in String alias, in byte[] certBuffer, in byte[] certChainBuffer, boolean isUserSelectable); void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback); diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 7b549cd59666..87f227129586 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -32,6 +32,8 @@ import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -624,6 +626,7 @@ public class AssistStructure implements Parcelable { int mMinEms = -1; int mMaxEms = -1; int mMaxLength = -1; + @Nullable String mTextIdEntry; // POJO used to override some autofill-related values when the node is parcelized. // Not written to parcel. @@ -701,7 +704,7 @@ public class AssistStructure implements Parcelable { final int flags = mFlags; if ((flags&FLAGS_HAS_ID) != 0) { mId = in.readInt(); - if (mId != 0) { + if (mId != View.NO_ID) { mIdEntry = preader.readString(); if (mIdEntry != null) { mIdType = preader.readString(); @@ -724,6 +727,7 @@ public class AssistStructure implements Parcelable { mMinEms = in.readInt(); mMaxEms = in.readInt(); mMaxLength = in.readInt(); + mTextIdEntry = preader.readString(); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { mX = in.readInt(); @@ -857,7 +861,7 @@ public class AssistStructure implements Parcelable { out.writeInt(writtenFlags); if ((flags&FLAGS_HAS_ID) != 0) { out.writeInt(mId); - if (mId != 0) { + if (mId != View.NO_ID) { pwriter.writeString(mIdEntry); if (mIdEntry != null) { pwriter.writeString(mIdType); @@ -890,6 +894,7 @@ public class AssistStructure implements Parcelable { out.writeInt(mMinEms); out.writeInt(mMaxEms); out.writeInt(mMaxLength); + pwriter.writeString(mTextIdEntry); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { out.writeInt(mX); @@ -1430,6 +1435,17 @@ public class AssistStructure implements Parcelable { } /** + * Gets the identifier used to set the text associated with this view. + * + * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, + * not for assist purposes. + */ + @Nullable + public String getTextIdEntry() { + return mTextIdEntry; + } + + /** * Return additional hint text associated with the node; this is typically used with * a node that takes user input, describing to the user what the input means. */ @@ -1684,6 +1700,11 @@ public class AssistStructure implements Parcelable { } @Override + public void setTextIdEntry(@NonNull String entryName) { + mNode.mTextIdEntry = Preconditions.checkNotNull(entryName); + } + + @Override public void setHint(CharSequence hint) { getNodeText().mHint = hint != null ? hint.toString() : null; } @@ -2082,6 +2103,7 @@ public class AssistStructure implements Parcelable { Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor()) + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor())); Log.i(TAG, prefix + " Input type: " + node.getInputType()); + Log.i(TAG, prefix + " Resource id: " + node.getTextIdEntry()); } String webDomain = node.getWebDomain(); if (webDomain != null) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 137c1697d498..4cedeaa0ada2 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -324,6 +324,15 @@ public abstract class Context { public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080; /** + * @hide Flag for {@link #bindService}: allows binding to a service provided + * by an instant app. Note that the caller may not have access to the instant + * app providing the service which is a violation of the instant app sandbox. + * This flag is intended ONLY for development/testing and should be used with + * great care. Only the system is allowed to use this flag. + */ + public static final int BIND_ALLOW_INSTANT = 0x00400000; + + /** * @hide Flag for {@link #bindService}: like {@link #BIND_NOT_FOREGROUND}, but puts it * up in to the important background state (instead of transient). */ @@ -3012,7 +3021,8 @@ public abstract class Context { SYSTEM_HEALTH_SERVICE, //@hide: INCIDENT_SERVICE, //@hide: STATS_COMPANION_SERVICE, - COMPANION_DEVICE_SERVICE + COMPANION_DEVICE_SERVICE, + CROSS_PROFILE_APPS_SERVICE }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3091,6 +3101,14 @@ public abstract class Context { * service objects between various different contexts (Activities, Applications, * Services, Providers, etc.) * + * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true, + * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE}, + * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE}, + * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE}, + * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>. + * Generally, if you are running as an instant app you should always check whether the result + * of this method is null. + * * @param name The name of the desired service. * * @return The service or null if the name does not exist. @@ -3174,6 +3192,14 @@ public abstract class Context { * Services, Providers, etc.) * </p> * + * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true, + * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE}, + * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE}, + * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE}, + * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>. + * Generally, if you are running as an instant app you should always check whether the result + * of this method is null. + * * @param serviceClass The class of the desired service. * @return The service or null if the class is not a supported system service. */ diff --git a/core/java/android/content/pm/PackageList.java b/core/java/android/content/pm/PackageList.java new file mode 100644 index 000000000000..cfd99abc6283 --- /dev/null +++ b/core/java/android/content/pm/PackageList.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManagerInternal.PackageListObserver; + +import com.android.server.LocalServices; + +import java.util.List; + +/** + * All of the package name installed on the system. + * <p>A self observable list that automatically removes the listener when it goes out of scope. + * + * @hide Only for use within the system server. + */ +public class PackageList implements PackageListObserver, AutoCloseable { + private final PackageListObserver mWrappedObserver; + private final List<String> mPackageNames; + + /** + * Create a new object. + * <p>Ownership of the given {@link List} transfers to this object and should not + * be modified by the caller. + */ + public PackageList(@NonNull List<String> packageNames, @Nullable PackageListObserver observer) { + mPackageNames = packageNames; + mWrappedObserver = observer; + } + + @Override + public void onPackageAdded(String packageName) { + if (mWrappedObserver != null) { + mWrappedObserver.onPackageAdded(packageName); + } + } + + @Override + public void onPackageRemoved(String packageName) { + if (mWrappedObserver != null) { + mWrappedObserver.onPackageRemoved(packageName); + } + } + + @Override + public void close() throws Exception { + LocalServices.getService(PackageManagerInternal.class).removePackageListObserver(this); + } + + /** + * Returns the names of packages installed on the system. + * <p>The list is a copy-in-time and the actual set of installed packages may differ. Real + * time updates to the package list are sent via the {@link PackageListObserver} callback. + */ + public @NonNull List<String> getPackageNames() { + return mPackageNames; + } +} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index ff02c4030941..2d7263293f08 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2075,6 +2075,13 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports cell-broadcast reception using the MBMS APIs. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports connecting to USB devices * as the USB host. diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 713cd109ef87..8ee8e102d9b5 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -53,6 +53,14 @@ public abstract class PackageManagerInternal { @Retention(RetentionPolicy.SOURCE) public @interface KnownPackage {} + /** Observer called whenever the list of packages changes */ + public interface PackageListObserver { + /** A package was added to the system. */ + void onPackageAdded(@NonNull String packageName); + /** A package was removed from the system. */ + void onPackageRemoved(@NonNull String packageName); + } + /** * Provider for package names. */ @@ -435,6 +443,35 @@ public abstract class PackageManagerInternal { public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName); /** + * Returns a list without a change observer. + * + * {@see #getPackageList(PackageListObserver)} + */ + public @NonNull PackageList getPackageList() { + return getPackageList(null); + } + + /** + * Returns the list of packages installed at the time of the method call. + * <p>The given observer is notified when the list of installed packages + * changes [eg. a package was installed or uninstalled]. It will not be + * notified if a package is updated. + * <p>The package list will not be updated automatically as packages are + * installed / uninstalled. Any changes must be handled within the observer. + */ + public abstract @NonNull PackageList getPackageList(@Nullable PackageListObserver observer); + + /** + * Removes the observer. + * <p>Generally not needed. {@link #getPackageList(PackageListObserver)} will automatically + * remove the observer. + * <p>Does nothing if the observer isn't currently registered. + * <p>Observers are notified asynchronously and it's possible for an observer to be + * invoked after its been removed. + */ + public abstract void removePackageListObserver(@NonNull PackageListObserver observer); + + /** * Returns a package object for the disabled system package name. */ public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName); diff --git a/core/java/android/content/pm/crossprofile/CrossProfileApps.java b/core/java/android/content/pm/crossprofile/CrossProfileApps.java index c9f184a7da53..414c13894f80 100644 --- a/core/java/android/content/pm/crossprofile/CrossProfileApps.java +++ b/core/java/android/content/pm/crossprofile/CrossProfileApps.java @@ -16,7 +16,6 @@ package android.content.pm.crossprofile; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -61,15 +60,10 @@ public class CrossProfileApps { * @param user The UserHandle of the profile, must be one of the users returned by * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will * be thrown. - * @param sourceBounds The Rect containing the source bounds of the clicked icon, see - * {@link android.content.Intent#setSourceBounds(Rect)}. - * @param startActivityOptions Options to pass to startActivity */ - public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user, - @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) { + public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user) { try { - mService.startActivityAsUser(mContext.getPackageName(), - component, sourceBounds, startActivityOptions, user); + mService.startActivityAsUser(mContext.getPackageName(), component, user); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl index dd8d04f6cf0e..227f91f5d3e1 100644 --- a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl @@ -26,6 +26,7 @@ import android.os.UserHandle; * @hide */ interface ICrossProfileApps { - void startActivityAsUser(in String callingPackage, in ComponentName component, in Rect sourceBounds, in Bundle startActivityOptions, in UserHandle user); + void startActivityAsUser(in String callingPackage, in ComponentName component, + in UserHandle user); List<UserHandle> getTargetUserProfiles(in String callingPackage); }
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 3a3048ef1de2..57ab18e20214 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; +import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.utils.TypeReference; import android.util.Rational; @@ -169,6 +170,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri private final CameraMetadataNative mProperties; private List<CameraCharacteristics.Key<?>> mKeys; private List<CaptureRequest.Key<?>> mAvailableRequestKeys; + private List<CaptureRequest.Key<?>> mAvailableSessionKeys; private List<CaptureResult.Key<?>> mAvailableResultKeys; /** @@ -251,6 +253,67 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } /** + * <p>Returns a subset of {@link #getAvailableCaptureRequestKeys} keys that the + * camera device can pass as part of the capture session initialization.</p> + * + * <p>This list includes keys that are difficult to apply per-frame and + * can result in unexpected delays when modified during the capture session + * lifetime. Typical examples include parameters that require a + * time-consuming hardware re-configuration or internal camera pipeline + * change. For performance reasons we suggest clients to pass their initial + * values as part of {@link SessionConfiguration#setSessionParameters}. Once + * the camera capture session is enabled it is also recommended to avoid + * changing them from their initial values set in + * {@link SessionConfiguration#setSessionParameters }. + * Control over session parameters can still be exerted in capture requests + * but clients should be aware and expect delays during their application. + * An example usage scenario could look like this:</p> + * <ul> + * <li>The camera client starts by quering the session parameter key list via + * {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li> + * <li>Before triggering the capture session create sequence, a capture request + * must be built via {@link CameraDevice#createCaptureRequest } using an + * appropriate template matching the particular use case.</li> + * <li>The client should go over the list of session parameters and check + * whether some of the keys listed matches with the parameters that + * they intend to modify as part of the first capture request.</li> + * <li>If there is no such match, the capture request can be passed + * unmodified to {@link SessionConfiguration#setSessionParameters }.</li> + * <li>If matches do exist, the client should update the respective values + * and pass the request to {@link SessionConfiguration#setSessionParameters }.</li> + * <li>After the capture session initialization completes the session parameter + * key list can continue to serve as reference when posting or updating + * further requests. As mentioned above further changes to session + * parameters should ideally be avoided, if updates are necessary + * however clients could expect a delay/glitch during the + * parameter switch.</li> + * </ul> + * + * <p>The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.</p> + * + * <p>Each key is only listed once in the list. The order of the keys is undefined.</p> + * + * @return List of keys that can be passed during capture session initialization. In case the + * camera device doesn't support such keys the list can be null. + */ + @SuppressWarnings({"unchecked"}) + public List<CaptureRequest.Key<?>> getAvailableSessionKeys() { + if (mAvailableSessionKeys == null) { + Object crKey = CaptureRequest.Key.class; + Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey; + + int[] filterTags = get(REQUEST_AVAILABLE_SESSION_KEYS); + if (filterTags == null) { + return null; + } + mAvailableSessionKeys = + getAvailableKeyList(CaptureRequest.class, crKeyTyped, filterTags); + } + return mAvailableSessionKeys; + } + + /** * Returns the list of keys supported by this {@link CameraDevice} for querying * with a {@link CaptureRequest}. * @@ -1571,6 +1634,48 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.request.availableCharacteristicsKeys", int[].class); /** + * <p>A subset of the available request keys that the camera device + * can pass as part of the capture session initialization.</p> + * <p>This is a subset of android.request.availableRequestKeys which + * contains a list of keys that are difficult to apply per-frame and + * can result in unexpected delays when modified during the capture session + * lifetime. Typical examples include parameters that require a + * time-consuming hardware re-configuration or internal camera pipeline + * change. For performance reasons we advise clients to pass their initial + * values as part of {@link SessionConfiguration#setSessionParameters }. Once + * the camera capture session is enabled it is also recommended to avoid + * changing them from their initial values set in + * {@link SessionConfiguration#setSessionParameters }. + * Control over session parameters can still be exerted in capture requests + * but clients should be aware and expect delays during their application. + * An example usage scenario could look like this:</p> + * <ul> + * <li>The camera client starts by quering the session parameter key list via + * {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li> + * <li>Before triggering the capture session create sequence, a capture request + * must be built via {@link CameraDevice#createCaptureRequest } using an + * appropriate template matching the particular use case.</li> + * <li>The client should go over the list of session parameters and check + * whether some of the keys listed matches with the parameters that + * they intend to modify as part of the first capture request.</li> + * <li>If there is no such match, the capture request can be passed + * unmodified to {@link SessionConfiguration#setSessionParameters }.</li> + * <li>If matches do exist, the client should update the respective values + * and pass the request to {@link SessionConfiguration#setSessionParameters }.</li> + * <li>After the capture session initialization completes the session parameter + * key list can continue to serve as reference when posting or updating + * further requests. As mentioned above further changes to session + * parameters should ideally be avoided, if updates are necessary + * however clients could expect a delay/glitch during the + * parameter switch.</li> + * </ul> + * <p>This key is available on all devices.</p> + * @hide + */ + public static final Key<int[]> REQUEST_AVAILABLE_SESSION_KEYS = + new Key<int[]>("android.request.availableSessionKeys", int[].class); + + /** * <p>The list of image formats that are supported by this * camera device for output streams.</p> * <p>All camera devices will support JPEG and YUV_420_888 formats.</p> diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 55343a2904f2..87e503def4e3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -26,6 +26,7 @@ import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_ import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.SessionConfiguration; import android.os.Handler; import android.view.Surface; @@ -811,6 +812,26 @@ public abstract class CameraDevice implements AutoCloseable { throws CameraAccessException; /** + * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper + * object that aggregates all supported parameters.</p> + * + * @param config A session configuration (see {@link SessionConfiguration}). + * + * @throws IllegalArgumentException In case the session configuration is invalid; or the output + * configurations are empty. + * @throws CameraAccessException In case the camera device is no longer connected or has + * encountered a fatal error. + * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler) + * @see #createCaptureSessionByOutputConfigurations + * @see #createReprocessableCaptureSession + * @see #createConstrainedHighSpeedCaptureSession + */ + public void createCaptureSession( + SessionConfiguration config) throws CameraAccessException { + throw new UnsupportedOperationException("No default implementation"); + } + + /** * <p>Create a {@link CaptureRequest.Builder} for new capture requests, * initialized with template for a target use case. The settings are chosen * to be the best options for the specific camera device, so it is not diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 0262ecb54f0d..77da2a51a016 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -21,15 +21,18 @@ import android.annotation.Nullable; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; +import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.TypeReference; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArraySet; +import android.util.Log; +import android.util.SparseArray; import android.view.Surface; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -198,7 +201,24 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } } - private final HashSet<Surface> mSurfaceSet; + private final String TAG = "CaptureRequest-JV"; + + private final ArraySet<Surface> mSurfaceSet = new ArraySet<Surface>(); + + // Speed up sending CaptureRequest across IPC: + // mSurfaceConverted should only be set to true during capture request + // submission by {@link #convertSurfaceToStreamId}. The method will convert + // surfaces to stream/surface indexes based on passed in stream configuration at that time. + // This will save significant unparcel time for remote camera device. + // Once the request is submitted, camera device will call {@link #recoverStreamIdToSurface} + // to reset the capture request back to its original state. + private final Object mSurfacesLock = new Object(); + private boolean mSurfaceConverted = false; + private int[] mStreamIdxArray; + private int[] mSurfaceIdxArray; + + private static final ArraySet<Surface> mEmptySurfaceSet = new ArraySet<Surface>(); + private final CameraMetadataNative mSettings; private boolean mIsReprocess; // If this request is part of constrained high speed request list that was created by @@ -218,7 +238,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest() { mSettings = new CameraMetadataNative(); setNativeInstance(mSettings); - mSurfaceSet = new HashSet<Surface>(); mIsReprocess = false; mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE; } @@ -232,7 +251,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest(CaptureRequest source) { mSettings = new CameraMetadataNative(source.mSettings); setNativeInstance(mSettings); - mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone(); + mSurfaceSet.addAll(source.mSurfaceSet); mIsReprocess = source.mIsReprocess; mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList; mReprocessableSessionId = source.mReprocessableSessionId; @@ -263,7 +282,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> int reprocessableSessionId) { mSettings = CameraMetadataNative.move(settings); setNativeInstance(mSettings); - mSurfaceSet = new HashSet<Surface>(); mIsReprocess = isReprocess; if (isReprocess) { if (reprocessableSessionId == CameraCaptureSession.SESSION_ID_NONE) { @@ -463,22 +481,25 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private void readFromParcel(Parcel in) { mSettings.readFromParcel(in); setNativeInstance(mSettings); - - mSurfaceSet.clear(); - - Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader()); - - if (parcelableArray == null) { - return; - } - - for (Parcelable p : parcelableArray) { - Surface s = (Surface) p; - mSurfaceSet.add(s); - } - mIsReprocess = (in.readInt() == 0) ? false : true; mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE; + + synchronized (mSurfacesLock) { + mSurfaceSet.clear(); + Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader()); + if (parcelableArray != null) { + for (Parcelable p : parcelableArray) { + Surface s = (Surface) p; + mSurfaceSet.add(s); + } + } + // Intentionally disallow java side readFromParcel to receive streamIdx/surfaceIdx + // Since there is no good way to convert indexes back to Surface + int streamSurfaceSize = in.readInt(); + if (streamSurfaceSize != 0) { + throw new RuntimeException("Reading cached CaptureRequest is not supported"); + } + } } @Override @@ -489,8 +510,21 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> @Override public void writeToParcel(Parcel dest, int flags) { mSettings.writeToParcel(dest, flags); - dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags); dest.writeInt(mIsReprocess ? 1 : 0); + + synchronized (mSurfacesLock) { + final ArraySet<Surface> surfaces = mSurfaceConverted ? mEmptySurfaceSet : mSurfaceSet; + dest.writeParcelableArray(surfaces.toArray(new Surface[surfaces.size()]), flags); + if (mSurfaceConverted) { + dest.writeInt(mStreamIdxArray.length); + for (int i = 0; i < mStreamIdxArray.length; i++) { + dest.writeInt(mStreamIdxArray[i]); + dest.writeInt(mSurfaceIdxArray[i]); + } + } else { + dest.writeInt(0); + } + } } /** @@ -508,6 +542,67 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } /** + * @hide + */ + public void convertSurfaceToStreamId( + final SparseArray<OutputConfiguration> configuredOutputs) { + synchronized (mSurfacesLock) { + if (mSurfaceConverted) { + Log.v(TAG, "Cannot convert already converted surfaces!"); + return; + } + + mStreamIdxArray = new int[mSurfaceSet.size()]; + mSurfaceIdxArray = new int[mSurfaceSet.size()]; + int i = 0; + for (Surface s : mSurfaceSet) { + boolean streamFound = false; + for (int j = 0; j < configuredOutputs.size(); ++j) { + int streamId = configuredOutputs.keyAt(j); + OutputConfiguration outConfig = configuredOutputs.valueAt(j); + int surfaceId = 0; + for (Surface outSurface : outConfig.getSurfaces()) { + if (s == outSurface) { + streamFound = true; + mStreamIdxArray[i] = streamId; + mSurfaceIdxArray[i] = surfaceId; + i++; + break; + } + surfaceId++; + } + if (streamFound) { + break; + } + } + if (!streamFound) { + mStreamIdxArray = null; + mSurfaceIdxArray = null; + throw new IllegalArgumentException( + "CaptureRequest contains unconfigured Input/Output Surface!"); + } + } + mSurfaceConverted = true; + } + } + + /** + * @hide + */ + public void recoverStreamIdToSurface() { + synchronized (mSurfacesLock) { + if (!mSurfaceConverted) { + Log.v(TAG, "Cannot convert already converted surfaces!"); + return; + } + + mStreamIdxArray = null; + mSurfaceIdxArray = null; + mSurfaceConverted = false; + } + } + + /** * A builder for capture requests. * * <p>To obtain a builder instance, use the diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 374789c6cf05..8b8bbc34f7d2 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -800,7 +800,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession try { // begin transition to unconfigured mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null, - /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE); + /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE, + /*sessionParams*/ null); } catch (CameraAccessException e) { // OK: do not throw checked exceptions. Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 6787d84b57d3..f1ffb890eecd 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -32,6 +32,7 @@ import android.hardware.camera2.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; @@ -362,7 +363,7 @@ public class CameraDeviceImpl extends CameraDevice outputConfigs.add(new OutputConfiguration(s)); } configureStreamsChecked(/*inputConfig*/null, outputConfigs, - /*operatingMode*/ICameraDeviceUser.NORMAL_MODE); + /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } @@ -382,12 +383,13 @@ public class CameraDeviceImpl extends CameraDevice * @param outputs a list of one or more surfaces, or {@code null} to unconfigure * @param operatingMode If the stream configuration is for a normal session, * a constrained high speed session, or something else. + * @param sessionParams Session parameters. * @return whether or not the configuration was successful * * @throws CameraAccessException if there were any unexpected problems during configuration */ public boolean configureStreamsChecked(InputConfiguration inputConfig, - List<OutputConfiguration> outputs, int operatingMode) + List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams) throws CameraAccessException { // Treat a null input the same an empty list if (outputs == null) { @@ -463,7 +465,11 @@ public class CameraDeviceImpl extends CameraDevice } } - mRemoteDevice.endConfigure(operatingMode); + if (sessionParams != null) { + mRemoteDevice.endConfigure(operatingMode, sessionParams.getNativeCopy()); + } else { + mRemoteDevice.endConfigure(operatingMode, null); + } success = true; } catch (IllegalArgumentException e) { @@ -499,7 +505,7 @@ public class CameraDeviceImpl extends CameraDevice outConfigurations.add(new OutputConfiguration(surface)); } createCaptureSessionInternal(null, outConfigurations, callback, handler, - /*operatingMode*/ICameraDeviceUser.NORMAL_MODE); + /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } @Override @@ -515,7 +521,7 @@ public class CameraDeviceImpl extends CameraDevice List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations); createCaptureSessionInternal(null, currentOutputs, callback, handler, - /*operatingMode*/ICameraDeviceUser.NORMAL_MODE); + /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null); } @Override @@ -535,7 +541,7 @@ public class CameraDeviceImpl extends CameraDevice outConfigurations.add(new OutputConfiguration(surface)); } createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler, - /*operatingMode*/ICameraDeviceUser.NORMAL_MODE); + /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null); } @Override @@ -563,7 +569,8 @@ public class CameraDeviceImpl extends CameraDevice currentOutputs.add(new OutputConfiguration(output)); } createCaptureSessionInternal(inputConfig, currentOutputs, - callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE); + callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, + /*sessionParams*/ null); } @Override @@ -574,16 +581,13 @@ public class CameraDeviceImpl extends CameraDevice throw new IllegalArgumentException( "Output surface list must not be null and the size must be no more than 2"); } - StreamConfigurationMap config = - getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config); - List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); for (Surface surface : outputs) { outConfigurations.add(new OutputConfiguration(surface)); } createCaptureSessionInternal(null, outConfigurations, callback, handler, - /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE); + /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE, + /*sessionParams*/ null); } @Override @@ -596,13 +600,30 @@ public class CameraDeviceImpl extends CameraDevice for (OutputConfiguration output : outputs) { currentOutputs.add(new OutputConfiguration(output)); } - createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode); + createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode, + /*sessionParams*/ null); + } + + @Override + public void createCaptureSession(SessionConfiguration config) + throws CameraAccessException { + if (config == null) { + throw new IllegalArgumentException("Invalid session configuration"); + } + + List<OutputConfiguration> outputConfigs = config.getOutputConfigurations(); + if (outputConfigs == null) { + throw new IllegalArgumentException("Invalid output configurations"); + } + createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs, + config.getStateCallback(), config.getHandler(), config.getSessionType(), + config.getSessionParameters()); } private void createCaptureSessionInternal(InputConfiguration inputConfig, List<OutputConfiguration> outputConfigurations, CameraCaptureSession.StateCallback callback, Handler handler, - int operatingMode) throws CameraAccessException { + int operatingMode, CaptureRequest sessionParams) throws CameraAccessException { synchronized(mInterfaceLock) { if (DEBUG) { Log.d(TAG, "createCaptureSessionInternal"); @@ -630,7 +651,7 @@ public class CameraDeviceImpl extends CameraDevice try { // configure streams and then block until IDLE configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations, - operatingMode); + operatingMode, sessionParams); if (configureSuccess == true && inputConfig != null) { input = mRemoteDevice.getInputSurface(); } @@ -646,6 +667,14 @@ public class CameraDeviceImpl extends CameraDevice // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. CameraCaptureSessionCore newSession = null; if (isConstrainedHighSpeed) { + ArrayList<Surface> surfaces = new ArrayList<>(outputConfigurations.size()); + for (OutputConfiguration outConfig : outputConfigurations) { + surfaces.add(outConfig.getSurface()); + } + StreamConfigurationMap config = + getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config); + newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++, callback, handler, this, mDeviceHandler, configureSuccess, mCharacteristics); @@ -779,6 +808,7 @@ public class CameraDeviceImpl extends CameraDevice } mRemoteDevice.updateOutputConfiguration(streamId, config); + mConfiguredOutputs.put(streamId, config); } } @@ -828,6 +858,7 @@ public class CameraDeviceImpl extends CameraDevice + " must have at least 1 surface"); } mRemoteDevice.finalizeOutputConfigurations(streamId, config); + mConfiguredOutputs.put(streamId, config); } } } @@ -950,11 +981,20 @@ public class CameraDeviceImpl extends CameraDevice SubmitInfo requestInfo; CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]); + // Convert Surface to streamIdx and surfaceIdx + for (CaptureRequest request : requestArray) { + request.convertSurfaceToStreamId(mConfiguredOutputs); + } + requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating); if (DEBUG) { Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber()); } + for (CaptureRequest request : requestArray) { + request.recoverStreamIdToSurface(); + } + if (callback != null) { mCaptureCallbackMap.put(requestInfo.getRequestId(), new CaptureCallbackHolder( diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index 0978ff87b38b..1f4ed13ee09e 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -106,9 +106,11 @@ public class ICameraDeviceUserWrapper { } } - public void endConfigure(int operatingMode) throws CameraAccessException { + public void endConfigure(int operatingMode, CameraMetadataNative sessionParams) + throws CameraAccessException { try { - mRemoteDevice.endConfigure(operatingMode); + mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ? + new CameraMetadataNative() : sessionParams); } catch (Throwable t) { CameraManager.throwAsPublicException(t); throw new UnsupportedOperationException("Unexpected exception", t); diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 119cca8d4715..eccab7500abf 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -498,7 +498,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override - public void endConfigure(int operatingMode) { + public void endConfigure(int operatingMode, CameraMetadataNative sessionParams) { if (DEBUG) { Log.d(TAG, "endConfigure called."); } diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java new file mode 100644 index 000000000000..a79a6c17f925 --- /dev/null +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.hardware.camera2.params; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.IntDef; +import android.os.Handler; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.InputConfiguration; +import android.hardware.camera2.params.OutputConfiguration; + +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import static com.android.internal.util.Preconditions.*; + +/** + * A helper class that aggregates all supported arguments for capture session initialization. + */ +public final class SessionConfiguration { + /** + * A regular session type containing instances of {@link OutputConfiguration} running + * at regular non high speed FPS ranges and optionally {@link InputConfiguration} for + * reprocessable sessions. + * + * @see CameraDevice#createCaptureSession + * @see CameraDevice#createReprocessableCaptureSession + */ + public static final int SESSION_REGULAR = CameraDevice.SESSION_OPERATION_MODE_NORMAL; + + /** + * A high speed session type that can only contain instances of {@link OutputConfiguration}. + * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration} + * are not supported. + * + * @see CameraDevice#createConstrainedHighSpeedCaptureSession + */ + public static final int SESSION_HIGH_SPEED = + CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED; + + /** + * First vendor-specific session mode + * @hide + */ + public static final int SESSION_VENDOR_START = + CameraDevice.SESSION_OPERATION_MODE_VENDOR_START; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SESSION_"}, value = + {SESSION_REGULAR, + SESSION_HIGH_SPEED }) + public @interface SessionMode {}; + + // Camera capture session related parameters. + private List<OutputConfiguration> mOutputConfigurations; + private CameraCaptureSession.StateCallback mStateCallback; + private int mSessionType; + private Handler mHandler = null; + private InputConfiguration mInputConfig = null; + private CaptureRequest mSessionParameters = null; + + /** + * Create a new {@link SessionConfiguration}. + * + * @param sessionType The session type. + * @param outputs A list of output configurations for the capture session. + * @param cb A state callback interface implementation. + * @param handler The handler on which the callback will be invoked. If it is + * set to null, the callback will be invoked on the current thread's + * {@link android.os.Looper looper}. + * + * @see #SESSION_REGULAR + * @see #SESSION_HIGH_SPEED + * @see CameraDevice#createCaptureSession(List, CameraCaptureSession.StateCallback, Handler) + * @see CameraDevice#createCaptureSessionByOutputConfigurations + * @see CameraDevice#createReprocessableCaptureSession + * @see CameraDevice#createConstrainedHighSpeedCaptureSession + */ + public SessionConfiguration(@SessionMode int sessionType, + @NonNull List<OutputConfiguration> outputs, + @NonNull CameraCaptureSession.StateCallback cb, @Nullable Handler handler) { + mSessionType = sessionType; + mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs)); + mStateCallback = cb; + mHandler = handler; + } + + /** + * Retrieve the type of the capture session. + * + * @return The capture session type. + */ + public @SessionMode int getSessionType() { + return mSessionType; + } + + /** + * Retrieve the {@link OutputConfiguration} list for the capture session. + * + * @return A list of output configurations for the capture session. + */ + public List<OutputConfiguration> getOutputConfigurations() { + return mOutputConfigurations; + } + + /** + * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session. + * + * @return A state callback interface implementation. + */ + public CameraCaptureSession.StateCallback getStateCallback() { + return mStateCallback; + } + + /** + * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session. + * + * @return The handler on which the callback will be invoked. If it is + * set to null, the callback will be invoked on the current thread's + * {@link android.os.Looper looper}. + */ + public Handler getHandler() { + return mHandler; + } + + /** + * Sets the {@link InputConfiguration} for a reprocessable session. Input configuration are not + * supported for {@link #SESSION_HIGH_SPEED}. + * + * @param input Input configuration. + * @throws UnsupportedOperationException In case it is called for {@link #SESSION_HIGH_SPEED} + * type session configuration. + */ + public void setInputConfiguration(@NonNull InputConfiguration input) { + if (mSessionType != SESSION_HIGH_SPEED) { + mInputConfig = input; + } else { + throw new UnsupportedOperationException("Method not supported for high speed session" + + " types"); + } + } + + /** + * Retrieve the {@link InputConfiguration}. + * + * @return The capture session input configuration. + */ + public InputConfiguration getInputConfiguration() { + return mInputConfig; + } + + /** + * Sets the session wide camera parameters (see {@link CaptureRequest}). This argument can + * be set for every supported session type and will be passed to the camera device as part + * of the capture session initialization. Session parameters are a subset of the available + * capture request parameters (see {@link CameraCharacteristics#getAvailableSessionKeys}) + * and their application can introduce internal camera delays. To improve camera performance + * it is suggested to change them sparingly within the lifetime of the capture session and + * to pass their initial values as part of this method. + * + * @param params A capture request that includes the initial values for any available + * session wide capture keys. + */ + public void setSessionParameters(CaptureRequest params) { + mSessionParameters = params; + } + + /** + * Retrieve the session wide camera parameters (see {@link CaptureRequest}). + * + * @return A capture request that includes the initial values for any available + * session wide capture keys. + */ + public CaptureRequest getSessionParameters() { + return mSessionParameters; + } +} diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java index 3003607e5f72..0a08353cbe4c 100644 --- a/core/java/android/hardware/display/BrightnessChangeEvent.java +++ b/core/java/android/hardware/display/BrightnessChangeEvent.java @@ -28,7 +28,7 @@ import android.os.Parcelable; */ public final class BrightnessChangeEvent implements Parcelable { /** Brightness in nits */ - public int brightness; + public float brightness; /** Timestamp of the change {@see System.currentTimeMillis()} */ public long timeStamp; @@ -58,7 +58,7 @@ public final class BrightnessChangeEvent implements Parcelable { public int colorTemperature; /** Brightness level before slider adjustment */ - public int lastBrightness; + public float lastBrightness; public BrightnessChangeEvent() { } @@ -78,7 +78,7 @@ public final class BrightnessChangeEvent implements Parcelable { } private BrightnessChangeEvent(Parcel source) { - brightness = source.readInt(); + brightness = source.readFloat(); timeStamp = source.readLong(); packageName = source.readString(); userId = source.readInt(); @@ -87,7 +87,7 @@ public final class BrightnessChangeEvent implements Parcelable { batteryLevel = source.readFloat(); nightMode = source.readBoolean(); colorTemperature = source.readInt(); - lastBrightness = source.readInt(); + lastBrightness = source.readFloat(); } public static final Creator<BrightnessChangeEvent> CREATOR = @@ -107,7 +107,7 @@ public final class BrightnessChangeEvent implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(brightness); + dest.writeFloat(brightness); dest.writeLong(timeStamp); dest.writeString(packageName); dest.writeInt(userId); @@ -116,6 +116,6 @@ public final class BrightnessChangeEvent implements Parcelable { dest.writeFloat(batteryLevel); dest.writeBoolean(nightMode); dest.writeInt(colorTemperature); - dest.writeInt(lastBrightness); + dest.writeFloat(lastBrightness); } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 7de667dcaa2b..97e9b9c2e2f4 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -628,13 +628,6 @@ public final class DisplayManager { } /** - * @hide STOPSHIP - remove when adaptive brightness accepts curves. - */ - public void setBrightness(int brightness) { - mGlobal.setBrightness(brightness); - } - - /** * Sets the global display brightness configuration. * * @hide diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index bf4cc1d826a9..cbb5a7de7db8 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -476,18 +476,6 @@ public final class DisplayManagerGlobal { } /** - * Set brightness but don't add a BrightnessChangeEvent - * STOPSHIP remove when adaptive brightness accepts curves. - */ - public void setBrightness(int brightness) { - try { - mDm.setBrightness(brightness); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** * Sets the global brightness configuration for a given user. * * @hide diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index cd551bd42e0f..3f6dd2e757ed 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -222,6 +222,11 @@ public abstract class DisplayManagerInternal { // set by the user as opposed to being programmatically controlled by apps. public boolean brightnessSetByUser; + // Set to true if screenBrightness or screenAutoBrightnessAdjustment are being set + // temporarily. This is typically set while the user has their finger on the brightness + // control, before they've selected the final brightness value. + public boolean brightnessIsTemporary; + // If true, enables automatic brightness control. public boolean useAutoBrightness; @@ -280,6 +285,7 @@ public abstract class DisplayManagerInternal { screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment; screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor; brightnessSetByUser = other.brightnessSetByUser; + brightnessIsTemporary = other.brightnessIsTemporary; useAutoBrightness = other.useAutoBrightness; blockScreenOn = other.blockScreenOn; lowPowerMode = other.lowPowerMode; @@ -303,6 +309,7 @@ public abstract class DisplayManagerInternal { && screenLowPowerBrightnessFactor == other.screenLowPowerBrightnessFactor && brightnessSetByUser == other.brightnessSetByUser + && brightnessIsTemporary == other.brightnessIsTemporary && useAutoBrightness == other.useAutoBrightness && blockScreenOn == other.blockScreenOn && lowPowerMode == other.lowPowerMode @@ -324,6 +331,7 @@ public abstract class DisplayManagerInternal { + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor + ", brightnessSetByUser=" + brightnessSetByUser + + ", brightnessIsTemporary=" + brightnessIsTemporary + ", useAutoBrightness=" + useAutoBrightness + ", blockScreenOn=" + blockScreenOn + ", lowPowerMode=" + lowPowerMode diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 8afae6ec9010..61c42e1ab491 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -87,10 +87,6 @@ interface IDisplayManager { // Requires BRIGHTNESS_SLIDER_USAGE permission. ParceledListSlice getBrightnessEvents(String callingPackage); - // STOPSHIP remove when adaptive brightness code is updated to accept curves. - // Requires BRIGHTNESS_SLIDER_USAGE permission. - void setBrightness(int brightness); - // Sets the global brightness configuration for a given user. Requires // CONFIGURE_DISPLAY_BRIGHTNESS, and INTERACT_ACROSS_USER if the user being configured is not // the same as the calling user. diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index 52527ed67ae4..0a21083a0262 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -15,9 +15,13 @@ */ package android.hardware.location; +import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.os.RemoteException; +import com.android.internal.util.Preconditions; + import dalvik.system.CloseGuard; import java.io.Closeable; @@ -31,16 +35,12 @@ import java.util.concurrent.atomic.AtomicBoolean; * * @hide */ +@SystemApi public class ContextHubClient implements Closeable { /* * The proxy to the client interface at the service. */ - private final IContextHubClient mClientProxy; - - /* - * The callback interface associated with this client. - */ - private final IContextHubClientCallback mCallbackInterface; + private IContextHubClient mClientProxy = null; /* * The Context Hub that this client is attached to. @@ -51,20 +51,33 @@ public class ContextHubClient implements Closeable { private final AtomicBoolean mIsClosed = new AtomicBoolean(false); - /* package */ ContextHubClient( - IContextHubClient clientProxy, IContextHubClientCallback callback, - ContextHubInfo hubInfo) { - mClientProxy = clientProxy; - mCallbackInterface = callback; + /* package */ ContextHubClient(ContextHubInfo hubInfo) { mAttachedHub = hubInfo; mCloseGuard.open("close"); } /** + * Sets the proxy interface of the client at the service. This method should always be called + * by the ContextHubManager after the client is registered at the service, and should only be + * called once. + * + * @param clientProxy the proxy of the client at the service + */ + /* package */ void setClientProxy(IContextHubClient clientProxy) { + Preconditions.checkNotNull(clientProxy, "IContextHubClient cannot be null"); + if (mClientProxy != null) { + throw new IllegalStateException("Cannot change client proxy multiple times"); + } + + mClientProxy = clientProxy; + } + + /** * Returns the hub that this client is attached to. * * @return the ContextHubInfo of the attached hub */ + @NonNull public ContextHubInfo getAttachedHub() { return mAttachedHub; } @@ -96,12 +109,16 @@ public class ContextHubClient implements Closeable { * * @return the result of sending the message defined as in ContextHubTransaction.Result * + * @throws NullPointerException if NanoAppMessage is null + * * @see NanoAppMessage * @see ContextHubTransaction.Result */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @ContextHubTransaction.Result - public int sendMessageToNanoApp(NanoAppMessage message) { + public int sendMessageToNanoApp(@NonNull NanoAppMessage message) { + Preconditions.checkNotNull(message, "NanoAppMessage cannot be null"); + try { return mClientProxy.sendMessageToNanoApp(message); } catch (RemoteException e) { diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java index ab19d547025d..cc2fe65dcb7e 100644 --- a/core/java/android/hardware/location/ContextHubClientCallback.java +++ b/core/java/android/hardware/location/ContextHubClientCallback.java @@ -15,15 +15,20 @@ */ package android.hardware.location; +import android.annotation.SystemApi; + +import java.util.concurrent.Executor; + /** * A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to * receive messages and life-cycle events from nanoapps in the Context Hub at which the client is * attached to. * - * This callback is registered through the - * {@link android.hardware.location.ContextHubManager#createClient() creation} of - * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are - * invoked in the following ways: + * This callback is registered through the {@link + * android.hardware.location.ContextHubManager#createClient( + * ContextHubInfo, ContextHubClientCallback, Executor) creation} of + * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are invoked in + * the following ways: * 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted * or targeted to a specific client. * 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and @@ -31,6 +36,7 @@ package android.hardware.location; * * @hide */ +@SystemApi public class ContextHubClientCallback { /** * Callback invoked when receiving a message from a nanoapp. @@ -38,48 +44,56 @@ public class ContextHubClientCallback { * The message contents of this callback may either be broadcasted or targeted to the * client receiving the invocation. * + * @param client the client that is associated with this callback * @param message the message sent by the nanoapp */ - public void onMessageFromNanoApp(NanoAppMessage message) {} + public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {} /** * Callback invoked when the attached Context Hub has reset. + * + * @param client the client that is associated with this callback */ - public void onHubReset() {} + public void onHubReset(ContextHubClient client) {} /** * Callback invoked when a nanoapp aborts at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had aborted * @param abortCode the reason for nanoapp's abort, specific to each nanoapp */ - public void onNanoAppAborted(long nanoAppId, int abortCode) {} + public void onNanoAppAborted(ContextHubClient client, long nanoAppId, int abortCode) {} /** * Callback invoked when a nanoapp is loaded at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been loaded */ - public void onNanoAppLoaded(long nanoAppId) {} + public void onNanoAppLoaded(ContextHubClient client, long nanoAppId) {} /** * Callback invoked when a nanoapp is unloaded from the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been unloaded */ - public void onNanoAppUnloaded(long nanoAppId) {} + public void onNanoAppUnloaded(ContextHubClient client, long nanoAppId) {} /** * Callback invoked when a nanoapp is enabled at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been enabled */ - public void onNanoAppEnabled(long nanoAppId) {} + public void onNanoAppEnabled(ContextHubClient client, long nanoAppId) {} /** * Callback invoked when a nanoapp is disabled at the attached Context Hub. * + * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been disabled */ - public void onNanoAppDisabled(long nanoAppId) {} + public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {} } diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java index c2b280016f68..36123e3d4229 100644 --- a/core/java/android/hardware/location/ContextHubInfo.java +++ b/core/java/android/hardware/location/ContextHubInfo.java @@ -221,9 +221,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE platform ID as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public long getChrePlatformId() { return mChrePlatformId; @@ -231,9 +228,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE API's major version as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public byte getChreApiMajorVersion() { return mChreApiMajorVersion; @@ -241,9 +235,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE API's minor version as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public byte getChreApiMinorVersion() { return mChreApiMinorVersion; @@ -251,9 +242,6 @@ public class ContextHubInfo implements Parcelable { /** * @return the CHRE patch version as defined in chre/version.h - * - * TODO(b/67734082): Expose as public API - * @hide */ public short getChrePatchVersion() { return mChrePatchVersion; diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index b7ce875ad1e1..be1efdea0260 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -15,6 +15,8 @@ */ package android.hardware.location; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; @@ -22,13 +24,17 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.util.List; +import java.util.concurrent.Executor; /** * A class that exposes the Context hubs on a device to applications. @@ -56,7 +62,11 @@ public final class ContextHubManager { /** * An interface to receive asynchronous communication from the context hub. + * + * @deprecated Use the more refined {@link android.hardware.location.ContextHubClientCallback} + * instead for notification callbacks. */ + @Deprecated public abstract static class Callback { protected Callback() {} @@ -72,7 +82,7 @@ public final class ContextHubManager { public abstract void onMessageReceipt( int hubHandle, int nanoAppHandle, - ContextHubMessage message); + @NonNull ContextHubMessage message); } /** @@ -95,8 +105,13 @@ public final class ContextHubManager { /** * Get a handle to all the context hubs in the system + * * @return array of context hub handles + * + * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the + * new APIs. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public int[] getContextHubHandles() { try { @@ -113,7 +128,11 @@ public final class ContextHubManager { * @return ContextHubInfo Information about the requested context hub. * * @see ContextHubInfo + * + * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the + * new APIs. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public ContextHubInfo getContextHubInfo(int hubHandle) { try { @@ -141,9 +160,12 @@ public final class ContextHubManager { * -1 otherwise * * @see NanoApp + * + * @deprecated Use {@link #loadNanoApp(ContextHubInfo, NanoAppBinary)} instead. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public int loadNanoApp(int hubHandle, NanoApp app) { + public int loadNanoApp(int hubHandle, @NonNull NanoApp app) { try { return mService.loadNanoApp(hubHandle, app); } catch (RemoteException e) { @@ -165,7 +187,10 @@ public final class ContextHubManager { * * @return 0 if the command for unloading was sent to the context hub; * -1 otherwise + * + * @deprecated Use {@link #unloadNanoApp(ContextHubInfo, long)} instead. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public int unloadNanoApp(int nanoAppHandle) { try { @@ -196,13 +221,18 @@ public final class ContextHubManager { * TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the * correct information. * - * @param nanoAppHandle handle of the nanoAppInstance - * @return NanoAppInstanceInfo Information about the nano app instance. + * @param nanoAppHandle handle of the nanoapp instance + * @return NanoAppInstanceInfo the NanoAppInstanceInfo of the nanoapp, or null if the nanoapp + * does not exist * * @see NanoAppInstanceInfo + * + * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub + * for loaded nanoapps. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) { + @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) { try { return mService.getNanoAppInstanceInfo(nanoAppHandle); } catch (RemoteException e) { @@ -219,9 +249,13 @@ public final class ContextHubManager { * @see NanoAppFilter * * @return int[] Array of handles to any found nano apps + * + * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub + * for loaded nanoapps. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) { + @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) { try { return mService.findNanoAppOnHub(hubHandle, filter); } catch (RemoteException e) { @@ -247,9 +281,16 @@ public final class ContextHubManager { * @see ContextHubMessage * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp( + * NanoAppMessage)} instead, after creating a + * {@link android.hardware.location.ContextHubClient} with + * {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} + * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)}. */ + @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage message) { + public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) { try { return mService.sendMessage(hubHandle, nanoAppHandle, message); } catch (RemoteException e) { @@ -263,11 +304,9 @@ public final class ContextHubManager { * @return the list of ContextHubInfo objects * * @see ContextHubInfo - * - * @hide */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public List<ContextHubInfo> getContextHubs() { + @NonNull public List<ContextHubInfo> getContextHubs() { try { return mService.getContextHubs(); } catch (RemoteException e) { @@ -339,13 +378,16 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @see NanoAppBinary + * @throws NullPointerException if hubInfo or NanoAppBinary is null * - * @hide + * @see NanoAppBinary */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> loadNanoApp( - ContextHubInfo hubInfo, NanoAppBinary appBinary) { + @NonNull public ContextHubTransaction<Void> loadNanoApp( + @NonNull ContextHubInfo hubInfo, @NonNull NanoAppBinary appBinary) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + Preconditions.checkNotNull(appBinary, "NanoAppBinary cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -367,10 +409,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + @NonNull public ContextHubTransaction<Void> unloadNanoApp( + @NonNull ContextHubInfo hubInfo, long nanoAppId) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -392,10 +437,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + @NonNull public ContextHubTransaction<Void> enableNanoApp( + @NonNull ContextHubInfo hubInfo, long nanoAppId) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -417,10 +465,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + @NonNull public ContextHubTransaction<Void> disableNanoApp( + @NonNull ContextHubInfo hubInfo, long nanoAppId) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<Void> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP); IContextHubTransactionCallback callback = createTransactionCallback(transaction); @@ -441,10 +492,13 @@ public final class ContextHubManager { * * @return the ContextHubTransaction of the request * - * @hide + * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) { + @NonNull public ContextHubTransaction<List<NanoAppState>> queryNanoApps( + @NonNull ContextHubInfo hubInfo) { + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + ContextHubTransaction<List<NanoAppState>> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_QUERY_NANOAPPS); IContextHubTransactionCallback callback = createQueryCallback(transaction); @@ -466,9 +520,14 @@ public final class ContextHubManager { * @see Callback * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} + * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to + * register a {@link android.hardware.location.ContextHubClientCallback}. */ + @Deprecated @SuppressLint("Doclava125") - public int registerCallback(Callback callback) { + public int registerCallback(@NonNull Callback callback) { return registerCallback(callback, null); } @@ -495,7 +554,12 @@ public final class ContextHubManager { * @see Callback * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} + * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to + * register a {@link android.hardware.location.ContextHubClientCallback}. */ + @Deprecated @SuppressLint("Doclava125") public int registerCallback(Callback callback, Handler handler) { synchronized(this) { @@ -512,47 +576,48 @@ public final class ContextHubManager { /** * Creates an interface to the ContextHubClient to send down to the service. * + * @param client the ContextHubClient object associated with this callback * @param callback the callback to invoke at the client process - * @param handler the handler to post callbacks for this client + * @param executor the executor to invoke callbacks for this client * * @return the callback interface */ private IContextHubClientCallback createClientCallback( - ContextHubClientCallback callback, Handler handler) { + ContextHubClient client, ContextHubClientCallback callback, Executor executor) { return new IContextHubClientCallback.Stub() { @Override public void onMessageFromNanoApp(NanoAppMessage message) { - handler.post(() -> callback.onMessageFromNanoApp(message)); + executor.execute(() -> callback.onMessageFromNanoApp(client, message)); } @Override public void onHubReset() { - handler.post(() -> callback.onHubReset()); + executor.execute(() -> callback.onHubReset(client)); } @Override public void onNanoAppAborted(long nanoAppId, int abortCode) { - handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode)); + executor.execute(() -> callback.onNanoAppAborted(client, nanoAppId, abortCode)); } @Override public void onNanoAppLoaded(long nanoAppId) { - handler.post(() -> callback.onNanoAppLoaded(nanoAppId)); + executor.execute(() -> callback.onNanoAppLoaded(client, nanoAppId)); } @Override public void onNanoAppUnloaded(long nanoAppId) { - handler.post(() -> callback.onNanoAppUnloaded(nanoAppId)); + executor.execute(() -> callback.onNanoAppUnloaded(client, nanoAppId)); } @Override public void onNanoAppEnabled(long nanoAppId) { - handler.post(() -> callback.onNanoAppEnabled(nanoAppId)); + executor.execute(() -> callback.onNanoAppEnabled(client, nanoAppId)); } @Override public void onNanoAppDisabled(long nanoAppId) { - handler.post(() -> callback.onNanoAppDisabled(nanoAppId)); + executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId)); } }; } @@ -564,38 +629,56 @@ public final class ContextHubManager { * registration succeeds, the client can send messages to nanoapps through the returned * {@link ContextHubClient} object, and receive notifications through the provided callback. * - * @param callback the notification callback to register * @param hubInfo the hub to attach this client to - * @param handler the handler to invoke the callback, if null uses the main thread's Looper + * @param callback the notification callback to register + * @param executor the executor to invoke the callback * @return the registered client object * * @throws IllegalArgumentException if hubInfo does not represent a valid hub * @throws IllegalStateException if there were too many registered clients at the service - * @throws NullPointerException if callback or hubInfo is null + * @throws NullPointerException if callback, hubInfo, or executor is null * - * @hide * @see ContextHubClientCallback */ - public ContextHubClient createClient( - ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) { - if (callback == null) { - throw new NullPointerException("Callback cannot be null"); - } - if (hubInfo == null) { - throw new NullPointerException("Hub info cannot be null"); - } - - Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler; - IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler); - - IContextHubClient client; + @NonNull public ContextHubClient createClient( + @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback, + @NonNull @CallbackExecutor Executor executor) { + Preconditions.checkNotNull(callback, "Callback cannot be null"); + Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null"); + Preconditions.checkNotNull(executor, "Executor cannot be null"); + + ContextHubClient client = new ContextHubClient(hubInfo); + IContextHubClientCallback clientInterface = createClientCallback( + client, callback, executor); + + IContextHubClient clientProxy; try { - client = mService.createClient(clientInterface, hubInfo.getId()); + clientProxy = mService.createClient(clientInterface, hubInfo.getId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - return new ContextHubClient(client, clientInterface, hubInfo); + client.setClientProxy(clientProxy); + return client; + } + + /** + * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} + * with the executor using the main thread's Looper. + * + * @param hubInfo the hub to attach this client to + * @param callback the notification callback to register + * @return the registered client object + * + * @throws IllegalArgumentException if hubInfo does not represent a valid hub + * @throws IllegalStateException if there were too many registered clients at the service + * @throws NullPointerException if callback or hubInfo is null + * + * @see ContextHubClientCallback + */ + @NonNull public ContextHubClient createClient( + @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) { + return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain())); } /** @@ -606,9 +689,13 @@ public final class ContextHubManager { * @param callback method to deregister * * @return int 0 on success, -1 otherwise + * + * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister + * a {@link android.hardware.location.ContextHubClientCallback}. */ @SuppressLint("Doclava125") - public int unregisterCallback(Callback callback) { + @Deprecated + public int unregisterCallback(@NonNull Callback callback) { synchronized(this) { if (callback != mCallback) { Log.w(TAG, "Cannot recognize callback!"); diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java index ec1e68fe680c..bc7efef55bcf 100644 --- a/core/java/android/hardware/location/ContextHubTransaction.java +++ b/core/java/android/hardware/location/ContextHubTransaction.java @@ -15,15 +15,19 @@ */ package android.hardware.location; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Handler; -import android.os.Looper; -import android.util.Log; +import android.os.HandlerExecutor; + +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -33,18 +37,20 @@ import java.util.concurrent.TimeoutException; * This object is generated as a result of an asynchronous request sent to the Context Hub * through the ContextHubManager APIs. The caller can either retrieve the result * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or - * asynchronously through a user-defined callback - * ({@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}). + * asynchronously through a user-defined listener + * ({@link #setOnCompleteListener(OnCompleteListener, Executor)} )}). * * @param <T> the type of the contents in the transaction response * * @hide */ +@SystemApi public class ContextHubTransaction<T> { private static final String TAG = "ContextHubTransaction"; /** * Constants describing the type of a transaction through the Context Hub Service. + * {@hide} */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "TYPE_" }, value = { @@ -64,6 +70,7 @@ public class ContextHubTransaction<T> { /** * Constants describing the result of a transaction or request through the Context Hub Service. + * {@hide} */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "RESULT_" }, value = { @@ -71,7 +78,7 @@ public class ContextHubTransaction<T> { RESULT_FAILED_UNKNOWN, RESULT_FAILED_BAD_PARAMS, RESULT_FAILED_UNINITIALIZED, - RESULT_FAILED_PENDING, + RESULT_FAILED_BUSY, RESULT_FAILED_AT_HUB, RESULT_FAILED_TIMEOUT, RESULT_FAILED_SERVICE_INTERNAL_FAILURE, @@ -94,7 +101,7 @@ public class ContextHubTransaction<T> { /** * Failure mode when there are too many transactions pending. */ - public static final int RESULT_FAILED_PENDING = 4; + public static final int RESULT_FAILED_BUSY = 4; /** * Failure mode when the request went through, but failed asynchronously at the hub. */ @@ -145,20 +152,20 @@ public class ContextHubTransaction<T> { } /** - * An interface describing the callback to be invoked when a transaction completes. + * An interface describing the listener for a transaction completion. * - * @param <C> the type of the contents in the transaction response + * @param <L> the type of the contents in the transaction response */ @FunctionalInterface - public interface Callback<C> { + public interface OnCompleteListener<L> { /** - * The callback to invoke when the transaction completes. + * The listener function to invoke when the transaction completes. * * @param transaction the transaction that this callback was attached to. * @param response the response of the transaction. */ void onComplete( - ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response); + ContextHubTransaction<L> transaction, ContextHubTransaction.Response<L> response); } /* @@ -173,14 +180,14 @@ public class ContextHubTransaction<T> { private ContextHubTransaction.Response<T> mResponse; /* - * The handler to invoke the aynsc response supplied by onComplete. + * The executor to invoke the onComplete async callback. */ - private Handler mHandler = null; + private Executor mExecutor = null; /* - * The callback to invoke when the transaction completes. + * The listener to be invoked when the transaction completes. */ - private ContextHubTransaction.Callback<T> mCallback = null; + private ContextHubTransaction.OnCompleteListener<T> mListener = null; /* * Synchronization latch used to block on response. @@ -258,73 +265,65 @@ public class ContextHubTransaction<T> { } /** - * Sets a callback to be invoked when the transaction completes. + * Sets the listener to be invoked invoked when the transaction completes. * * This function provides an asynchronous approach to retrieve the result of the * transaction. When the transaction response has been provided by the Context Hub, - * the given callback will be posted by the provided handler. + * the given listener will be invoked. * - * If the transaction has already completed at the time of invocation, the callback - * will be immediately posted by the handler. If the transaction has been invalidated, - * the callback will never be invoked. + * If the transaction has already completed at the time of invocation, the listener + * will be immediately invoked. If the transaction has been invalidated, + * the listener will never be invoked. * * A transaction can be invalidated if the process owning the transaction is no longer active * and the reference to this object is lost. * - * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback)} can only be - * invoked once, or an IllegalStateException will be thrown. + * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener)} can + * only be invoked once, or an IllegalStateException will be thrown. * - * @param callback the callback to be invoked upon completion - * @param handler the handler to post the callback + * @param listener the listener to be invoked upon completion + * @param executor the executor to invoke the callback * * @throws IllegalStateException if this method is called multiple times * @throws NullPointerException if the callback or handler is null */ - public void setOnCompleteCallback( - @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) { + public void setOnCompleteListener( + @NonNull ContextHubTransaction.OnCompleteListener<T> listener, + @NonNull @CallbackExecutor Executor executor) { synchronized (this) { - if (callback == null) { - throw new NullPointerException("Callback cannot be null"); - } - if (handler == null) { - throw new NullPointerException("Handler cannot be null"); - } - if (mCallback != null) { + Preconditions.checkNotNull(listener, "OnCompleteListener cannot be null"); + Preconditions.checkNotNull(executor, "Executor cannot be null"); + if (mListener != null) { throw new IllegalStateException( - "Cannot set ContextHubTransaction callback multiple times"); + "Cannot set ContextHubTransaction listener multiple times"); } - mCallback = callback; - mHandler = handler; + mListener = listener; + mExecutor = executor; if (mDoneSignal.getCount() == 0) { - boolean callbackPosted = mHandler.post(() -> { - mCallback.onComplete(this, mResponse); - }); - - if (!callbackPosted) { - Log.e(TAG, "Failed to post callback to Handler"); - } + mExecutor.execute(() -> mListener.onComplete(this, mResponse)); } } } /** - * Sets a callback to be invoked when the transaction completes. + * Sets the listener to be invoked invoked when the transaction completes. * - * Equivalent to {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)} - * with the handler being that of the main thread's Looper. + * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener, + * Executor)} with the executor using the main thread's Looper. * - * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)} - * can only be invoked once, or an IllegalStateException will be thrown. + * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener, + * Executor)} can only be invoked once, or an IllegalStateException will be thrown. * - * @param callback the callback to be invoked upon completion + * @param listener the listener to be invoked upon completion * * @throws IllegalStateException if this method is called multiple times * @throws NullPointerException if the callback is null */ - public void setOnCompleteCallback(@NonNull ContextHubTransaction.Callback<T> callback) { - setOnCompleteCallback(callback, new Handler(Looper.getMainLooper())); + public void setOnCompleteListener( + @NonNull ContextHubTransaction.OnCompleteListener<T> listener) { + setOnCompleteListener(listener, new HandlerExecutor(Handler.getMain())); } /** @@ -339,11 +338,9 @@ public class ContextHubTransaction<T> { * @throws IllegalStateException if this method is invoked multiple times * @throws NullPointerException if the response is null */ - void setResponse(ContextHubTransaction.Response<T> response) { + /* package */ void setResponse(ContextHubTransaction.Response<T> response) { synchronized (this) { - if (response == null) { - throw new NullPointerException("Response cannot be null"); - } + Preconditions.checkNotNull(response, "Response cannot be null"); if (mIsResponseSet) { throw new IllegalStateException( "Cannot set response of ContextHubTransaction multiple times"); @@ -353,14 +350,8 @@ public class ContextHubTransaction<T> { mIsResponseSet = true; mDoneSignal.countDown(); - if (mCallback != null) { - boolean callbackPosted = mHandler.post(() -> { - mCallback.onComplete(this, mResponse); - }); - - if (!callbackPosted) { - Log.e(TAG, "Failed to post callback to Handler"); - } + if (mListener != null) { + mExecutor.execute(() -> mListener.onComplete(this, mResponse)); } } } diff --git a/core/java/android/hardware/location/NanoAppBinary.java b/core/java/android/hardware/location/NanoAppBinary.java index 934e9e48c01a..ba01ca251321 100644 --- a/core/java/android/hardware/location/NanoAppBinary.java +++ b/core/java/android/hardware/location/NanoAppBinary.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -27,6 +28,7 @@ import java.util.Arrays; /** * @hide */ +@SystemApi public final class NanoAppBinary implements Parcelable { private static final String TAG = "NanoAppBinary"; diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java index 202867490fb9..716a1946c540 100644 --- a/core/java/android/hardware/location/NanoAppMessage.java +++ b/core/java/android/hardware/location/NanoAppMessage.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -25,6 +26,7 @@ import android.os.Parcelable; * * @hide */ +@SystemApi public final class NanoAppMessage implements Parcelable { private long mNanoAppId; private int mMessageType; diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java index 644031b034d5..d05277d484a6 100644 --- a/core/java/android/hardware/location/NanoAppState.java +++ b/core/java/android/hardware/location/NanoAppState.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +24,7 @@ import android.os.Parcelable; * * @hide */ +@SystemApi public final class NanoAppState implements Parcelable { private long mNanoAppId; private int mNanoAppVersion; diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 4d54e31b8c5d..9b6515c9ebf1 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -38,6 +38,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -645,7 +646,8 @@ public class RadioManager { private final boolean mAf; private final boolean mEa; - FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, + /** @hide */ + public FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, boolean stereo, boolean rds, boolean ta, boolean af, boolean ea) { super(region, type, lowerLimit, upperLimit, spacing); mStereo = stereo; @@ -771,7 +773,8 @@ public class RadioManager { private final boolean mStereo; - AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, + /** @hide */ + public AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, boolean stereo) { super(region, type, lowerLimit, upperLimit, spacing); mStereo = stereo; @@ -843,10 +846,10 @@ public class RadioManager { /** Radio band configuration. */ public static class BandConfig implements Parcelable { - final BandDescriptor mDescriptor; + @NonNull final BandDescriptor mDescriptor; BandConfig(BandDescriptor descriptor) { - mDescriptor = descriptor; + mDescriptor = Objects.requireNonNull(descriptor); } BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) { @@ -968,7 +971,8 @@ public class RadioManager { private final boolean mAf; private final boolean mEa; - FmBandConfig(FmBandDescriptor descriptor) { + /** @hide */ + public FmBandConfig(FmBandDescriptor descriptor) { super((BandDescriptor)descriptor); mStereo = descriptor.isStereoSupported(); mRds = descriptor.isRdsSupported(); @@ -1204,7 +1208,8 @@ public class RadioManager { public static class AmBandConfig extends BandConfig { private final boolean mStereo; - AmBandConfig(AmBandDescriptor descriptor) { + /** @hide */ + public AmBandConfig(AmBandDescriptor descriptor) { super((BandDescriptor)descriptor); mStereo = descriptor.isStereoSupported(); } diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java index 42e43c8aea1e..5425bf534ebd 100644 --- a/core/java/android/net/NetworkWatchlistManager.java +++ b/core/java/android/net/NetworkWatchlistManager.java @@ -59,8 +59,8 @@ public class NetworkWatchlistManager { /** * Report network watchlist records if necessary. * - * Watchlist report process will run summarize records into a single report, then the - * report will be processed by differential privacy framework and store it on disk. + * Watchlist report process will summarize records into a single report, then the + * report will be processed by differential privacy framework and stored on disk. * * @hide */ @@ -72,4 +72,18 @@ public class NetworkWatchlistManager { e.rethrowFromSystemServer(); } } + + /** + * Reload network watchlist. + * + * @hide + */ + public void reloadWatchlist() { + try { + mNetworkWatchlistManager.reloadWatchlist(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reload watchlist"); + e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 430c28b73b13..d4d74f438e5d 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -180,6 +180,11 @@ public abstract class BatteryStats implements Parcelable { public static final int FOREGROUND_SERVICE = 22; /** + * A constant indicating an aggregate wifi multicast timer + */ + public static final int WIFI_AGGREGATE_MULTICAST_ENABLED = 23; + + /** * Include all of the data in the stats, including previously saved data. */ public static final int STATS_SINCE_CHARGED = 0; @@ -227,8 +232,11 @@ public abstract class BatteryStats implements Parcelable { * New in version 28: * - Light/Deep Doze power * - WiFi Multicast Wakelock statistics (count & duration) + * New in version 29: + * - Process states re-ordered. TOP_SLEEPING now below BACKGROUND. HEAVY_WEIGHT introduced. + * - CPU times per UID process state */ - static final int CHECKIN_VERSION = 28; + static final int CHECKIN_VERSION = 29; /** * Old version, we hit 9 and ran out of room, need to remove. @@ -2331,6 +2339,22 @@ public abstract class BatteryStats implements Parcelable { }; /** + * Returns total time for WiFi Multicast Wakelock timer. + * Note that this may be different from the sum of per uid timer values. + * + * {@hide} + */ + public abstract long getWifiMulticastWakelockTime(long elapsedRealtimeUs, int which); + + /** + * Returns total time for WiFi Multicast Wakelock timer + * Note that this may be different from the sum of per uid timer values. + * + * {@hide} + */ + public abstract int getWifiMulticastWakelockCount(int which); + + /** * Returns the time in microseconds that wifi has been on while the device was * running on battery. * @@ -3439,16 +3463,13 @@ public abstract class BatteryStats implements Parcelable { screenDozeTime / 1000); - // Calculate both wakelock and wifi multicast wakelock times across all uids. + // Calculate wakelock times across all uids. long fullWakeLockTimeTotal = 0; long partialWakeLockTimeTotal = 0; - long multicastWakeLockTimeTotalMicros = 0; - int multicastWakeLockCountTotal = 0; for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); - // First calculating the wakelock stats final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -3466,13 +3487,6 @@ public abstract class BatteryStats implements Parcelable { rawRealtime, which); } } - - // Now calculating the wifi multicast wakelock stats - final Timer mcTimer = u.getMulticastWakelockStats(); - if (mcTimer != null) { - multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); - multicastWakeLockCountTotal += mcTimer.getCountLocked(which); - } } // Dump network stats @@ -3589,6 +3603,9 @@ public abstract class BatteryStats implements Parcelable { dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args); // Dump Multicast total stats + final long multicastWakeLockTimeTotalMicros = + getWifiMulticastWakelockTime(rawRealtime, which); + final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which); dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA, multicastWakeLockTimeTotalMicros / 1000, multicastWakeLockCountTotal); @@ -4453,18 +4470,15 @@ public abstract class BatteryStats implements Parcelable { pw.print(" Connectivity changes: "); pw.println(connChanges); } - // Calculate both wakelock and wifi multicast wakelock times across all uids. + // Calculate wakelock times across all uids. long fullWakeLockTimeTotalMicros = 0; long partialWakeLockTimeTotalMicros = 0; - long multicastWakeLockTimeTotalMicros = 0; - int multicastWakeLockCountTotal = 0; final ArrayList<TimerEntry> timers = new ArrayList<>(); for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); - // First calculate wakelock statistics final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -4492,13 +4506,6 @@ public abstract class BatteryStats implements Parcelable { } } } - - // Next calculate wifi multicast wakelock statistics - final Timer mcTimer = u.getMulticastWakelockStats(); - if (mcTimer != null) { - multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); - multicastWakeLockCountTotal += mcTimer.getCountLocked(which); - } } final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); @@ -4528,6 +4535,9 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + final long multicastWakeLockTimeTotalMicros = + getWifiMulticastWakelockTime(rawRealtime, which); + final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which); if (multicastWakeLockTimeTotalMicros != 0) { sb.setLength(0); sb.append(prefix); @@ -7048,6 +7058,28 @@ public abstract class BatteryStats implements Parcelable { } } } + + for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) { + final long[] timesMs = u.getCpuFreqTimes(which, procState); + if (timesMs != null && timesMs.length == cpuFreqs.length) { + long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(which, procState); + if (screenOffTimesMs == null) { + screenOffTimesMs = new long[timesMs.length]; + } + final long procToken = proto.start(UidProto.Cpu.BY_PROCESS_STATE); + proto.write(UidProto.Cpu.ByProcessState.PROCESS_STATE, procState); + for (int ic = 0; ic < timesMs.length; ++ic) { + long cToken = proto.start(UidProto.Cpu.ByProcessState.BY_FREQUENCY); + proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1); + proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS, + timesMs[ic]); + proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS, + screenOffTimesMs[ic]); + proto.end(cToken); + } + proto.end(procToken); + } + } proto.end(cpuToken); // Flashlight (FLASHLIGHT_DATA) @@ -7532,22 +7564,9 @@ public abstract class BatteryStats implements Parcelable { proto.end(mToken); // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA) - // Calculate multicast wakelock stats across all uids. - long multicastWakeLockTimeTotalUs = 0; - int multicastWakeLockCountTotal = 0; - - for (int iu = 0; iu < uidStats.size(); iu++) { - final Uid u = uidStats.valueAt(iu); - - final Timer mcTimer = u.getMulticastWakelockStats(); - - if (mcTimer != null) { - multicastWakeLockTimeTotalUs += - mcTimer.getTotalTimeLocked(rawRealtimeUs, which); - multicastWakeLockCountTotal += mcTimer.getCountLocked(which); - } - } - + final long multicastWakeLockTimeTotalUs = + getWifiMulticastWakelockTime(rawRealtimeUs, which); + final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which); final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL); proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS, multicastWakeLockTimeTotalUs / 1000); diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index b7a464544fc7..33470f36d938 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -35,6 +35,9 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** * Base class for a remotable object, the core part of a lightweight @@ -901,17 +904,62 @@ final class BinderProxy implements IBinder { keyArray[size] = key; } if (size >= mWarnBucketSize) { - final int total_size = size(); + final int totalSize = size(); Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size - + " total = " + total_size); + + " total = " + totalSize); mWarnBucketSize += WARN_INCREMENT; - if (Build.IS_DEBUGGABLE && total_size > CRASH_AT_SIZE) { - throw new AssertionError("Binder ProxyMap has too many entries. " - + "BinderProxy leak?"); + if (Build.IS_DEBUGGABLE && totalSize > CRASH_AT_SIZE) { + diagnosticCrash(); } } } + /** + * Dump a histogram to the logcat, then throw an assertion error. Used to diagnose + * abnormally large proxy maps. + */ + private void diagnosticCrash() { + Map<String, Integer> counts = new HashMap<>(); + for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) { + if (a != null) { + for (WeakReference<BinderProxy> weakRef : a) { + BinderProxy bp = weakRef.get(); + String key; + if (bp == null) { + key = "<cleared weak-ref>"; + } else { + try { + key = bp.getInterfaceDescriptor(); + } catch (Throwable t) { + key = "<exception during getDescriptor>"; + } + } + Integer i = counts.get(key); + if (i == null) { + counts.put(key, 1); + } else { + counts.put(key, i + 1); + } + } + } + } + Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray( + new Map.Entry[counts.size()]); + Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b) + -> b.getValue().compareTo(a.getValue())); + Log.v(Binder.TAG, "BinderProxy descriptor histogram (top ten):"); + int printLength = Math.min(10, sorted.length); + for (int i = 0; i < printLength; i++) { + Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i].getKey() + " x" + + sorted[i].getValue()); + } + + // Now throw an assertion. + final int totalSize = size(); + throw new AssertionError("Binder ProxyMap has too many entries: " + totalSize + + ". BinderProxy leak?"); + } + // Corresponding ArrayLists in the following two arrays always have the same size. // They contain no empty entries. However WeakReferences in the values ArrayLists // may have been cleared. diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 3ca1005b8c98..5c5e351d2eeb 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -388,6 +388,8 @@ public class Handler { * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. + * @param token An instance which can be used to cancel {@code r} via + * {@link #removeCallbacksAndMessages}. * @param uptimeMillis The absolute time at which the callback should run, * using the {@link android.os.SystemClock#uptimeMillis} time-base. * @@ -430,6 +432,32 @@ public class Handler { } /** + * Causes the Runnable r to be added to the message queue, to be run + * after the specified amount of time elapses. + * The runnable will be run on the thread to which this handler + * is attached. + * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> + * Time spent in deep sleep will add an additional delay to execution. + * + * @param r The Runnable that will be executed. + * @param token An instance which can be used to cancel {@code r} via + * {@link #removeCallbacksAndMessages}. + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- + * if the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean postDelayed(Runnable r, Object token, long delayMillis) + { + return sendMessageDelayed(getPostMessage(r, token), delayMillis); + } + + /** * Posts a message to an object that implements Runnable. * Causes the Runnable r to executed on the next iteration through the * message queue. The runnable will be run on the thread to which this diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 3db12ed0815f..29812e8ab06e 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -71,7 +71,7 @@ interface IStatsManager { * Fetches data for the specified configuration key. Returns a byte array representing proto * wire-encoded of ConfigMetricsReportList. */ - byte[] getData(in String key); + byte[] getData(in long key); /** * Fetches metadata across statsd. Returns byte array representing wire-encoded proto. @@ -86,7 +86,7 @@ interface IStatsManager { * * Returns if this configuration was correctly registered. */ - boolean addConfiguration(in String configKey, in byte[] config, in String pkg, in String cls); + boolean addConfiguration(in long configKey, in byte[] config, in String pkg, in String cls); /** * Removes the configuration with the matching config key. No-op if this config key does not @@ -94,5 +94,5 @@ interface IStatsManager { * * Returns if this configuration key was removed. */ - boolean removeConfiguration(in String configKey); + boolean removeConfiguration(in long configKey); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 56c639135657..cd6d41b3f43c 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1540,7 +1540,7 @@ public final class PowerManager { */ public void setWorkSource(WorkSource ws) { synchronized (mToken) { - if (ws != null && ws.size() == 0) { + if (ws != null && ws.isEmpty()) { ws = null; } @@ -1552,7 +1552,7 @@ public final class PowerManager { changed = true; mWorkSource = new WorkSource(ws); } else { - changed = mWorkSource.diff(ws); + changed = !mWorkSource.equals(ws); if (changed) { mWorkSource.set(ws); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 75cbd57fe178..38993b71a31d 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -194,6 +194,21 @@ public class UserManager { public static final String DISALLOW_SHARE_LOCATION = "no_share_location"; /** + * Specifies if airplane mode is disallowed on the device. + * + * <p> This restriction can only be set by the device owner and the profile owner on the + * primary user and it applies globally - i.e. it disables airplane mode on the entire device. + * <p>The default value is <code>false</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode"; + + /** * Specifies if a user is disallowed from enabling the * "Unknown Sources" setting, that allows installation of apps from unknown sources. * The default value is <code>false</code>. @@ -335,6 +350,28 @@ public class UserManager { public static final String DISALLOW_CONFIG_VPN = "no_config_vpn"; /** + * Specifies if a user is disallowed from configuring location mode. Device owner and profile + * owners can set this restriction and it only applies on the managed user. + * + * <p>In a managed profile, location sharing is forced off when it's off on primary user, so + * user can still turn off location sharing on managed profile when the restriction is set by + * profile owner on managed profile. + * + * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION}, + * as the device owner or profile owner can still enable or disable location mode via + * {@link DevicePolicyManager#setSecureSetting} when this restriction is on. + * + * <p>The default value is <code>false</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode"; + + /** * Specifies if date, time and timezone configuring is disallowed. * * <p>When restriction is set by device owners, it applies globally - i.e., it disables date, @@ -2160,7 +2197,8 @@ public class UserManager { /** * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify - * a target to start when user is unlocked. + * a target to start when user is unlocked. If {@code target} is specified, caller must have + * the {@link android.Manifest.permission#MANAGE_USERS} permission. * * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)} * @hide diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index bf145a08e06c..401b4a36a743 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -456,6 +456,16 @@ public class WorkSource implements Parcelable { } /** + * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to + * attribute usage to. + * + * @hide for internal use only. + */ + public boolean isEmpty() { + return mNum == 0 && (mChains == null || mChains.isEmpty()); + } + + /** * @return the list of {@code WorkChains} associated with this {@code WorkSource}. * @hide */ @@ -842,6 +852,14 @@ public class WorkSource implements Parcelable { return this; } + /** + * Return the UID to which this WorkChain should be attributed to, i.e, the UID that + * initiated the work and not the UID performing it. + */ + public int getAttributionUid() { + return mUids[0]; + } + // TODO: The following three trivial getters are purely for testing and will be removed // once we have higher level logic in place, e.g for serializing this WorkChain to a proto, // diffing it etc. @@ -932,6 +950,55 @@ public class WorkSource implements Parcelable { }; } + /** + * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}. + * + * Returns {@code null} if no differences exist, otherwise returns a two element array. The + * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in + * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in + * {@code oldWs} but not in {@code newWs}. + * + * @hide + */ + public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) { + ArrayList<WorkChain> newChains = null; + ArrayList<WorkChain> goneChains = null; + + // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across + // WorkSource objects. We can replace this with something smarter, for e.g by defining + // a Comparator between WorkChains. It's unclear whether that will be more efficient if + // the number of chains associated with a WorkSource is expected to be small + if (oldWs.mChains != null) { + for (int i = 0; i < oldWs.mChains.size(); ++i) { + final WorkChain wc = oldWs.mChains.get(i); + if (newWs.mChains == null || !newWs.mChains.contains(wc)) { + if (goneChains == null) { + goneChains = new ArrayList<>(oldWs.mChains.size()); + } + goneChains.add(wc); + } + } + } + + if (newWs.mChains != null) { + for (int i = 0; i < newWs.mChains.size(); ++i) { + final WorkChain wc = newWs.mChains.get(i); + if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) { + if (newChains == null) { + newChains = new ArrayList<>(newWs.mChains.size()); + } + newChains.add(wc); + } + } + } + + if (newChains != null || goneChains != null) { + return new ArrayList[] { newChains, goneChains }; + } + + return null; + } + @Override public int describeContents() { return 0; @@ -991,6 +1058,25 @@ public class WorkSource implements Parcelable { } proto.end(contentProto); } + + if (mChains != null) { + for (int i = 0; i < mChains.size(); i++) { + final WorkChain wc = mChains.get(i); + final long workChain = proto.start(WorkSourceProto.WORK_CHAINS); + + final String[] tags = wc.getTags(); + final int[] uids = wc.getUids(); + for (int j = 0; j < tags.length; j++) { + final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS); + proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]); + proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]); + proto.end(contentProto); + } + + proto.end(workChain); + } + } + proto.end(workSourceToken); } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 9833fe15dd3c..4c587a836de8 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1115,12 +1115,14 @@ public class StorageManager { /** {@hide} */ public static Pair<String, Long> getPrimaryStoragePathAndSize() { return Pair.create(null, - FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace())); + FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace() + + Environment.getRootDirectory().getTotalSpace())); } /** {@hide} */ public long getPrimaryStorageSize() { - return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()); + return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace() + + Environment.getRootDirectory().getTotalSpace()); } /** {@hide} */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 22232634743d..2f8651416057 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5778,6 +5778,14 @@ public final class Settings { "touch_exploration_granted_accessibility_services"; /** + * Uri of the slice that's presented on the keyguard. + * Defaults to a slice with the date and next alarm. + * + * @hide + */ + public static final String KEYGUARD_SLICE_URI = "keyguard_slice_uri"; + + /** * Whether to speak passwords while in accessibility mode. * * @deprecated The speaking of passwords is controlled by individual accessibility services. @@ -7252,8 +7260,11 @@ public final class Settings { * full_backup_interval_milliseconds (long) * full_backup_require_charging (boolean) * full_backup_required_network_type (int) + * backup_finished_notification_receivers (String[]) * </pre> * + * backup_finished_notification_receivers uses ":" as delimeter for values. + * * <p> * Type: string * @hide @@ -9571,6 +9582,31 @@ public final class Settings { public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants"; /** + * Battery tip specific settings + * This is encoded as a key=value list, separated by commas. Ex: + * + * "battery_tip_enabled=true,summary_enabled=true,high_usage_enabled=true," + * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50" + * + * The following keys are supported: + * + * <pre> + * battery_tip_enabled (boolean) + * summary_enabled (boolean) + * battery_saver_tip_enabled (boolean) + * high_usage_enabled (boolean) + * high_usage_app_count (int) + * app_restriction_enabled (boolean) + * reduced_battery_enabled (boolean) + * reduced_battery_percent (int) + * low_battery_enabled (boolean) + * low_battery_hour (int) + * </pre> + * @hide + */ + public static final String BATTERY_TIP_CONSTANTS = "battery_tip_constants"; + + /** * Always on display(AOD) specific settings * This is encoded as a key=value list, separated by commas. Ex: * @@ -11108,6 +11144,7 @@ public final class Settings { INSTANT_APP_SETTINGS.add(DEBUG_VIEW_ATTRIBUTES); INSTANT_APP_SETTINGS.add(WTF_IS_FATAL); INSTANT_APP_SETTINGS.add(SEND_ACTION_APP_ERROR); + INSTANT_APP_SETTINGS.add(ZEN_MODE); } /** diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index 42282ac2858b..57477f580a42 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -56,7 +56,7 @@ interface IKeystoreService { int clear_uid(long uid); // Keymaster 0.4 methods - int addRngEntropy(in byte[] data); + int addRngEntropy(in byte[] data, int flags); int generateKey(String alias, in KeymasterArguments arguments, in byte[] entropy, int uid, int flags, out KeyCharacteristics characteristics); int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId, @@ -78,4 +78,8 @@ interface IKeystoreService { int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain); int attestDeviceIds(in KeymasterArguments params, out KeymasterCertificateChain chain); int onDeviceOffBody(); + int importWrappedKey(in String wrappedKeyAlias, in byte[] wrappedKey, + in String wrappingKeyAlias, in byte[] maskingKey, in KeymasterArguments arguments, + in long rootSid, in long fingerprintSid, + out KeyCharacteristics characteristics); } diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java index f88768b184fd..0cf8da5b3a86 100644 --- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java +++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java @@ -45,6 +45,8 @@ public class RecoverableKeyStoreLoader { public static final int NO_ERROR = KeyStore.NO_ERROR; public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR; public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; + public static final int NO_SNAPSHOT_PENDING_ERROR = 21; + /** * 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. @@ -209,7 +211,7 @@ public class RecoverableKeyStoreLoader { * version. Version zero is used, if no snapshots were created for the account. * * @return Map from recovery agent accounts to snapshot versions. - * @see KeyStoreRecoveryData.getSnapshotVersion + * @see KeyStoreRecoveryData#getSnapshotVersion * @hide */ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions() @@ -231,7 +233,7 @@ public class RecoverableKeyStoreLoader { /** * Server parameters used to generate new recovery key blobs. This value will be included in * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included - * in vaultParams {@link startRecoverySession} + * in vaultParams {@link #startRecoverySession} * * @param serverParameters included in recovery key blob. * @see #getRecoveryData @@ -423,19 +425,21 @@ public class RecoverableKeyStoreLoader { /** * Imports keys. * - * @param sessionId Id for recovery session, same as in = {@link startRecoverySession}. + * @param sessionId Id for recovery session, same as in + * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}. * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob * and session. KeyStore only uses package names from the application info in {@link * KeyEntryRecoveryData}. Caller is responsibility to perform certificates check. + * @return Map from alias to raw key material. */ - public void recoverKeys( + public Map<String, byte[]> recoverKeys( @NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RecoverableKeyStoreLoaderException { try { - mBinder.recoverKeys( + return (Map<String, byte[]>) mBinder.recoverKeys( sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -443,4 +447,21 @@ public class RecoverableKeyStoreLoader { throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); } } + + /** + * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the + * raw material of the key. + * + * @throws RecoverableKeyStoreLoaderException if an error occurred generating and storing the + * key. + */ + public byte[] generateAndStoreKey(String alias) throws RecoverableKeyStoreLoaderException { + try { + return mBinder.generateAndStoreKey(alias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } + } } diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index df0842f7fb0d..fb530074d5b0 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -23,6 +23,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; +import android.telephony.euicc.EuiccManager.OtaStatus; import android.util.ArraySet; import java.util.concurrent.LinkedBlockingQueue; @@ -203,6 +204,16 @@ public abstract class EuiccService extends Service { public abstract String onGetEid(int slotId); /** + * Return the status of OTA update. + * + * @param slotId ID of the SIM slot to use for the operation. This is currently not populated + * but is here to future-proof the APIs. + * @return The status of Euicc OTA update. + * @see android.telephony.euicc.EuiccManager#getOtaStatus + */ + public abstract @OtaStatus int onGetOtaStatus(int slotId); + + /** * Populate {@link DownloadableSubscription} metadata for the given downloadable subscription. * * @param slotId ID of the SIM slot to use for the operation. This is currently not populated @@ -385,6 +396,21 @@ public abstract class EuiccService extends Service { } @Override + public void getOtaStatus(int slotId, IGetOtaStatusCallback callback) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + int status = EuiccService.this.onGetOtaStatus(slotId); + try { + callback.onSuccess(status); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); + } + + @Override public void getDownloadableSubscriptionMetadata(int slotId, DownloadableSubscription subscription, boolean forceDeactivateSim, diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl index e10dd8cdf616..a24e5c35c1cb 100644 --- a/core/java/android/service/euicc/IEuiccService.aidl +++ b/core/java/android/service/euicc/IEuiccService.aidl @@ -24,6 +24,7 @@ import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback; import android.service.euicc.IGetEidCallback; import android.service.euicc.IGetEuiccInfoCallback; import android.service.euicc.IGetEuiccProfileInfoListCallback; +import android.service.euicc.IGetOtaStatusCallback; import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; @@ -37,6 +38,7 @@ oneway interface IEuiccService { void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription, boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback); void getEid(int slotId, in IGetEidCallback callback); + void getOtaStatus(int slotId, in IGetOtaStatusCallback callback); void getEuiccProfileInfoList(int slotId, in IGetEuiccProfileInfoListCallback callback); void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim, in IGetDefaultDownloadableSubscriptionListCallback callback); diff --git a/core/java/android/service/euicc/IGetOtaStatusCallback.aidl b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl new file mode 100644 index 000000000000..f6678889ccc7 --- /dev/null +++ b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.euicc; + +/** @hide */ +oneway interface IGetOtaStatusCallback { + void onSuccess(int status); +}
\ No newline at end of file diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java index 8c90156d159d..ad3b4b6d6343 100644 --- a/core/java/android/text/format/Formatter.java +++ b/core/java/android/text/format/Formatter.java @@ -20,11 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; -import android.icu.text.DecimalFormat; import android.icu.text.MeasureFormat; -import android.icu.text.NumberFormat; -import android.icu.text.UnicodeSet; -import android.icu.text.UnicodeSetSpanner; import android.icu.util.Measure; import android.icu.util.MeasureUnit; import android.net.NetworkUtils; @@ -32,8 +28,6 @@ import android.text.BidiFormatter; import android.text.TextUtils; import android.view.View; -import java.lang.reflect.Constructor; -import java.math.BigDecimal; import java.util.Locale; /** @@ -43,8 +37,6 @@ import java.util.Locale; public final class Formatter { /** {@hide} */ - public static final int FLAG_DEFAULT = 0; - /** {@hide} */ public static final int FLAG_SHORTER = 1 << 0; /** {@hide} */ public static final int FLAG_CALCULATE_ROUNDED = 1 << 1; @@ -66,9 +58,7 @@ public final class Formatter { return context.getResources().getConfiguration().getLocales().get(0); } - /** - * Wraps the source string in bidi formatting characters in RTL locales. - */ + /* Wraps the source string in bidi formatting characters in RTL locales */ private static String bidiWrap(@NonNull Context context, String source) { final Locale locale = localeFromContext(context); if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) { @@ -97,7 +87,12 @@ public final class Formatter { * @return formatted string with the number */ public static String formatFileSize(@Nullable Context context, long sizeBytes) { - return formatFileSize(context, sizeBytes, FLAG_DEFAULT); + if (context == null) { + return ""; + } + final BytesResult res = formatBytes(context.getResources(), sizeBytes, 0); + return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix, + res.value, res.units)); } /** @@ -105,207 +100,88 @@ public final class Formatter { * (showing fewer digits of precision). */ public static String formatShortFileSize(@Nullable Context context, long sizeBytes) { - return formatFileSize(context, sizeBytes, FLAG_SHORTER); - } - - private static String formatFileSize(@Nullable Context context, long sizeBytes, int flags) { if (context == null) { return ""; } - final RoundedBytesResult res = RoundedBytesResult.roundBytes(sizeBytes, flags); - return bidiWrap(context, formatRoundedBytesResult(context, res)); - } - - private static String getSuffixOverride(@NonNull Resources res, MeasureUnit unit) { - if (unit == MeasureUnit.BYTE) { - return res.getString(com.android.internal.R.string.byteShort); - } else { // unit == PETABYTE - return res.getString(com.android.internal.R.string.petabyteShort); - } - } - - private static NumberFormat getNumberFormatter(Locale locale, int fractionDigits) { - final NumberFormat numberFormatter = NumberFormat.getInstance(locale); - numberFormatter.setMinimumFractionDigits(fractionDigits); - numberFormatter.setMaximumFractionDigits(fractionDigits); - numberFormatter.setGroupingUsed(false); - if (numberFormatter instanceof DecimalFormat) { - // We do this only for DecimalFormat, since in the general NumberFormat case, calling - // setRoundingMode may throw an exception. - numberFormatter.setRoundingMode(BigDecimal.ROUND_HALF_UP); - } - return numberFormatter; - } - - private static String deleteFirstFromString(String source, String toDelete) { - final int location = source.indexOf(toDelete); - if (location == -1) { - return source; - } else { - return source.substring(0, location) - + source.substring(location + toDelete.length(), source.length()); - } - } - - private static String formatMeasureShort(Locale locale, NumberFormat numberFormatter, - float value, MeasureUnit units) { - final MeasureFormat measureFormatter = MeasureFormat.getInstance( - locale, MeasureFormat.FormatWidth.SHORT, numberFormatter); - return measureFormatter.format(new Measure(value, units)); - } - - private static final UnicodeSetSpanner SPACES_AND_CONTROLS = - new UnicodeSetSpanner(new UnicodeSet("[[:Zs:][:Cf:]]").freeze()); - - private static String formatRoundedBytesResult( - @NonNull Context context, @NonNull RoundedBytesResult input) { - final Locale locale = localeFromContext(context); - final NumberFormat numberFormatter = getNumberFormatter(locale, input.fractionDigits); - if (input.units == MeasureUnit.BYTE || input.units == PETABYTE) { - // ICU spells out "byte" instead of "B", and can't format petabytes yet. - final String formattedNumber = numberFormatter.format(input.value); - return context.getString(com.android.internal.R.string.fileSizeSuffix, - formattedNumber, getSuffixOverride(context.getResources(), input.units)); - } else { - return formatMeasureShort(locale, numberFormatter, input.value, input.units); - } + final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SHORTER); + return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix, + res.value, res.units)); } /** {@hide} */ public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) { - final RoundedBytesResult rounded = RoundedBytesResult.roundBytes(sizeBytes, flags); - final Locale locale = res.getConfiguration().getLocales().get(0); - final NumberFormat numberFormatter = getNumberFormatter(locale, rounded.fractionDigits); - final String formattedNumber = numberFormatter.format(rounded.value); - final String units; - if (rounded.units == MeasureUnit.BYTE || rounded.units == PETABYTE) { - // ICU spells out "byte" instead of "B", and can't format petabytes yet. - units = getSuffixOverride(res, rounded.units); - } else { - // Since ICU does not give us access to the pattern, we need to extract the unit string - // from ICU, which we do by taking out the formatted number out of the formatted string - // and trimming the result of spaces and controls. - final String formattedMeasure = formatMeasureShort( - locale, numberFormatter, rounded.value, rounded.units); - final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber); - units = SPACES_AND_CONTROLS.trim(numberRemoved).toString(); + final boolean isNegative = (sizeBytes < 0); + float result = isNegative ? -sizeBytes : sizeBytes; + int suffix = com.android.internal.R.string.byteShort; + long mult = 1; + if (result > 900) { + suffix = com.android.internal.R.string.kilobyteShort; + mult = 1000; + result = result / 1000; } - return new BytesResult(formattedNumber, units, rounded.roundedBytes); - } - - /** - * ICU doesn't support PETABYTE yet. Fake it so that we can treat all units the same way. - */ - private static final MeasureUnit PETABYTE = createPetaByte(); - - /** - * Create a petabyte MeasureUnit without registering it with ICU. - * ICU doesn't support user-create MeasureUnit and the only public (but hidden) method to do so - * is {@link MeasureUnit#internalGetInstance(String, String)} which also registers the unit as - * an available type and thus leaks it to code that doesn't expect or support it. - * <p>This method uses reflection to create an instance of MeasureUnit to avoid leaking it. This - * instance is <b>only</b> to be used in this class. - */ - private static MeasureUnit createPetaByte() { - try { - Constructor<MeasureUnit> constructor = MeasureUnit.class - .getDeclaredConstructor(String.class, String.class); - constructor.setAccessible(true); - return constructor.newInstance("digital", "petabyte"); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to create petabyte MeasureUnit", e); + if (result > 900) { + suffix = com.android.internal.R.string.megabyteShort; + mult *= 1000; + result = result / 1000; } - } - - private static class RoundedBytesResult { - public final float value; - public final MeasureUnit units; - public final int fractionDigits; - public final long roundedBytes; - - private RoundedBytesResult( - float value, MeasureUnit units, int fractionDigits, long roundedBytes) { - this.value = value; - this.units = units; - this.fractionDigits = fractionDigits; - this.roundedBytes = roundedBytes; + if (result > 900) { + suffix = com.android.internal.R.string.gigabyteShort; + mult *= 1000; + result = result / 1000; } - - /** - * Returns a RoundedBytesResult object based on the input size in bytes and the rounding - * flags. The result can be used for formatting. - */ - static RoundedBytesResult roundBytes(long sizeBytes, int flags) { - final boolean isNegative = (sizeBytes < 0); - float result = isNegative ? -sizeBytes : sizeBytes; - MeasureUnit units = MeasureUnit.BYTE; - long mult = 1; - if (result > 900) { - units = MeasureUnit.KILOBYTE; - mult = 1000; - result = result / 1000; - } - if (result > 900) { - units = MeasureUnit.MEGABYTE; - mult *= 1000; - result = result / 1000; - } - if (result > 900) { - units = MeasureUnit.GIGABYTE; - mult *= 1000; - result = result / 1000; - } - if (result > 900) { - units = MeasureUnit.TERABYTE; - mult *= 1000; - result = result / 1000; - } - if (result > 900) { - units = PETABYTE; - mult *= 1000; - result = result / 1000; + if (result > 900) { + suffix = com.android.internal.R.string.terabyteShort; + mult *= 1000; + result = result / 1000; + } + if (result > 900) { + suffix = com.android.internal.R.string.petabyteShort; + mult *= 1000; + result = result / 1000; + } + // Note we calculate the rounded long by ourselves, but still let String.format() + // compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to + // floating point errors. + final int roundFactor; + final String roundFormat; + if (mult == 1 || result >= 100) { + roundFactor = 1; + roundFormat = "%.0f"; + } else if (result < 1) { + roundFactor = 100; + roundFormat = "%.2f"; + } else if (result < 10) { + if ((flags & FLAG_SHORTER) != 0) { + roundFactor = 10; + roundFormat = "%.1f"; + } else { + roundFactor = 100; + roundFormat = "%.2f"; } - // Note we calculate the rounded long by ourselves, but still let NumberFormat compute - // the rounded value. NumberFormat.format(0.1) might not return "0.1" due to floating - // point errors. - final int roundFactor; - final int roundDigits; - if (mult == 1 || result >= 100) { + } else { // 10 <= result < 100 + if ((flags & FLAG_SHORTER) != 0) { roundFactor = 1; - roundDigits = 0; - } else if (result < 1) { + roundFormat = "%.0f"; + } else { roundFactor = 100; - roundDigits = 2; - } else if (result < 10) { - if ((flags & FLAG_SHORTER) != 0) { - roundFactor = 10; - roundDigits = 1; - } else { - roundFactor = 100; - roundDigits = 2; - } - } else { // 10 <= result < 100 - if ((flags & FLAG_SHORTER) != 0) { - roundFactor = 1; - roundDigits = 0; - } else { - roundFactor = 100; - roundDigits = 2; - } + roundFormat = "%.2f"; } + } - if (isNegative) { - result = -result; - } + if (isNegative) { + result = -result; + } + final String roundedString = String.format(roundFormat, result); - // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like - // 80PB so it's okay (for now)... - final long roundedBytes = - (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0 - : (((long) Math.round(result * roundFactor)) * mult / roundFactor); + // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so + // it's okay (for now)... + final long roundedBytes = + (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0 + : (((long) Math.round(result * roundFactor)) * mult / roundFactor); - return new RoundedBytesResult(result, units, roundDigits, roundedBytes); - } + final String units = res.getString(suffix); + + return new BytesResult(roundedString, units, roundedBytes); } /** diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 87f3bc7aba0b..d31bc1fbda76 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,12 +38,12 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); - DEFAULT_FLAGS.put("new_settings_suggestion", "true"); DEFAULT_FLAGS.put("settings_search_v2", "true"); DEFAULT_FLAGS.put("settings_app_info_v2", "false"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); DEFAULT_FLAGS.put("settings_battery_v2", "false"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); + DEFAULT_FLAGS.put("settings_security_settings_v2", "false"); } /** diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java index 4f76463a653e..68d347c912bf 100644 --- a/core/java/android/util/SparseBooleanArray.java +++ b/core/java/android/util/SparseBooleanArray.java @@ -117,7 +117,11 @@ public class SparseBooleanArray implements Cloneable { } } - /** @hide */ + /** + * Removes the mapping at the specified index. + * <p> + * For indices outside of the range {@code 0...size()-1}, the behavior is undefined. + */ public void removeAt(int index) { System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1)); diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java index 26a3c361e8c1..c25b272c11b9 100644 --- a/core/java/android/util/StatsManager.java +++ b/core/java/android/util/StatsManager.java @@ -53,7 +53,7 @@ public final class StatsManager { * @return true if successful */ @RequiresPermission(Manifest.permission.DUMP) - public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) { + public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); @@ -76,7 +76,7 @@ public final class StatsManager { * @return true if successful */ @RequiresPermission(Manifest.permission.DUMP) - public boolean removeConfiguration(String configKey) { + public boolean removeConfiguration(long configKey) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); @@ -100,7 +100,7 @@ public final class StatsManager { * @return Serialized ConfigMetricsReportList proto. Returns null on failure. */ @RequiresPermission(Manifest.permission.DUMP) - public byte[] getData(String configKey) { + public byte[] getData(long configKey) { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 0a54f3a59a7d..530937e720a7 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -16,6 +16,21 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; + import android.util.ArrayMap; import android.util.Pair; @@ -23,56 +38,47 @@ import java.io.ByteArrayInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.RandomAccessFile; -import java.math.BigInteger; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Principal; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; -import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; -import java.security.spec.MGF1ParameterSpec; -import java.security.spec.PSSParameterSpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Set; /** * APK Signature Scheme v2 verifier. * + * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single + * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and + * uncompressed contents of ZIP entries. + * + * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a> + * * @hide for internal use only. */ public class ApkSignatureSchemeV2Verifier { /** - * {@code .SF} file header section attribute indicating that the APK is signed not just with - * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute - * facilitates v2 signature stripping detection. - * - * <p>The attribute contains a comma-separated set of signature scheme IDs. + * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing. */ - public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed"; public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2; + private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; + /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. * @@ -103,7 +109,7 @@ public class ApkSignatureSchemeV2Verifier { /** * Returns the certificates associated with each signer for the given APK without verification. * This method is dangerous and should not be used, unless the caller is absolutely certain the - * APK is trusted. Specifically, verification is only done for the APK Signature Scheme V2 + * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v2 * Block while gathering signer information. The APK contents are not verified. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. @@ -120,6 +126,7 @@ public class ApkSignatureSchemeV2Verifier { return verify(apk, verifyIntegrity); } } + /** * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates * associated with each signer. @@ -144,30 +151,7 @@ public class ApkSignatureSchemeV2Verifier { */ private static SignatureInfo findSignature(RandomAccessFile apk) throws IOException, SignatureNotFoundException { - // Find the ZIP End of Central Directory (EoCD) record. - Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk); - ByteBuffer eocd = eocdAndOffsetInFile.first; - long eocdOffset = eocdAndOffsetInFile.second; - if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { - throw new SignatureNotFoundException("ZIP64 APK not supported"); - } - - // Find the APK Signing Block. The block immediately precedes the Central Directory. - long centralDirOffset = getCentralDirOffset(eocd, eocdOffset); - Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile = - findApkSigningBlock(apk, centralDirOffset); - ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first; - long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second; - - // Find the APK Signature Scheme v2 Block inside the APK Signing Block. - ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock); - - return new SignatureInfo( - apkSignatureSchemeV2Block, - apkSigningBlockOffset, - centralDirOffset, - eocdOffset, - eocd); + return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID); } /** @@ -218,7 +202,7 @@ public class ApkSignatureSchemeV2Verifier { } if (doVerifyIntegrity) { - verifyIntegrity( + ApkSigningBlockUtils.verifyIntegrity( contentDigests, apkFileDescriptor, signatureInfo.apkSigningBlockOffset, @@ -349,7 +333,8 @@ public class ApkSignatureSchemeV2Verifier { } catch (CertificateException e) { throw new SecurityException("Failed to decode certificate #" + certificateCount, e); } - certificate = new VerbatimX509Certificate(certificate, encodedCert); + certificate = new VerbatimX509Certificate( + certificate, encodedCert); certs.add(certificate); } @@ -363,235 +348,44 @@ public class ApkSignatureSchemeV2Verifier { "Public key mismatch between certificate and signature record"); } + ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData); + verifyAdditionalAttributes(additionalAttrs); + return certs.toArray(new X509Certificate[certs.size()]); } - private static void verifyIntegrity( - Map<Integer, byte[]> expectedDigests, - FileDescriptor apkFileDescriptor, - long apkSigningBlockOffset, - long centralDirOffset, - long eocdOffset, - ByteBuffer eocdBuf) throws SecurityException { - - if (expectedDigests.isEmpty()) { - throw new SecurityException("No digests provided"); - } - - // We need to verify the integrity of the following three sections of the file: - // 1. Everything up to the start of the APK Signing Block. - // 2. ZIP Central Directory. - // 3. ZIP End of Central Directory (EoCD). - // Each of these sections is represented as a separate DataSource instance below. - - // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to - // avoid wasting physical memory. In most APK verification scenarios, the contents of the - // APK are already there in the OS's page cache and thus mmap does not use additional - // physical memory. - DataSource beforeApkSigningBlock = - new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset); - DataSource centralDir = - new MemoryMappedFileDataSource( - apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset); + // Attribute to check whether a newer APK Signature Scheme signature was stripped + private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d; - // For the purposes of integrity verification, ZIP End of Central Directory's field Start of - // Central Directory must be considered to point to the offset of the APK Signing Block. - eocdBuf = eocdBuf.duplicate(); - eocdBuf.order(ByteOrder.LITTLE_ENDIAN); - ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset); - DataSource eocd = new ByteBufferDataSource(eocdBuf); - - int[] digestAlgorithms = new int[expectedDigests.size()]; - int digestAlgorithmCount = 0; - for (int digestAlgorithm : expectedDigests.keySet()) { - digestAlgorithms[digestAlgorithmCount] = digestAlgorithm; - digestAlgorithmCount++; - } - byte[][] actualDigests; - try { - actualDigests = - computeContentDigests( - digestAlgorithms, - new DataSource[] {beforeApkSigningBlock, centralDir, eocd}); - } catch (DigestException e) { - throw new SecurityException("Failed to compute digest(s) of contents", e); - } - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] expectedDigest = expectedDigests.get(digestAlgorithm); - byte[] actualDigest = actualDigests[i]; - if (!MessageDigest.isEqual(expectedDigest, actualDigest)) { - throw new SecurityException( - getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) - + " digest of contents did not verify"); + private static void verifyAdditionalAttributes(ByteBuffer attrs) + throws SecurityException, IOException { + while (attrs.hasRemaining()) { + ByteBuffer attr = getLengthPrefixedSlice(attrs); + if (attr.remaining() < 4) { + throw new IOException("Remaining buffer too short to contain additional attribute " + + "ID. Remaining: " + attr.remaining()); } - } - } - - private static byte[][] computeContentDigests( - int[] digestAlgorithms, - DataSource[] contents) throws DigestException { - // For each digest algorithm the result is computed as follows: - // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. - // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. - // No chunks are produced for empty (zero length) segments. - // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's - // length in bytes (uint32 little-endian) and the chunk's contents. - // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of - // chunks (uint32 little-endian) and the concatenation of digests of chunks of all - // segments in-order. - - long totalChunkCountLong = 0; - for (DataSource input : contents) { - totalChunkCountLong += getChunkCount(input.size()); - } - if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) { - throw new DigestException("Too many chunks: " + totalChunkCountLong); - } - int totalChunkCount = (int) totalChunkCountLong; - - byte[][] digestsOfChunks = new byte[digestAlgorithms.length][]; - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); - byte[] concatenationOfChunkCountAndChunkDigests = - new byte[5 + totalChunkCount * digestOutputSizeBytes]; - concatenationOfChunkCountAndChunkDigests[0] = 0x5a; - setUnsignedInt32LittleEndian( - totalChunkCount, - concatenationOfChunkCountAndChunkDigests, - 1); - digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; - } - - byte[] chunkContentPrefix = new byte[5]; - chunkContentPrefix[0] = (byte) 0xa5; - int chunkIndex = 0; - MessageDigest[] mds = new MessageDigest[digestAlgorithms.length]; - for (int i = 0; i < digestAlgorithms.length; i++) { - String jcaAlgorithmName = - getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]); - try { - mds[i] = MessageDigest.getInstance(jcaAlgorithmName); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); - } - } - // TODO: Compute digests of chunks in parallel when beneficial. This requires some research - // into how to parallelize (if at all) based on the capabilities of the hardware on which - // this code is running and based on the size of input. - DataDigester digester = new MultipleDigestDataDigester(mds); - int dataSourceIndex = 0; - for (DataSource input : contents) { - long inputOffset = 0; - long inputRemaining = input.size(); - while (inputRemaining > 0) { - int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES); - setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); - for (int i = 0; i < mds.length; i++) { - mds[i].update(chunkContentPrefix); - } - try { - input.feedIntoDataDigester(digester, inputOffset, chunkSize); - } catch (IOException e) { - throw new DigestException( - "Failed to digest chunk #" + chunkIndex + " of section #" - + dataSourceIndex, - e); - } - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; - int expectedDigestSizeBytes = - getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); - MessageDigest md = mds[i]; - int actualDigestSizeBytes = - md.digest( - concatenationOfChunkCountAndChunkDigests, - 5 + chunkIndex * expectedDigestSizeBytes, - expectedDigestSizeBytes); - if (actualDigestSizeBytes != expectedDigestSizeBytes) { - throw new RuntimeException( - "Unexpected output size of " + md.getAlgorithm() + " digest: " - + actualDigestSizeBytes); + int id = attr.getInt(); + switch (id) { + case STRIPPING_PROTECTION_ATTR_ID: + if (attr.remaining() < 4) { + throw new IOException("V2 Signature Scheme Stripping Protection Attribute " + + " value too small. Expected 4 bytes, but found " + + attr.remaining()); } - } - inputOffset += chunkSize; - inputRemaining -= chunkSize; - chunkIndex++; - } - dataSourceIndex++; - } - - byte[][] result = new byte[digestAlgorithms.length][]; - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] input = digestsOfChunks[i]; - String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); - MessageDigest md; - try { - md = MessageDigest.getInstance(jcaAlgorithmName); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); + int vers = attr.getInt(); + if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { + throw new SecurityException("V2 signature indicates APK is signed using APK" + + " Signature Scheme v3, but none was found. Signature stripped?"); + } + break; + default: + // not the droid we're looking for, move along, move along. + break; } - byte[] output = md.digest(input); - result[i] = output; } - return result; + return; } - - /** - * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. - * - * @throws IOException if an I/O error occurs while reading the file. - * @throws SignatureNotFoundException if the EoCD could not be found. - */ - private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk) - throws IOException, SignatureNotFoundException { - Pair<ByteBuffer, Long> eocdAndOffsetInFile = - ZipUtils.findZipEndOfCentralDirectoryRecord(apk); - if (eocdAndOffsetInFile == null) { - throw new SignatureNotFoundException( - "Not an APK file: ZIP End of Central Directory record not found"); - } - return eocdAndOffsetInFile; - } - - private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) - throws SignatureNotFoundException { - // Look up the offset of ZIP Central Directory. - long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); - if (centralDirOffset > eocdOffset) { - throw new SignatureNotFoundException( - "ZIP Central Directory offset out of range: " + centralDirOffset - + ". ZIP End of Central Directory offset: " + eocdOffset); - } - long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); - if (centralDirOffset + centralDirSize != eocdOffset) { - throw new SignatureNotFoundException( - "ZIP Central Directory is not immediately followed by End of Central" - + " Directory"); - } - return centralDirOffset; - } - - private static final long getChunkCount(long inputSizeBytes) { - return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES; - } - - private static final int CHUNK_SIZE_BYTES = 1024 * 1024; - - private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; - private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; - private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; - private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; - private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; - private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; - private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; - - private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; - private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; - private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: @@ -606,519 +400,4 @@ public class ApkSignatureSchemeV2Verifier { return false; } } - - private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { - int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); - int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); - return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2); - } - - private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) { - switch (digestAlgorithm1) { - case CONTENT_DIGEST_CHUNKED_SHA256: - switch (digestAlgorithm2) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 0; - case CONTENT_DIGEST_CHUNKED_SHA512: - return -1; - default: - throw new IllegalArgumentException( - "Unknown digestAlgorithm2: " + digestAlgorithm2); - } - case CONTENT_DIGEST_CHUNKED_SHA512: - switch (digestAlgorithm2) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 1; - case CONTENT_DIGEST_CHUNKED_SHA512: - return 0; - default: - throw new IllegalArgumentException( - "Unknown digestAlgorithm2: " + digestAlgorithm2); - } - default: - throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1); - } - } - - private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - case SIGNATURE_ECDSA_WITH_SHA256: - case SIGNATURE_DSA_WITH_SHA256: - return CONTENT_DIGEST_CHUNKED_SHA256; - case SIGNATURE_RSA_PSS_WITH_SHA512: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - case SIGNATURE_ECDSA_WITH_SHA512: - return CONTENT_DIGEST_CHUNKED_SHA512; - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { - switch (digestAlgorithm) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return "SHA-256"; - case CONTENT_DIGEST_CHUNKED_SHA512: - return "SHA-512"; - default: - throw new IllegalArgumentException( - "Unknown content digest algorthm: " + digestAlgorithm); - } - } - - private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { - switch (digestAlgorithm) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 256 / 8; - case CONTENT_DIGEST_CHUNKED_SHA512: - return 512 / 8; - default: - throw new IllegalArgumentException( - "Unknown content digest algorthm: " + digestAlgorithm); - } - } - - private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - case SIGNATURE_RSA_PSS_WITH_SHA512: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - return "RSA"; - case SIGNATURE_ECDSA_WITH_SHA256: - case SIGNATURE_ECDSA_WITH_SHA512: - return "EC"; - case SIGNATURE_DSA_WITH_SHA256: - return "DSA"; - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - private static Pair<String, ? extends AlgorithmParameterSpec> - getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - return Pair.create( - "SHA256withRSA/PSS", - new PSSParameterSpec( - "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); - case SIGNATURE_RSA_PSS_WITH_SHA512: - return Pair.create( - "SHA512withRSA/PSS", - new PSSParameterSpec( - "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - return Pair.create("SHA256withRSA", null); - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - return Pair.create("SHA512withRSA", null); - case SIGNATURE_ECDSA_WITH_SHA256: - return Pair.create("SHA256withECDSA", null); - case SIGNATURE_ECDSA_WITH_SHA512: - return Pair.create("SHA512withECDSA", null); - case SIGNATURE_DSA_WITH_SHA256: - return Pair.create("SHA256withDSA", null); - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - /** - * Returns new byte buffer whose content is a shared subsequence of this buffer's content - * between the specified start (inclusive) and end (exclusive) positions. As opposed to - * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source - * buffer's byte order. - */ - private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { - if (start < 0) { - throw new IllegalArgumentException("start: " + start); - } - if (end < start) { - throw new IllegalArgumentException("end < start: " + end + " < " + start); - } - int capacity = source.capacity(); - if (end > source.capacity()) { - throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); - } - int originalLimit = source.limit(); - int originalPosition = source.position(); - try { - source.position(0); - source.limit(end); - source.position(start); - ByteBuffer result = source.slice(); - result.order(source.order()); - return result; - } finally { - source.position(0); - source.limit(originalLimit); - source.position(originalPosition); - } - } - - /** - * Relative <em>get</em> method for reading {@code size} number of bytes from the current - * position of this buffer. - * - * <p>This method reads the next {@code size} bytes at this buffer's current position, - * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to - * {@code size}, byte order set to this buffer's byte order; and then increments the position by - * {@code size}. - */ - private static ByteBuffer getByteBuffer(ByteBuffer source, int size) - throws BufferUnderflowException { - if (size < 0) { - throw new IllegalArgumentException("size: " + size); - } - int originalLimit = source.limit(); - int position = source.position(); - int limit = position + size; - if ((limit < position) || (limit > originalLimit)) { - throw new BufferUnderflowException(); - } - source.limit(limit); - try { - ByteBuffer result = source.slice(); - result.order(source.order()); - source.position(limit); - return result; - } finally { - source.limit(originalLimit); - } - } - - private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException { - if (source.remaining() < 4) { - throw new IOException( - "Remaining buffer too short to contain length of length-prefixed field." - + " Remaining: " + source.remaining()); - } - int len = source.getInt(); - if (len < 0) { - throw new IllegalArgumentException("Negative length"); - } else if (len > source.remaining()) { - throw new IOException("Length-prefixed field longer than remaining buffer." - + " Field length: " + len + ", remaining: " + source.remaining()); - } - return getByteBuffer(source, len); - } - - private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException { - int len = buf.getInt(); - if (len < 0) { - throw new IOException("Negative length"); - } else if (len > buf.remaining()) { - throw new IOException("Underflow while reading length-prefixed value. Length: " + len - + ", available: " + buf.remaining()); - } - byte[] result = new byte[len]; - buf.get(result); - return result; - } - - private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { - result[offset] = (byte) (value & 0xff); - result[offset + 1] = (byte) ((value >>> 8) & 0xff); - result[offset + 2] = (byte) ((value >>> 16) & 0xff); - result[offset + 3] = (byte) ((value >>> 24) & 0xff); - } - - private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; - private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; - private static final int APK_SIG_BLOCK_MIN_SIZE = 32; - - private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; - - private static Pair<ByteBuffer, Long> findApkSigningBlock( - RandomAccessFile apk, long centralDirOffset) - throws IOException, SignatureNotFoundException { - // FORMAT: - // OFFSET DATA TYPE DESCRIPTION - // * @+0 bytes uint64: size in bytes (excluding this field) - // * @+8 bytes payload - // * @-24 bytes uint64: size in bytes (same as the one above) - // * @-16 bytes uint128: magic - - if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) { - throw new SignatureNotFoundException( - "APK too small for APK Signing Block. ZIP Central Directory offset: " - + centralDirOffset); - } - // Read the magic and offset in file from the footer section of the block: - // * uint64: size of block - // * 16 bytes: magic - ByteBuffer footer = ByteBuffer.allocate(24); - footer.order(ByteOrder.LITTLE_ENDIAN); - apk.seek(centralDirOffset - footer.capacity()); - apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity()); - if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) - || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { - throw new SignatureNotFoundException( - "No APK Signing Block before ZIP Central Directory"); - } - // Read and compare size fields - long apkSigBlockSizeInFooter = footer.getLong(0); - if ((apkSigBlockSizeInFooter < footer.capacity()) - || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { - throw new SignatureNotFoundException( - "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); - } - int totalSize = (int) (apkSigBlockSizeInFooter + 8); - long apkSigBlockOffset = centralDirOffset - totalSize; - if (apkSigBlockOffset < 0) { - throw new SignatureNotFoundException( - "APK Signing Block offset out of range: " + apkSigBlockOffset); - } - ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize); - apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); - apk.seek(apkSigBlockOffset); - apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity()); - long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); - if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { - throw new SignatureNotFoundException( - "APK Signing Block sizes in header and footer do not match: " - + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); - } - return Pair.create(apkSigBlock, apkSigBlockOffset); - } - - private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock) - throws SignatureNotFoundException { - checkByteOrderLittleEndian(apkSigningBlock); - // FORMAT: - // OFFSET DATA TYPE DESCRIPTION - // * @+0 bytes uint64: size in bytes (excluding this field) - // * @+8 bytes pairs - // * @-24 bytes uint64: size in bytes (same as the one above) - // * @-16 bytes uint128: magic - ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); - - int entryCount = 0; - while (pairs.hasRemaining()) { - entryCount++; - if (pairs.remaining() < 8) { - throw new SignatureNotFoundException( - "Insufficient data to read size of APK Signing Block entry #" + entryCount); - } - long lenLong = pairs.getLong(); - if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { - throw new SignatureNotFoundException( - "APK Signing Block entry #" + entryCount - + " size out of range: " + lenLong); - } - int len = (int) lenLong; - int nextEntryPos = pairs.position() + len; - if (len > pairs.remaining()) { - throw new SignatureNotFoundException( - "APK Signing Block entry #" + entryCount + " size out of range: " + len - + ", available: " + pairs.remaining()); - } - int id = pairs.getInt(); - if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) { - return getByteBuffer(pairs, len - 4); - } - pairs.position(nextEntryPos); - } - - throw new SignatureNotFoundException( - "No APK Signature Scheme v2 block in APK Signing Block"); - } - - private static void checkByteOrderLittleEndian(ByteBuffer buffer) { - if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { - throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); - } - } - - /** - * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded. - */ - private static class MultipleDigestDataDigester implements DataDigester { - private final MessageDigest[] mMds; - - MultipleDigestDataDigester(MessageDigest[] mds) { - mMds = mds; - } - - @Override - public void consume(ByteBuffer buffer) { - buffer = buffer.slice(); - for (MessageDigest md : mMds) { - buffer.position(0); - md.update(buffer); - } - } - - @Override - public void finish() {} - } - - /** - * For legacy reasons we need to return exactly the original encoded certificate bytes, instead - * of letting the underlying implementation have a shot at re-encoding the data. - */ - private static class VerbatimX509Certificate extends WrappedX509Certificate { - private byte[] encodedVerbatim; - - public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { - super(wrapped); - this.encodedVerbatim = encodedVerbatim; - } - - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return encodedVerbatim; - } - } - - private static class WrappedX509Certificate extends X509Certificate { - private final X509Certificate wrapped; - - public WrappedX509Certificate(X509Certificate wrapped) { - this.wrapped = wrapped; - } - - @Override - public Set<String> getCriticalExtensionOIDs() { - return wrapped.getCriticalExtensionOIDs(); - } - - @Override - public byte[] getExtensionValue(String oid) { - return wrapped.getExtensionValue(oid); - } - - @Override - public Set<String> getNonCriticalExtensionOIDs() { - return wrapped.getNonCriticalExtensionOIDs(); - } - - @Override - public boolean hasUnsupportedCriticalExtension() { - return wrapped.hasUnsupportedCriticalExtension(); - } - - @Override - public void checkValidity() - throws CertificateExpiredException, CertificateNotYetValidException { - wrapped.checkValidity(); - } - - @Override - public void checkValidity(Date date) - throws CertificateExpiredException, CertificateNotYetValidException { - wrapped.checkValidity(date); - } - - @Override - public int getVersion() { - return wrapped.getVersion(); - } - - @Override - public BigInteger getSerialNumber() { - return wrapped.getSerialNumber(); - } - - @Override - public Principal getIssuerDN() { - return wrapped.getIssuerDN(); - } - - @Override - public Principal getSubjectDN() { - return wrapped.getSubjectDN(); - } - - @Override - public Date getNotBefore() { - return wrapped.getNotBefore(); - } - - @Override - public Date getNotAfter() { - return wrapped.getNotAfter(); - } - - @Override - public byte[] getTBSCertificate() throws CertificateEncodingException { - return wrapped.getTBSCertificate(); - } - - @Override - public byte[] getSignature() { - return wrapped.getSignature(); - } - - @Override - public String getSigAlgName() { - return wrapped.getSigAlgName(); - } - - @Override - public String getSigAlgOID() { - return wrapped.getSigAlgOID(); - } - - @Override - public byte[] getSigAlgParams() { - return wrapped.getSigAlgParams(); - } - - @Override - public boolean[] getIssuerUniqueID() { - return wrapped.getIssuerUniqueID(); - } - - @Override - public boolean[] getSubjectUniqueID() { - return wrapped.getSubjectUniqueID(); - } - - @Override - public boolean[] getKeyUsage() { - return wrapped.getKeyUsage(); - } - - @Override - public int getBasicConstraints() { - return wrapped.getBasicConstraints(); - } - - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return wrapped.getEncoded(); - } - - @Override - public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, - InvalidKeyException, NoSuchProviderException, SignatureException { - wrapped.verify(key); - } - - @Override - public void verify(PublicKey key, String sigProvider) - throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchProviderException, SignatureException { - wrapped.verify(key, sigProvider); - } - - @Override - public String toString() { - return wrapped.toString(); - } - - @Override - public PublicKey getPublicKey() { - return wrapped.getPublicKey(); - } - } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java new file mode 100644 index 000000000000..e43dee356064 --- /dev/null +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util.apk; + +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256; +import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512; +import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; + +import android.os.Build; +import android.util.ArrayMap; +import android.util.Pair; + +import java.io.ByteArrayInputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * APK Signature Scheme v3 verifier. + * + * @hide for internal use only. + */ +public class ApkSignatureSchemeV3Verifier { + + /** + * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing. + */ + public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3; + + private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; + + /** + * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature. + * + * <p><b>NOTE: This method does not verify the signature.</b> + */ + public static boolean hasSignature(String apkFile) throws IOException { + try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { + findSignature(apk); + return true; + } catch (SignatureNotFoundException e) { + return false; + } + } + + /** + * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates + * associated with each signer. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not + * verify. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + public static VerifiedSigner verify(String apkFile) + throws SignatureNotFoundException, SecurityException, IOException { + return verify(apkFile, true); + } + + /** + * Returns the certificates associated with each signer for the given APK without verification. + * This method is dangerous and should not be used, unless the caller is absolutely certain the + * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3 + * Block while gathering signer information. The APK contents are not verified. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile) + throws SignatureNotFoundException, SecurityException, IOException { + return verify(apkFile, false); + } + + private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) + throws SignatureNotFoundException, SecurityException, IOException { + try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { + return verify(apk, verifyIntegrity); + } + } + + /** + * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates + * associated with each signer. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not + * verify. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) + throws SignatureNotFoundException, SecurityException, IOException { + SignatureInfo signatureInfo = findSignature(apk); + return verify(apk.getFD(), signatureInfo, verifyIntegrity); + } + + /** + * Returns the APK Signature Scheme v3 block contained in the provided APK file and the + * additional information relevant for verifying the block against the file. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + private static SignatureInfo findSignature(RandomAccessFile apk) + throws IOException, SignatureNotFoundException { + return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); + } + + /** + * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3 + * Block. + * + * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it + * against the APK file. + */ + private static VerifiedSigner verify( + FileDescriptor apkFileDescriptor, + SignatureInfo signatureInfo, + boolean doVerifyIntegrity) throws SecurityException { + int signerCount = 0; + Map<Integer, byte[]> contentDigests = new ArrayMap<>(); + VerifiedSigner result = null; + CertificateFactory certFactory; + try { + certFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); + } + ByteBuffer signers; + try { + signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); + } catch (IOException e) { + throw new SecurityException("Failed to read list of signers", e); + } + while (signers.hasRemaining()) { + try { + ByteBuffer signer = getLengthPrefixedSlice(signers); + result = verifySigner(signer, contentDigests, certFactory); + signerCount++; + } catch (PlatformNotSupportedException e) { + // this signer is for a different platform, ignore it. + continue; + } catch (IOException | BufferUnderflowException | SecurityException e) { + throw new SecurityException( + "Failed to parse/verify signer #" + signerCount + " block", + e); + } + } + + if (signerCount < 1 || result == null) { + throw new SecurityException("No signers found"); + } + + if (signerCount != 1) { + throw new SecurityException("APK Signature Scheme V3 only supports one signer: " + + "multiple signers found."); + } + + if (contentDigests.isEmpty()) { + throw new SecurityException("No content digests found"); + } + + if (doVerifyIntegrity) { + ApkSigningBlockUtils.verifyIntegrity( + contentDigests, + apkFileDescriptor, + signatureInfo.apkSigningBlockOffset, + signatureInfo.centralDirOffset, + signatureInfo.eocdOffset, + signatureInfo.eocd); + } + + return result; + } + + private static VerifiedSigner verifySigner( + ByteBuffer signerBlock, + Map<Integer, byte[]> contentDigests, + CertificateFactory certFactory) + throws SecurityException, IOException, PlatformNotSupportedException { + ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); + int minSdkVersion = signerBlock.getInt(); + int maxSdkVersion = signerBlock.getInt(); + + if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) { + // this signature isn't meant to be used with this platform, skip it. + throw new PlatformNotSupportedException( + "Signer not supported by this platform " + + "version. This platform: " + Build.VERSION.SDK_INT + + ", signer minSdkVersion: " + minSdkVersion + + ", maxSdkVersion: " + maxSdkVersion); + } + + ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); + byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); + + int signatureCount = 0; + int bestSigAlgorithm = -1; + byte[] bestSigAlgorithmSignatureBytes = null; + List<Integer> signaturesSigAlgorithms = new ArrayList<>(); + while (signatures.hasRemaining()) { + signatureCount++; + try { + ByteBuffer signature = getLengthPrefixedSlice(signatures); + if (signature.remaining() < 8) { + throw new SecurityException("Signature record too short"); + } + int sigAlgorithm = signature.getInt(); + signaturesSigAlgorithms.add(sigAlgorithm); + if (!isSupportedSignatureAlgorithm(sigAlgorithm)) { + continue; + } + if ((bestSigAlgorithm == -1) + || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { + bestSigAlgorithm = sigAlgorithm; + bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature); + } + } catch (IOException | BufferUnderflowException e) { + throw new SecurityException( + "Failed to parse signature record #" + signatureCount, + e); + } + } + if (bestSigAlgorithm == -1) { + if (signatureCount == 0) { + throw new SecurityException("No signatures found"); + } else { + throw new SecurityException("No supported signatures found"); + } + } + + String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm); + Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = + getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm); + String jcaSignatureAlgorithm = signatureAlgorithmParams.first; + AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; + boolean sigVerified; + try { + PublicKey publicKey = + KeyFactory.getInstance(keyAlgorithm) + .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); + Signature sig = Signature.getInstance(jcaSignatureAlgorithm); + sig.initVerify(publicKey); + if (jcaSignatureAlgorithmParams != null) { + sig.setParameter(jcaSignatureAlgorithmParams); + } + sig.update(signedData); + sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify " + jcaSignatureAlgorithm + " signature", e); + } + if (!sigVerified) { + throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); + } + + // Signature over signedData has verified. + + byte[] contentDigest = null; + signedData.clear(); + ByteBuffer digests = getLengthPrefixedSlice(signedData); + List<Integer> digestsSigAlgorithms = new ArrayList<>(); + int digestCount = 0; + while (digests.hasRemaining()) { + digestCount++; + try { + ByteBuffer digest = getLengthPrefixedSlice(digests); + if (digest.remaining() < 8) { + throw new IOException("Record too short"); + } + int sigAlgorithm = digest.getInt(); + digestsSigAlgorithms.add(sigAlgorithm); + if (sigAlgorithm == bestSigAlgorithm) { + contentDigest = readLengthPrefixedByteArray(digest); + } + } catch (IOException | BufferUnderflowException e) { + throw new IOException("Failed to parse digest record #" + digestCount, e); + } + } + + if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) { + throw new SecurityException( + "Signature algorithms don't match between digests and signatures records"); + } + int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm); + byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest); + if ((previousSignerDigest != null) + && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) { + throw new SecurityException( + getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) + + " contents digest does not match the digest specified by a preceding signer"); + } + + ByteBuffer certificates = getLengthPrefixedSlice(signedData); + List<X509Certificate> certs = new ArrayList<>(); + int certificateCount = 0; + while (certificates.hasRemaining()) { + certificateCount++; + byte[] encodedCert = readLengthPrefixedByteArray(certificates); + X509Certificate certificate; + try { + certificate = (X509Certificate) + certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate #" + certificateCount, e); + } + certificate = new VerbatimX509Certificate( + certificate, encodedCert); + certs.add(certificate); + } + + if (certs.isEmpty()) { + throw new SecurityException("No certificates listed"); + } + X509Certificate mainCertificate = certs.get(0); + byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); + if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { + throw new SecurityException( + "Public key mismatch between certificate and signature record"); + } + + int signedMinSDK = signedData.getInt(); + if (signedMinSDK != minSdkVersion) { + throw new SecurityException( + "minSdkVersion mismatch between signed and unsigned in v3 signer block."); + } + + int signedMaxSDK = signedData.getInt(); + if (signedMaxSDK != maxSdkVersion) { + throw new SecurityException( + "maxSdkVersion mismatch between signed and unsigned in v3 signer block."); + } + + ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData); + return verifyAdditionalAttributes(additionalAttrs, certs, certFactory); + } + + private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c; + + private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs, + List<X509Certificate> certs, CertificateFactory certFactory) throws IOException { + X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); + VerifiedProofOfRotation por = null; + + while (attrs.hasRemaining()) { + ByteBuffer attr = getLengthPrefixedSlice(attrs); + if (attr.remaining() < 4) { + throw new IOException("Remaining buffer too short to contain additional attribute " + + "ID. Remaining: " + attr.remaining()); + } + int id = attr.getInt(); + switch(id) { + case PROOF_OF_ROTATION_ATTR_ID: + if (por != null) { + throw new SecurityException("Encountered multiple Proof-of-rotation records" + + " when verifying APK Signature Scheme v3 signature"); + } + por = verifyProofOfRotationStruct(attr, certFactory); + // make sure that the last certificate in the Proof-of-rotation record matches + // the one used to sign this APK. + try { + if (por.certs.size() > 0 + && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(), + certChain[0].getEncoded())) { + throw new SecurityException("Terminal certificate in Proof-of-rotation" + + " record does not match APK signing certificate"); + } + } catch (CertificateEncodingException e) { + throw new SecurityException("Failed to encode certificate when comparing" + + " Proof-of-rotation record and signing certificate", e); + } + + break; + default: + // not the droid we're looking for, move along, move along. + break; + } + } + return new VerifiedSigner(certChain, por); + } + + private static VerifiedProofOfRotation verifyProofOfRotationStruct( + ByteBuffer porBuf, + CertificateFactory certFactory) + throws SecurityException, IOException { + int levelCount = 0; + int lastSigAlgorithm = -1; + X509Certificate lastCert = null; + List<X509Certificate> certs = new ArrayList<>(); + List<Integer> flagsList = new ArrayList<>(); + + // Proof-of-rotation struct: + // is basically a singly linked list of nodes, called levels here, each of which have the + // following structure: + // * length-prefix for the entire level + // - length-prefixed signed data (if previous level exists) + // * length-prefixed X509 Certificate + // * uint32 signature algorithm ID describing how this signed data was signed + // - uint32 flags describing how to treat the cert contained in this level + // - uint32 signature algorithm ID to use to verify the signature of the next level. The + // algorithm here must match the one in the signed data section of the next level. + // - length-prefixed signature over the signed data in this level. The signature here + // is verified using the certificate from the previous level. + // The linking is provided by the certificate of each level signing the one of the next. + while (porBuf.hasRemaining()) { + levelCount++; + try { + ByteBuffer level = getLengthPrefixedSlice(porBuf); + ByteBuffer signedData = getLengthPrefixedSlice(level); + int flags = level.getInt(); + int sigAlgorithm = level.getInt(); + byte[] signature = readLengthPrefixedByteArray(level); + + if (lastCert != null) { + // Use previous level cert to verify current level + Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams = + getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm); + PublicKey publicKey = lastCert.getPublicKey(); + Signature sig = Signature.getInstance(sigAlgParams.first); + sig.initVerify(publicKey); + if (sigAlgParams.second != null) { + sig.setParameter(sigAlgParams.second); + } + sig.update(signedData); + if (!sig.verify(signature)) { + throw new SecurityException("Unable to verify signature of certificate #" + + levelCount + " using " + sigAlgParams.first + " when verifying" + + " Proof-of-rotation record"); + } + } + + byte[] encodedCert = readLengthPrefixedByteArray(signedData); + int signedSigAlgorithm = signedData.getInt(); + if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) { + throw new SecurityException("Signing algorithm ID mismatch for certificate #" + + levelCount + " when verifying Proof-of-rotation record"); + } + lastCert = (X509Certificate) + certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); + lastCert = new VerbatimX509Certificate(lastCert, encodedCert); + + lastSigAlgorithm = sigAlgorithm; + certs.add(lastCert); + flagsList.add(flags); + } catch (IOException | BufferUnderflowException e) { + throw new IOException("Failed to parse Proof-of-rotation record", e); + } catch (NoSuchAlgorithmException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify signature over signed data for certificate #" + + levelCount + " when verifying Proof-of-rotation record", e); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate #" + levelCount + + " when verifying Proof-of-rotation record", e); + } + } + return new VerifiedProofOfRotation(certs, flagsList); + } + + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + case SIGNATURE_RSA_PSS_WITH_SHA512: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + case SIGNATURE_ECDSA_WITH_SHA256: + case SIGNATURE_ECDSA_WITH_SHA512: + case SIGNATURE_DSA_WITH_SHA256: + return true; + default: + return false; + } + } + + /** + * Verified processed proof of rotation. + * + * @hide for internal use only. + */ + public static class VerifiedProofOfRotation { + public final List<X509Certificate> certs; + public final List<Integer> flagsList; + + public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) { + this.certs = certs; + this.flagsList = flagsList; + } + } + + /** + * Verified APK Signature Scheme v3 signer, including the proof of rotation structure. + * + * @hide for internal use only. + */ + public static class VerifiedSigner { + public final X509Certificate[] certs; + public final VerifiedProofOfRotation por; + + public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) { + this.certs = certs; + this.por = por; + } + + } + + private static class PlatformNotSupportedException extends Exception { + + PlatformNotSupportedException(String s) { + super(s); + } + } +} diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 17b11a9b5170..81467292d491 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -54,6 +54,7 @@ public class ApkSignatureVerifier { public static final int VERSION_JAR_SIGNATURE_SCHEME = 1; public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; + public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3; private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>(); @@ -65,7 +66,45 @@ public class ApkSignatureVerifier { public static Result verify(String apkPath, int minSignatureSchemeVersion) throws PackageParserException { - // first try v2 + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) { + // V3 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + + // first try v3 + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3"); + try { + ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = + ApkSignatureSchemeV3Verifier.verify(apkPath); + Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; + Signature[] signerSigs = convertToSignatures(signerCerts); + return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3); + } catch (SignatureNotFoundException e) { + // not signed with v2, try older if allowed + if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No APK Signature Scheme v3 signature in package " + apkPath, e); + } + } catch (Exception e) { + // APK Signature Scheme v2 signature found but did not verify + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed to collect certificates from " + apkPath + + " using APK Signature Scheme v2", e); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) { + // V2 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + + // try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); @@ -87,6 +126,14 @@ public class ApkSignatureVerifier { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) { + // V1 and is older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + // v2 didn't work, try jarsigner return verifyV1Signature(apkPath, true); } @@ -245,6 +292,44 @@ public class ApkSignatureVerifier { public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion) throws PackageParserException { + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) { + // V3 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + + // first try v3 + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3"); + try { + ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = + ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath); + Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; + Signature[] signerSigs = convertToSignatures(signerCerts); + return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3); + } catch (SignatureNotFoundException e) { + // not signed with v2, try older if allowed + if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No APK Signature Scheme v3 signature in package " + apkPath, e); + } + } catch (Exception e) { + // APK Signature Scheme v2 signature found but did not verify + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed to collect certificates from " + apkPath + + " using APK Signature Scheme v2", e); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) { + // V2 and before are older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + // first try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2"); try { @@ -267,6 +352,14 @@ public class ApkSignatureVerifier { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + // redundant, protective version check + if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) { + // V1 and is older than the requested minimum signing version + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No signature found in package of version " + minSignatureSchemeVersion + + " or newer for package " + apkPath); + } + // v2 didn't work, try jarsigner return verifyV1Signature(apkPath, false); } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java new file mode 100644 index 000000000000..9279510ae58f --- /dev/null +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util.apk; + +import android.util.Pair; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; +import java.util.Map; + +/** + * Utility class for an APK Signature Scheme using the APK Signing Block. + * + * @hide for internal use only. + */ +final class ApkSigningBlockUtils { + + private ApkSigningBlockUtils() { + } + + /** + * Returns the APK Signature Scheme block contained in the provided APK file and the + * additional information relevant for verifying the block against the file. + * + * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs + * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 + * block ID. + * + * @throws SignatureNotFoundException if the APK is not signed using this scheme. + * @throws IOException if an I/O error occurs while reading the APK file. + */ + static SignatureInfo findSignature(RandomAccessFile apk, int blockId) + throws IOException, SignatureNotFoundException { + // Find the ZIP End of Central Directory (EoCD) record. + Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk); + ByteBuffer eocd = eocdAndOffsetInFile.first; + long eocdOffset = eocdAndOffsetInFile.second; + if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { + throw new SignatureNotFoundException("ZIP64 APK not supported"); + } + + // Find the APK Signing Block. The block immediately precedes the Central Directory. + long centralDirOffset = getCentralDirOffset(eocd, eocdOffset); + Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile = + findApkSigningBlock(apk, centralDirOffset); + ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first; + long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second; + + // Find the APK Signature Scheme Block inside the APK Signing Block. + ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock, + blockId); + + return new SignatureInfo( + apkSignatureSchemeBlock, + apkSigningBlockOffset, + centralDirOffset, + eocdOffset, + eocd); + } + + static void verifyIntegrity( + Map<Integer, byte[]> expectedDigests, + FileDescriptor apkFileDescriptor, + long apkSigningBlockOffset, + long centralDirOffset, + long eocdOffset, + ByteBuffer eocdBuf) throws SecurityException { + + if (expectedDigests.isEmpty()) { + throw new SecurityException("No digests provided"); + } + + // We need to verify the integrity of the following three sections of the file: + // 1. Everything up to the start of the APK Signing Block. + // 2. ZIP Central Directory. + // 3. ZIP End of Central Directory (EoCD). + // Each of these sections is represented as a separate DataSource instance below. + + // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to + // avoid wasting physical memory. In most APK verification scenarios, the contents of the + // APK are already there in the OS's page cache and thus mmap does not use additional + // physical memory. + DataSource beforeApkSigningBlock = + new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset); + DataSource centralDir = + new MemoryMappedFileDataSource( + apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset); + + // For the purposes of integrity verification, ZIP End of Central Directory's field Start of + // Central Directory must be considered to point to the offset of the APK Signing Block. + eocdBuf = eocdBuf.duplicate(); + eocdBuf.order(ByteOrder.LITTLE_ENDIAN); + ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset); + DataSource eocd = new ByteBufferDataSource(eocdBuf); + + int[] digestAlgorithms = new int[expectedDigests.size()]; + int digestAlgorithmCount = 0; + for (int digestAlgorithm : expectedDigests.keySet()) { + digestAlgorithms[digestAlgorithmCount] = digestAlgorithm; + digestAlgorithmCount++; + } + byte[][] actualDigests; + try { + actualDigests = + computeContentDigests( + digestAlgorithms, + new DataSource[] {beforeApkSigningBlock, centralDir, eocd}); + } catch (DigestException e) { + throw new SecurityException("Failed to compute digest(s) of contents", e); + } + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + byte[] expectedDigest = expectedDigests.get(digestAlgorithm); + byte[] actualDigest = actualDigests[i]; + if (!MessageDigest.isEqual(expectedDigest, actualDigest)) { + throw new SecurityException( + getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) + + " digest of contents did not verify"); + } + } + } + + private static byte[][] computeContentDigests( + int[] digestAlgorithms, + DataSource[] contents) throws DigestException { + // For each digest algorithm the result is computed as follows: + // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. + // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. + // No chunks are produced for empty (zero length) segments. + // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's + // length in bytes (uint32 little-endian) and the chunk's contents. + // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of + // chunks (uint32 little-endian) and the concatenation of digests of chunks of all + // segments in-order. + + long totalChunkCountLong = 0; + for (DataSource input : contents) { + totalChunkCountLong += getChunkCount(input.size()); + } + if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) { + throw new DigestException("Too many chunks: " + totalChunkCountLong); + } + int totalChunkCount = (int) totalChunkCountLong; + + byte[][] digestsOfChunks = new byte[digestAlgorithms.length][]; + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); + byte[] concatenationOfChunkCountAndChunkDigests = + new byte[5 + totalChunkCount * digestOutputSizeBytes]; + concatenationOfChunkCountAndChunkDigests[0] = 0x5a; + setUnsignedInt32LittleEndian( + totalChunkCount, + concatenationOfChunkCountAndChunkDigests, + 1); + digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; + } + + byte[] chunkContentPrefix = new byte[5]; + chunkContentPrefix[0] = (byte) 0xa5; + int chunkIndex = 0; + MessageDigest[] mds = new MessageDigest[digestAlgorithms.length]; + for (int i = 0; i < digestAlgorithms.length; i++) { + String jcaAlgorithmName = + getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]); + try { + mds[i] = MessageDigest.getInstance(jcaAlgorithmName); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); + } + } + // TODO: Compute digests of chunks in parallel when beneficial. This requires some research + // into how to parallelize (if at all) based on the capabilities of the hardware on which + // this code is running and based on the size of input. + DataDigester digester = new MultipleDigestDataDigester(mds); + int dataSourceIndex = 0; + for (DataSource input : contents) { + long inputOffset = 0; + long inputRemaining = input.size(); + while (inputRemaining > 0) { + int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES); + setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); + for (int i = 0; i < mds.length; i++) { + mds[i].update(chunkContentPrefix); + } + try { + input.feedIntoDataDigester(digester, inputOffset, chunkSize); + } catch (IOException e) { + throw new DigestException( + "Failed to digest chunk #" + chunkIndex + " of section #" + + dataSourceIndex, + e); + } + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; + int expectedDigestSizeBytes = + getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); + MessageDigest md = mds[i]; + int actualDigestSizeBytes = + md.digest( + concatenationOfChunkCountAndChunkDigests, + 5 + chunkIndex * expectedDigestSizeBytes, + expectedDigestSizeBytes); + if (actualDigestSizeBytes != expectedDigestSizeBytes) { + throw new RuntimeException( + "Unexpected output size of " + md.getAlgorithm() + " digest: " + + actualDigestSizeBytes); + } + } + inputOffset += chunkSize; + inputRemaining -= chunkSize; + chunkIndex++; + } + dataSourceIndex++; + } + + byte[][] result = new byte[digestAlgorithms.length][]; + for (int i = 0; i < digestAlgorithms.length; i++) { + int digestAlgorithm = digestAlgorithms[i]; + byte[] input = digestsOfChunks[i]; + String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); + MessageDigest md; + try { + md = MessageDigest.getInstance(jcaAlgorithmName); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); + } + byte[] output = md.digest(input); + result[i] = output; + } + return result; + } + + /** + * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. + * + * @throws IOException if an I/O error occurs while reading the file. + * @throws SignatureNotFoundException if the EoCD could not be found. + */ + static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk) + throws IOException, SignatureNotFoundException { + Pair<ByteBuffer, Long> eocdAndOffsetInFile = + ZipUtils.findZipEndOfCentralDirectoryRecord(apk); + if (eocdAndOffsetInFile == null) { + throw new SignatureNotFoundException( + "Not an APK file: ZIP End of Central Directory record not found"); + } + return eocdAndOffsetInFile; + } + + static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) + throws SignatureNotFoundException { + // Look up the offset of ZIP Central Directory. + long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); + if (centralDirOffset > eocdOffset) { + throw new SignatureNotFoundException( + "ZIP Central Directory offset out of range: " + centralDirOffset + + ". ZIP End of Central Directory offset: " + eocdOffset); + } + long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); + if (centralDirOffset + centralDirSize != eocdOffset) { + throw new SignatureNotFoundException( + "ZIP Central Directory is not immediately followed by End of Central" + + " Directory"); + } + return centralDirOffset; + } + + private static long getChunkCount(long inputSizeBytes) { + return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES; + } + + private static final int CHUNK_SIZE_BYTES = 1024 * 1024; + + static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; + static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; + static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; + static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; + static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; + static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; + static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; + + static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; + static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; + + static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { + int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); + int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); + return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2); + } + + private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) { + switch (digestAlgorithm1) { + case CONTENT_DIGEST_CHUNKED_SHA256: + switch (digestAlgorithm2) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return 0; + case CONTENT_DIGEST_CHUNKED_SHA512: + return -1; + default: + throw new IllegalArgumentException( + "Unknown digestAlgorithm2: " + digestAlgorithm2); + } + case CONTENT_DIGEST_CHUNKED_SHA512: + switch (digestAlgorithm2) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return 1; + case CONTENT_DIGEST_CHUNKED_SHA512: + return 0; + default: + throw new IllegalArgumentException( + "Unknown digestAlgorithm2: " + digestAlgorithm2); + } + default: + throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1); + } + } + + static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + case SIGNATURE_ECDSA_WITH_SHA256: + case SIGNATURE_DSA_WITH_SHA256: + return CONTENT_DIGEST_CHUNKED_SHA256; + case SIGNATURE_RSA_PSS_WITH_SHA512: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + case SIGNATURE_ECDSA_WITH_SHA512: + return CONTENT_DIGEST_CHUNKED_SHA512; + default: + throw new IllegalArgumentException( + "Unknown signature algorithm: 0x" + + Long.toHexString(sigAlgorithm & 0xffffffff)); + } + } + + static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { + switch (digestAlgorithm) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return "SHA-256"; + case CONTENT_DIGEST_CHUNKED_SHA512: + return "SHA-512"; + default: + throw new IllegalArgumentException( + "Unknown content digest algorthm: " + digestAlgorithm); + } + } + + private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { + switch (digestAlgorithm) { + case CONTENT_DIGEST_CHUNKED_SHA256: + return 256 / 8; + case CONTENT_DIGEST_CHUNKED_SHA512: + return 512 / 8; + default: + throw new IllegalArgumentException( + "Unknown content digest algorthm: " + digestAlgorithm); + } + } + + static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + case SIGNATURE_RSA_PSS_WITH_SHA512: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + return "RSA"; + case SIGNATURE_ECDSA_WITH_SHA256: + case SIGNATURE_ECDSA_WITH_SHA512: + return "EC"; + case SIGNATURE_DSA_WITH_SHA256: + return "DSA"; + default: + throw new IllegalArgumentException( + "Unknown signature algorithm: 0x" + + Long.toHexString(sigAlgorithm & 0xffffffff)); + } + } + + static Pair<String, ? extends AlgorithmParameterSpec> + getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { + switch (sigAlgorithm) { + case SIGNATURE_RSA_PSS_WITH_SHA256: + return Pair.create( + "SHA256withRSA/PSS", + new PSSParameterSpec( + "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); + case SIGNATURE_RSA_PSS_WITH_SHA512: + return Pair.create( + "SHA512withRSA/PSS", + new PSSParameterSpec( + "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: + return Pair.create("SHA256withRSA", null); + case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: + return Pair.create("SHA512withRSA", null); + case SIGNATURE_ECDSA_WITH_SHA256: + return Pair.create("SHA256withECDSA", null); + case SIGNATURE_ECDSA_WITH_SHA512: + return Pair.create("SHA512withECDSA", null); + case SIGNATURE_DSA_WITH_SHA256: + return Pair.create("SHA256withDSA", null); + default: + throw new IllegalArgumentException( + "Unknown signature algorithm: 0x" + + Long.toHexString(sigAlgorithm & 0xffffffff)); + } + } + + /** + * Returns new byte buffer whose content is a shared subsequence of this buffer's content + * between the specified start (inclusive) and end (exclusive) positions. As opposed to + * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source + * buffer's byte order. + */ + static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { + if (start < 0) { + throw new IllegalArgumentException("start: " + start); + } + if (end < start) { + throw new IllegalArgumentException("end < start: " + end + " < " + start); + } + int capacity = source.capacity(); + if (end > source.capacity()) { + throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); + } + int originalLimit = source.limit(); + int originalPosition = source.position(); + try { + source.position(0); + source.limit(end); + source.position(start); + ByteBuffer result = source.slice(); + result.order(source.order()); + return result; + } finally { + source.position(0); + source.limit(originalLimit); + source.position(originalPosition); + } + } + + /** + * Relative <em>get</em> method for reading {@code size} number of bytes from the current + * position of this buffer. + * + * <p>This method reads the next {@code size} bytes at this buffer's current position, + * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to + * {@code size}, byte order set to this buffer's byte order; and then increments the position by + * {@code size}. + */ + static ByteBuffer getByteBuffer(ByteBuffer source, int size) + throws BufferUnderflowException { + if (size < 0) { + throw new IllegalArgumentException("size: " + size); + } + int originalLimit = source.limit(); + int position = source.position(); + int limit = position + size; + if ((limit < position) || (limit > originalLimit)) { + throw new BufferUnderflowException(); + } + source.limit(limit); + try { + ByteBuffer result = source.slice(); + result.order(source.order()); + source.position(limit); + return result; + } finally { + source.limit(originalLimit); + } + } + + static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException { + if (source.remaining() < 4) { + throw new IOException( + "Remaining buffer too short to contain length of length-prefixed field." + + " Remaining: " + source.remaining()); + } + int len = source.getInt(); + if (len < 0) { + throw new IllegalArgumentException("Negative length"); + } else if (len > source.remaining()) { + throw new IOException("Length-prefixed field longer than remaining buffer." + + " Field length: " + len + ", remaining: " + source.remaining()); + } + return getByteBuffer(source, len); + } + + static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException { + int len = buf.getInt(); + if (len < 0) { + throw new IOException("Negative length"); + } else if (len > buf.remaining()) { + throw new IOException("Underflow while reading length-prefixed value. Length: " + len + + ", available: " + buf.remaining()); + } + byte[] result = new byte[len]; + buf.get(result); + return result; + } + + static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { + result[offset] = (byte) (value & 0xff); + result[offset + 1] = (byte) ((value >>> 8) & 0xff); + result[offset + 2] = (byte) ((value >>> 16) & 0xff); + result[offset + 3] = (byte) ((value >>> 24) & 0xff); + } + + private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; + private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; + private static final int APK_SIG_BLOCK_MIN_SIZE = 32; + + static Pair<ByteBuffer, Long> findApkSigningBlock( + RandomAccessFile apk, long centralDirOffset) + throws IOException, SignatureNotFoundException { + // FORMAT: + // OFFSET DATA TYPE DESCRIPTION + // * @+0 bytes uint64: size in bytes (excluding this field) + // * @+8 bytes payload + // * @-24 bytes uint64: size in bytes (same as the one above) + // * @-16 bytes uint128: magic + + if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) { + throw new SignatureNotFoundException( + "APK too small for APK Signing Block. ZIP Central Directory offset: " + + centralDirOffset); + } + // Read the magic and offset in file from the footer section of the block: + // * uint64: size of block + // * 16 bytes: magic + ByteBuffer footer = ByteBuffer.allocate(24); + footer.order(ByteOrder.LITTLE_ENDIAN); + apk.seek(centralDirOffset - footer.capacity()); + apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity()); + if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) + || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { + throw new SignatureNotFoundException( + "No APK Signing Block before ZIP Central Directory"); + } + // Read and compare size fields + long apkSigBlockSizeInFooter = footer.getLong(0); + if ((apkSigBlockSizeInFooter < footer.capacity()) + || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { + throw new SignatureNotFoundException( + "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); + } + int totalSize = (int) (apkSigBlockSizeInFooter + 8); + long apkSigBlockOffset = centralDirOffset - totalSize; + if (apkSigBlockOffset < 0) { + throw new SignatureNotFoundException( + "APK Signing Block offset out of range: " + apkSigBlockOffset); + } + ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize); + apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); + apk.seek(apkSigBlockOffset); + apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity()); + long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); + if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { + throw new SignatureNotFoundException( + "APK Signing Block sizes in header and footer do not match: " + + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); + } + return Pair.create(apkSigBlock, apkSigBlockOffset); + } + + static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId) + throws SignatureNotFoundException { + checkByteOrderLittleEndian(apkSigningBlock); + // FORMAT: + // OFFSET DATA TYPE DESCRIPTION + // * @+0 bytes uint64: size in bytes (excluding this field) + // * @+8 bytes pairs + // * @-24 bytes uint64: size in bytes (same as the one above) + // * @-16 bytes uint128: magic + ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); + + int entryCount = 0; + while (pairs.hasRemaining()) { + entryCount++; + if (pairs.remaining() < 8) { + throw new SignatureNotFoundException( + "Insufficient data to read size of APK Signing Block entry #" + entryCount); + } + long lenLong = pairs.getLong(); + if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { + throw new SignatureNotFoundException( + "APK Signing Block entry #" + entryCount + + " size out of range: " + lenLong); + } + int len = (int) lenLong; + int nextEntryPos = pairs.position() + len; + if (len > pairs.remaining()) { + throw new SignatureNotFoundException( + "APK Signing Block entry #" + entryCount + " size out of range: " + len + + ", available: " + pairs.remaining()); + } + int id = pairs.getInt(); + if (id == blockId) { + return getByteBuffer(pairs, len - 4); + } + pairs.position(nextEntryPos); + } + + throw new SignatureNotFoundException( + "No block with ID " + blockId + " in APK Signing Block."); + } + + private static void checkByteOrderLittleEndian(ByteBuffer buffer) { + if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { + throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); + } + } + + /** + * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed. + */ + private static class MultipleDigestDataDigester implements DataDigester { + private final MessageDigest[] mMds; + + MultipleDigestDataDigester(MessageDigest[] mds) { + mMds = mds; + } + + @Override + public void consume(ByteBuffer buffer) { + buffer = buffer.slice(); + for (MessageDigest md : mMds) { + buffer.position(0); + md.update(buffer); + } + } + + @Override + public void finish() {} + } + +} diff --git a/core/java/android/util/apk/VerbatimX509Certificate.java b/core/java/android/util/apk/VerbatimX509Certificate.java new file mode 100644 index 000000000000..9984c6d26c64 --- /dev/null +++ b/core/java/android/util/apk/VerbatimX509Certificate.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util.apk; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +/** + * For legacy reasons we need to return exactly the original encoded certificate bytes, instead + * of letting the underlying implementation have a shot at re-encoding the data. + */ +class VerbatimX509Certificate extends WrappedX509Certificate { + private final byte[] mEncodedVerbatim; + + VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { + super(wrapped); + this.mEncodedVerbatim = encodedVerbatim; + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return mEncodedVerbatim; + } +} diff --git a/core/java/android/util/apk/WrappedX509Certificate.java b/core/java/android/util/apk/WrappedX509Certificate.java new file mode 100644 index 000000000000..fdaa42028e8d --- /dev/null +++ b/core/java/android/util/apk/WrappedX509Certificate.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util.apk; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Set; + +class WrappedX509Certificate extends X509Certificate { + private final X509Certificate mWrapped; + + WrappedX509Certificate(X509Certificate wrapped) { + this.mWrapped = wrapped; + } + + @Override + public Set<String> getCriticalExtensionOIDs() { + return mWrapped.getCriticalExtensionOIDs(); + } + + @Override + public byte[] getExtensionValue(String oid) { + return mWrapped.getExtensionValue(oid); + } + + @Override + public Set<String> getNonCriticalExtensionOIDs() { + return mWrapped.getNonCriticalExtensionOIDs(); + } + + @Override + public boolean hasUnsupportedCriticalExtension() { + return mWrapped.hasUnsupportedCriticalExtension(); + } + + @Override + public void checkValidity() + throws CertificateExpiredException, CertificateNotYetValidException { + mWrapped.checkValidity(); + } + + @Override + public void checkValidity(Date date) + throws CertificateExpiredException, CertificateNotYetValidException { + mWrapped.checkValidity(date); + } + + @Override + public int getVersion() { + return mWrapped.getVersion(); + } + + @Override + public BigInteger getSerialNumber() { + return mWrapped.getSerialNumber(); + } + + @Override + public Principal getIssuerDN() { + return mWrapped.getIssuerDN(); + } + + @Override + public Principal getSubjectDN() { + return mWrapped.getSubjectDN(); + } + + @Override + public Date getNotBefore() { + return mWrapped.getNotBefore(); + } + + @Override + public Date getNotAfter() { + return mWrapped.getNotAfter(); + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return mWrapped.getTBSCertificate(); + } + + @Override + public byte[] getSignature() { + return mWrapped.getSignature(); + } + + @Override + public String getSigAlgName() { + return mWrapped.getSigAlgName(); + } + + @Override + public String getSigAlgOID() { + return mWrapped.getSigAlgOID(); + } + + @Override + public byte[] getSigAlgParams() { + return mWrapped.getSigAlgParams(); + } + + @Override + public boolean[] getIssuerUniqueID() { + return mWrapped.getIssuerUniqueID(); + } + + @Override + public boolean[] getSubjectUniqueID() { + return mWrapped.getSubjectUniqueID(); + } + + @Override + public boolean[] getKeyUsage() { + return mWrapped.getKeyUsage(); + } + + @Override + public int getBasicConstraints() { + return mWrapped.getBasicConstraints(); + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return mWrapped.getEncoded(); + } + + @Override + public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchProviderException, SignatureException { + mWrapped.verify(key); + } + + @Override + public void verify(PublicKey key, String sigProvider) + throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + mWrapped.verify(key, sigProvider); + } + + @Override + public String toString() { + return mWrapped.toString(); + } + + @Override + public PublicKey getPublicKey() { + return mWrapped.getPublicKey(); + } +} diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java index debc170fa537..45254908c5c9 100644 --- a/core/java/android/util/jar/StrictJarVerifier.java +++ b/core/java/android/util/jar/StrictJarVerifier.java @@ -18,6 +18,8 @@ package android.util.jar; import android.util.apk.ApkSignatureSchemeV2Verifier; +import android.util.apk.ApkSignatureSchemeV3Verifier; + import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -36,6 +38,7 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarFile; + import sun.security.jca.Providers; import sun.security.pkcs.PKCS7; import sun.security.pkcs.SignerInfo; @@ -56,6 +59,15 @@ import sun.security.pkcs.SignerInfo; */ class StrictJarVerifier { /** + * {@code .SF} file header section attribute indicating that the APK is signed not just with + * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute + * facilitates v2 signature stripping detection. + * + * <p>The attribute contains a comma-separated set of signature scheme IDs. + */ + private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed"; + + /** * List of accepted digest algorithms. This list is in order from most * preferred to least preferred. */ @@ -373,17 +385,17 @@ class StrictJarVerifier { return; } - // If requested, check whether APK Signature Scheme v2 signature was stripped. + // If requested, check whether a newer APK Signature Scheme signature was stripped. if (signatureSchemeRollbackProtectionsEnforced) { String apkSignatureSchemeIdList = - attributes.getValue( - ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME); + attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME); if (apkSignatureSchemeIdList != null) { // This field contains a comma-separated list of APK signature scheme IDs which // were used to sign this APK. If an ID is known to us, it means signatures of that // scheme were stripped from the APK because otherwise we wouldn't have fallen back // to verifying the APK using the JAR signature scheme. boolean v2SignatureGenerated = false; + boolean v3SignatureGenerated = false; StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ","); while (tokenizer.hasMoreTokens()) { String idText = tokenizer.nextToken().trim(); @@ -402,6 +414,12 @@ class StrictJarVerifier { v2SignatureGenerated = true; break; } + if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { + // This APK was supposed to be signed with APK Signature Scheme v3 but no + // such signature was found. + v3SignatureGenerated = true; + break; + } } if (v2SignatureGenerated) { @@ -409,6 +427,11 @@ class StrictJarVerifier { + " is signed using APK Signature Scheme v2, but no such signature was" + " found. Signature stripped?"); } + if (v3SignatureGenerated) { + throw new SecurityException(signatureFile + " indicates " + jarName + + " is signed using APK Signature Scheme v3, but no such signature was" + + " found. Signature stripped?"); + } } } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index d665dde39afe..1d94abeb51a2 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -26,6 +26,8 @@ import android.util.Pair; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import com.android.internal.util.Preconditions; + import java.util.List; /** @@ -204,6 +206,16 @@ public abstract class ViewStructure { public abstract void setTextLines(int[] charOffsets, int[] baselines); /** + * Sets the identifier used to set the text associated with this view. + * + * <p>Should only be set when the node is used for autofill purposes - it will be ignored + * when used for Assist. + */ + public void setTextIdEntry(@NonNull String entryName) { + Preconditions.checkNotNull(entryName); + } + + /** * Set optional hint text associated with this view; this is for example the text that is * shown by an EditText when it is empty to indicate to the user the kind of text to input. */ diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 1d19a9f5969a..98715395f976 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -350,21 +350,22 @@ import java.util.List; * view.</br> * </p> * <p> - * <b>Windows changed</b> - represents the event of changes in the windows shown on + * <b>Windows changed</b> - represents a change in the windows shown on * the screen such as a window appeared, a window disappeared, a window size changed, - * a window layer changed, etc.</br> + * a window layer changed, etc. These events should only come from the system, which is responsible + * for managing windows. For regions of the user interface that are presented as windows but are + * controlled by an app's process, use {@link #TYPE_WINDOW_STATE_CHANGED}.</br> * <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br> * <em>Properties:</em></br> * <ul> * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getWindowChanges()}</li> - The specific change to the source window * </ul> * <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window - * source of the event via {@link AccessibilityEvent#getSource()} to get the source - * node on which then call {@link AccessibilityNodeInfo#getWindow() - * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can - * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows() - * android.accessibilityservice.AccessibilityService.getWindows()}. + * source of the event by looking through the list returned by + * {@link android.accessibilityservice.AccessibilityService#getWindows()} for the window whose ID + * matches {@link #getWindowId()}. * </p> * <p> * <b>NOTIFICATION TYPES</b></br> @@ -712,6 +713,88 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window was added. + */ + public static final int WINDOWS_CHANGE_ADDED = 0x00000001; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * A window was removed. + */ + public static final int WINDOWS_CHANGE_REMOVED = 0x00000002; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's title changed. + */ + public static final int WINDOWS_CHANGE_TITLE = 0x00000004; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's bounds changed. + */ + public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's layer changed. + */ + public static final int WINDOWS_CHANGE_LAYER = 0x00000010; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's {@link AccessibilityWindowInfo#isActive()} changed. + */ + public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's {@link AccessibilityWindowInfo#isFocused()} changed. + */ + public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed. + */ + public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's parent changed. + */ + public static final int WINDOWS_CHANGE_PARENT = 0x00000100; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's children changed. + */ + public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window either entered or exited picture-in-picture mode. + */ + public static final int WINDOWS_CHANGE_PIP = 0x00000400; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "WINDOWS_CHANGE_" }, value = { + WINDOWS_CHANGE_ADDED, + WINDOWS_CHANGE_REMOVED, + WINDOWS_CHANGE_TITLE, + WINDOWS_CHANGE_BOUNDS, + WINDOWS_CHANGE_LAYER, + WINDOWS_CHANGE_ACTIVE, + WINDOWS_CHANGE_FOCUSED, + WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED, + WINDOWS_CHANGE_PARENT, + WINDOWS_CHANGE_CHILDREN, + WINDOWS_CHANGE_PIP + }) + public @interface WindowsChangeTypes {} /** @hide */ @IntDef(flag = true, prefix = { "TYPE_" }, value = { @@ -782,6 +865,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par int mMovementGranularity; int mAction; int mContentChangeTypes; + int mWindowChangeTypes; private ArrayList<AccessibilityRecord> mRecords; @@ -802,6 +886,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mMovementGranularity = event.mMovementGranularity; mAction = event.mAction; mContentChangeTypes = event.mContentChangeTypes; + mWindowChangeTypes = event.mWindowChangeTypes; mEventTime = event.mEventTime; mPackageName = event.mPackageName; } @@ -919,6 +1004,43 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Get the bit mask of change types signaled by a {@link #TYPE_WINDOWS_CHANGED} event. A + * single event may represent multiple change types. + * + * @return The bit mask of change types. + */ + @WindowsChangeTypes + public int getWindowChanges() { + return mWindowChangeTypes; + } + + /** @hide */ + public void setWindowChanges(@WindowsChangeTypes int changes) { + mWindowChangeTypes = changes; + } + + private static String windowChangeTypesToString(@WindowsChangeTypes int types) { + return BitUtils.flagsToString(types, AccessibilityEvent::singleWindowChangeTypeToString); + } + + private static String singleWindowChangeTypeToString(int type) { + switch (type) { + case WINDOWS_CHANGE_ADDED: return "WINDOWS_CHANGE_ADDED"; + case WINDOWS_CHANGE_REMOVED: return "WINDOWS_CHANGE_REMOVED"; + case WINDOWS_CHANGE_TITLE: return "WINDOWS_CHANGE_TITLE"; + case WINDOWS_CHANGE_BOUNDS: return "WINDOWS_CHANGE_BOUNDS"; + case WINDOWS_CHANGE_LAYER: return "WINDOWS_CHANGE_LAYER"; + case WINDOWS_CHANGE_ACTIVE: return "WINDOWS_CHANGE_ACTIVE"; + case WINDOWS_CHANGE_FOCUSED: return "WINDOWS_CHANGE_FOCUSED"; + case WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED: + return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED"; + case WINDOWS_CHANGE_PARENT: return "WINDOWS_CHANGE_PARENT"; + case WINDOWS_CHANGE_CHILDREN: return "WINDOWS_CHANGE_CHILDREN"; + default: return Integer.toHexString(type); + } + } + + /** * Sets the event type. * * @param eventType The event type. @@ -1025,6 +1147,26 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Convenience method to obtain a {@link #TYPE_WINDOWS_CHANGED} event for a specific window and + * change set. + * + * @param windowId The ID of the window that changed + * @param windowChangeTypes The changes to populate + * @return An instance of a TYPE_WINDOWS_CHANGED, populated with the requested fields and with + * importantForAccessibility set to {@code true}. + * + * @hide + */ + public static AccessibilityEvent obtainWindowsChangedEvent( + int windowId, int windowChangeTypes) { + final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED); + event.setWindowId(windowId); + event.setWindowChanges(windowChangeTypes); + event.setImportantForAccessibility(true); + return event; + } + + /** * Returns a cached instance if such is available or a new one is * instantiated with its type property set. * @@ -1099,6 +1241,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mMovementGranularity = 0; mAction = 0; mContentChangeTypes = 0; + mWindowChangeTypes = 0; mPackageName = null; mEventTime = 0; if (mRecords != null) { @@ -1120,6 +1263,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mMovementGranularity = parcel.readInt(); mAction = parcel.readInt(); mContentChangeTypes = parcel.readInt(); + mWindowChangeTypes = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); mConnectionId = parcel.readInt(); @@ -1178,6 +1322,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(mMovementGranularity); parcel.writeInt(mAction); parcel.writeInt(mContentChangeTypes); + parcel.writeInt(mWindowChangeTypes); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); parcel.writeInt(mConnectionId); @@ -1238,11 +1383,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("; PackageName: ").append(mPackageName); builder.append("; MovementGranularity: ").append(mMovementGranularity); builder.append("; Action: ").append(mAction); + builder.append("; ContentChangeTypes: ").append( + contentChangeTypesToString(mContentChangeTypes)); + builder.append("; WindowChangeTypes: ").append( + windowChangeTypesToString(mWindowChangeTypes)); builder.append(super.toString()); if (DEBUG) { builder.append("\n"); - builder.append("; ContentChangeTypes: ").append( - contentChangeTypesToString(mContentChangeTypes)); builder.append("; sourceWindowId: ").append(mSourceWindowId); builder.append("; mSourceNodeId: ").append(mSourceNodeId); for (int i = 0; i < getRecordCount(); i++) { diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index ef1a3f3bcc8f..c1c9174c0f9f 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -21,9 +21,12 @@ import android.annotation.TestApi; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.LongArray; import android.util.Pools.SynchronizedPool; +import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; /** @@ -575,7 +578,7 @@ public final class AccessibilityWindowInfo implements Parcelable { StringBuilder builder = new StringBuilder(); builder.append("AccessibilityWindowInfo["); builder.append("title=").append(mTitle); - builder.append("id=").append(mId); + builder.append(", id=").append(mId); builder.append(", type=").append(typeToString(mType)); builder.append(", layer=").append(mLayer); builder.append(", bounds=").append(mBoundsInScreen); @@ -713,6 +716,60 @@ public final class AccessibilityWindowInfo implements Parcelable { return false; } + /** + * Reports how this window differs from a possibly different state of the same window. The + * argument must have the same id and type as neither of those properties may change. + * + * @param other The new state. + * @return A set of flags showing how the window has changes, or 0 if the two states are the + * same. + * + * @hide + */ + @WindowsChangeTypes + public int differenceFrom(AccessibilityWindowInfo other) { + if (other.mId != mId) { + throw new IllegalArgumentException("Not same window."); + } + if (other.mType != mType) { + throw new IllegalArgumentException("Not same type."); + } + int changes = 0; + if (!TextUtils.equals(mTitle, other.mTitle)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_TITLE; + } + + if (!mBoundsInScreen.equals(other.mBoundsInScreen)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_BOUNDS; + } + if (mLayer != other.mLayer) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_LAYER; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE) + != other.getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_ACTIVE; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED) + != other.getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_FOCUSED; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED) + != other.getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE) + != other.getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_PIP; + } + if (mParentId != other.mParentId) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_PARENT; + } + if (!Objects.equals(mChildIds, other.mChildIds)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_CHILDREN; + } + return changes; + } + public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR = new Creator<AccessibilityWindowInfo>() { @Override diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java index 63519a655113..03ff0ca63282 100644 --- a/core/java/android/webkit/UserPackage.java +++ b/core/java/android/webkit/UserPackage.java @@ -34,7 +34,7 @@ public class UserPackage { private final UserInfo mUserInfo; private final PackageInfo mPackageInfo; - public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1; + public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.P; public UserPackage(UserInfo user, PackageInfo packageInfo) { this.mUserInfo = user; diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index e3efad024341..b3522ec94c0c 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -47,7 +47,7 @@ public final class WebViewFactory { // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote. /** @hide */ private static final String CHROMIUM_WEBVIEW_FACTORY = - "com.android.webview.chromium.WebViewChromiumFactoryProviderForOMR1"; + "com.android.webview.chromium.WebViewChromiumFactoryProviderForP"; private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create"; diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 56c3e4a5ee77..336c20cdcdc0 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -105,6 +105,11 @@ public class EditText extends TextView { @Override public Editable getText() { + CharSequence text = super.getText(); + if (text instanceof Editable) { + return (Editable) super.getText(); + } + super.setText(text, BufferType.EDITABLE); return (Editable) super.getText(); } diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index bd48f4554c5d..26dfcc2d668a 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -125,7 +125,7 @@ public final class Magnifier { mView.getWidth() - mBitmap.getWidth())); final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; - if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) { + if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) { performPixelCopy(startX, startY); mPrevPosInView.x = xPosInView; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1e17f34af2a6..1618d620738f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -44,6 +44,7 @@ import android.content.UndoManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.ResourceId; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -785,9 +786,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // mAutoSizeStepGranularityInPx. private boolean mHasPresetAutoSizeValues = false; - // Indicates whether the text was set from resources or dynamically, so it can be used to + // Indicates whether the text was set statically or dynamically, so it can be used to // sanitize autofill requests. - private boolean mTextFromResource = false; + private boolean mSetFromXmlOrResourceId = false; + // Resource id used to set the text - used for autofill purposes. + private @StringRes int mTextId = ResourceId.ID_NULL; /** * Kick-start the font cache for the zygote process (to pay the cost of @@ -926,7 +929,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int n = a.getIndexCount(); - boolean fromResourceId = false; + // Must set id in a temporary variable because it will be reset by setText() + boolean setFromXml = false; for (int i = 0; i < n; i++) { int attr = a.getIndex(i); @@ -1068,7 +1072,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_text: - fromResourceId = true; + setFromXml = true; + mTextId = a.getResourceId(attr, ResourceId.ID_NULL); text = a.getText(attr); break; @@ -1460,8 +1465,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } setText(text, bufferType); - if (fromResourceId) { - mTextFromResource = true; + if (setFromXml) { + mSetFromXmlOrResourceId = true; } if (hint != null) setHint(hint); @@ -5278,7 +5283,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) { - mTextFromResource = false; + mSetFromXmlOrResourceId = false; if (text == null) { text = ""; } @@ -5516,7 +5521,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @android.view.RemotableViewMethod public final void setText(@StringRes int resid) { setText(getContext().getResources().getText(resid)); - mTextFromResource = true; + mSetFromXmlOrResourceId = true; + mTextId = resid; } /** @@ -5543,7 +5549,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public final void setText(@StringRes int resid, BufferType type) { setText(getContext().getResources().getText(resid), type); - mTextFromResource = true; + mSetFromXmlOrResourceId = true; + mTextId = resid; } /** @@ -10234,7 +10241,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean isPassword = hasPasswordTransformationMethod() || isPasswordInputType(getInputType()); if (forAutofill) { - structure.setDataIsSensitive(!mTextFromResource); + structure.setDataIsSensitive(!mSetFromXmlOrResourceId); + if (mTextId != ResourceId.ID_NULL) { + structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); + } } if (!isPassword || forAutofill) { diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 46f47a31441c..d13824152414 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -199,7 +199,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { text.setTextLocale(item.getLocale()); text.setContentDescription(item.getContentDescription(mCountryMode)); if (mCountryMode) { - int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent()); + int layoutDir = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); //noinspection ResourceType convertView.setLayoutDirection(layoutDir); text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL diff --git a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl index 7e88369055b8..ee01a23af686 100644 --- a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl +++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl @@ -22,5 +22,6 @@ import android.os.SharedMemory; interface INetworkWatchlistManager { boolean startWatchlistLogging(); boolean stopWatchlistLogging(); + void reloadWatchlist(); void reportWatchlistIfNecessary(); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 9ab16d8848fd..1739bed7112f 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -45,6 +45,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; +import android.os.WorkSource.WorkChain; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; @@ -79,6 +80,7 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; +import java.util.List; import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -504,8 +506,8 @@ public class BatteryStatsImpl extends BatteryStats { final HistoryEventTracker mActiveEvents = new HistoryEventTracker(); long mHistoryBaseTime; - boolean mHaveBatteryLevel = false; - boolean mRecordingHistory = false; + protected boolean mHaveBatteryLevel = false; + protected boolean mRecordingHistory = false; int mNumHistoryItems; final Parcel mHistoryBuffer = Parcel.obtain(); @@ -650,6 +652,14 @@ public class BatteryStatsImpl extends BatteryStats { new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; /** + * The WiFi Overall wakelock timer + * This timer tracks the actual aggregate time for which MC wakelocks are enabled + * since addition of per UID timers would not result in an accurate value due to overlapp of + * per uid wakelock timers + */ + StopwatchTimer mWifiMulticastWakelockTimer; + + /** * The WiFi controller activity (time in tx, rx, idle, and power consumed) for the device. */ ControllerActivityCounterImpl mWifiActivity; @@ -3973,30 +3983,85 @@ public class BatteryStatsImpl extends BatteryStats { addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid); } - public void noteAlarmStartLocked(String name, int uid) { + public void noteAlarmStartLocked(String name, WorkSource workSource, int uid) { + noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_START, name, workSource, uid); + } + + public void noteAlarmFinishLocked(String name, WorkSource workSource, int uid) { + noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_FINISH, name, workSource, uid); + } + + private void noteAlarmStartOrFinishLocked(int historyItem, String name, WorkSource workSource, + int uid) { if (!mRecordAllHistory) { return; } - uid = mapUid(uid); + final long elapsedRealtime = mClocks.elapsedRealtime(); final long uptime = mClocks.uptimeMillis(); - if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_START, name, uid, 0)) { - return; + + if (workSource != null) { + for (int i = 0; i < workSource.size(); ++i) { + uid = mapUid(workSource.get(i)); + if (mActiveEvents.updateState(historyItem, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid); + } + } + + List<WorkChain> workChains = workSource.getWorkChains(); + if (workChains != null) { + for (int i = 0; i < workChains.size(); ++i) { + uid = mapUid(workChains.get(i).getAttributionUid()); + if (mActiveEvents.updateState(historyItem, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid); + } + } + } + } else { + uid = mapUid(uid); + + if (mActiveEvents.updateState(historyItem, name, uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid); + } } - addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_START, name, uid); } - public void noteAlarmFinishLocked(String name, int uid) { - if (!mRecordAllHistory) { + public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource, + String tag) { + if (!isOnBattery()) { return; } - uid = mapUid(uid); - final long elapsedRealtime = mClocks.elapsedRealtime(); - final long uptime = mClocks.uptimeMillis(); - if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_FINISH, name, uid, 0)) { - return; + + if (workSource != null) { + for (int i = 0; i < workSource.size(); ++i) { + uid = workSource.get(i); + final String workSourceName = workSource.getName(i); + + BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, + workSourceName != null ? workSourceName : packageName); + pkg.noteWakeupAlarmLocked(tag); + + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag); + } + + ArrayList<WorkChain> workChains = workSource.getWorkChains(); + if (workChains != null) { + for (int i = 0; i < workChains.size(); ++i) { + final WorkChain wc = workChains.get(i); + uid = wc.getAttributionUid(); + + BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName); + pkg.noteWakeupAlarmLocked(tag); + + // TODO(statsd): Log the full attribution chain here once it's available + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag); + } + } + } else { + BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName); + pkg.noteWakeupAlarmLocked(tag); + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag); } - addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_FINISH, name, uid); } private void requestWakelockCpuUpdate() { @@ -4064,8 +4129,8 @@ public class BatteryStatsImpl extends BatteryStats { private String mInitialAcquireWakeName; private int mInitialAcquireWakeUid = -1; - public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type, - boolean unimportantForLogging, long elapsedRealtime, long uptime) { + public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName, + int type, boolean unimportantForLogging, long elapsedRealtime, long uptime) { uid = mapUid(uid); if (type == WAKE_TYPE_PARTIAL) { // Only care about partial wake locks, since full wake locks @@ -4113,12 +4178,18 @@ public class BatteryStatsImpl extends BatteryStats { } requestWakelockCpuUpdate(); } + getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime); + + if (wc != null) { + StatsLog.write( + StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1); + } } } - public void noteStopWakeLocked(int uid, int pid, String name, String historyName, int type, - long elapsedRealtime, long uptime) { + public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName, + int type, long elapsedRealtime, long uptime) { uid = mapUid(uid); if (type == WAKE_TYPE_PARTIAL) { mWakeLockNesting--; @@ -4148,7 +4219,12 @@ public class BatteryStatsImpl extends BatteryStats { } requestWakelockCpuUpdate(); } + getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime); + if (wc != null) { + StatsLog.write( + StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0); + } } } @@ -4158,8 +4234,17 @@ public class BatteryStatsImpl extends BatteryStats { final long uptime = mClocks.uptimeMillis(); final int N = ws.size(); for (int i=0; i<N; i++) { - noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging, - elapsedRealtime, uptime); + noteStartWakeLocked(ws.get(i), pid, null, name, historyName, type, + unimportantForLogging, elapsedRealtime, uptime); + } + + List<WorkChain> wcs = ws.getWorkChains(); + if (wcs != null) { + for (int i = 0; i < wcs.size(); ++i) { + final WorkChain wc = wcs.get(i); + noteStartWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type, + unimportantForLogging, elapsedRealtime, uptime); + } } } @@ -4168,17 +4253,46 @@ public class BatteryStatsImpl extends BatteryStats { String newHistoryName, int newType, boolean newUnimportantForLogging) { final long elapsedRealtime = mClocks.elapsedRealtime(); final long uptime = mClocks.uptimeMillis(); + + List<WorkChain>[] wcs = WorkSource.diffChains(ws, newWs); + // For correct semantics, we start the need worksources first, so that we won't // make inappropriate history items as if all wake locks went away and new ones // appeared. This is okay because tracking of wake locks allows nesting. + // + // First the starts : final int NN = newWs.size(); for (int i=0; i<NN; i++) { - noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType, + noteStartWakeLocked(newWs.get(i), newPid, null, newName, newHistoryName, newType, newUnimportantForLogging, elapsedRealtime, uptime); } + if (wcs != null) { + List<WorkChain> newChains = wcs[0]; + if (newChains != null) { + for (int i = 0; i < newChains.size(); ++i) { + final WorkChain newChain = newChains.get(i); + noteStartWakeLocked(newChain.getAttributionUid(), newPid, newChain, newName, + newHistoryName, newType, newUnimportantForLogging, elapsedRealtime, + uptime); + } + } + } + + // Then the stops : final int NO = ws.size(); for (int i=0; i<NO; i++) { - noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime); + noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime, + uptime); + } + if (wcs != null) { + List<WorkChain> goneChains = wcs[1]; + if (goneChains != null) { + for (int i = 0; i < goneChains.size(); ++i) { + final WorkChain goneChain = goneChains.get(i); + noteStopWakeLocked(goneChain.getAttributionUid(), pid, goneChain, name, + historyName, type, elapsedRealtime, uptime); + } + } } } @@ -4188,7 +4302,17 @@ public class BatteryStatsImpl extends BatteryStats { final long uptime = mClocks.uptimeMillis(); final int N = ws.size(); for (int i=0; i<N; i++) { - noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime); + noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime, + uptime); + } + + List<WorkChain> wcs = ws.getWorkChains(); + if (wcs != null) { + for (int i = 0; i < wcs.size(); ++i) { + final WorkChain wc = wcs.get(i); + noteStopWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type, + elapsedRealtime, uptime); + } } } @@ -4432,10 +4556,10 @@ public class BatteryStatsImpl extends BatteryStats { updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state, mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000); // Fake a wake lock, so we consider the device waked as long as the screen is on. - noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false, + noteStartWakeLocked(-1, -1, null, "screen", null, WAKE_TYPE_PARTIAL, false, elapsedRealtime, uptime); } else if (isScreenOn(oldState)) { - noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL, + noteStopWakeLocked(-1, -1, null, "screen", "screen", WAKE_TYPE_PARTIAL, elapsedRealtime, uptime); updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state, mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000); @@ -5473,6 +5597,12 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + + // Start Wifi Multicast overall timer + if (!mWifiMulticastWakelockTimer.isRunningLocked()) { + if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started"); + mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtime); + } } mWifiMulticastNesting++; getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime); @@ -5488,6 +5618,12 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); + + // Stop Wifi Multicast overall timer + if (mWifiMulticastWakelockTimer.isRunningLocked()) { + if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped"); + mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtime); + } } getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime); } @@ -5774,6 +5910,16 @@ public class BatteryStatsImpl extends BatteryStats { return (int)mMobileRadioActiveUnknownCount.getCountLocked(which); } + @Override public long getWifiMulticastWakelockTime( + long elapsedRealtimeUs, int which) { + return mWifiMulticastWakelockTimer.getTotalTimeLocked( + elapsedRealtimeUs, which); + } + + @Override public int getWifiMulticastWakelockCount(int which) { + return mWifiMulticastWakelockTimer.getCountLocked(which); + } + @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) { return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -9227,8 +9373,6 @@ public class BatteryStatsImpl extends BatteryStats { Wakelock wl = mWakelockStats.startObject(name); if (wl != null) { getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs); - // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere) - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1); } if (type == WAKE_TYPE_PARTIAL) { createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs); @@ -9247,8 +9391,6 @@ public class BatteryStatsImpl extends BatteryStats { StopwatchTimer wlt = getWakelockTimerLocked(wl, type); wlt.stopRunningLocked(elapsedRealtimeMs); if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped - // TODO(statsd): Possibly use a worksource instead of a uid. - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0); } } if (type == WAKE_TYPE_PARTIAL) { @@ -9378,6 +9520,8 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase); mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase); mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase); + mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null, + WIFI_AGGREGATE_MULTICAST_ENABLED, null, mOnBatteryTimeBase); mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase); mGlobalWifiRunningTimer = new StopwatchTimer(mClocks, null, -5, null, mOnBatteryTimeBase); for (int i=0; i<NUM_WIFI_STATES; i++) { @@ -10079,6 +10223,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].reset(false); } + mWifiMulticastWakelockTimer.reset(false); mWifiActivity.reset(false); mBluetoothActivity.reset(false); mModemActivity.reset(false); @@ -12525,6 +12670,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in); mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in); mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in); + mWifiMulticastWakelockTimer.readSummaryFromParcelLocked(in); mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mWifiOn = false; mWifiOnTimer.readSummaryFromParcelLocked(in); @@ -12965,6 +13111,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out); mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out); mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out); + mWifiMulticastWakelockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); for (int i=0; i<NUM_WIFI_STATES; i++) { @@ -13428,6 +13575,8 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in); mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in); mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in); + mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null, -4, null, + mOnBatteryTimeBase, in); mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mWifiOn = false; mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase, in); @@ -13634,6 +13783,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.writeToParcel(out); mMobileRadioActiveUnknownTime.writeToParcel(out); mMobileRadioActiveUnknownCount.writeToParcel(out); + mWifiMulticastWakelockTimer.writeToParcel(out, uSecRealtime); mWifiOnTimer.writeToParcel(out, uSecRealtime); mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime); for (int i=0; i<NUM_WIFI_STATES; i++) { @@ -13820,6 +13970,8 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveTimer.logState(pr, " "); pr.println("*** Mobile network active adjusted timer:"); mMobileRadioActiveAdjustedTime.logState(pr, " "); + pr.println("*** Wifi Multicast WakeLock Timer:"); + mWifiMulticastWakelockTimer.logState(pr, " "); pr.println("*** mWifiRadioPowerState=" + mWifiRadioPowerState); pr.println("*** Wifi timer:"); mWifiOnTimer.logState(pr, " "); diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java index 17b140dec396..87c25e9663d8 100755 --- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java +++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java @@ -59,6 +59,21 @@ import java.util.function.Supplier; * You can fill the 'missing argument' spot with {@link #__()} * (which is the factory function for {@link ArgumentPlaceholder}) * + * NOTE: It is highly recommended to <b>only</b> use {@code ClassName::methodName} + * (aka unbounded method references) as the 1st argument for any of the + * factories ({@code obtain*(...)}) to avoid unwanted allocations. + * This means <b>not</b> using: + * <ul> + * <li>{@code someVar::methodName} or {@code this::methodName} as it captures the reference + * on the left of {@code ::}, resulting in an allocation on each evaluation of such + * bounded method references</li> + * + * <li>A lambda expression, e.g. {@code () -> toString()} due to how easy it is to accidentally + * capture state from outside. In the above lambda expression for example, no variable from + * outer scope is explicitly mentioned, yet one is still captured due to {@code toString()} + * being an equivalent of {@code this.toString()}</li> + * </ul> + * * @hide */ @SuppressWarnings({"unchecked", "unused", "WeakerAccess"}) diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java index 82affe2f86b1..ab8be33599fa 100644 --- a/core/java/com/android/internal/widget/ButtonBarLayout.java +++ b/core/java/com/android/internal/widget/ButtonBarLayout.java @@ -30,9 +30,6 @@ import com.android.internal.R; * orientation when it can't fit its child views horizontally. */ public class ButtonBarLayout extends LinearLayout { - /** Minimum screen height required for button stacking. */ - private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320; - /** Amount of the second button to "peek" above the fold when stacked. */ private static final int PEEK_BUTTON_DP = 16; @@ -46,12 +43,8 @@ public class ButtonBarLayout extends LinearLayout { public ButtonBarLayout(Context context, AttributeSet attrs) { super(context, attrs); - final boolean allowStackingDefault = - context.getResources().getConfiguration().screenHeightDp - >= ALLOW_STACKING_MIN_HEIGHT_DP; final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout); - mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, - allowStackingDefault); + mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true); ta.recycle(); } diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 43536a522a47..77250eb01731 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -66,6 +66,7 @@ interface ILockSettings { void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList, int userId); KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId); + byte[] generateAndStoreKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId); Map getRecoverySnapshotVersions(int userId); void setServerParameters(long serverParameters, int userId); @@ -78,6 +79,6 @@ interface ILockSettings { byte[] startRecoverySession(in String sessionId, in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge, in List<KeyStoreRecoveryMetadata> secrets, int userId); - void recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, + Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, in List<KeyEntryRecoveryData> applicationKeys, int userId); } diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto index 0fa428e9c114..9999b4e26a68 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -537,7 +537,24 @@ message UidProto { // Screen-off CPU time in milliseconds. optional int64 screen_off_duration_ms = 3; } + // CPU times accumulated across all process states. repeated ByFrequency by_frequency = 3; + + enum ProcessState { + TOP = 0; + FOREGROUND_SERVICE = 1; + FOREGROUND = 2; + BACKGROUND = 3; + TOP_SLEEPING = 4; + HEAVY_WEIGHT = 5; + CACHED = 6; + } + // CPU times at different process states. + message ByProcessState { + optional ProcessState process_state = 1; + repeated ByFrequency by_frequency = 2; + } + repeated ByProcessState by_process_state = 4; } optional Cpu cpu = 7; @@ -655,7 +672,7 @@ message UidProto { // In approximate order or priority (top being what the framework considers // most important and is thus least likely to kill when resources are // needed: - // top > foreground service > top sleeping > foreground > background > cache + // top > foreground service > foreground > background > top sleeping > heavy weight > cache enum State { // Time this uid has any processes in the top state (or above such as // persistent). @@ -663,20 +680,26 @@ message UidProto { // Time this uid has any process with a started out bound foreground // service, but none in the "top" state. PROCESS_STATE_FOREGROUND_SERVICE = 1; - // Time this uid has any process that is top while the device is sleeping, - // but none in the "foreground service" or better state. Sleeping is - // mostly screen off, but also includes the time when the screen is on but - // the device has not yet been unlocked. - PROCESS_STATE_TOP_SLEEPING = 2; // Time this uid has any process in an active foreground state, but none // in the "top sleeping" or better state. - PROCESS_STATE_FOREGROUND = 3; + PROCESS_STATE_FOREGROUND = 2; // Time this uid has any process in an active background state, but none // in the "foreground" or better state. - PROCESS_STATE_BACKGROUND = 4; + PROCESS_STATE_BACKGROUND = 3; + // Time this uid has any process that is top while the device is sleeping, + // but not active for any other reason. We consider is a kind of cached + // process for execution restrictions. Sleeping is mostly screen off, but + // also includes the time when the screen is on but the device has not yet + // been unlocked. + PROCESS_STATE_TOP_SLEEPING = 4; + // Time this uid has any process that is in the background but it has an + // activity marked as "can't save state". This is essentially a cached + // process, though the system will try much harder than normal to avoid + // killing it. + PROCESS_STATE_HEAVY_WEIGHT = 5; // Time this uid has any processes that are sitting around cached, not in // one of the other active states. - PROCESS_STATE_CACHED = 5; + PROCESS_STATE_CACHED = 6; } optional State state = 1; optional int64 duration_ms = 2; diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto new file mode 100644 index 000000000000..75d0dd3504e3 --- /dev/null +++ b/core/proto/android/os/batterytype.proto @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.os; + +option java_multiple_files = true; + +message BatteryTypeProto { + optional string type = 1; +} diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto index 522ff24c1e60..cd151e253e7a 100644 --- a/core/proto/android/os/cpuinfo.proto +++ b/core/proto/android/os/cpuinfo.proto @@ -81,7 +81,9 @@ message CpuInfo { optional string virt = 8; // virtual memory size, i.e. 14.0G, 13.5M optional string res = 9; // Resident size, i.e. 0, 3.1G - // How Android memory manager will treat the task + // How Android memory manager will treat the task. + // TODO: use PsDumpProto.Process.Policy instead once we extern variables + // and are able to include the same .h file in two files. enum Policy { POLICY_UNKNOWN = 0; POLICY_fg = 1; // foreground, the name is lower case for parsing the value diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index ac8f26d94ca2..a6db31f67b33 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -18,12 +18,14 @@ syntax = "proto2"; option java_multiple_files = true; option java_outer_classname = "IncidentProtoMetadata"; +import "frameworks/base/core/proto/android/os/batterytype.proto"; import "frameworks/base/core/proto/android/os/cpufreq.proto"; import "frameworks/base/core/proto/android/os/cpuinfo.proto"; import "frameworks/base/core/proto/android/os/incidentheader.proto"; import "frameworks/base/core/proto/android/os/kernelwake.proto"; import "frameworks/base/core/proto/android/os/pagetypeinfo.proto"; import "frameworks/base/core/proto/android/os/procrank.proto"; +import "frameworks/base/core/proto/android/os/ps.proto"; import "frameworks/base/core/proto/android/os/system_properties.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/activitymanagerservice.proto"; @@ -65,7 +67,7 @@ message IncidentProto { // Device information optional SystemPropertiesProto system_properties = 1000 [ (section).type = SECTION_COMMAND, - (section).args = "/system/bin/getprop" + (section).args = "getprop" ]; // Linux services @@ -86,7 +88,7 @@ message IncidentProto { optional CpuInfo cpu_info = 2003 [ (section).type = SECTION_COMMAND, - (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name" + (section).args = "top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name" ]; optional CpuFreq cpu_freq = 2004 [ @@ -94,6 +96,16 @@ message IncidentProto { (section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state" ]; + optional PsDumpProto processes_and_threads = 2005 [ + (section).type = SECTION_COMMAND, + (section).args = "ps -A -T -Z -O pri,nice,rtprio,sched,pcy,time" + ]; + + optional BatteryTypeProto battery_type = 2006 [ + (section).type = SECTION_FILE, + (section).args = "/sys/class/power_supply/bms/battery_type" + ]; + // System Services optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [ (section).type = SECTION_DUMPSYS, diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto new file mode 100644 index 000000000000..88c6609a92b6 --- /dev/null +++ b/core/proto/android/os/ps.proto @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.os; + +option java_multiple_files = true; + +message PsDumpProto { + message Process { + // Security label, most commonly used for SELinux context data. + optional string label = 1; + optional string user = 2; + // Process ID number. + optional int32 pid = 3; + // The unique number representing a dispatchable entity (alias lwp, + // spid). This value may also appear as: a process ID (pid); a process + // group ID (pgrp); a session ID for the session leader (sid); a thread + // group ID for the thread group leader (tgid); and a tty process group + // ID for the process group leader (tpgid). + optional int32 tid = 4; + // Parent process ID. + optional int32 ppid = 5; + // Virtual set size (memory size) of the process, in KiB. + optional int32 vsz = 6; + // Resident set size. How many physical pages are associated with the + // process; real memory usage, in KiB. + optional int32 rss = 7; + // Name of the kernel function in which the process is sleeping, a "-" + // if the process is running, or a "*" if the process is multi-threaded + // and ps is not displaying threads. + optional string wchan = 8; + // Memory address of the process. + optional string addr = 9; + + enum ProcessStateCode { + STATE_UNKNOWN = 0; + // Uninterruptible sleep (usually IO). + STATE_D = 1; + // Running or runnable (on run queue). + STATE_R = 2; + // Interruptible sleep (waiting for an event to complete). + STATE_S = 3; + // Stopped by job control signal. + STATE_T = 4; + // Stopped by debugger during the tracing. + STATE_TRACING = 5; + // Dead (should never be seen). + STATE_X = 6; + // Defunct ("zombie") process. Terminated but not reaped by its + // parent. + STATE_Z = 7; + } + // Minimal state display + optional ProcessStateCode s = 10; + // Priority of the process. Higher number means lower priority. + optional int32 pri = 11; + // Nice value. This ranges from 19 (nicest) to -20 (not nice to others). + optional sint32 ni = 12; + // Realtime priority. + optional string rtprio = 13; // Number or - + + enum SchedulingPolicy { + option allow_alias = true; + + // Regular names conflict with macros defined in + // bionic/libc/kernel/uapi/linux/sched.h. + SCH_OTHER = 0; + SCH_NORMAL = 0; + + SCH_FIFO = 1; + SCH_RR = 2; + SCH_BATCH = 3; + SCH_ISO = 4; + SCH_IDLE = 5; + } + // Scheduling policy of the process. + optional SchedulingPolicy sch = 14; + + // How Android memory manager will treat the task + enum Policy { + POLICY_UNKNOWN = 0; + // Foreground. + POLICY_FG = 1; + // Background. + POLICY_BG = 2; + POLICY_TA = 3; // TODO: figure out what this value is + } + optional Policy pcy = 15; + // Total CPU time, "[DD-]HH:MM:SS" format. + optional string time = 16; + // Command with all its arguments as a string. + optional string cmd = 17; + } + repeated Process processes = 1; +} diff --git a/core/proto/android/os/worksource.proto b/core/proto/android/os/worksource.proto index c60edfc3862c..2f8b2fbbaa2b 100644 --- a/core/proto/android/os/worksource.proto +++ b/core/proto/android/os/worksource.proto @@ -25,5 +25,10 @@ message WorkSourceProto { optional string name = 2; } + message WorkChain { + repeated WorkSourceContentProto nodes = 1; + } + repeated WorkSourceContentProto work_source_contents = 1; -}
\ No newline at end of file + repeated WorkChain work_chains = 2; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 86d2ee3cd081..52bfcdebf436 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3534,11 +3534,19 @@ @hide --> <permission android:name="android.permission.ACCESS_INSTANT_APPS" android:protectionLevel="signature|installer|verifier" /> + <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/> <!-- Allows the holder to view the instant applications on the device. @hide --> <permission android:name="android.permission.VIEW_INSTANT_APPS" - android:protectionLevel="signature|preinstalled" /> + android:protectionLevel="signature|preinstalled" /> + + <!-- Allows the holder to manage whether the system can bind to services + provided by instant apps. This permission is intended to protect + test/development fucntionality and should be used only in such cases. + @hide --> + <permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE" + android:protectionLevel="signature" /> <!-- Allows receiving the usage of media resource e.g. video/audio codec and graphic memory. @@ -3862,6 +3870,14 @@ </intent-filter> </receiver> + <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver" + android:permission="android.permission.UPDATE_CONFIG"> + <intent-filter> + <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" /> + <data android:scheme="content" android:host="*" android:mimeType="*/*" /> + </intent-filter> + </receiver> + <receiver android:name="com.android.server.updates.ApnDbInstallReceiver" android:permission="android.permission.UPDATE_CONFIG"> <intent-filter> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 64febf1f7ee4..1b3d6ce36247 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8699,8 +8699,11 @@ common values are 400 for regular weight and 700 for bold weight. If unspecified, the value in the font's header tables will be used. --> <attr name="fontWeight" format="integer" /> - <!-- The index of the font in the tcc font file. If the font file referenced is not in the - tcc format, this attribute needs not be specified. --> + <!-- The index of the font in the ttc (TrueType Collection) font file. If the font file + referenced is not in the ttc format, this attribute needs not be specified. + {@see android.graphics.Typeface#Builder.setTtcIndex(int)}. + The default value is 0. More details about the TrueType Collection font format can be found + here: https://en.wikipedia.org/wiki/TrueType#TrueType_Collection. --> <attr name="ttcIndex" format="integer" /> <!-- The variation settings to be applied to the font. The string should be in the following format: "'tag1' value1, 'tag2' value2, ...". If the default variation settings should be diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml index e2498df71e45..70485111d208 100644 --- a/core/res/res/values/colors_device_defaults.xml +++ b/core/res/res/values/colors_device_defaults.xml @@ -31,10 +31,8 @@ <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color> <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color> - <color name="accent_device_default_700">@color/accent_material_700</color> <color name="accent_device_default_light">@color/accent_material_light</color> <color name="accent_device_default_dark">@color/accent_material_dark</color> - <color name="accent_device_default_50">@color/accent_material_50</color> <color name="background_device_default_dark">@color/background_material_dark</color> <color name="background_device_default_light">@color/background_material_light</color> diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index 8c37d4b053a1..04131009b141 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -39,10 +39,8 @@ <color name="tertiary_material_settings">@color/material_blue_grey_700</color> <color name="quaternary_material_settings">@color/material_blue_grey_200</color> - <color name="accent_material_700">@color/material_deep_teal_700</color> <color name="accent_material_light">@color/material_deep_teal_500</color> <color name="accent_material_dark">@color/material_deep_teal_200</color> - <color name="accent_material_50">@color/material_deep_teal_50</color> <color name="button_material_dark">#ff5a595b</color> <color name="button_material_light">#ffd6d7d7</color> @@ -95,12 +93,10 @@ <color name="material_grey_100">#fff5f5f5</color> <color name="material_grey_50">#fffafafa</color> - <color name="material_deep_teal_50">#ffe0f2f1</color> <color name="material_deep_teal_100">#ffb2dfdb</color> <color name="material_deep_teal_200">#ff80cbc4</color> <color name="material_deep_teal_300">#ff4db6ac</color> <color name="material_deep_teal_500">#ff009688</color> - <color name="material_deep_teal_700">#ff00796b</color> <color name="material_blue_grey_200">#ffb0bec5</color> <color name="material_blue_grey_700">#ff455a64</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dc791cf68b6b..fd05bb442247 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1302,8 +1302,8 @@ If this is defined then: - config_autoBrightnessLcdBacklightValues should not be defined - - config_screenBrightnessMinimumNits must be defined - - config_screenBrightnessMaximumNits must be defined + - config_screenBrightnessNits must be defined + - config_screenBrightnessBacklight must be defined This array should have size one greater than the size of the config_autoBrightnessLevels array. The brightness values must be non-negative and non-decreasing. This must be @@ -1347,28 +1347,23 @@ <item>200</item> </integer-array> - <!-- The minimum brightness of the display in nits. On OLED displays this should be measured - with an all white image while the display is fully on and the backlight is set to - config_screenBrightnessSettingMinimum or config_screenBrightnessSettingDark, whichever - is darker. - - If this and config_screenBrightnessMinimumNits are set, then the display's brightness - range is assumed to be linear between - (config_screenBrightnessSettingMinimum, config_screenBrightnessMinimumNits) and - (config_screenBrightnessSettingMaximum, config_screenBrightnessMaximumNits). --> - <item name="config_screenBrightnessMinimumNits" format="float" type="dimen">-1.0</item> - - <!-- The maximum brightness of the display in nits. On OLED displays this should be measured - with an all white image while the display is fully on and the "backlight" is set to - config_screenBrightnessSettingMaximum. Note that this value should *not* reflect the - maximum brightness value for any high brightness modes but only the maximum brightness - value obtainable in a sustainable manner. - - If this and config_screenBrightnessMinimumNits are set to something non-negative, then the - display's brightness range is assumed to be linear between - (config_screenBrightnessSettingMinimum, config_screenBrightnessMaximumNits) and - (config_screenBrightnessSettingMaximum, config_screenBrightnessMaximumNits). --> - <item name="config_screenBrightnessMaximumNits" format="float" type="dimen">-1.0</item> + <!-- An array describing the screen's backlight values corresponding to the brightness + values in the config_screenBrightnessNits array. + + This array should be equal in size to config_screenBrightnessBacklight. --> + <integer-array name="config_screenBrightnessBacklight"> + </integer-array> + + <!-- An array of floats describing the screen brightness in nits corresponding to the backlight + values in the config_screenBrightnessBacklight array. On OLED displays these values + should be measured with an all white image while the display is in the fully on state. + Note that this value should *not* reflect the maximum brightness value for any high + brightness modes but only the maximum brightness value obtainable in a sustainable manner. + + This array should be equal in size to config_screenBrightnessBacklight --> + <array name="config_screenBrightnessNits"> + </array> + <!-- Array of ambient lux threshold values. This is used for determining hysteresis constraint values by calculating the index to use for lookup and then setting the constraint value diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 5783435e13bd..c54f79983f02 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -20,13 +20,23 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Suffix added to a number to signify size in bytes. --> <string name="byteShort">B</string> + <!-- Suffix added to a number to signify size in kilobytes (1000 bytes). + If you retain the Latin script for the localization, please use the lowercase + 'k', as it signifies 1000 bytes as opposed to 1024 bytes. --> + <string name="kilobyteShort">kB</string> + <!-- Suffix added to a number to signify size in megabytes. --> + <string name="megabyteShort">MB</string> + <!-- Suffix added to a number to signify size in gigabytes. --> + <string name="gigabyteShort">GB</string> + <!-- Suffix added to a number to signify size in terabytes. --> + <string name="terabyteShort">TB</string> <!-- Suffix added to a number to signify size in petabytes. --> <string name="petabyteShort">PB</string> - <!-- Format string used to add a suffix like "B" or "PB" to a number - to display a size in bytes or petabytes. - Some languages may want to remove the space between the placeholders - or replace it with a non-breaking space. --> - <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="B">%2$s</xliff:g></string> + <!-- Format string used to add a suffix like "kB" or "MB" to a number + to display a size in kilobytes, megabytes, or other size units. + Some languages (like French) will want to add a space between + the placeholders. --> + <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="MB">%2$s</xliff:g></string> <!-- Used in Contacts for a field that has no label and in Note Pad for a note with no name. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7f714460992e..4343ba01702b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -705,6 +705,7 @@ <java-symbol type="string" name="fileSizeSuffix" /> <java-symbol type="string" name="force_close" /> <java-symbol type="string" name="gadget_host_error_inflating" /> + <java-symbol type="string" name="gigabyteShort" /> <java-symbol type="string" name="gpsNotifMessage" /> <java-symbol type="string" name="gpsNotifTicker" /> <java-symbol type="string" name="gpsNotifTitle" /> @@ -760,6 +761,7 @@ <java-symbol type="string" name="keyboardview_keycode_enter" /> <java-symbol type="string" name="keyboardview_keycode_mode_change" /> <java-symbol type="string" name="keyboardview_keycode_shift" /> + <java-symbol type="string" name="kilobyteShort" /> <java-symbol type="string" name="last_month" /> <java-symbol type="string" name="launchBrowserDefault" /> <java-symbol type="string" name="lock_to_app_toast" /> @@ -779,6 +781,7 @@ <java-symbol type="string" name="lockscreen_emergency_call" /> <java-symbol type="string" name="lockscreen_return_to_call" /> <java-symbol type="string" name="low_memory" /> + <java-symbol type="string" name="megabyteShort" /> <java-symbol type="string" name="midnight" /> <java-symbol type="string" name="mismatchPin" /> <java-symbol type="string" name="mmiComplete" /> @@ -981,6 +984,7 @@ <java-symbol type="string" name="sync_really_delete" /> <java-symbol type="string" name="sync_too_many_deletes_desc" /> <java-symbol type="string" name="sync_undo_deletes" /> + <java-symbol type="string" name="terabyteShort" /> <java-symbol type="string" name="text_copied" /> <java-symbol type="string" name="time_of_day" /> <java-symbol type="string" name="time_picker_decrement_hour_button" /> @@ -1574,6 +1578,8 @@ <java-symbol type="anim" name="voice_activity_close_enter" /> <java-symbol type="anim" name="voice_activity_open_exit" /> <java-symbol type="anim" name="voice_activity_open_enter" /> + <java-symbol type="anim" name="activity_open_exit" /> + <java-symbol type="anim" name="activity_open_enter" /> <java-symbol type="array" name="config_autoRotationTiltTolerance" /> <java-symbol type="array" name="config_keyboardTapVibePattern" /> @@ -3191,7 +3197,7 @@ <java-symbol type="string" name="global_action_logout" /> <java-symbol type="drawable" name="ic_logout" /> - <java-symbol type="dimen" name="config_screenBrightnessMinimumNits" /> - <java-symbol type="dimen" name="config_screenBrightnessMaximumNits" /> <java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" /> + <java-symbol type="array" name="config_screenBrightnessBacklight" /> + <java-symbol type="array" name="config_screenBrightnessNits" /> </resources> diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java index 704b780b0e57..90b457561180 100644 --- a/core/tests/coretests/src/android/os/WorkSourceTest.java +++ b/core/tests/coretests/src/android/os/WorkSourceTest.java @@ -20,6 +20,7 @@ import android.os.WorkSource.WorkChain; import junit.framework.TestCase; +import java.util.ArrayList; import java.util.List; /** @@ -218,4 +219,116 @@ public class WorkSourceTest extends TestCase { assertEquals(20, ws2.get(0)); assertEquals("foo", ws2.getName(0)); } + + public void testDiffChains_noChanges() { + // WorkSources with no chains. + assertEquals(null, WorkSource.diffChains(new WorkSource(), new WorkSource())); + + // WorkSources with the same chains. + WorkSource ws1 = new WorkSource(); + ws1.createWorkChain().addNode(50, "tag"); + ws1.createWorkChain().addNode(60, "tag2"); + + WorkSource ws2 = new WorkSource(); + ws2.createWorkChain().addNode(50, "tag"); + ws2.createWorkChain().addNode(60, "tag2"); + + assertEquals(null, WorkSource.diffChains(ws1, ws1)); + assertEquals(null, WorkSource.diffChains(ws2, ws1)); + } + + public void testDiffChains_noChains() { + // Diffs against a worksource with no chains. + WorkSource ws1 = new WorkSource(); + WorkSource ws2 = new WorkSource(); + ws2.createWorkChain().addNode(70, "tag"); + ws2.createWorkChain().addNode(60, "tag2"); + + // The "old" work source has no chains, so "newChains" should be non-null. + ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2); + assertNotNull(diffs[0]); + assertNull(diffs[1]); + assertEquals(2, diffs[0].size()); + assertEquals(ws2.getWorkChains(), diffs[0]); + + // The "new" work source has no chains, so "oldChains" should be non-null. + diffs = WorkSource.diffChains(ws2, ws1); + assertNull(diffs[0]); + assertNotNull(diffs[1]); + assertEquals(2, diffs[1].size()); + assertEquals(ws2.getWorkChains(), diffs[1]); + } + + public void testDiffChains_onlyAdditionsOrRemovals() { + WorkSource ws1 = new WorkSource(); + WorkSource ws2 = new WorkSource(); + ws2.createWorkChain().addNode(70, "tag"); + ws2.createWorkChain().addNode(60, "tag2"); + + // Both work sources have WorkChains : test the case where changes were only added + // or were only removed. + ws1.createWorkChain().addNode(70, "tag"); + + // The "new" work source only contains additions (60, "tag2") in this case. + ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2); + assertNotNull(diffs[0]); + assertNull(diffs[1]); + assertEquals(1, diffs[0].size()); + assertEquals(new WorkChain().addNode(60, "tag2"), diffs[0].get(0)); + + // The "new" work source only contains removals (60, "tag2") in this case. + diffs = WorkSource.diffChains(ws2, ws1); + assertNull(diffs[0]); + assertNotNull(diffs[1]); + assertEquals(1, diffs[1].size()); + assertEquals(new WorkChain().addNode(60, "tag2"), diffs[1].get(0)); + } + + + public void testDiffChains_generalCase() { + WorkSource ws1 = new WorkSource(); + WorkSource ws2 = new WorkSource(); + + // Both work sources have WorkChains, test the case where chains were added AND removed. + ws1.createWorkChain().addNode(0, "tag0"); + ws2.createWorkChain().addNode(0, "tag0_changed"); + ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2); + assertNotNull(diffs[0]); + assertNotNull(diffs[1]); + assertEquals(ws2.getWorkChains(), diffs[0]); + assertEquals(ws1.getWorkChains(), diffs[1]); + + // Give both WorkSources a chain in common; it should not be a part of any diffs. + ws1.createWorkChain().addNode(1, "tag1"); + ws2.createWorkChain().addNode(1, "tag1"); + diffs = WorkSource.diffChains(ws1, ws2); + assertNotNull(diffs[0]); + assertNotNull(diffs[1]); + assertEquals(1, diffs[0].size()); + assertEquals(1, diffs[1].size()); + assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0)); + assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0)); + + // Finally, test the case where more than one chain was added / removed. + ws1.createWorkChain().addNode(2, "tag2"); + ws2.createWorkChain().addNode(2, "tag2_changed"); + diffs = WorkSource.diffChains(ws1, ws2); + assertNotNull(diffs[0]); + assertNotNull(diffs[1]); + assertEquals(2, diffs[0].size()); + assertEquals(2, diffs[1].size()); + assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0)); + assertEquals(new WorkChain().addNode(2, "tag2_changed"), diffs[0].get(1)); + assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0)); + assertEquals(new WorkChain().addNode(2, "tag2"), diffs[1].get(1)); + } + + public void testGetAttributionId() { + WorkSource ws1 = new WorkSource(); + WorkChain wc = ws1.createWorkChain(); + wc.addNode(100, "tag"); + assertEquals(100, wc.getAttributionUid()); + wc.addNode(200, "tag2"); + assertEquals(100, wc.getAttributionUid()); + } } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 78d7785fd007..fc8650086cd8 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -173,6 +173,7 @@ public class SettingsBackupTest { Settings.Global.DEVICE_DEMO_MODE, Settings.Global.DEVICE_IDLE_CONSTANTS, Settings.Global.BATTERY_SAVER_CONSTANTS, + Settings.Global.BATTERY_TIP_CONSTANTS, Settings.Global.DEFAULT_SM_DP_PLUS, Settings.Global.DEVICE_NAME, Settings.Global.DEVICE_POLICY_CONSTANTS, @@ -524,7 +525,8 @@ public class SettingsBackupTest { Settings.Secure.VOICE_INTERACTION_SERVICE, Settings.Secure.VOICE_RECOGNITION_SERVICE, Settings.Secure.INSTANT_APPS_ENABLED, - Settings.Secure.BACKUP_MANAGER_CONSTANTS); + Settings.Secure.BACKUP_MANAGER_CONSTANTS, + Settings.Secure.KEYGUARD_SLICE_URI); @Test public void systemSettingsBackedUpOrBlacklisted() { diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java index 9c544f47717c..04d2dad4e224 100644 --- a/core/tests/coretests/src/android/text/format/FormatterTest.java +++ b/core/tests/coretests/src/android/text/format/FormatterTest.java @@ -17,7 +17,6 @@ package android.text.format; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import android.content.Context; import android.content.res.Configuration; @@ -210,24 +209,4 @@ public class FormatterTest { Locale.setDefault(locale); } - - /** - * Verifies that Formatter doesn't "leak" the locally defined petabyte unit into ICU via the - * {@link MeasureUnit} registry. This test can fail for two reasons: - * 1. we regressed and started leaking again. In this case the code needs to be fixed. - * 2. ICU started supporting petabyte as a unit, in which case change one needs to revert this - * change (I494fb59a3b3742f35cbdf6b8705817f404a2c6b0), remove Formatter.PETABYTE and replace any - * usages of that field with just MeasureUnit.PETABYTE. - */ - // http://b/65632959 - @Test - public void doesNotLeakPetabyte() { - // Ensure that the Formatter class is loaded when we call .getAvailable(). - Formatter.formatFileSize(mContext, Long.MAX_VALUE); - Set<MeasureUnit> digitalUnits = MeasureUnit.getAvailable("digital"); - for (MeasureUnit unit : digitalUnits) { - // This assert can fail if we don't leak PETABYTE, but ICU has added it, see #2 above. - assertNotEquals("petabyte", unit.getSubtype()); - } - } } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java new file mode 100644 index 000000000000..fed197ed2505 --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * AccessibilityEvent is public, so CTS covers it pretty well. Verifying hidden methods here. + */ +@RunWith(AndroidJUnit4.class) +public class AccessibilityEventTest { + @Test + public void testImportantForAccessibiity_getSetWorkAcrossParceling() { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setImportantForAccessibility(true); + assertTrue(copyEventViaParcel(event).isImportantForAccessibility()); + + event.setImportantForAccessibility(false); + assertFalse(copyEventViaParcel(event).isImportantForAccessibility()); + } + + @Test + public void testSouceNodeId_getSetWorkAcrossParceling() { + final long sourceNodeId = 0x1234567890ABCDEFL; + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setSourceNodeId(sourceNodeId); + assertEquals(sourceNodeId, copyEventViaParcel(event).getSourceNodeId()); + } + + @Test + public void testWindowChanges_getSetWorkAcrossParceling() { + final int windowChanges = AccessibilityEvent.WINDOWS_CHANGE_TITLE + | AccessibilityEvent.WINDOWS_CHANGE_ACTIVE + | AccessibilityEvent.WINDOWS_CHANGE_FOCUSED; + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setWindowChanges(windowChanges); + assertEquals(windowChanges, copyEventViaParcel(event).getWindowChanges()); + } + + private AccessibilityEvent copyEventViaParcel(AccessibilityEvent event) { + Parcel parcel = Parcel.obtain(); + event.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return AccessibilityEvent.CREATOR.createFromParcel(parcel); + } +} diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java index 7875c1755c50..5dec42e19aa1 100644 --- a/core/tests/coretests/src/android/widget/TextViewTest.java +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -35,6 +35,7 @@ import android.text.GetChars; import android.text.Layout; import android.text.Selection; import android.text.Spannable; +import android.util.TypedValue; import android.view.View; import org.junit.Before; @@ -274,7 +275,7 @@ public class TextViewTest { new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) { mTextView = new TextView(mActivity); mTextView.setTypeface(setup.getTypefaceFor("sans-serif")); - mTextView.setTextSize(100); + mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 100); mTextView.setText("aaaaa aabaa aaaaa"); // This should result in three lines. mTextView.setPadding(0, 0, 0, 0); mTextView.setIncludeFontPadding(false); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index 0afec34da958..a55563afbcc9 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -15,16 +15,19 @@ */ package com.android.internal.os; +import static android.os.BatteryStats.STATS_CURRENT; import static android.os.BatteryStats.STATS_SINCE_CHARGED; import static android.os.BatteryStats.WAKE_TYPE_PARTIAL; import android.app.ActivityManager; import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.BatteryStats.HistoryItem; import android.os.WorkSource; import android.support.test.filters.SmallTest; import android.view.Display; +import com.android.internal.os.BatteryStatsImpl.Uid; import junit.framework.TestCase; import java.util.ArrayList; @@ -44,11 +47,14 @@ import java.util.Map; * Run: adb shell am instrument -e class com.android.internal.os.BatteryStatsNoteTest -w \ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner */ -public class BatteryStatsNoteTest extends TestCase{ +public class BatteryStatsNoteTest extends TestCase { + private static final int UID = 10500; private static final WorkSource WS = new WorkSource(UID); - /** Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. */ + /** + * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. + */ @SmallTest public void testNoteBluetoothScanResultLocked() throws Exception { MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClocks()); @@ -75,7 +81,9 @@ public class BatteryStatsNoteTest extends TestCase{ .getCountLocked(STATS_SINCE_CHARGED)); } - /** Test BatteryStatsImpl.Uid.noteStartWakeLocked. */ + /** + * Test BatteryStatsImpl.Uid.noteStartWakeLocked. + */ @SmallTest public void testNoteStartWakeLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms @@ -86,7 +94,8 @@ public class BatteryStatsNoteTest extends TestCase{ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); - bi.getUidStatsLocked(UID).noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime); + bi.getUidStatsLocked(UID) + .noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime); clocks.realtime = clocks.uptime = 100; bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); @@ -94,7 +103,8 @@ public class BatteryStatsNoteTest extends TestCase{ clocks.realtime = clocks.uptime = 220; bi.getUidStatsLocked(UID).noteStopWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime); - BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID).getAggregatedPartialWakelockTimer(); + BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID) + .getAggregatedPartialWakelockTimer(); long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED); long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED); assertEquals(220_000, actualTime); @@ -102,7 +112,9 @@ public class BatteryStatsNoteTest extends TestCase{ } - /** Test BatteryStatsImpl.noteUidProcessStateLocked. */ + /** + * Test BatteryStatsImpl.noteUidProcessStateLocked. + */ @SmallTest public void testNoteUidProcessStateLocked() throws Exception { final MockClocks clocks = new MockClocks(); @@ -145,7 +157,6 @@ public class BatteryStatsNoteTest extends TestCase{ expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get( @@ -153,19 +164,16 @@ public class BatteryStatsNoteTest extends TestCase{ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP_SLEEPING); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) @@ -174,7 +182,6 @@ public class BatteryStatsNoteTest extends TestCase{ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER); assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); - actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED, elapsedTimeUs, STATS_SINCE_CHARGED); expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HOME) @@ -191,7 +198,9 @@ public class BatteryStatsNoteTest extends TestCase{ assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs); } - /** Test BatteryStatsImpl.updateTimeBasesLocked. */ + /** + * Test BatteryStatsImpl.updateTimeBasesLocked. + */ @SmallTest public void testUpdateTimeBasesLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms @@ -213,7 +222,9 @@ public class BatteryStatsNoteTest extends TestCase{ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); } - /** Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. */ + /** + * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. + */ @SmallTest public void testNoteScreenStateLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms @@ -232,15 +243,13 @@ public class BatteryStatsNoteTest extends TestCase{ assertEquals(bi.getScreenState(), Display.STATE_OFF); } - /** Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly. + /** + * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly. * - * Unknown and doze should both be subset of off state + * Unknown and doze should both be subset of off state * - * Timeline 0----100----200----310----400------------1000 - * Unknown ------- - * On ------- - * Off ------- ---------------------- - * Doze ---------------- + * Timeline 0----100----200----310----400------------1000 Unknown ------- On ------- Off + * ------- ---------------------- Doze ---------------- */ @SmallTest public void testNoteScreenStateTimersLocked() throws Exception { @@ -280,4 +289,161 @@ public class BatteryStatsNoteTest extends TestCase{ assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED)); } + @SmallTest + public void testAlarmStartAndFinishLocked() throws Exception { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + clocks.realtime = clocks.uptime = 100; + bi.noteAlarmStartLocked("foo", null, UID); + clocks.realtime = clocks.uptime = 5000; + bi.noteAlarmFinishLocked("foo", null, UID); + + HistoryItem item = new HistoryItem(); + assertTrue(bi.startIteratingHistoryLocked()); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(UID, item.eventTag.uid); + + // TODO(narayan): Figure out why this event is written to the history buffer. See + // test below where it is being interspersed between multiple START events too. + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_NONE, item.eventCode); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode); + assertTrue(item.isDeltaData()); + assertEquals("foo", item.eventTag.string); + assertEquals(UID, item.eventTag.uid); + + assertFalse(bi.getNextHistoryLocked(item)); + } + + @SmallTest + public void testAlarmStartAndFinishLocked_workSource() throws Exception { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + WorkSource ws = new WorkSource(); + ws.add(100); + ws.createWorkChain().addNode(500, "tag"); + bi.noteAlarmStartLocked("foo", ws, UID); + clocks.realtime = clocks.uptime = 5000; + bi.noteAlarmFinishLocked("foo", ws, UID); + + HistoryItem item = new HistoryItem(); + assertTrue(bi.startIteratingHistoryLocked()); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(100, item.eventTag.uid); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_NONE, item.eventCode); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(500, item.eventTag.uid); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(100, item.eventTag.uid); + + assertTrue(bi.getNextHistoryLocked(item)); + assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode); + assertEquals("foo", item.eventTag.string); + assertEquals(500, item.eventTag.uid); + } + + @SmallTest + public void testNoteWakupAlarmLocked() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + bi.mForceOnBattery = true; + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + bi.noteWakupAlarmLocked("com.foo.bar", UID, null, "tag"); + + Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar"); + assertEquals(1, pkg.getWakeupAlarmStats().get("tag").getCountLocked(STATS_CURRENT)); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + } + + @SmallTest + public void testNoteWakupAlarmLocked_workSource_uid() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + bi.mForceOnBattery = true; + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + WorkSource ws = new WorkSource(); + ws.add(100); + + // When a WorkSource is present, "UID" should not be used - only the uids present in the + // WorkSource should be reported. + bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag"); + Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.bar"); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + + // If the WorkSource contains a "name", it should be interpreted as a package name and + // the packageName supplied as an argument must be ignored. + ws = new WorkSource(); + ws.add(100, "com.foo.baz_alternate"); + bi.noteWakupAlarmLocked("com.foo.baz", UID, ws, "tag"); + pkg = bi.getPackageStatsLocked(100, "com.foo.baz"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate"); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + } + + @SmallTest + public void testNoteWakupAlarmLocked_workSource_workChain() { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setRecordAllHistoryLocked(true); + bi.forceRecordAllHistory(); + bi.mForceOnBattery = true; + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); + + WorkSource ws = new WorkSource(); + ws.createWorkChain().addNode(100, "com.foo.baz_alternate"); + bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag"); + + // For WorkChains, again we must only attribute to the uids present in the WorkSource + // (and not to "UID"). However, unlike the older "tags" we do not change the packagename + // supplied as an argument, given that we're logging the entire attribution chain. + Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.bar"); + assertEquals(1, pkg.getWakeupAlarmStats().size()); + pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate"); + assertEquals(0, pkg.getWakeupAlarmStats().size()); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index de2fd12267d5..f19ff6708c69 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import android.os.Handler; +import android.os.Looper; import android.util.SparseIntArray; import java.util.ArrayList; @@ -37,6 +39,9 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { mOnBatteryTimeBase); mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); setExternalStatsSyncLocked(new DummyExternalStatsSync()); + + // A no-op handler. + mHandler = new Handler(Looper.getMainLooper()) {}; } MockBatteryStatsImpl() { @@ -59,6 +64,12 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return mForceOnBattery ? true : super.isOnBattery(); } + public void forceRecordAllHistory() { + mHaveBatteryLevel = true; + mRecordingHistory = true; + mRecordAllHistory = true; + } + public TimeBase getOnBatteryBackgroundTimeBase(int uid) { return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase; } diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 2333feceb1db..1affba053057 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -150,6 +150,7 @@ <assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" /> <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" /> <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" /> + <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" /> <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" /> diff --git a/data/sounds/AudioPackageGo.mk b/data/sounds/AudioPackageGo.mk index ae742df8f9bb..0296219fe9b4 100644 --- a/data/sounds/AudioPackageGo.mk +++ b/data/sounds/AudioPackageGo.mk @@ -35,6 +35,7 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \ $(LOCAL_PATH)/ringtones/ogg/Kuma.ogg:system/media/audio/ringtones/Kuma.ogg \ $(LOCAL_PATH)/ringtones/ogg/Themos.ogg:system/media/audio/ringtones/Themos.ogg \ + $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \ $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \ diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 3d65bd226faf..68b7ac287e98 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -429,7 +429,7 @@ public class Typeface { } /** - * Sets an index of the font collection. + * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}. * * Can not be used for Typeface source. build() method will return null for invalid index. * @param ttcIndex An index of the font collection. If the font source is not font diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index 7c7417dfaaac..5a8fa0700328 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -34,7 +34,8 @@ interface IKeyChainService { void setUserSelectable(String alias, boolean isUserSelectable); boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec); - boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain); + boolean attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags, + out KeymasterCertificateChain chain); boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain); // APIs used by CertInstaller and DevicePolicyManager diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 399dddd71a2a..fabcdf008c47 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -95,6 +95,16 @@ public class KeyStore { public static final int FLAG_ENCRYPTED = 1; /** + * Select Software keymaster device, which as of this writing is the lowest security + * level available on an android device. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided + * A TEE based keymaster implementation is implied. + * + * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h + * For historical reasons this corresponds to the KEYSTORE_FLAG_FALLBACK flag. + */ + public static final int FLAG_SOFTWARE = 1 << 1; + + /** * A private flag that's only available to system server to indicate that this key is part of * device encryption flow so it receives special treatment from keystore. For example this key * will not be super encrypted, and it will be stored separately under an unique UID instead @@ -104,6 +114,16 @@ public class KeyStore { */ public static final int FLAG_CRITICAL_TO_DEVICE_ENCRYPTION = 1 << 3; + /** + * Select Strongbox keymaster device, which as of this writing the the highest security level + * available an android devices. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided + * A TEE based keymaster implementation is implied. + * + * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h + */ + public static final int FLAG_STRONGBOX = 1 << 4; + + // States public enum State { UNLOCKED, LOCKED, UNINITIALIZED }; @@ -440,9 +460,9 @@ public class KeyStore { return mError; } - public boolean addRngEntropy(byte[] data) { + public boolean addRngEntropy(byte[] data, int flags) { try { - return mBinder.addRngEntropy(data) == NO_ERROR; + return mBinder.addRngEntropy(data, flags) == NO_ERROR; } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return false; diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java index 0811100f74c7..efee8b497ab8 100644 --- a/keystore/java/android/security/keystore/AttestationUtils.java +++ b/keystore/java/android/security/keystore/AttestationUtils.java @@ -99,48 +99,35 @@ public abstract class AttestationUtils { } } + @NonNull private static KeymasterArguments prepareAttestationArgumentsForDeviceId( + Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws + DeviceIdAttestationException { + // Verify that device ID attestation types are provided. + if (idTypes == null) { + throw new NullPointerException("Missing id types"); + } + + return prepareAttestationArguments(context, idTypes, attestationChallenge); + } + /** - * Performs attestation of the device's identifiers. This method returns a certificate chain - * whose first element contains the requested device identifiers in an extension. The device's - * manufacturer, model, brand, device and product are always also included in the attestation. - * If the device supports attestation in secure hardware, the chain will be rooted at a - * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See - * <a href="https://developer.android.com/training/articles/security-key-attestation.html"> - * Key Attestation</a> for the format of the certificate extension. - * <p> - * Attestation will only be successful when all of the following are true: - * 1) The device has been set up to support device identifier attestation at the factory. - * 2) The user has not permanently disabled device identifier attestation. - * 3) You have permission to access the device identifiers you are requesting attestation for. - * <p> - * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is - * unsuccessful, the device may not support it in general or the user may have permanently - * disabled it. - * - * @param context the context to use for retrieving device identifiers. - * @param idTypes the types of device identifiers to attest. - * @param attestationChallenge a blob to include in the certificate alongside the device - * identifiers. - * - * @return a certificate chain containing the requested device identifiers in the first element - * - * @exception SecurityException if you are not permitted to obtain an attestation of the - * device's identifiers. - * @exception DeviceIdAttestationException if the attestation operation fails. + * Prepares Keymaster Arguments with attestation data. + * @hide should only be used by KeyChain. */ - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - @NonNull public static X509Certificate[] attestDeviceIds(Context context, + @NonNull public static KeymasterArguments prepareAttestationArguments(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws DeviceIdAttestationException { // Check method arguments, retrieve requested device IDs and prepare attestation arguments. - if (idTypes == null) { - throw new NullPointerException("Missing id types"); - } if (attestationChallenge == null) { throw new NullPointerException("Missing attestation challenge"); } final KeymasterArguments attestArgs = new KeymasterArguments(); attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge); + // Return early if the caller did not request any device identifiers to be included in the + // attestation record. + if (idTypes == null) { + return attestArgs; + } final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); for (int idType : idTypes) { idTypesSet.add(idType); @@ -191,6 +178,44 @@ public abstract class AttestationUtils { Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)); attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL, Build.MODEL.getBytes(StandardCharsets.UTF_8)); + return attestArgs; + } + + /** + * Performs attestation of the device's identifiers. This method returns a certificate chain + * whose first element contains the requested device identifiers in an extension. The device's + * manufacturer, model, brand, device and product are always also included in the attestation. + * If the device supports attestation in secure hardware, the chain will be rooted at a + * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See + * <a href="https://developer.android.com/training/articles/security-key-attestation.html"> + * Key Attestation</a> for the format of the certificate extension. + * <p> + * Attestation will only be successful when all of the following are true: + * 1) The device has been set up to support device identifier attestation at the factory. + * 2) The user has not permanently disabled device identifier attestation. + * 3) You have permission to access the device identifiers you are requesting attestation for. + * <p> + * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is + * unsuccessful, the device may not support it in general or the user may have permanently + * disabled it. + * + * @param context the context to use for retrieving device identifiers. + * @param idTypes the types of device identifiers to attest. + * @param attestationChallenge a blob to include in the certificate alongside the device + * identifiers. + * + * @return a certificate chain containing the requested device identifiers in the first element + * + * @exception SecurityException if you are not permitted to obtain an attestation of the + * device's identifiers. + * @exception DeviceIdAttestationException if the attestation operation fails. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @NonNull public static X509Certificate[] attestDeviceIds(Context context, + @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws + DeviceIdAttestationException { + final KeymasterArguments attestArgs = prepareAttestationArgumentsForDeviceId( + context, idTypes, attestationChallenge); // Perform attestation. final KeymasterCertificateChain outChain = new KeymasterCertificateChain(); diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 1e424258b016..4a0d6ee19978 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -122,15 +122,19 @@ void TestUtils::layoutTextUnscaled(const SkPaint& paint, const char* text, void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, float y) { auto utf16 = asciiToUtf16(text); - canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, paint, - nullptr); + SkPaint glyphPaint(paint); + glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, + glyphPaint, nullptr); } void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, const SkPath& path) { auto utf16 = asciiToUtf16(text); - canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint, - nullptr); + SkPaint glyphPaint(paint); + glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint, + nullptr); } void TestUtils::TestTask::run() { diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index bf0aed98cd70..38999cb1d2ec 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -40,22 +40,18 @@ public: } void doFrame(int frameNr) override { - std::unique_ptr<uint16_t[]> text = - TestUtils::asciiToUtf16("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); - ssize_t textLength = 26 * 2; + const char* text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::unique_ptr<Canvas> canvas( Canvas::create_recording_canvas(container->stagingProperties().getWidth(), container->stagingProperties().getHeight())); Paint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setColor(Color::Black); for (int i = 0; i < 5; i++) { paint.setTextSize(10 + (frameNr % 20) + i * 20); - canvas->drawText(text.get(), 0, textLength, textLength, 0, 100 * (i + 2), - minikin::Bidi::FORCE_LTR, paint, nullptr); + TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2)); } container->setStagingDisplayList(canvas->finishRecording()); diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 6bae80c120ab..58c99800875b 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -36,7 +36,6 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase { SkPaint textPaint; textPaint.setTextSize(dp(20)); textPaint.setAntiAlias(true); - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30)); SkPoint pts[2]; diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index d7ec288a8a7b..fd8c252ff318 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -83,7 +83,6 @@ class ListViewAnimation : public TestListViewSceneBase { canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); SkPaint textPaint; - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500); textPaint.setTextSize(dp(20)); textPaint.setAntiAlias(true); diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index 9eddc209a9a9..aa537b4f329c 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -38,7 +38,6 @@ public: card = TestUtils::createNode( 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index fee0659fa81d..3befce4a395f 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -42,10 +42,8 @@ public: mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255)); mBluePaint.setTextSize(padding); - mBluePaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0)); mGreenPaint.setTextSize(padding); - mGreenPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); // interleave drawText and drawRect with saveLayer ops for (int i = 0; i < regions; i++, top += smallRectHeight) { @@ -54,18 +52,15 @@ public: canvas.drawColor(SkColorSetARGB(255, 255, 255, 0), SkBlendMode::kSrcOver); std::string stri = std::to_string(i); std::string offscreen = "offscreen line " + stri; - std::unique_ptr<uint16_t[]> offtext = TestUtils::asciiToUtf16(offscreen.c_str()); - canvas.drawText(offtext.get(), 0, offscreen.length(), offscreen.length(), bounds.fLeft, - top + padding, minikin::Bidi::FORCE_LTR, mBluePaint, nullptr); + TestUtils::drawUtf8ToCanvas(&canvas, offscreen.c_str(), mBluePaint, bounds.fLeft, + top + padding); canvas.restore(); canvas.drawRect(bounds.fLeft, top + padding, bounds.fRight, top + smallRectHeight - padding, mBluePaint); std::string onscreen = "onscreen line " + stri; - std::unique_ptr<uint16_t[]> ontext = TestUtils::asciiToUtf16(onscreen.c_str()); - canvas.drawText(ontext.get(), 0, onscreen.length(), onscreen.length(), bounds.fLeft, - top + smallRectHeight - padding, minikin::Bidi::FORCE_LTR, mGreenPaint, - nullptr); + TestUtils::drawUtf8ToCanvas(&canvas, onscreen.c_str(), mGreenPaint, bounds.fLeft, + top + smallRectHeight - padding); } } void doFrame(int frameNr) override {} diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index a502116c30a0..a16b17849fc6 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -29,7 +29,6 @@ public: card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props, Canvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp index c845e6c89747..003d8e92fd9f 100644 --- a/libs/hwui/tests/common/scenes/TvApp.cpp +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -117,7 +117,6 @@ private: canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver); SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(24); diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index e56d2f864f77..4eb77514f4ae 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -537,7 +537,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, regionClipStopsMerge) { canvas.save(SaveFlags::MatrixClip); canvas.clipPath(&path, SkClipOp::kIntersect); SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); @@ -569,7 +568,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textMerging) { auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped @@ -603,7 +601,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStrikethrough) { textPaint.setAntiAlias(true); textPaint.setTextSize(20); textPaint.setFlags(textPaint.getFlags() | SkPaint::kStrikeThruText_ReserveFlag); - textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); for (int i = 0; i < LOOPS; i++) { TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); } @@ -654,7 +651,6 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStyle) { auto node = TestUtils::createNode<RecordingCanvas>( 0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) { SkPaint paint; - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); paint.setTextSize(50); paint.setStrokeWidth(10); diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 5aae15f478b8..8a9e34f81c6d 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -175,7 +175,6 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); }); @@ -196,7 +195,6 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { uint32_t flags = paint.getFlags(); @@ -238,7 +236,6 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setTextAlign(SkPaint::kLeft_Align); TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); paint.setTextAlign(SkPaint::kCenter_Align); @@ -805,9 +802,7 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawText) { Paint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO"); - canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL); + TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25); }); int count = 0; @@ -829,9 +824,7 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, drawTextInHighContrast) { paint.setColor(SK_ColorWHITE); paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO"); - canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL); + TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25); }); Properties::enableHighContrastText = false; diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index 4138f595b091..1d7dc3d06ee4 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -36,7 +36,6 @@ OPENGL_PIPELINE_TEST(SkiaCanvasProxy, drawGlyphsViaPicture) { SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); static const char* text = "testing text bounds"; // draw text directly into Recording canvas diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp index 78d75d65837c..92d05e44c6ca 100644 --- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp +++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp @@ -29,6 +29,7 @@ using namespace android::uirenderer; RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) { SkPaint paint; paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); GammaFontRenderer gammaFontRenderer; FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 8f341a8fa6ee..f9075cfd10d9 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -71,6 +71,7 @@ interface ILocationManager void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener); int getGnssYearOfHardware(); + String getGnssHardwareModelName(); int getGnssBatchSize(String packageName); boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index d15ab33eed35..4802b2357906 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -19,6 +19,7 @@ package android.location; import com.android.internal.location.ProviderProperties; import android.Manifest; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -225,6 +226,12 @@ public class LocationManager { public static final String HIGH_POWER_REQUEST_CHANGE_ACTION = "android.location.HIGH_POWER_REQUEST_CHANGE"; + /** + * The value returned by {@link LocationManager#getGnssHardwareModelName()} when the hardware + * does not support providing the actual value. + */ + public static final String GNSS_HARDWARE_MODEL_NAME_UNKNOWN = "Model Name Unknown"; + // Map from LocationListeners to their associated ListenerTransport objects private HashMap<LocationListener,ListenerTransport> mListeners = new HashMap<LocationListener,ListenerTransport>(); @@ -1969,11 +1976,10 @@ public class LocationManager { } /** - * Returns the system information of the GPS hardware. - * May return 0 if GPS hardware is earlier than 2016. - * @hide + * Returns the model year of the GNSS hardware and software build. + * + * May return 0 if the model year is less than 2016. */ - @TestApi public int getGnssYearOfHardware() { try { return mService.getGnssYearOfHardware(); @@ -1983,6 +1989,22 @@ public class LocationManager { } /** + * Returns the Model Name (including Vendor and Hardware/Software Version) of the GNSS hardware + * driver. + * + * Will return {@link LocationManager#GNSS_HARDWARE_MODEL_NAME_UNKNOWN} when the GNSS hardware + * abstraction layer does not support providing this value. + */ + @NonNull + public String getGnssHardwareModelName() { + try { + return mService.getGnssHardwareModelName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the batch size (in number of Location objects) that are supported by the batching * interface. * diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java index 6d9c5e2ad5fc..5d0c8e234d40 100644 --- a/media/java/android/media/AudioFocusInfo.java +++ b/media/java/android/media/AudioFocusInfo.java @@ -130,13 +130,11 @@ public final class AudioFocusInfo implements Parcelable { dest.writeInt(mSdkTarget); } - @SystemApi @Override public int hashCode() { return Objects.hash(mAttributes, mClientUid, mClientId, mPackageName, mGainRequest, mFlags); } - @SystemApi @Override public boolean equals(Object obj) { if (this == obj) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f87c8461c7c6..913b5e841112 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1635,6 +1635,21 @@ public class AudioManager { } /** + * Broadcast Action: microphone muting state changed. + * + * You <em>cannot</em> receive this through components declared + * in manifests, only by explicitly registering for it with + * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver()}. + * + * <p>The intent has no extra values, use {@link #isMicrophoneMute} to check whether the + * microphone is muted. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MICROPHONE_MUTE_CHANGED = + "android.media.action.MICROPHONE_MUTE_CHANGED"; + + /** * Sets the audio mode. * <p> * The audio mode encompasses audio routing AND the behavior of diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java index 1993b459da53..373247162563 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java @@ -73,6 +73,7 @@ public class LocalBluetoothManager { mCachedDeviceManager, context); mProfileManager = new LocalBluetoothProfileManager(context, mLocalAdapter, mCachedDeviceManager, mEventManager); + mEventManager.readPairedDevices(); } public LocalBluetoothAdapter getBluetoothAdapter() { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 58f122619cd6..754b88117613 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -622,6 +622,14 @@ public class AccessPoint implements Comparable<AccessPoint> { return mRssi; } + public ConcurrentHashMap<String, ScanResult> getScanResults() { + return mScanResultCache; + } + + public Map<String, TimestampedScoredNetwork> getScoredNetworkCache() { + return mScoredNetworkCache; + } + /** * Updates {@link #mRssi}. * @@ -845,41 +853,8 @@ public class AccessPoint implements Comparable<AccessPoint> { } if (WifiTracker.sVerboseLogging) { - // Add RSSI/band information for this config, what was seen up to 6 seconds ago - // verbose WiFi Logging is only turned on thru developers settings - if (isActive() && mInfo != null) { - summary.append(" f=" + Integer.toString(mInfo.getFrequency())); - } - summary.append(" " + getVisibilityStatus()); - if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) { - summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString()); - if (config.getNetworkSelectionStatus().getDisableTime() > 0) { - long now = System.currentTimeMillis(); - long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000; - long sec = diff%60; //seconds - long min = (diff/60)%60; //minutes - long hour = (min/60)%60; //hours - summary.append(", "); - if (hour > 0) summary.append(Long.toString(hour) + "h "); - summary.append( Long.toString(min) + "m "); - summary.append( Long.toString(sec) + "s "); - } - summary.append(")"); - } - - if (config != null) { - WifiConfiguration.NetworkSelectionStatus networkStatus = - config.getNetworkSelectionStatus(); - for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE; - index < WifiConfiguration.NetworkSelectionStatus - .NETWORK_SELECTION_DISABLED_MAX; index++) { - if (networkStatus.getDisableReasonCounter(index) != 0) { - summary.append(" " + WifiConfiguration.NetworkSelectionStatus - .getNetworkDisableReasonString(index) + "=" - + networkStatus.getDisableReasonCounter(index)); - } - } - } + evictOldScanResults(); + summary.append(WifiUtils.buildLoggingSummary(this, config)); } // If Speed label and summary are both present, use the preference combination to combine @@ -897,127 +872,6 @@ public class AccessPoint implements Comparable<AccessPoint> { } /** - * Returns the visibility status of the WifiConfiguration. - * - * @return autojoin debugging information - * TODO: use a string formatter - * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"] - * For instance [-40,5/-30,2] - */ - private String getVisibilityStatus() { - StringBuilder visibility = new StringBuilder(); - StringBuilder scans24GHz = new StringBuilder(); - StringBuilder scans5GHz = new StringBuilder(); - String bssid = null; - - long now = System.currentTimeMillis(); - - if (isActive() && mInfo != null) { - bssid = mInfo.getBSSID(); - if (bssid != null) { - visibility.append(" ").append(bssid); - } - visibility.append(" rssi=").append(mInfo.getRssi()); - visibility.append(" "); - visibility.append(" score=").append(mInfo.score); - if (mSpeed != Speed.NONE) { - visibility.append(" speed=").append(getSpeedLabel()); - } - visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate)); - visibility.append(String.format("%.1f,", mInfo.txRetriesRate)); - visibility.append(String.format("%.1f ", mInfo.txBadRate)); - visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate)); - } - - int maxRssi5 = WifiConfiguration.INVALID_RSSI; - int maxRssi24 = WifiConfiguration.INVALID_RSSI; - final int maxDisplayedScans = 4; - int num5 = 0; // number of scanned BSSID on 5GHz band - int num24 = 0; // number of scanned BSSID on 2.4Ghz band - int numBlackListed = 0; - evictOldScanResults(); - - // TODO: sort list by RSSI or age - long nowMs = SystemClock.elapsedRealtime(); - for (ScanResult result : mScanResultCache.values()) { - if (result.frequency >= LOWER_FREQ_5GHZ - && result.frequency <= HIGHER_FREQ_5GHZ) { - // Strictly speaking: [4915, 5825] - num5++; - - if (result.level > maxRssi5) { - maxRssi5 = result.level; - } - if (num5 <= maxDisplayedScans) { - scans5GHz.append(verboseScanResultSummary(result, bssid, nowMs)); - } - } else if (result.frequency >= LOWER_FREQ_24GHZ - && result.frequency <= HIGHER_FREQ_24GHZ) { - // Strictly speaking: [2412, 2482] - num24++; - - if (result.level > maxRssi24) { - maxRssi24 = result.level; - } - if (num24 <= maxDisplayedScans) { - scans24GHz.append(verboseScanResultSummary(result, bssid, nowMs)); - } - } - } - visibility.append(" ["); - if (num24 > 0) { - visibility.append("(").append(num24).append(")"); - if (num24 > maxDisplayedScans) { - visibility.append("max=").append(maxRssi24).append(","); - } - visibility.append(scans24GHz.toString()); - } - visibility.append(";"); - if (num5 > 0) { - visibility.append("(").append(num5).append(")"); - if (num5 > maxDisplayedScans) { - visibility.append("max=").append(maxRssi5).append(","); - } - visibility.append(scans5GHz.toString()); - } - if (numBlackListed > 0) - visibility.append("!").append(numBlackListed); - visibility.append("]"); - - return visibility.toString(); - } - - @VisibleForTesting - /* package */ String verboseScanResultSummary(ScanResult result, String bssid, long nowMs) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(" \n{").append(result.BSSID); - if (result.BSSID.equals(bssid)) { - stringBuilder.append("*"); - } - stringBuilder.append("=").append(result.frequency); - stringBuilder.append(",").append(result.level); - int speed = getSpecificApSpeed(result); - if (speed != Speed.NONE) { - stringBuilder.append(",") - .append(getSpeedLabel(speed)); - } - int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000; - stringBuilder.append(",").append(ageSeconds).append("s"); - stringBuilder.append("}"); - return stringBuilder.toString(); - } - - @Speed private int getSpecificApSpeed(ScanResult result) { - TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID); - if (timedScore == null) { - return Speed.NONE; - } - // For debugging purposes we may want to use mRssi rather than result.level as the average - // speed wil be determined by mRssi - return timedScore.getScore().calculateBadge(result.level); - } - - /** * Return whether this is the active connection. * For ephemeral connections (networkId is invalid), this returns false if the network is * disconnected. @@ -1275,7 +1129,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } @Nullable - private String getSpeedLabel(@Speed int speed) { + String getSpeedLabel(@Speed int speed) { switch (speed) { case Speed.VERY_FAST: return mContext.getString(R.string.speed_label_very_fast); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java new file mode 100644 index 000000000000..932c6fd82c50 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.wifi; + +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.os.SystemClock; +import android.support.annotation.VisibleForTesting; + +import java.util.Map; + +public class WifiUtils { + + public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) { + final StringBuilder summary = new StringBuilder(); + final WifiInfo info = accessPoint.getInfo(); + // Add RSSI/band information for this config, what was seen up to 6 seconds ago + // verbose WiFi Logging is only turned on thru developers settings + if (accessPoint.isActive() && info != null) { + summary.append(" f=" + Integer.toString(info.getFrequency())); + } + summary.append(" " + getVisibilityStatus(accessPoint)); + if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) { + summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString()); + if (config.getNetworkSelectionStatus().getDisableTime() > 0) { + long now = System.currentTimeMillis(); + long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000; + long sec = diff % 60; //seconds + long min = (diff / 60) % 60; //minutes + long hour = (min / 60) % 60; //hours + summary.append(", "); + if (hour > 0) summary.append(Long.toString(hour) + "h "); + summary.append(Long.toString(min) + "m "); + summary.append(Long.toString(sec) + "s "); + } + summary.append(")"); + } + + if (config != null) { + WifiConfiguration.NetworkSelectionStatus networkStatus = + config.getNetworkSelectionStatus(); + for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE; + index < WifiConfiguration.NetworkSelectionStatus + .NETWORK_SELECTION_DISABLED_MAX; index++) { + if (networkStatus.getDisableReasonCounter(index) != 0) { + summary.append(" " + WifiConfiguration.NetworkSelectionStatus + .getNetworkDisableReasonString(index) + "=" + + networkStatus.getDisableReasonCounter(index)); + } + } + } + + return summary.toString(); + } + + /** + * Returns the visibility status of the WifiConfiguration. + * + * @return autojoin debugging information + * TODO: use a string formatter + * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"] + * For instance [-40,5/-30,2] + */ + private static String getVisibilityStatus(AccessPoint accessPoint) { + final WifiInfo info = accessPoint.getInfo(); + StringBuilder visibility = new StringBuilder(); + StringBuilder scans24GHz = new StringBuilder(); + StringBuilder scans5GHz = new StringBuilder(); + String bssid = null; + + if (accessPoint.isActive() && info != null) { + bssid = info.getBSSID(); + if (bssid != null) { + visibility.append(" ").append(bssid); + } + visibility.append(" rssi=").append(info.getRssi()); + visibility.append(" "); + visibility.append(" score=").append(info.score); + if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) { + visibility.append(" speed=").append(accessPoint.getSpeedLabel()); + } + visibility.append(String.format(" tx=%.1f,", info.txSuccessRate)); + visibility.append(String.format("%.1f,", info.txRetriesRate)); + visibility.append(String.format("%.1f ", info.txBadRate)); + visibility.append(String.format("rx=%.1f", info.rxSuccessRate)); + } + + int maxRssi5 = WifiConfiguration.INVALID_RSSI; + int maxRssi24 = WifiConfiguration.INVALID_RSSI; + final int maxDisplayedScans = 4; + int num5 = 0; // number of scanned BSSID on 5GHz band + int num24 = 0; // number of scanned BSSID on 2.4Ghz band + int numBlackListed = 0; + + // TODO: sort list by RSSI or age + long nowMs = SystemClock.elapsedRealtime(); + for (ScanResult result : accessPoint.getScanResults().values()) { + if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ + && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) { + // Strictly speaking: [4915, 5825] + num5++; + + if (result.level > maxRssi5) { + maxRssi5 = result.level; + } + if (num5 <= maxDisplayedScans) { + scans5GHz.append( + verboseScanResultSummary(accessPoint, result, bssid, + nowMs)); + } + } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ + && result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ) { + // Strictly speaking: [2412, 2482] + num24++; + + if (result.level > maxRssi24) { + maxRssi24 = result.level; + } + if (num24 <= maxDisplayedScans) { + scans24GHz.append( + verboseScanResultSummary(accessPoint, result, bssid, + nowMs)); + } + } + } + visibility.append(" ["); + if (num24 > 0) { + visibility.append("(").append(num24).append(")"); + if (num24 > maxDisplayedScans) { + visibility.append("max=").append(maxRssi24).append(","); + } + visibility.append(scans24GHz.toString()); + } + visibility.append(";"); + if (num5 > 0) { + visibility.append("(").append(num5).append(")"); + if (num5 > maxDisplayedScans) { + visibility.append("max=").append(maxRssi5).append(","); + } + visibility.append(scans5GHz.toString()); + } + if (numBlackListed > 0) { + visibility.append("!").append(numBlackListed); + } + visibility.append("]"); + + return visibility.toString(); + } + + @VisibleForTesting + /* package */ static String verboseScanResultSummary(AccessPoint accessPoint, ScanResult result, + String bssid, long nowMs) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(" \n{").append(result.BSSID); + if (result.BSSID.equals(bssid)) { + stringBuilder.append("*"); + } + stringBuilder.append("=").append(result.frequency); + stringBuilder.append(",").append(result.level); + int speed = getSpecificApSpeed(result, accessPoint.getScoredNetworkCache()); + if (speed != AccessPoint.Speed.NONE) { + stringBuilder.append(",") + .append(accessPoint.getSpeedLabel(speed)); + } + int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000; + stringBuilder.append(",").append(ageSeconds).append("s"); + stringBuilder.append("}"); + return stringBuilder.toString(); + } + + @AccessPoint.Speed + private static int getSpecificApSpeed(ScanResult result, + Map<String, TimestampedScoredNetwork> scoredNetworkCache) { + TimestampedScoredNetwork timedScore = scoredNetworkCache.get(result.BSSID); + if (timedScore == null) { + return AccessPoint.Speed.NONE; + } + // For debugging purposes we may want to use mRssi rather than result.level as the average + // speed wil be determined by mRssi + return timedScore.getScore().calculateBadge(result.level); + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 66f4a0114dc1..ec594a69ef03 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -66,7 +66,6 @@ import java.util.Collections; public class AccessPointTest { private static final String TEST_SSID = "\"test_ssid\""; - private static final int NUM_SCAN_RESULTS = 5; private static final ArrayList<ScanResult> SCAN_RESULTS = buildScanResultCache(); @@ -439,26 +438,6 @@ public class AccessPointTest { } @Test - public void testVerboseSummaryString_showsScanResultSpeedLabel() { - WifiTracker.sVerboseLogging = true; - - Bundle bundle = new Bundle(); - ArrayList<ScanResult> scanResults = buildScanResultCache(); - bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults); - AccessPoint ap = new AccessPoint(mContext, bundle); - - when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class))) - .thenReturn(buildScoredNetworkWithMockBadgeCurve()); - when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST); - - ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */, - MAX_SCORE_CACHE_AGE_MILLIS); - String summary = ap.verboseScanResultSummary(scanResults.get(0), null, 0); - - assertThat(summary.contains(mContext.getString(R.string.speed_label_very_fast))).isTrue(); - } - - @Test public void testSummaryString_concatenatesSpeedLabel() { AccessPoint ap = createAccessPointWithScanResultCache(); ap.update(new WifiConfiguration()); @@ -559,7 +538,6 @@ public class AccessPointTest { private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() { return buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve); - } private ScoredNetwork buildScoredNetworkWithGivenBadgeCurve(RssiCurve badgeCurve) { @@ -570,7 +548,6 @@ public class AccessPointTest { badgeCurve, false /* meteredHint */, attr1); - } private AccessPoint createAccessPointWithScanResultCache() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java new file mode 100644 index 000000000000..c5795d34eae8 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.wifi; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.NetworkKey; +import android.net.RssiCurve; +import android.net.ScoredNetwork; +import android.net.WifiKey; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiNetworkScoreCache; +import android.os.Bundle; +import android.os.SystemClock; +import android.text.format.DateUtils; + +import com.android.settingslib.R; +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class WifiUtilsTest { + private static final String TEST_SSID = "\"test_ssid\""; + private static final String TEST_BSSID = "00:00:00:00:00:00"; + private static final long MAX_SCORE_CACHE_AGE_MILLIS = + 20 * DateUtils.MINUTE_IN_MILLIS; + + private Context mContext; + @Mock + private RssiCurve mockBadgeCurve; + @Mock + private WifiNetworkScoreCache mockWifiNetworkScoreCache; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + } + + @Test + public void testVerboseSummaryString_showsScanResultSpeedLabel() { + WifiTracker.sVerboseLogging = true; + + Bundle bundle = new Bundle(); + ArrayList<ScanResult> scanResults = buildScanResultCache(); + bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults); + AccessPoint ap = new AccessPoint(mContext, bundle); + + when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class))) + .thenReturn(buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve)); + when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST); + + ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */, + MAX_SCORE_CACHE_AGE_MILLIS); + String summary = WifiUtils.verboseScanResultSummary(ap, scanResults.get(0), null, 0); + + assertThat(summary.contains(mContext.getString(R.string.speed_label_very_fast))).isTrue(); + } + + private static ArrayList<ScanResult> buildScanResultCache() { + ArrayList<ScanResult> scanResults = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + ScanResult scanResult = createScanResult(TEST_SSID, "bssid-" + i, i); + scanResults.add(scanResult); + } + return scanResults; + } + + private static ScanResult createScanResult(String ssid, String bssid, int rssi) { + ScanResult scanResult = new ScanResult(); + scanResult.SSID = ssid; + scanResult.level = rssi; + scanResult.BSSID = bssid; + scanResult.timestamp = SystemClock.elapsedRealtime() * 1000; + scanResult.capabilities = ""; + return scanResult; + } + + private ScoredNetwork buildScoredNetworkWithGivenBadgeCurve(RssiCurve badgeCurve) { + Bundle attr1 = new Bundle(); + attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, badgeCurve); + return new ScoredNetwork( + new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)), + badgeCurve, + false /* meteredHint */, + attr1); + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index f4ec93666490..bef2bcbd80f1 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1710,18 +1710,9 @@ public class SettingsProvider extends ContentProvider { } private List<String> getSettingsNamesLocked(int settingsType, int userId) { - boolean instantApp; - if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) { - instantApp = false; - } else { - ApplicationInfo ai = getCallingApplicationInfoOrThrow(); - instantApp = ai.isInstantApp(); - } - if (instantApp) { - return new ArrayList<String>(getInstantAppAccessibleSettings(settingsType)); - } else { - return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId); - } + // Don't enforce the instant app whitelist for now -- its too prone to unintended breakage + // in the current form. + return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId); } private void enforceSettingReadable(String settingName, int settingsType, int userId) { @@ -1734,8 +1725,10 @@ public class SettingsProvider extends ContentProvider { } if (!getInstantAppAccessibleSettings(settingsType).contains(settingName) && !getOverlayInstantAppAccessibleSettings(settingsType).contains(settingName)) { - throw new SecurityException("Setting " + settingName + " is not accessible from" - + " ephemeral package " + getCallingPackage()); + // Don't enforce the instant app whitelist for now -- its too prone to unintended + // breakage in the current form. + Slog.w(LOG_TAG, "Instant App " + ai.packageName + + " trying to access unexposed setting, this will be an error in the future."); } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index eab42dac5a27..d675a7a83056 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -132,6 +132,7 @@ <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" /> <!-- Permission needed to access privileged VR APIs --> <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" /> + <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE" /> <application android:label="@string/app_label" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 2c5eb27abe3d..73fcdd7aa90d 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -39,7 +39,12 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-v7-mediarouter \ android-support-v7-palette \ android-support-v14-preference \ - android-support-v17-leanback + android-support-v17-leanback \ + android-slices-core \ + android-slices-view \ + android-slices-builders \ + apptoolkit-arch-core-runtime \ + apptoolkit-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-tags \ diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml index 020cfeeaa194..b154d46f162c 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml @@ -24,31 +24,20 @@ android:layout_marginEnd="16dp" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/date_owner_info_margin" android:layout_gravity="center_horizontal" - android:paddingTop="4dp" android:clipToPadding="false" android:orientation="vertical" android:layout_centerHorizontal="true"> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="end" - android:fadingEdge="horizontal" - android:gravity="center" - android:textSize="22sp" - android:textColor="?attr/wallpaperTextColor" + android:layout_marginBottom="@dimen/widget_vertical_padding" + android:theme="@style/TextAppearance.Keyguard" /> - <TextView android:id="@+id/text" + <LinearLayout android:id="@+id/row" android:layout_width="match_parent" android:layout_height="wrap_content" - android:singleLine="true" + android:orientation="horizontal" android:gravity="center" - android:visibility="gone" - android:textSize="16sp" - android:textColor="?attr/wallpaperTextColor" - android:layout_marginTop="4dp" - android:ellipsize="end" /> </com.android.keyguard.KeyguardSliceView>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index 138733e646ce..c97cfc4bb835 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -34,6 +34,7 @@ android:orientation="vertical"> <RelativeLayout android:id="@+id/keyguard_clock_container" + android:animateLayoutChanges="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top"> @@ -59,14 +60,23 @@ android:layout_toEndOf="@id/clock_view" android:visibility="invisible" android:src="@drawable/ic_aod_charging_24dp" - android:contentDescription="@string/accessibility_ambient_display_charging" - /> + android:contentDescription="@string/accessibility_ambient_display_charging" /> + <View + android:id="@+id/clock_separator" + android:layout_width="16dp" + android:layout_height="1dp" + android:layout_marginTop="10dp" + android:layout_below="@id/clock_view" + android:background="#f00" + android:layout_centerHorizontal="true" /> <include layout="@layout/keyguard_status_area" android:id="@+id/keyguard_status_area" + android:layout_marginTop="10dp" + android:layout_marginBottom="@dimen/widget_vertical_padding" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@id/clock_view" /> + android:layout_below="@id/clock_separator" /> </RelativeLayout> <TextView @@ -83,6 +93,5 @@ android:letterSpacing="0.05" android:ellipsize="marquee" android:singleLine="true" /> - </LinearLayout> </com.android.keyguard.KeyguardStatusView> diff --git a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml index 1b6fa4cf4892..3fb86d03a167 100644 --- a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml @@ -16,5 +16,5 @@ --> <resources> - <dimen name="widget_big_font_size">72dp</dimen> + <dimen name="widget_big_font_size">64dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml index 1b6fa4cf4892..3fb86d03a167 100644 --- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml @@ -16,5 +16,5 @@ --> <resources> - <dimen name="widget_big_font_size">72dp</dimen> + <dimen name="widget_big_font_size">64dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index bcac07295cce..463af61c16f1 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -42,9 +42,22 @@ <dimen name="eca_overlap">-10dip</dimen> <!-- Default clock parameters --> - <dimen name="bottom_text_spacing_digital">-1dp</dimen> - <dimen name="widget_label_font_size">14sp</dimen> - <dimen name="widget_big_font_size">72dp</dimen> + <dimen name="bottom_text_spacing_digital">-10dp</dimen> + <!-- Slice header --> + <dimen name="widget_title_font_size">28sp</dimen> + <!-- Slice subtitle --> + <dimen name="widget_label_font_size">16sp</dimen> + <!-- Clock without header --> + <dimen name="widget_big_font_size">64dp</dimen> + <!-- Clock with header --> + <dimen name="widget_small_font_size">22dp</dimen> + <!-- Dash between clock and header --> + <dimen name="widget_vertical_padding">16dp</dimen> + <!-- Subtitle paddings --> + <dimen name="widget_separator_thickness">2dp</dimen> + <dimen name="widget_horizontal_padding">8dp</dimen> + <dimen name="widget_icon_size">16dp</dimen> + <dimen name="widget_icon_padding">4dp</dimen> <!-- The y translation to apply at the start in appear animations. --> <dimen name="appear_y_translation_start">32dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 826e3ea1f7e5..d50bab533856 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -78,4 +78,18 @@ <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item> </style> + <style name="TextAppearance.Keyguard" parent="Theme.SystemUI"> + <item name="android:textSize">@dimen/widget_title_font_size</item> + <item name="android:gravity">center</item> + <item name="android:ellipsize">end</item> + <item name="android:maxLines">2</item> + </style> + + <style name="TextAppearance.Keyguard.Secondary"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textSize">@dimen/widget_label_font_size</item> + <item name="android:singleLine">true</item> + </style> + </resources> diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 8b7f6926c9f7..03d587bd447c 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -60,14 +60,24 @@ </FrameLayout> </FrameLayout> - <ImageView - android:id="@+id/dismiss" - android:layout_width="@dimen/pip_action_size" - android:layout_height="@dimen/pip_action_size" - android:layout_gravity="top|end" - android:padding="@dimen/pip_action_padding" - android:contentDescription="@string/pip_phone_close" - android:src="@drawable/ic_close_white" - android:background="?android:selectableItemBackgroundBorderless" /> + <ImageView + android:id="@+id/settings" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="top|start" + android:padding="@dimen/pip_action_padding" + android:contentDescription="@string/pip_phone_settings" + android:src="@drawable/ic_settings" + android:background="?android:selectableItemBackgroundBorderless" /> + + <ImageView + android:id="@+id/dismiss" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="top|end" + android:padding="@dimen/pip_action_padding" + android:contentDescription="@string/pip_phone_close" + android:src="@drawable/ic_close_white" + android:background="?android:selectableItemBackgroundBorderless" /> </FrameLayout> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index fd205dd55662..98537a102e1a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1895,6 +1895,9 @@ <!-- Label for PIP close button [CHAR LIMIT=NONE]--> <string name="pip_phone_close">Close</string> + <!-- Label for PIP settings button [CHAR LIMIT=NONE]--> + <string name="pip_phone_settings">Settings</string> + <!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]--> <string name="pip_phone_dismiss_hint">Drag down to dismiss</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index eb2d12edd059..1c99d3846d8e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -413,15 +413,4 @@ public class ActivityManagerWrapper { Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e); } } - - /** - * Cancels the current thumbnail transtion to/from Recents for the given task id. - */ - public void cancelThumbnailTransition(int taskId) { - try { - ActivityManager.getService().cancelTaskThumbnailTransition(taskId); - } catch (RemoteException e) { - Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e); - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index cb3d59c4dff6..b9bf80de304b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -17,38 +17,56 @@ package com.android.keyguard; import android.app.PendingIntent; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; import android.content.Context; -import android.database.ContentObserver; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Handler; +import android.provider.Settings; import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.graphics.ColorUtils; +import com.android.settingslib.Utils; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.android.systemui.tuner.TunerService; -import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +import androidx.app.slice.Slice; +import androidx.app.slice.SliceItem; +import androidx.app.slice.core.SliceQuery; +import androidx.app.slice.widget.SliceLiveData; /** * View visible under the clock on the lock screen and AoD. */ -public class KeyguardSliceView extends LinearLayout { +public class KeyguardSliceView extends LinearLayout implements View.OnClickListener, + Observer<Slice>, TunerService.Tunable { - private final Uri mKeyguardSliceUri; + private static final String TAG = "KeyguardSliceView"; + private final HashMap<View, PendingIntent> mClickActions; + private Uri mKeyguardSliceUri; private TextView mTitle; - private TextView mText; - private Slice mSlice; - private PendingIntent mSliceAction; + private LinearLayout mRow; private int mTextColor; private float mDarkAmount = 0; - private final ContentObserver mObserver; + private LiveData<Slice> mLiveData; + private int mIconSize; + private Consumer<Boolean> mListener; + private boolean mHasHeader; public KeyguardSliceView(Context context) { this(context, null, 0); @@ -60,16 +78,20 @@ public class KeyguardSliceView extends LinearLayout { public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mObserver = new KeyguardSliceObserver(new Handler()); - mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);; + + TunerService tunerService = Dependency.get(TunerService.class); + tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI); + + mClickActions = new HashMap<>(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mTitle = findViewById(R.id.title); - mText = findViewById(R.id.text); - mTextColor = mTitle.getCurrentTextColor(); + mRow = findViewById(R.id.row); + mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor); + mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); } @Override @@ -77,57 +99,103 @@ public class KeyguardSliceView extends LinearLayout { super.onAttachedToWindow(); // Set initial content - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, - Collections.emptyList())); + showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri)); // Make sure we always have the most current slice - getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri, - false /* notifyDescendants */, mObserver); + mLiveData.observeForever(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - getContext().getContentResolver().unregisterContentObserver(mObserver); + mLiveData.removeObserver(this); } private void showSlice(Slice slice) { - // Items will be wrapped into an action when they have tap targets. - SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION); - if (actionSlice != null) { - mSlice = actionSlice.getSlice(); - mSliceAction = actionSlice.getAction(); - } else { - mSlice = slice; - mSliceAction = null; - } - if (mSlice == null) { - setVisibility(GONE); - return; - } + // Main area + SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE, + null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM}); + mHasHeader = mainItem != null; - SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null); - if (title == null) { + List<SliceItem> subItems = SliceQuery.findAll(slice, + android.app.slice.SliceItem.FORMAT_SLICE, + new String[]{android.app.slice.Slice.HINT_LIST_ITEM}, + null /* nonHints */); + + if (!mHasHeader) { mTitle.setVisibility(GONE); } else { mTitle.setVisibility(VISIBLE); - mTitle.setText(title.getText()); + SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(), + android.app.slice.SliceItem.FORMAT_TEXT, + new String[]{android.app.slice.Slice.HINT_TITLE}, + null /* nonHints */); + mTitle.setText(mainTitle.getText()); } - SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE); - if (text == null) { - mText.setVisibility(GONE); - } else { - mText.setVisibility(VISIBLE); - mText.setText(text.getText()); + mClickActions.clear(); + final int subItemsCount = subItems.size(); + + for (int i = 0; i < subItemsCount; i++) { + SliceItem item = subItems.get(i); + final Uri itemTag = item.getSlice().getUri(); + // Try to reuse the view if already exists in the layout + KeyguardSliceButton button = mRow.findViewWithTag(itemTag); + if (button == null) { + button = new KeyguardSliceButton(mContext); + button.setTextColor(mTextColor); + button.setTag(itemTag); + } else { + mRow.removeView(button); + } + button.setHasDivider(i < subItemsCount - 1); + mRow.addView(button, i); + + PendingIntent pendingIntent; + try { + pendingIntent = item.getAction(); + } catch (RuntimeException e) { + Log.w(TAG, "Cannot retrieve action from keyguard slice", e); + pendingIntent = null; + } + mClickActions.put(button, pendingIntent); + + SliceItem title = SliceQuery.find(item.getSlice(), + android.app.slice.SliceItem.FORMAT_TEXT, + new String[]{android.app.slice.Slice.HINT_TITLE}, + null /* nonHints */); + button.setText(title.getText()); + + Drawable iconDrawable = null; + SliceItem icon = SliceQuery.find(item.getSlice(), + android.app.slice.SliceItem.FORMAT_IMAGE); + if (icon != null) { + iconDrawable = icon.getIcon().loadDrawable(mContext); + final int width = (int) (iconDrawable.getIntrinsicWidth() + / (float) iconDrawable.getIntrinsicHeight() * mIconSize); + iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize); + } + button.setCompoundDrawablesRelative(iconDrawable, null, null, null); + button.setOnClickListener(this); + } + + // Removing old views + for (int i = 0; i < mRow.getChildCount(); i++) { + View child = mRow.getChildAt(i); + if (!mClickActions.containsKey(child)) { + mRow.removeView(child); + i--; + } } - final int visibility = title == null && text == null ? GONE : VISIBLE; + final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE; if (visibility != getVisibility()) { setVisibility(visibility); } + + mListener.accept(mHasHeader); } public void setDark(float darkAmount) { @@ -135,30 +203,113 @@ public class KeyguardSliceView extends LinearLayout { updateTextColors(); } - public void setTextColor(int textColor) { - mTextColor = textColor; - } - private void updateTextColors() { final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); mTitle.setTextColor(blendedColor); - mText.setTextColor(blendedColor); + int childCount = mRow.getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = mRow.getChildAt(i); + if (v instanceof Button) { + ((Button) v).setTextColor(blendedColor); + } + } + } + + @Override + public void onClick(View v) { + final PendingIntent action = mClickActions.get(v); + if (action != null) { + try { + action.send(); + } catch (PendingIntent.CanceledException e) { + Log.i(TAG, "Pending intent cancelled, nothing to launch", e); + } + } } - private class KeyguardSliceObserver extends ContentObserver { - KeyguardSliceObserver(Handler handler) { - super(handler); + public void setListener(Consumer<Boolean> listener) { + mListener = listener; + } + + public boolean hasHeader() { + return mHasHeader; + } + + /** + * LiveData observer lifecycle. + * @param slice the new slice content. + */ + @Override + public void onChanged(Slice slice) { + showSlice(slice); + } + + @Override + public void onTuningChanged(String key, String newValue) { + setupUri(newValue); + } + + public void setupUri(String uriString) { + if (uriString == null) { + uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI; + } + + boolean wasObserving = false; + if (mLiveData != null && mLiveData.hasActiveObservers()) { + wasObserving = true; + mLiveData.removeObserver(this); + } + + mKeyguardSliceUri = Uri.parse(uriString); + mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri); + + if (wasObserving) { + mLiveData.observeForever(this); + showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri)); + } + } + + /** + * Representation of an item that appears under the clock on main keyguard message. + * Shows optional separator. + */ + private class KeyguardSliceButton extends Button { + + private final Paint mPaint; + private boolean mHasDivider; + + public KeyguardSliceButton(Context context) { + super(context, null /* attrs */, + com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary); + mPaint = new Paint(); + mPaint.setStyle(Paint.Style.STROKE); + float dividerWidth = context.getResources() + .getDimension(R.dimen.widget_separator_thickness); + mPaint.setStrokeWidth(dividerWidth); + int horizontalPadding = (int) context.getResources() + .getDimension(R.dimen.widget_horizontal_padding); + setPadding(horizontalPadding, 0, horizontalPadding, 0); + setCompoundDrawablePadding((int) context.getResources() + .getDimension(R.dimen.widget_icon_padding)); + } + + public void setHasDivider(boolean hasDivider) { + mHasDivider = hasDivider; } @Override - public void onChange(boolean selfChange) { - this.onChange(selfChange, null); + public void setTextColor(int color) { + super.setTextColor(color); + mPaint.setColor(color); } @Override - public void onChange(boolean selfChange, Uri uri) { - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, - Collections.emptyList())); + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mHasDivider) { + final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth(); + canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint); + } } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 78cf2b9bf1ed..4b9a8744900d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -28,6 +28,7 @@ import android.os.UserHandle; import android.support.v4.graphics.ColorUtils; import android.text.TextUtils; import android.text.format.DateFormat; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Slog; @@ -38,11 +39,11 @@ import android.widget.GridLayout; import android.widget.TextClock; import android.widget.TextView; -import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.settingslib.Utils; import com.android.systemui.ChargingView; +import com.google.android.collect.Sets; + import java.util.Locale; public class KeyguardStatusView extends GridLayout { @@ -52,8 +53,11 @@ public class KeyguardStatusView extends GridLayout { private final LockPatternUtils mLockPatternUtils; private final AlarmManager mAlarmManager; + private final float mSmallClockScale; + private final float mWidgetPadding; private TextClock mClockView; + private View mClockSeparator; private TextView mOwnerInfo; private ViewGroup mClockContainer; private ChargingView mBatteryDoze; @@ -61,7 +65,7 @@ public class KeyguardStatusView extends GridLayout { private Runnable mPendingMarqueeStart; private Handler mHandler; - private View[] mVisibleInDoze; + private ArraySet<View> mVisibleInDoze; private boolean mPulsing; private float mDarkAmount = 0; private int mTextColor; @@ -112,6 +116,9 @@ public class KeyguardStatusView extends GridLayout { mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mLockPatternUtils = new LockPatternUtils(getContext()); mHandler = new Handler(Looper.myLooper()); + mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size) + / getResources().getDimension(R.dimen.widget_big_font_size); + mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding); } private void setEnableMarquee(boolean enabled) { @@ -150,9 +157,14 @@ public class KeyguardStatusView extends GridLayout { mOwnerInfo = findViewById(R.id.owner_info); mBatteryDoze = findViewById(R.id.battery_doze); mKeyguardSlice = findViewById(R.id.keyguard_status_area); - mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice}; + mClockSeparator = findViewById(R.id.clock_separator); + mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice, + mClockSeparator); mTextColor = mClockView.getCurrentTextColor(); + mKeyguardSlice.setListener(this::onSliceContentChanged); + onSliceContentChanged(mKeyguardSlice.hasHeader()); + boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); setEnableMarquee(shouldMarquee); refresh(); @@ -163,6 +175,22 @@ public class KeyguardStatusView extends GridLayout { mClockView.setElegantTextHeight(false); } + private void onSliceContentChanged(boolean hasHeader) { + final float clockScale = hasHeader ? mSmallClockScale : 1; + float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f; + if (hasHeader) { + translation -= mWidgetPadding; + } + mClockView.setTranslationY(translation); + mClockView.setScaleX(clockScale); + mClockView.setScaleY(clockScale); + final float batteryTranslation = + -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2; + mBatteryDoze.setTranslationX(batteryTranslation); + mBatteryDoze.setTranslationY(translation); + mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE); + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -201,17 +229,6 @@ public class KeyguardStatusView extends GridLayout { return mClockView.getTextSize(); } - public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { - if (info == null) { - return ""; - } - String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser()) - ? "EHm" - : "Ehma"; - String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); - return DateFormat.format(pattern, info.getTriggerTime()).toString(); - } - private void updateOwnerInfo() { if (mOwnerInfo == null) return; String ownerInfo = getOwnerInfo(); @@ -303,7 +320,7 @@ public class KeyguardStatusView extends GridLayout { final int N = mClockContainer.getChildCount(); for (int i = 0; i < N; i++) { View child = mClockContainer.getChildAt(i); - if (ArrayUtils.contains(mVisibleInDoze, child)) { + if (mVisibleInDoze.contains(child)) { continue; } child.setAlpha(dark ? 0 : 1); @@ -312,10 +329,12 @@ public class KeyguardStatusView extends GridLayout { mOwnerInfo.setAlpha(dark ? 0 : 1); } + final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount); updateDozeVisibleViews(); mBatteryDoze.setDark(dark); mKeyguardSlice.setDark(darkAmount); - mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount)); + mClockView.setTextColor(blendedTextColor); + mClockSeparator.setBackgroundColor(blendedTextColor); } public void setPulsing(boolean pulsing) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 3177c03d1afa..0f3daf57e8c1 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -23,18 +23,23 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.NotificationEntryManager; import com.android.systemui.statusbar.NotificationGutsManager; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLogger; +import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.LightBarController; @@ -46,6 +51,7 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.util.function.Consumer; @@ -118,16 +124,16 @@ public class SystemUIFactory { Context context) { providers.put(NotificationLockscreenUserManager.class, () -> new NotificationLockscreenUserManager(context)); + providers.put(VisualStabilityManager.class, VisualStabilityManager::new); providers.put(NotificationGroupManager.class, NotificationGroupManager::new); - providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager( - Dependency.get(NotificationLockscreenUserManager.class), context)); + providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context)); + providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context)); providers.put(NotificationRemoteInputManager.class, - () -> new NotificationRemoteInputManager( - Dependency.get(NotificationLockscreenUserManager.class), context)); - providers.put(NotificationListener.class, () -> new NotificationListener( - Dependency.get(NotificationRemoteInputManager.class), context)); - providers.put(NotificationLogger.class, () -> new NotificationLogger( - Dependency.get(NotificationListener.class), - Dependency.get(UiOffloadThread.class))); + () -> new NotificationRemoteInputManager(context)); + providers.put(NotificationListener.class, () -> new NotificationListener(context)); + providers.put(NotificationLogger.class, NotificationLogger::new); + providers.put(NotificationViewHierarchyManager.class, + () -> new NotificationViewHierarchyManager(context)); + providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context)); } } diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java new file mode 100644 index 000000000000..a89a8ef5b70e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.car; + +import android.content.Context; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.NotificationEntryManager; + +public class CarNotificationEntryManager extends NotificationEntryManager { + public CarNotificationEntryManager(Context context) { + super(context); + } + + /** + * Returns the + * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will + * be triggered when a notification card is long-pressed. + */ + @Override + public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + // For the automative use case, we do not want to the user to be able to interact with + // a notification other than a regular click. As a result, just return null for the + // long click listener. + return null; + } + + @Override + public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { + // Because space is usually constrained in the auto use-case, there should not be a + // pinned notification when the shade has been expanded. Ensure this by not pinning any + // notification if the shade is already opened. + if (!mPresenter.isPresenterFullyCollapsed()) { + return false; + } + + return super.shouldPeek(entry, sbn); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java index 5a19e7dc60ed..174584de9405 100644 --- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java @@ -18,9 +18,22 @@ package com.android.systemui.car; import android.content.Context; import android.util.ArrayMap; +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.Dependency; import com.android.systemui.Dependency.DependencyProvider; +import com.android.systemui.ForegroundServiceController; import com.android.systemui.SystemUIFactory; +import com.android.systemui.UiOffloadThread; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.statusbar.NotificationEntryManager; +import com.android.systemui.statusbar.NotificationGutsManager; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.volume.car.CarVolumeDialogController; /** @@ -32,5 +45,7 @@ public class CarSystemUIFactory extends SystemUIFactory { Context context) { super.injectDependencies(providers, context); providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context)); + providers.put(NotificationEntryManager.class, + () -> new CarNotificationEntryManager(context)); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 6ddc76b595b2..bd46c5f8ad0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -16,38 +16,55 @@ package com.android.systemui.keyguard; +import android.app.ActivityManager; +import android.app.AlarmManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.drawable.Icon; import android.icu.text.DateFormat; import android.icu.text.DisplayContext; import android.net.Uri; import android.os.Handler; -import android.app.slice.Slice; -import android.app.slice.SliceProvider; +import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; import java.util.Date; import java.util.Locale; +import androidx.app.slice.Slice; +import androidx.app.slice.SliceProvider; +import androidx.app.slice.builders.ListBuilder; +import androidx.app.slice.builders.ListBuilder.RowBuilder; + /** * Simple Slice provider that shows the current date. */ -public class KeyguardSliceProvider extends SliceProvider { +public class KeyguardSliceProvider extends SliceProvider implements + NextAlarmController.NextAlarmChangeCallback { public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; + public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; + public static final String KEYGUARD_NEXT_ALARM_URI = + "content://com.android.systemui.keyguard/alarm"; private final Date mCurrentTime = new Date(); protected final Uri mSliceUri; + protected final Uri mDateUri; + protected final Uri mAlarmUri; private final Handler mHandler; private String mDatePattern; private DateFormat mDateFormat; private String mLastText; private boolean mRegistered; private boolean mRegisteredEveryMinute; + private String mNextAlarm; + private NextAlarmController mNextAlarmController; /** * Receiver responsible for time ticking and updating the date format. @@ -80,23 +97,49 @@ public class KeyguardSliceProvider extends SliceProvider { KeyguardSliceProvider(Handler handler) { mHandler = handler; mSliceUri = Uri.parse(KEYGUARD_SLICE_URI); + mDateUri = Uri.parse(KEYGUARD_DATE_URI); + mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); } @Override public Slice onBindSlice(Uri sliceUri) { - return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build(); + ListBuilder builder = new ListBuilder(mSliceUri) + .addRow(new RowBuilder(mDateUri).setTitle(mLastText)); + if (!TextUtils.isEmpty(mNextAlarm)) { + Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); + builder.addRow(new RowBuilder(mAlarmUri).setTitle(mNextAlarm).addEndItem(icon)); + } + + return builder.build(); } @Override - public boolean onCreate() { - + public boolean onCreateSliceProvider() { + mNextAlarmController = new NextAlarmControllerImpl(getContext()); + mNextAlarmController.addCallback(this); mDatePattern = getContext().getString(R.string.system_ui_date_pattern); - registerClockUpdate(false /* everyMinute */); updateClock(); return true; } + public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) { + if (info == null) { + return ""; + } + String skeleton = android.text.format.DateFormat + .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma"; + String pattern = android.text.format.DateFormat + .getBestDateTimePattern(Locale.getDefault(), skeleton); + return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString(); + } + + /** + * Registers a broadcast receiver for clock updates, include date, time zone and manually + * changing the date/time via the settings app. + * + * @param everyMinute {@code true} if you also want updates every minute. + */ protected void registerClockUpdate(boolean everyMinute) { if (mRegistered) { if (mRegisteredEveryMinute == everyMinute) { @@ -156,4 +199,10 @@ public class KeyguardSliceProvider extends SliceProvider { void cleanDateFormat() { mDateFormat = null; } + + @Override + public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { + mNextAlarm = formatNextAlarm(getContext(), nextAlarm); + getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */); + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index b5c0d5386767..f5f06db348ab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -133,12 +133,19 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener + mNotificationRampTimeMs + "ms"); } try { Thread.sleep(mNotificationRampTimeMs); - player.start(); } catch (InterruptedException e) { Log.e(mTag, "Exception while sleeping to sync notification playback" + " with ducking", e); } - if (DEBUG) { Log.d(mTag, "player.start"); } + try { + player.start(); + if (DEBUG) { Log.d(mTag, "player.start"); } + } catch (Exception e) { + player.release(); + player = null; + // playing the notification didn't work, revert the focus request + abandonAudioFocusAfterError(); + } if (mPlayer != null) { if (DEBUG) { Log.d(mTag, "mPlayer.release"); } mPlayer.release(); @@ -147,6 +154,8 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } catch (Exception e) { Log.w(mTag, "error loading sound for " + mCmd.uri, e); + // playing the notification didn't work, revert the focus request + abandonAudioFocusAfterError(); } this.notify(); } @@ -154,6 +163,16 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } }; + private void abandonAudioFocusAfterError() { + synchronized (mQueueAudioFocusLock) { + if (mAudioManagerWithAudioFocus != null) { + if (DEBUG) Log.d(mTag, "abandoning focus after playback error"); + mAudioManagerWithAudioFocus.abandonAudioFocus(null); + mAudioManagerWithAudioFocus = null; + } + } + } + private void startSound(Command cmd) { // Preparing can be slow, so if there is something else // is playing, let it continue until we're done, so there diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java new file mode 100644 index 000000000000..f0e4ccc139ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.pip.phone; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; + +import android.app.AppOpsManager; +import android.app.AppOpsManager.OnOpChangedListener; +import android.app.IActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.util.Pair; + +public class PipAppOpsListener { + private static final String TAG = PipAppOpsListener.class.getSimpleName(); + + private Context mContext; + private IActivityManager mActivityManager; + private AppOpsManager mAppOpsManager; + + private PipMotionHelper mMotionHelper; + + private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { + @Override + public void onOpChanged(String op, String packageName) { + try { + // Dismiss the PiP once the user disables the app ops setting for that package + final Pair<ComponentName, Integer> topPipActivityInfo = + PipUtils.getTopPinnedActivity(mContext, mActivityManager); + if (topPipActivityInfo.first != null) { + final ApplicationInfo appInfo = mContext.getPackageManager() + .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second); + if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && + mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, + packageName) != MODE_ALLOWED) { + mMotionHelper.dismissPip(); + } + } + } catch (NameNotFoundException e) { + // Unregister the listener if the package can't be found + unregisterAppOpsListener(); + } + } + }; + + public PipAppOpsListener(Context context, IActivityManager activityManager, + PipMotionHelper motionHelper) { + mContext = context; + mActivityManager = activityManager; + mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mMotionHelper = motionHelper; + } + + public void onActivityPinned(String packageName) { + // Register for changes to the app ops setting for this package while it is in PiP + registerAppOpsListener(packageName); + } + + public void onActivityUnpinned() { + // Unregister for changes to the previously PiP'ed package + unregisterAppOpsListener(); + } + + private void registerAppOpsListener(String packageName) { + mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName, + mAppOpsChangedListener); + } + + private void unregisterAppOpsListener() { + mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 2963506865ef..36531bb727a4 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -64,8 +64,8 @@ public class PipManager implements BasePipManager { private InputConsumerController mInputConsumerController; private PipMenuActivityController mMenuController; private PipMediaController mMediaController; - private PipNotificationController mNotificationController; private PipTouchHandler mTouchHandler; + private PipAppOpsListener mAppOpsListener; /** * Handler for system task stack changes. @@ -76,8 +76,7 @@ public class PipManager implements BasePipManager { mTouchHandler.onActivityPinned(); mMediaController.onActivityPinned(); mMenuController.onActivityPinned(); - mNotificationController.onActivityPinned(packageName, userId, - true /* deferUntilAnimationEnds */); + mAppOpsListener.onActivityPinned(packageName); SystemServicesProxy.getInstance(mContext).setPipVisibility(true); } @@ -90,7 +89,7 @@ public class PipManager implements BasePipManager { final int userId = topActivity != null ? topPipActivityInfo.second : 0; mMenuController.onActivityUnpinned(); mTouchHandler.onActivityUnpinned(topActivity); - mNotificationController.onActivityUnpinned(topActivity, userId); + mAppOpsListener.onActivityUnpinned(); SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null); } @@ -107,7 +106,6 @@ public class PipManager implements BasePipManager { mTouchHandler.setTouchEnabled(true); mTouchHandler.onPinnedStackAnimationEnded(); mMenuController.onPinnedStackAnimationEnded(); - mNotificationController.onPinnedStackAnimationEnded(); } @Override @@ -182,7 +180,7 @@ public class PipManager implements BasePipManager { mInputConsumerController); mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController, mInputConsumerController); - mNotificationController = new PipNotificationController(context, mActivityManager, + mAppOpsListener = new PipAppOpsListener(context, mActivityManager, mTouchHandler.getMotionHelper()); EventBus.getDefault().register(this); } @@ -198,20 +196,6 @@ public class PipManager implements BasePipManager { * Expands the PIP. */ public final void onBusEvent(ExpandPipEvent event) { - if (event.clearThumbnailWindows) { - try { - StackInfo stackInfo = mActivityManager.getStackInfo( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (stackInfo != null && stackInfo.taskIds != null) { - ActivityManagerWrapper am = ActivityManagerWrapper.getInstance(); - for (int taskId : stackInfo.taskIds) { - am.cancelThumbnailTransition(taskId); - } - } - } catch (RemoteException e) { - // Do nothing - } - } mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 90f7b8db1c59..bfe07a980ce7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -16,6 +16,10 @@ package com.android.systemui.pip.phone; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; + import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER; @@ -39,6 +43,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.PendingIntent.CanceledException; import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Color; @@ -46,12 +51,15 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -105,6 +113,7 @@ public class PipMenuActivity extends Activity { private Drawable mBackgroundDrawable; private View mMenuContainer; private LinearLayout mActionsGroup; + private View mSettingsButton; private View mDismissButton; private ImageView mExpandButton; private int mBetweenActionPaddingLand; @@ -218,6 +227,11 @@ public class PipMenuActivity extends Activity { } return true; }); + mSettingsButton = findViewById(R.id.settings); + mSettingsButton.setAlpha(0); + mSettingsButton.setOnClickListener((v) -> { + showSettings(); + }); mDismissButton = findViewById(R.id.dismiss); mDismissButton.setAlpha(0); mDismissButton.setOnClickListener((v) -> { @@ -352,12 +366,14 @@ public class PipMenuActivity extends Activity { ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 1f); menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 1f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); if (menuState == MENU_STATE_FULL) { - mMenuContainerAnimator.playTogether(menuAnim, dismissAnim); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); } else { - mMenuContainerAnimator.play(dismissAnim); + mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); @@ -394,9 +410,11 @@ public class PipMenuActivity extends Activity { ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 0f); menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 0f); ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 0f); - mMenuContainerAnimator.playTogether(menuAnim, dismissAnim); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { @@ -526,12 +544,14 @@ public class PipMenuActivity extends Activity { final float menuAlpha = 1 - fraction; if (mMenuState == MENU_STATE_FULL) { mMenuContainer.setAlpha(menuAlpha); + mSettingsButton.setAlpha(menuAlpha); mDismissButton.setAlpha(menuAlpha); final float interpolatedAlpha = MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction; alpha = (int) (interpolatedAlpha * 255); } else { if (mMenuState == MENU_STATE_CLOSE) { + mSettingsButton.setAlpha(menuAlpha); mDismissButton.setAlpha(menuAlpha); } alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255); @@ -588,6 +608,19 @@ public class PipMenuActivity extends Activity { sendMessage(m, "Could not notify controller to show PIP menu"); } + private void showSettings() { + final Pair<ComponentName, Integer> topPipActivityInfo = + PipUtils.getTopPinnedActivity(this, ActivityManager.getService()); + if (topPipActivityInfo.first != null) { + final UserHandle user = UserHandle.of(topPipActivityInfo.second); + final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, + Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); + settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); + settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); + startActivity(settingsIntent); + } + } + private void notifyActivityCallback(Messenger callback) { Message m = Message.obtain(); m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java deleted file mode 100644 index 6d083e9d601d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.pip.phone; - -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; -import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; - -import android.app.AppOpsManager; -import android.app.AppOpsManager.OnOpChangedListener; -import android.app.IActivityManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.UserHandle; -import android.util.IconDrawableFactory; -import android.util.Log; -import android.util.Pair; - -import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.util.NotificationChannels; - -/** - * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture. - */ -public class PipNotificationController { - private static final String TAG = PipNotificationController.class.getSimpleName(); - - private static final String NOTIFICATION_TAG = PipNotificationController.class.getName(); - private static final int NOTIFICATION_ID = 0; - - private Context mContext; - private IActivityManager mActivityManager; - private AppOpsManager mAppOpsManager; - private NotificationManager mNotificationManager; - private IconDrawableFactory mIconDrawableFactory; - - private PipMotionHelper mMotionHelper; - - // Used when building a deferred notification - private String mDeferredNotificationPackageName; - private int mDeferredNotificationUserId; - - private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { - @Override - public void onOpChanged(String op, String packageName) { - try { - // Dismiss the PiP once the user disables the app ops setting for that package - final Pair<ComponentName, Integer> topPipActivityInfo = - PipUtils.getTopPinnedActivity(mContext, mActivityManager); - if (topPipActivityInfo.first != null) { - final ApplicationInfo appInfo = mContext.getPackageManager() - .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second); - if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && - mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, - packageName) != MODE_ALLOWED) { - mMotionHelper.dismissPip(); - } - } - } catch (NameNotFoundException e) { - // Unregister the listener if the package can't be found - unregisterAppOpsListener(); - } - } - }; - - public PipNotificationController(Context context, IActivityManager activityManager, - PipMotionHelper motionHelper) { - mContext = context; - mActivityManager = activityManager; - mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mNotificationManager = NotificationManager.from(context); - mMotionHelper = motionHelper; - mIconDrawableFactory = IconDrawableFactory.newInstance(context); - } - - public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) { - // Clear any existing notification - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - - if (deferUntilAnimationEnds) { - mDeferredNotificationPackageName = packageName; - mDeferredNotificationUserId = userId; - } else { - showNotificationForApp(packageName, userId); - } - - // Register for changes to the app ops setting for this package while it is in PiP - registerAppOpsListener(packageName); - } - - public void onPinnedStackAnimationEnded() { - if (mDeferredNotificationPackageName != null) { - showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId); - mDeferredNotificationPackageName = null; - mDeferredNotificationUserId = 0; - } - } - - public void onActivityUnpinned(ComponentName topPipActivity, int userId) { - // Unregister for changes to the previously PiP'ed package - unregisterAppOpsListener(); - - // Reset the deferred notification package - mDeferredNotificationPackageName = null; - mDeferredNotificationUserId = 0; - - if (topPipActivity != null) { - // onActivityUnpinned() is only called after the transition is complete, so we don't - // need to defer until the animation ends to update the notification - onActivityPinned(topPipActivity.getPackageName(), userId, - false /* deferUntilAnimationEnds */); - } else { - mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - } - } - - /** - * Builds and shows the notification for the given app. - */ - private void showNotificationForApp(String packageName, int userId) { - // Build a new notification - try { - final UserHandle user = UserHandle.of(userId); - final Context userContext = mContext.createPackageContextAsUser( - mContext.getPackageName(), 0, user); - final Notification.Builder builder = - new Notification.Builder(userContext, NotificationChannels.GENERAL) - .setLocalOnly(true) - .setOngoing(true) - .setSmallIcon(R.drawable.pip_notification_icon) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); - if (updateNotificationForApp(builder, packageName, user)) { - SystemUI.overrideNotificationAppName(mContext, builder); - - // Show the new notification - mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build()); - } - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not show notification for application", e); - } - } - - /** - * Updates the notification builder with app-specific information, returning whether it was - * successful. - */ - private boolean updateNotificationForApp(Notification.Builder builder, String packageName, - UserHandle user) throws NameNotFoundException { - final PackageManager pm = mContext.getPackageManager(); - final ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier()); - } catch (NameNotFoundException e) { - Log.e(TAG, "Could not update notification for application", e); - return false; - } - - if (appInfo != null) { - final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user) - .toString(); - final String message = mContext.getString(R.string.pip_notification_message, appName); - final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, - Uri.fromParts("package", packageName, null)); - settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); - settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); - - final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo); - builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName)) - .setContentText(message) - .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(), - settingsIntent, FLAG_CANCEL_CURRENT, null, user)) - .setStyle(new Notification.BigTextStyle().bigText(message)) - .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap()); - return true; - } - return false; - } - - private void registerAppOpsListener(String packageName) { - mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName, - mAppOpsChangedListener); - } - - private void unregisterAppOpsListener() { - mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); - } - - /** - * Bakes a drawable into a bitmap. - */ - private Bitmap createBitmap(Drawable d) { - Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), - Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - d.draw(c); - c.setBitmap(null); - return bitmap; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 0b7b6d555fdf..927a49cb60f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -18,11 +18,6 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE; - -import android.app.ActivityManager; -import android.app.AlarmManager; -import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -31,7 +26,6 @@ import android.graphics.PorterDuff.Mode; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.UserManager; -import android.provider.AlarmClock; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; @@ -39,24 +33,19 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.TextView; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.settingslib.drawable.UserIconDrawable; import com.android.systemui.Dependency; -import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.R.dimen; -import com.android.systemui.R.id; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.TouchAnimator.Builder; -import com.android.systemui.qs.TouchAnimator.ListenerAdapter; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.ExpandableIndicator; import com.android.systemui.statusbar.phone.MultiUserSwitch; @@ -65,8 +54,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; -import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; import com.android.systemui.tuner.TunerService; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index ca9a5533db28..06dfd183b3aa 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -709,7 +709,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) { ActivityManagerWrapper am = ActivityManagerWrapper.getInstance(); am.cancelWindowTransition(launchState.launchedToTaskId); - am.cancelThumbnailTransition(getTaskId()); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java index 8fe4975f1a2a..37266f6ff39f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java @@ -22,5 +22,4 @@ import com.android.systemui.recents.events.EventBus; * This is sent when the PiP should be expanded due to being relaunched. */ public class ExpandPipEvent extends EventBus.Event { - public final boolean clearThumbnailWindows = true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java new file mode 100644 index 000000000000..6bbd09f715db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.statusbar; + +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; +import static com.android.systemui.statusbar.NotificationRemoteInputManager + .FORCE_REMOTE_INPUT_HISTORY; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.os.Build; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; +import android.util.EventLog; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.NotificationMessagingUtil; +import com.android.systemui.DejankUtils; +import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; +import com.android.systemui.EventLogTags; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.R; +import com.android.systemui.UiOffloadThread; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.NotificationInflater; +import com.android.systemui.statusbar.notification.RowInflaterTask; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.leak.LeakDetector; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. + * It also handles tasks such as their inflation and their interaction with other + * Notification.*Manager objects. + */ +public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback, + ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler, + VisualStabilityManager.Callback { + private static final String TAG = "NotificationEntryManager"; + protected static final boolean DEBUG = false; + protected static final boolean ENABLE_HEADS_UP = true; + protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; + + protected final NotificationMessagingUtil mMessagingUtil; + protected final Context mContext; + protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); + protected final NotificationClicker mNotificationClicker = new NotificationClicker(); + protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch = + new ArraySet<>(); + + // Dependencies: + protected final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); + protected final NotificationGroupManager mGroupManager = + Dependency.get(NotificationGroupManager.class); + protected final NotificationGutsManager mGutsManager = + Dependency.get(NotificationGutsManager.class); + protected final NotificationRemoteInputManager mRemoteInputManager = + Dependency.get(NotificationRemoteInputManager.class); + protected final NotificationMediaManager mMediaManager = + Dependency.get(NotificationMediaManager.class); + protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); + protected final DeviceProvisionedController mDeviceProvisionedController = + Dependency.get(DeviceProvisionedController.class); + protected final VisualStabilityManager mVisualStabilityManager = + Dependency.get(VisualStabilityManager.class); + protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); + protected final ForegroundServiceController mForegroundServiceController = + Dependency.get(ForegroundServiceController.class); + protected final NotificationListener mNotificationListener = + Dependency.get(NotificationListener.class); + + protected IStatusBarService mBarService; + protected NotificationPresenter mPresenter; + protected Callback mCallback; + protected PowerManager mPowerManager; + protected SystemServicesProxy mSystemServicesProxy; + protected NotificationListenerService.RankingMap mLatestRankingMap; + protected HeadsUpManager mHeadsUpManager; + protected NotificationData mNotificationData; + protected ContentObserver mHeadsUpObserver; + protected boolean mUseHeadsUp = false; + protected boolean mDisableNotificationAlerts; + protected NotificationListContainer mListContainer; + + private final class NotificationClicker implements View.OnClickListener { + + @Override + public void onClick(final View v) { + if (!(v instanceof ExpandableNotificationRow)) { + Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); + return; + } + + mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v); + + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; + final StatusBarNotification sbn = row.getStatusBarNotification(); + if (sbn == null) { + Log.e(TAG, "NotificationClicker called on an unclickable notification,"); + return; + } + + // Check if the notification is displaying the menu, if so slide notification back + if (row.getProvider() != null && row.getProvider().isMenuVisible()) { + row.animateTranslateNotification(0); + return; + } + + // Mark notification for one frame. + row.setJustClicked(true); + DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); + + mCallback.onNotificationClicked(sbn, row); + } + + public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + if (notification.contentIntent != null || notification.fullScreenIntent != null) { + row.setOnClickListener(this); + } else { + row.setOnClickListener(null); + } + } + } + + private final DeviceProvisionedController.DeviceProvisionedListener + mDeviceProvisionedListener = + new DeviceProvisionedController.DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + updateNotifications(); + } + }; + + public NotificationListenerService.RankingMap getLatestRankingMap() { + return mLatestRankingMap; + } + + public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) { + mLatestRankingMap = latestRankingMap; + } + + public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { + mDisableNotificationAlerts = disableNotificationAlerts; + mHeadsUpObserver.onChange(true); + } + + public void destroy() { + mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); + } + + public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { + removeNotification(entry.key, getLatestRankingMap()); + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { + setLatestRankingMap(null); + } + } else { + updateNotificationRanking(null); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NotificationEntryManager state:"); + pw.print(" mPendingNotifications="); + if (mPendingNotifications.size() == 0) { + pw.println("null"); + } else { + for (NotificationData.Entry entry : mPendingNotifications.values()) { + pw.println(entry.notification); + } + } + pw.print(" mUseHeadsUp="); + pw.println(mUseHeadsUp); + } + + public NotificationEntryManager(Context context) { + mContext = context; + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mMessagingUtil = new NotificationMessagingUtil(context); + mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationListContainer listContainer, Callback callback, + HeadsUpManager headsUpManager) { + mPresenter = presenter; + mCallback = callback; + mNotificationData = new NotificationData(presenter); + mHeadsUpManager = headsUpManager; + mNotificationData.setHeadsUpManager(mHeadsUpManager); + mListContainer = listContainer; + + mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) { + @Override + public void onChange(boolean selfChange) { + boolean wasUsing = mUseHeadsUp; + mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts + && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, + Settings.Global.HEADS_UP_OFF); + Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); + if (wasUsing != mUseHeadsUp) { + if (!mUseHeadsUp) { + Log.d(TAG, + "dismissing any existing heads up notification on disable event"); + mHeadsUpManager.releaseAllImmediately(); + } + } + } + }; + + if (ENABLE_HEADS_UP) { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), + true, + mHeadsUpObserver); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, + mHeadsUpObserver); + } + + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + + mHeadsUpObserver.onChange(true); // set up + } + + public NotificationData getNotificationData() { + return mNotificationData; + } + + public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + return mGutsManager::openGuts; + } + + @Override + public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mUiOffloadThread.submit(() -> { + try { + mBarService.onNotificationExpansionChanged(key, userAction, expanded); + } catch (RemoteException e) { + // Ignore. + } + }); + } + + @Override + public void onReorderingAllowed() { + updateNotifications(); + } + + private boolean shouldSuppressFullScreenIntent(String key) { + if (mPresenter.isDeviceInVrMode()) { + return true; + } + + if (mPowerManager.isInteractive()) { + return mNotificationData.shouldSuppressScreenOn(key); + } else { + return mNotificationData.shouldSuppressScreenOff(key); + } + } + + private void inflateViews(NotificationData.Entry entry, ViewGroup parent) { + PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, + entry.notification.getUser().getIdentifier()); + + final StatusBarNotification sbn = entry.notification; + if (entry.row != null) { + entry.reset(); + updateNotification(entry, pmUser, sbn, entry.row); + } else { + new RowInflaterTask().inflate(mContext, parent, entry, + row -> { + bindRow(entry, pmUser, sbn, row); + updateNotification(entry, pmUser, sbn, row); + }); + } + } + + private void bindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setExpansionLogger(this, entry.notification.getKey()); + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setOnExpandClickListener(mPresenter); + row.setInflationCallback(this); + row.setLongPressListener(getNotificationLongClicker()); + mRemoteInputManager.bindRow(row); + + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + final String pkg = sbn.getPackageName(); + String appname = pkg; + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + appname = String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + row.setAppName(appname); + row.setOnDismissRunnable(() -> + performRemoveNotification(row.getStatusBarNotification())); + row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (ENABLE_REMOTE_INPUT) { + row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + mCallback.onBindRow(entry, pmUser, sbn, row); + } + + public void performRemoveNotification(StatusBarNotification n) { + NotificationData.Entry entry = mNotificationData.get(n.getKey()); + mRemoteInputManager.onPerformRemoveNotification(n, entry); + final String pkg = n.getPackageName(); + final String tag = n.getTag(); + final int id = n.getId(); + final int userId = n.getUserId(); + try { + int dismissalSurface = NotificationStats.DISMISSAL_SHADE; + if (isHeadsUp(n.getKey())) { + dismissalSurface = NotificationStats.DISMISSAL_PEEK; + } else if (mListContainer.hasPulsingNotifications()) { + dismissalSurface = NotificationStats.DISMISSAL_AOD; + } + mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface); + removeNotification(n.getKey(), null); + + } catch (RemoteException ex) { + // system process is dead if we're here. + } + + mCallback.onPerformRemoveNotification(n); + } + + /** + * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService + * about the failure. + * + * WARNING: this will call back into us. Don't hold any locks. + */ + void handleNotificationError(StatusBarNotification n, String message) { + removeNotification(n.getKey(), null); + try { + mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), + n.getInitialPid(), message, n.getUserId()); + } catch (RemoteException ex) { + // The end is nigh. + } + } + + private void abortExistingInflation(String key) { + if (mPendingNotifications.containsKey(key)) { + NotificationData.Entry entry = mPendingNotifications.get(key); + entry.abortTask(); + mPendingNotifications.remove(key); + } + NotificationData.Entry addedEntry = mNotificationData.get(key); + if (addedEntry != null) { + addedEntry.abortTask(); + } + } + + @Override + public void handleInflationException(StatusBarNotification notification, Exception e) { + handleNotificationError(notification, e.getMessage()); + } + + private void addEntry(NotificationData.Entry shadeEntry) { + boolean isHeadsUped = shouldPeek(shadeEntry); + if (isHeadsUped) { + mHeadsUpManager.showNotification(shadeEntry); + // Mark as seen immediately + setNotificationShown(shadeEntry.notification); + } + addNotificationViews(shadeEntry); + mCallback.onNotificationAdded(shadeEntry); + } + + @Override + public void onAsyncInflationFinished(NotificationData.Entry entry) { + mPendingNotifications.remove(entry.key); + // If there was an async task started after the removal, we don't want to add it back to + // the list, otherwise we might get leaks. + boolean isNew = mNotificationData.get(entry.key) == null; + if (isNew && !entry.row.isRemoved()) { + addEntry(entry); + } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { + mVisualStabilityManager.onLowPriorityUpdated(entry); + mPresenter.updateNotificationViews(); + } + entry.row.setLowPriorityStateUpdated(false); + } + + @Override + public void removeNotification(String key, NotificationListenerService.RankingMap ranking) { + boolean deferRemoval = false; + abortExistingInflation(key); + if (mHeadsUpManager.isHeadsUp(key)) { + // A cancel() in response to a remote input shouldn't be delayed, as it makes the + // sending look longer than it takes. + // Also we should not defer the removal if reordering isn't allowed since otherwise + // some notifications can't disappear before the panel is closed. + boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) + && !FORCE_REMOTE_INPUT_HISTORY + || !mVisualStabilityManager.isReorderingAllowed(); + deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); + } + mMediaManager.onNotificationRemoved(key); + + NotificationData.Entry entry = mNotificationData.get(key); + if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key) + && entry.row != null && !entry.row.isDismissed()) { + StatusBarNotification sbn = entry.notification; + + Notification.Builder b = Notification.Builder + .recoverBuilder(mContext, sbn.getNotification().clone()); + CharSequence[] oldHistory = sbn.getNotification().extras + .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + CharSequence[] newHistory; + if (oldHistory == null) { + newHistory = new CharSequence[1]; + } else { + newHistory = new CharSequence[oldHistory.length + 1]; + System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); + } + newHistory[0] = String.valueOf(entry.remoteInputText); + b.setRemoteInputHistory(newHistory); + + Notification newNotification = b.build(); + + // Undo any compatibility view inflation + newNotification.contentView = sbn.getNotification().contentView; + newNotification.bigContentView = sbn.getNotification().bigContentView; + newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; + + StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), + newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + boolean updated = false; + try { + updateNotificationInternal(newSbn, null); + updated = true; + } catch (InflationException e) { + deferRemoval = false; + } + if (updated) { + Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); + mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key); + return; + } + } + if (deferRemoval) { + mLatestRankingMap = ranking; + mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); + return; + } + + if (mRemoteInputManager.onRemoveNotification(entry)) { + mLatestRankingMap = ranking; + return; + } + + if (entry != null && mGutsManager.getExposedGuts() != null + && mGutsManager.getExposedGuts() == entry.row.getGuts() + && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { + Log.w(TAG, "Keeping notification because it's showing guts. " + key); + mLatestRankingMap = ranking; + mGutsManager.setKeyToRemoveOnGutsClosed(key); + return; + } + + if (entry != null) { + mForegroundServiceController.removeNotification(entry.notification); + } + + if (entry != null && entry.row != null) { + entry.row.setRemoved(); + mListContainer.cleanUpViewState(entry.row); + } + // Let's remove the children if this was a summary + handleGroupSummaryRemoved(key); + StatusBarNotification old = removeNotificationViews(key, ranking); + + mCallback.onNotificationRemoved(key, old); + } + + private StatusBarNotification removeNotificationViews(String key, + NotificationListenerService.RankingMap ranking) { + NotificationData.Entry entry = mNotificationData.remove(key, ranking); + if (entry == null) { + Log.w(TAG, "removeNotification for unknown key: " + key); + return null; + } + updateNotifications(); + Dependency.get(LeakDetector.class).trackGarbage(entry); + return entry.notification; + } + + /** + * Ensures that the group children are cancelled immediately when the group summary is cancelled + * instead of waiting for the notification manager to send all cancels. Otherwise this could + * lead to flickers. + * + * This also ensures that the animation looks nice and only consists of a single disappear + * animation instead of multiple. + * @param key the key of the notification was removed + * + */ + private void handleGroupSummaryRemoved(String key) { + NotificationData.Entry entry = mNotificationData.get(key); + if (entry != null && entry.row != null + && entry.row.isSummaryWithChildren()) { + if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { + // We don't want to remove children for autobundled notifications as they are not + // always cancelled. We only remove them if they were dismissed by the user. + return; + } + List<ExpandableNotificationRow> notificationChildren = + entry.row.getNotificationChildren(); + for (int i = 0; i < notificationChildren.size(); i++) { + ExpandableNotificationRow row = notificationChildren.get(i); + if ((row.getStatusBarNotification().getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + // the child is a foreground service notification which we can't remove! + continue; + } + row.setKeepInParent(true); + // we need to set this state earlier as otherwise we might generate some weird + // animations + row.setRemoved(); + } + } + } + + public void updateNotificationsOnDensityOrFontScaleChanged() { + ArrayList<NotificationData.Entry> activeNotifications = + mNotificationData.getActiveNotifications(); + for (int i = 0; i < activeNotifications.size(); i++) { + NotificationData.Entry entry = activeNotifications.get(i); + boolean exposedGuts = mGutsManager.getExposedGuts() != null + && entry.row.getGuts() == mGutsManager.getExposedGuts(); + entry.row.onDensityOrFontScaleChanged(); + if (exposedGuts) { + mGutsManager.setExposedGuts(entry.row.getGuts()); + mGutsManager.bindGuts(entry.row); + } + } + } + + private void updateNotification(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry)); + boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); + boolean isUpdate = mNotificationData.get(entry.key) != null; + boolean wasLowPriority = row.isLowPriority(); + row.setIsLowPriority(isLowPriority); + row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); + // bind the click event to the content area + mNotificationClicker.register(row, sbn); + + // Extract target SDK version. + try { + ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); + entry.targetSdk = info.targetSdkVersion; + } catch (PackageManager.NameNotFoundException ex) { + Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); + } + row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD + && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + entry.autoRedacted = entry.notification.getNotification().publicVersion == null; + + entry.row = row; + entry.row.setOnActivatedListener(mPresenter); + + boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, + mNotificationData.getImportance(sbn.getKey())); + boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight + && !mPresenter.isPresenterFullyCollapsed(); + row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + row.updateNotification(entry); + } + + + protected void addNotificationViews(NotificationData.Entry entry) { + if (entry == null) { + return; + } + // Add the expanded view and icon. + mNotificationData.add(entry); + updateNotifications(); + } + + protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) + throws InflationException { + if (DEBUG) { + Log.d(TAG, "createNotificationViews(notification=" + sbn); + } + NotificationData.Entry entry = new NotificationData.Entry(sbn); + Dependency.get(LeakDetector.class).trackInstance(entry); + entry.createIcons(mContext, sbn); + // Construct the expanded view. + inflateViews(entry, mListContainer.getViewParentForNotification(entry)); + return entry; + } + + private void addNotificationInternal(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) throws InflationException { + String key = notification.getKey(); + if (DEBUG) Log.d(TAG, "addNotification key=" + key); + + mNotificationData.updateRanking(ranking); + NotificationData.Entry shadeEntry = createNotificationViews(notification); + boolean isHeadsUped = shouldPeek(shadeEntry); + if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { + if (shouldSuppressFullScreenIntent(key)) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); + } + } else if (mNotificationData.getImportance(key) + < NotificationManager.IMPORTANCE_HIGH) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: not important enough: " + + key); + } + } else { + // Stop screensaver if the notification has a fullscreen intent. + // (like an incoming phone call) + SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); + + // not immersive & a fullscreen alert should be shown + if (DEBUG) + Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); + try { + EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, + key); + notification.getNotification().fullScreenIntent.send(); + shadeEntry.notifyFullScreenIntentLaunched(); + mMetricsLogger.count("note_fullscreen", 1); + } catch (PendingIntent.CanceledException e) { + } + } + } + abortExistingInflation(key); + + mForegroundServiceController.addNotification(notification, + mNotificationData.getImportance(key)); + + mPendingNotifications.put(key, shadeEntry); + } + + @Override + public void addNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) { + try { + addNotificationInternal(notification, ranking); + } catch (InflationException e) { + handleInflationException(notification, e); + } + } + + private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) { + return oldEntry == null || !oldEntry.hasInterrupted() + || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; + } + + private void updateNotificationInternal(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) throws InflationException { + if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); + + final String key = notification.getKey(); + abortExistingInflation(key); + NotificationData.Entry entry = mNotificationData.get(key); + if (entry == null) { + return; + } + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + mRemoteInputManager.onUpdateNotification(entry); + + if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { + mGutsManager.setKeyToRemoveOnGutsClosed(null); + Log.w(TAG, "Notification that was kept for guts was updated. " + key); + } + + Notification n = notification.getNotification(); + mNotificationData.updateRanking(ranking); + + final StatusBarNotification oldNotification = entry.notification; + entry.notification = notification; + mGroupManager.onEntryUpdated(entry, oldNotification); + + entry.updateIcons(mContext, notification); + inflateViews(entry, mListContainer.getViewParentForNotification(entry)); + + mForegroundServiceController.updateNotification(notification, + mNotificationData.getImportance(key)); + + boolean shouldPeek = shouldPeek(entry, notification); + boolean alertAgain = alertAgain(entry, n); + + updateHeadsUp(key, entry, shouldPeek, alertAgain); + updateNotifications(); + + if (!notification.isClearable()) { + // The user may have performed a dismiss action on the notification, since it's + // not clearable we should snap it back. + mListContainer.snapViewIfNeeded(entry.row); + } + + if (DEBUG) { + // Is this for you? + boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification); + Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); + } + + mCallback.onNotificationUpdated(notification); + } + + @Override + public void updateNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) { + try { + updateNotificationInternal(notification, ranking); + } catch (InflationException e) { + handleInflationException(notification, e); + } + } + + public void updateNotifications() { + mNotificationData.filterAndSort(); + + mPresenter.updateNotificationViews(); + } + + public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) { + mNotificationData.updateRanking(ranking); + updateNotifications(); + } + + protected boolean shouldPeek(NotificationData.Entry entry) { + return shouldPeek(entry, entry.notification); + } + + public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { + if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode"); + return false; + } + + if (mNotificationData.shouldFilterOut(sbn)) { + if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); + return false; + } + + boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming(); + + if (!inUse && !mPresenter.isDozing()) { + if (DEBUG) { + Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); + } + return false; + } + + if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); + return false; + } + + if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); + return false; + } + + if (entry.hasJustLaunchedFullScreenIntent()) { + if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); + return false; + } + + if (isSnoozedPackage(sbn)) { + if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); + return false; + } + + // Allow peeking for DEFAULT notifications only if we're on Ambient Display. + int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT + : NotificationManager.IMPORTANCE_HIGH; + if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { + if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); + return false; + } + + // Don't peek notifications that are suppressed due to group alert behavior + if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior"); + return false; + } + + if (!mCallback.shouldPeek(entry, sbn)) { + return false; + } + + return true; + } + + protected void setNotificationShown(StatusBarNotification n) { + setNotificationsShown(new String[]{n.getKey()}); + } + + protected void setNotificationsShown(String[] keys) { + try { + mNotificationListener.setNotificationsShown(keys); + } catch (RuntimeException e) { + Log.d(TAG, "failed setNotificationsShown: ", e); + } + } + + protected boolean isSnoozedPackage(StatusBarNotification sbn) { + return mHeadsUpManager.isSnoozed(sbn.getPackageName()); + } + + protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek, + boolean alertAgain) { + final boolean wasHeadsUp = isHeadsUp(key); + if (wasHeadsUp) { + if (!shouldPeek) { + // We don't want this to be interrupting anymore, lets remove it + mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); + } else { + mHeadsUpManager.updateNotification(entry, alertAgain); + } + } else if (shouldPeek && alertAgain) { + // This notification was updated to be a heads-up, show it! + mHeadsUpManager.showNotification(entry); + } + } + + protected boolean isHeadsUp(String key) { + return mHeadsUpManager.isHeadsUp(key); + } + + /** + * Callback for NotificationEntryManager. + */ + public interface Callback { + + /** + * Called when a new entry is created. + * + * @param shadeEntry entry that was created + */ + void onNotificationAdded(NotificationData.Entry shadeEntry); + + /** + * Called when a notification was updated. + * + * @param notification notification that was updated + */ + void onNotificationUpdated(StatusBarNotification notification); + + /** + * Called when a notification was removed. + * + * @param key key of notification that was removed + * @param old StatusBarNotification of the notification before it was removed + */ + void onNotificationRemoved(String key, StatusBarNotification old); + + + /** + * Called when a notification is clicked. + * + * @param sbn notification that was clicked + * @param row row for that notification + */ + void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row); + + /** + * Called when a new notification and row is created. + * + * @param entry entry for the notification + * @param pmUser package manager for user + * @param sbn notification + * @param row row for the notification + */ + void onBindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row); + + /** + * Removes a notification immediately. + * + * @param statusBarNotification notification that is being removed + */ + void onPerformRemoveNotification(StatusBarNotification statusBarNotification); + + /** + * Returns true if NotificationEntryManager should peek this notification. + * + * @param entry entry of the notification that might be peeked + * @param sbn notification that might be peeked + * @return true if the notification should be peeked + */ + boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java index 2e572e17557a..87ad6f6bc948 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java @@ -42,7 +42,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; import java.io.FileDescriptor; @@ -67,24 +66,22 @@ public class NotificationGutsManager implements Dumpable { private final Set<String> mNonBlockablePkgs; private final Context mContext; private final AccessibilityManager mAccessibilityManager; - private final NotificationLockscreenUserManager mLockscreenUserManager; + + // Dependencies: + private final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); // which notification is currently being longpress-examined by the user private NotificationGuts mNotificationGutsExposed; private NotificationMenuRowPlugin.MenuItem mGutsMenuItem; - private NotificationPresenter mPresenter; - - // TODO: Create NotificationListContainer interface and use it instead of - // NotificationStackScrollLayout here - private NotificationStackScrollLayout mStackScroller; + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; + private NotificationListContainer mListContainer; private NotificationInfo.CheckSaveListener mCheckSaveListener; private OnSettingsClickListener mOnSettingsClickListener; private String mKeyToRemoveOnGutsClosed; - public NotificationGutsManager( - NotificationLockscreenUserManager lockscreenUserManager, - Context context) { - mLockscreenUserManager = lockscreenUserManager; + public NotificationGutsManager(Context context) { mContext = context; Resources res = context.getResources(); @@ -96,12 +93,13 @@ public class NotificationGutsManager implements Dumpable { mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); } - public void setUp(NotificationPresenter presenter, - NotificationStackScrollLayout stackScroller, + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager, NotificationListContainer listContainer, NotificationInfo.CheckSaveListener checkSaveListener, OnSettingsClickListener onSettingsClickListener) { mPresenter = presenter; - mStackScroller = stackScroller; + mEntryManager = entryManager; + mListContainer = listContainer; mCheckSaveListener = checkSaveListener; mOnSettingsClickListener = onSettingsClickListener; } @@ -158,7 +156,7 @@ public class NotificationGutsManager implements Dumpable { final NotificationGuts guts = row.getGuts(); guts.setClosedListener((NotificationGuts g) -> { if (!g.willBeRemoved() && !row.isRemoved()) { - mStackScroller.onHeightChanged( + mListContainer.onHeightChanged( row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */); } if (mNotificationGutsExposed == g) { @@ -168,18 +166,18 @@ public class NotificationGutsManager implements Dumpable { String key = sbn.getKey(); if (key.equals(mKeyToRemoveOnGutsClosed)) { mKeyToRemoveOnGutsClosed = null; - mPresenter.removeNotification(key, mPresenter.getLatestRankingMap()); + mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap()); } }); View gutsView = item.getGutsView(); if (gutsView instanceof NotificationSnooze) { NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView; - snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper()); + snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper()); snoozeGuts.setStatusBarNotification(sbn); snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria); guts.setHeightChangedListener((NotificationGuts g) -> { - mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */); + mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */); }); } @@ -257,7 +255,7 @@ public class NotificationGutsManager implements Dumpable { mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force); } if (resetMenu) { - mStackScroller.resetExposedMenuView(false /* animate */, true /* force */); + mListContainer.resetExposedMenuView(false /* animate */, true /* force */); } } @@ -350,7 +348,7 @@ public class NotificationGutsManager implements Dumpable { !mAccessibilityManager.isTouchExplorationEnabled()); guts.setExposed(true /* exposed */, needsFalsingProtection); row.closeRemoteInput(); - mStackScroller.onHeightChanged(row, true /* needsAnimation */); + mListContainer.onHeightChanged(row, true /* needsAnimation */); mNotificationGutsExposed = guts; mGutsMenuItem = item; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java new file mode 100644 index 000000000000..43be44deedc8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; + +/** + * Interface representing the entity that contains notifications. It can have + * notification views added and removed from it, and will manage displaying them to the user. + */ +public interface NotificationListContainer { + + /** + * Called when a child is being transferred. + * + * @param childTransferInProgress whether child transfer is in progress + */ + void setChildTransferInProgress(boolean childTransferInProgress); + + /** + * Change the position of child to a new location + * + * @param child the view to change the position for + * @param newIndex the new index + */ + void changeViewPosition(View child, int newIndex); + + /** + * Called when a child was added to a group. + * + * @param row row of the group child that was added + */ + void notifyGroupChildAdded(View row); + + /** + * Called when a child was removed from a group. + * + * @param row row of the child that was removed + * @param childrenContainer ViewGroup of the group that the child was removed from + */ + void notifyGroupChildRemoved(View row, ViewGroup childrenContainer); + + /** + * Generate an animation for an added child view. + * + * @param child The view to be added. + * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. + */ + void generateAddAnimation(View child, boolean fromMoreCard); + + /** + * Generate a child order changed event. + */ + void generateChildOrderChangedEvent(); + + /** + * Returns the number of children in the NotificationListContainer. + * + * @return the number of children in the NotificationListContainer + */ + int getContainerChildCount(); + + /** + * Gets the ith child in the NotificationListContainer. + * + * @param i ith child to get + * @return the ith child in the list container + */ + View getContainerChildAt(int i); + + /** + * Remove a view from the container + * + * @param v view to remove + */ + void removeContainerView(View v); + + /** + * Add a view to the container + * + * @param v view to add + */ + void addContainerView(View v); + + /** + * Sets the maximum number of notifications to display. + * + * @param maxNotifications max number of notifications to display + */ + void setMaxDisplayedNotifications(int maxNotifications); + + /** + * Handle snapping a non-dismissable row back if the user tried to dismiss it. + * + * @param row row to snap back + */ + void snapViewIfNeeded(ExpandableNotificationRow row); + + /** + * Get the view parent for a notification entry. For example, NotificationStackScrollLayout. + * + * @param entry entry to get the view parent for + * @return the view parent for entry + */ + ViewGroup getViewParentForNotification(NotificationData.Entry entry); + + /** + * Called when the height of an expandable view changes. + * + * @param view view whose height changed + * @param animate whether this change should be animated + */ + void onHeightChanged(ExpandableView view, boolean animate); + + /** + * Resets the currently exposed menu view. + * + * @param animate whether to animate the closing/change of menu view + * @param force reset the menu view even if it looks like it is already reset + */ + void resetExposedMenuView(boolean animate, boolean force); + + /** + * Returns the NotificationSwipeActionHelper for the NotificationListContainer. + * + * @return swipe action helper for the list container + */ + NotificationSwipeActionHelper getSwipeActionHelper(); + + /** + * Called when a notification is removed from the shade. This cleans up the state for a + * given view. + * + * @param view view to clean up view state for + */ + void cleanUpViewState(View view); + + /** + * Returns whether an ExpandableNotificationRow is in a visible location or not. + * + * @param row + * @return true if row is in a visible location + */ + boolean isInVisibleLocation(ExpandableNotificationRow row); + + /** + * Sets a listener to listen for changes in notification locations. + * + * @param listener listener to set + */ + void setChildLocationsChangedListener( + NotificationLogger.OnChildLocationsChangedListener listener); + + /** + * Called when an update to the notification view hierarchy is completed. + */ + default void onNotificationViewUpdateFinished() {} + + /** + * Returns true if there are pulsing notifications. + * + * @return true if has pulsing notifications + */ + boolean hasPulsingNotifications(); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index a72e8ac77af5..0144f4244a8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.Log; +import com.android.systemui.Dependency; import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; /** @@ -36,14 +37,16 @@ import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; public class NotificationListener extends NotificationListenerWithPlugins { private static final String TAG = "NotificationListener"; - private final NotificationRemoteInputManager mRemoteInputManager; + // Dependencies: + private final NotificationRemoteInputManager mRemoteInputManager = + Dependency.get(NotificationRemoteInputManager.class); + private final Context mContext; - private NotificationPresenter mPresenter; + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; - public NotificationListener(NotificationRemoteInputManager remoteInputManager, - Context context) { - mRemoteInputManager = remoteInputManager; + public NotificationListener(Context context) { mContext = context; } @@ -59,7 +62,7 @@ public class NotificationListener extends NotificationListenerWithPlugins { final RankingMap currentRanking = getCurrentRanking(); mPresenter.getHandler().post(() -> { for (StatusBarNotification sbn : notifications) { - mPresenter.addNotification(sbn, currentRanking); + mEntryManager.addNotification(sbn, currentRanking); } }); } @@ -73,7 +76,8 @@ public class NotificationListener extends NotificationListenerWithPlugins { processForRemoteInput(sbn.getNotification(), mContext); String key = sbn.getKey(); mRemoteInputManager.getKeysKeptForRemoteInput().remove(key); - boolean isUpdate = mPresenter.getNotificationData().get(key) != null; + boolean isUpdate = + mEntryManager.getNotificationData().get(key) != null; // In case we don't allow child notifications, we ignore children of // notifications that have a summary, since` we're not going to show them // anyway. This is true also when the summary is canceled, @@ -86,16 +90,17 @@ public class NotificationListener extends NotificationListenerWithPlugins { // Remove existing notification to avoid stale data. if (isUpdate) { - mPresenter.removeNotification(key, rankingMap); + mEntryManager.removeNotification(key, rankingMap); } else { - mPresenter.getNotificationData().updateRanking(rankingMap); + mEntryManager.getNotificationData() + .updateRanking(rankingMap); } return; } if (isUpdate) { - mPresenter.updateNotification(sbn, rankingMap); + mEntryManager.updateNotification(sbn, rankingMap); } else { - mPresenter.addNotification(sbn, rankingMap); + mEntryManager.addNotification(sbn, rankingMap); } }); } @@ -107,7 +112,9 @@ public class NotificationListener extends NotificationListenerWithPlugins { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) { final String key = sbn.getKey(); - mPresenter.getHandler().post(() -> mPresenter.removeNotification(key, rankingMap)); + mPresenter.getHandler().post(() -> { + mEntryManager.removeNotification(key, rankingMap); + }); } } @@ -116,12 +123,16 @@ public class NotificationListener extends NotificationListenerWithPlugins { if (DEBUG) Log.d(TAG, "onRankingUpdate"); if (rankingMap != null) { RankingMap r = onPluginRankingUpdate(rankingMap); - mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r)); + mPresenter.getHandler().post(() -> { + mEntryManager.updateNotificationRanking(r); + }); } } - public void setUpWithPresenter(NotificationPresenter presenter) { + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager) { mPresenter = presenter; + mEntryManager = entryManager; try { registerAsSystemService(mContext, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index 644d834e42c6..bcdc269f712d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -81,7 +81,7 @@ public class NotificationLockscreenUserManager implements Dumpable { isCurrentProfile(getSendingUserId())) { mUsersAllowingPrivateNotifications.clear(); updateLockscreenNotificationSetting(); - mPresenter.updateNotifications(); + mEntryManager.updateNotifications(); } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) { if (userId != mCurrentUserId && isCurrentProfile(userId)) { mPresenter.onWorkChallengeChanged(); @@ -108,29 +108,15 @@ public class NotificationLockscreenUserManager implements Dumpable { // Start the overview connection to the launcher service Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser(); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - List<ActivityManager.RecentTaskInfo> recentTask = null; try { - recentTask = ActivityManager.getService().getRecentTasks(1, - ActivityManager.RECENT_WITH_EXCLUDED, - mCurrentUserId).getList(); + final int lastResumedActivityUserId = + ActivityManager.getService().getLastResumedActivityUserId(); + if (mUserManager.isManagedProfile(lastResumedActivityUserId)) { + showForegroundManagedProfileActivityToast(); + } } catch (RemoteException e) { // Abandon hope activity manager not running. } - if (recentTask != null && recentTask.size() > 0) { - UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId); - if (user != null && user.isManagedProfile()) { - Toast toast = Toast.makeText(mContext, - R.string.managed_profile_foreground_toast, - Toast.LENGTH_SHORT); - TextView text = toast.getView().findViewById(android.R.id.message); - text.setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.stat_sys_managed_profile_status, 0, 0, 0); - int paddingPx = mContext.getResources().getDimensionPixelSize( - R.dimen.managed_profile_toast_padding); - text.setCompoundDrawablePadding(paddingPx); - toast.show(); - } - } } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) { final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT); final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); @@ -157,6 +143,7 @@ public class NotificationLockscreenUserManager implements Dumpable { protected int mCurrentUserId = 0; protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; protected ContentObserver mLockscreenSettingsObserver; protected ContentObserver mSettingsObserver; @@ -170,8 +157,10 @@ public class NotificationLockscreenUserManager implements Dumpable { ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } - public void setUpWithPresenter(NotificationPresenter presenter) { + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager) { mPresenter = presenter; + mEntryManager = entryManager; mLockscreenSettingsObserver = new ContentObserver(mPresenter.getHandler()) { @Override @@ -182,7 +171,7 @@ public class NotificationLockscreenUserManager implements Dumpable { mUsersAllowingNotifications.clear(); // ... and refresh all the notifications updateLockscreenNotificationSetting(); - mPresenter.updateNotifications(); + mEntryManager.updateNotifications(); } }; @@ -191,7 +180,7 @@ public class NotificationLockscreenUserManager implements Dumpable { public void onChange(boolean selfChange) { updateLockscreenNotificationSetting(); if (mDeviceProvisionedController.isDeviceProvisioned()) { - mPresenter.updateNotifications(); + mEntryManager.updateNotifications(); } } }; @@ -242,6 +231,19 @@ public class NotificationLockscreenUserManager implements Dumpable { mSettingsObserver.onChange(false); // set up } + private void showForegroundManagedProfileActivityToast() { + Toast toast = Toast.makeText(mContext, + R.string.managed_profile_foreground_toast, + Toast.LENGTH_SHORT); + TextView text = toast.getView().findViewById(android.R.id.message); + text.setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.stat_sys_managed_profile_status, 0, 0, 0); + int paddingPx = mContext.getResources().getDimensionPixelSize( + R.dimen.managed_profile_toast_padding); + text.setCompoundDrawablePadding(paddingPx); + toast.show(); + } + public boolean shouldShowLockscreenNotifications() { return mShowLockscreenNotifications; } @@ -271,13 +273,13 @@ public class NotificationLockscreenUserManager implements Dumpable { */ public boolean shouldHideNotifications(String key) { return isLockscreenPublicMode(mCurrentUserId) - && mPresenter.getNotificationData().getVisibilityOverride(key) == + && mEntryManager.getNotificationData().getVisibilityOverride(key) == Notification.VISIBILITY_SECRET; } public boolean shouldShowOnKeyguard(StatusBarNotification sbn) { return mShowLockscreenNotifications - && !mPresenter.getNotificationData().isAmbient(sbn.getKey()); + && !mEntryManager.getNotificationData().isAmbient(sbn.getKey()); } private void setShowLockscreenNotifications(boolean show) { @@ -395,7 +397,7 @@ public class NotificationLockscreenUserManager implements Dumpable { } private boolean packageHasVisibilityOverride(String key) { - return mPresenter.getNotificationData().getVisibilityOverride(key) == + return mEntryManager.getNotificationData().getVisibilityOverride(key) == Notification.VISIBILITY_PRIVATE; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java index e58d80186b3a..4225f83c5b11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java @@ -27,8 +27,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.Dependency; import com.android.systemui.UiOffloadThread; -import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; import java.util.Collection; @@ -47,21 +47,22 @@ public class NotificationLogger { /** Keys of notifications currently visible to the user. */ private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = new ArraySet<>(); - private final NotificationListenerService mNotificationListener; - private final UiOffloadThread mUiOffloadThread; - protected NotificationPresenter mPresenter; + // Dependencies: + private final NotificationListenerService mNotificationListener = + Dependency.get(NotificationListener.class); + private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); + + protected NotificationEntryManager mEntryManager; protected Handler mHandler = new Handler(); protected IStatusBarService mBarService; private long mLastVisibilityReportUptimeMs; - private NotificationStackScrollLayout mStackScroller; + private NotificationListContainer mListContainer; - protected final NotificationStackScrollLayout.OnChildLocationsChangedListener - mNotificationLocationsChangedListener = - new NotificationStackScrollLayout.OnChildLocationsChangedListener() { + protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener = + new OnChildLocationsChangedListener() { @Override - public void onChildLocationsChanged( - NotificationStackScrollLayout stackScrollLayout) { + public void onChildLocationsChanged() { if (mHandler.hasCallbacks(mVisibilityReporter)) { // Visibilities will be reported when the existing // callback is executed. @@ -99,13 +100,13 @@ public class NotificationLogger { // notifications. // 3. Report newly visible and no-longer visible notifications. // 4. Keep currently visible notifications for next report. - ArrayList<NotificationData.Entry> activeNotifications = mPresenter. - getNotificationData().getActiveNotifications(); + ArrayList<NotificationData.Entry> activeNotifications = mEntryManager + .getNotificationData().getActiveNotifications(); int N = activeNotifications.size(); for (int i = 0; i < N; i++) { NotificationData.Entry entry = activeNotifications.get(i); String key = entry.notification.getKey(); - boolean isVisible = mStackScroller.isInVisibleLocation(entry.row); + boolean isVisible = mListContainer.isInVisibleLocation(entry.row); NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); if (isVisible) { @@ -135,19 +136,15 @@ public class NotificationLogger { } }; - public NotificationLogger(NotificationListenerService notificationListener, - UiOffloadThread uiOffloadThread) { - mNotificationListener = notificationListener; - mUiOffloadThread = uiOffloadThread; + public NotificationLogger() { mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } - // TODO: Remove dependency on NotificationStackScrollLayout. - public void setUpWithPresenter(NotificationPresenter presenter, - NotificationStackScrollLayout stackScroller) { - mPresenter = presenter; - mStackScroller = stackScroller; + public void setUpWithEntryManager(NotificationEntryManager entryManager, + NotificationListContainer listContainer) { + mEntryManager = entryManager; + mListContainer = listContainer; } public void stopNotificationLogging() { @@ -159,18 +156,18 @@ public class NotificationLogger { recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); } mHandler.removeCallbacks(mVisibilityReporter); - mStackScroller.setChildLocationsChangedListener(null); + mListContainer.setChildLocationsChangedListener(null); } public void startNotificationLogging() { - mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener); + mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener); // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't // cause the scroller to emit child location events. Hence generate // one ourselves to guarantee that we're reporting visible // notifications. // (Note that in cases where the scroller does emit events, this // additional event doesn't break anything.) - mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller); + mNotificationLocationsChangedListener.onChildLocationsChanged(); } private void logNotificationVisibilityChanges( @@ -220,4 +217,11 @@ public class NotificationLogger { public Runnable getVisibilityReporter() { return mVisibilityReporter; } + + /** + * A listener that is notified when some child locations might have changed. + */ + public interface OnChildLocationsChangedListener { + void onChildLocationsChanged(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 283a6e385582..852239a2143b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -40,9 +40,11 @@ public class NotificationMediaManager implements Dumpable { private static final String TAG = "NotificationMediaManager"; public static final boolean DEBUG_MEDIA = false; - private final NotificationPresenter mPresenter; private final Context mContext; private final MediaSessionManager mMediaSessionManager; + + protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; private MediaController mMediaController; private String mMediaNotificationKey; private MediaMetadata mMediaMetadata; @@ -73,8 +75,7 @@ public class NotificationMediaManager implements Dumpable { } }; - public NotificationMediaManager(NotificationPresenter presenter, Context context) { - mPresenter = presenter; + public NotificationMediaManager(Context context) { mContext = context; mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); @@ -82,6 +83,12 @@ public class NotificationMediaManager implements Dumpable { // in session state } + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager) { + mPresenter = presenter; + mEntryManager = entryManager; + } + public void onNotificationRemoved(String key) { if (key.equals(mMediaNotificationKey)) { clearCurrentMediaNotification(); @@ -100,8 +107,8 @@ public class NotificationMediaManager implements Dumpable { public void findAndUpdateMediaNotifications() { boolean metaDataChanged = false; - synchronized (mPresenter.getNotificationData()) { - ArrayList<NotificationData.Entry> activeNotifications = mPresenter + synchronized (mEntryManager.getNotificationData()) { + ArrayList<NotificationData.Entry> activeNotifications = mEntryManager .getNotificationData().getActiveNotifications(); final int N = activeNotifications.size(); @@ -188,7 +195,7 @@ public class NotificationMediaManager implements Dumpable { } if (metaDataChanged) { - mPresenter.updateNotifications(); + mEntryManager.updateNotifications(); } mPresenter.updateMediaMetaData(metaDataChanged, true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index 33c72534b1b6..12641a0eca8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -16,12 +16,11 @@ package com.android.systemui.statusbar; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Handler; -import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; import android.view.View; -import java.util.Set; - /** * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods * for both querying the state of the system (some modularised piece of functionality may @@ -29,9 +28,11 @@ import java.util.Set; * for affecting the state of the system (e.g. starting an intent, given that the presenter may * want to perform some action before doing so). */ -public interface NotificationPresenter extends NotificationUpdateHandler, - NotificationData.Environment, NotificationRemoteInputManager.Callback { - +public interface NotificationPresenter extends NotificationData.Environment, + NotificationRemoteInputManager.Callback, + ExpandableNotificationRow.OnExpandClickListener, + ActivatableNotificationView.OnActivatedListener, + NotificationEntryManager.Callback { /** * Returns true if the presenter is not visible. For example, it may not be necessary to do * animations if this returns true. @@ -50,32 +51,15 @@ public interface NotificationPresenter extends NotificationUpdateHandler, void startNotificationGutsIntent(Intent intent, int appUid); /** - * Returns NotificationData. - */ - NotificationData getNotificationData(); - - /** * Returns the Handler for NotificationPresenter. */ Handler getHandler(); - // TODO: Create NotificationEntryManager and move this method to there. - /** - * Signals that some notifications have changed, and NotificationPresenter should update itself. - */ - void updateNotifications(); - /** * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. */ void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation); - // TODO: Create NotificationEntryManager and move this method to there. - /** - * Gets the latest ranking map. - */ - NotificationListenerService.RankingMap getLatestRankingMap(); - /** * Called when the locked status of the device is changed for a work profile. */ @@ -107,4 +91,32 @@ public interface NotificationPresenter extends NotificationUpdateHandler, * @return true iff the device is locked */ boolean isDeviceLocked(int userId); + + /** + * @return true iff the device is in vr mode + */ + boolean isDeviceInVrMode(); + + /** + * Updates the visual representation of the notifications. + */ + void updateNotificationViews(); + + /** + * @return true iff the device is dozing + */ + boolean isDozing(); + + /** + * Returns the maximum number of notifications to show while locked. + * + * @param recompute whether something has changed that means we should recompute this value + * @return the maximum number of notifications to show while locked + */ + int getMaxNotificationsWhileLocked(boolean recompute); + + /** + * Called when the row states are updated by NotificationViewHierarchyManager. + */ + void onUpdateRowStates(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 7827f62970e7..f25379ab0b22 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -39,6 +39,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.RemoteInputView; @@ -70,7 +71,10 @@ public class NotificationRemoteInputManager implements Dumpable { protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>(); - protected final NotificationLockscreenUserManager mLockscreenUserManager; + + // Dependencies: + protected final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); /** * Notifications with keys in this set are not actually around anymore. We kept them around @@ -83,6 +87,7 @@ public class NotificationRemoteInputManager implements Dumpable { protected RemoteInputController mRemoteInputController; protected NotificationPresenter mPresenter; + protected NotificationEntryManager mEntryManager; protected IStatusBarService mBarService; protected Callback mCallback; @@ -263,9 +268,7 @@ public class NotificationRemoteInputManager implements Dumpable { } }; - public NotificationRemoteInputManager(NotificationLockscreenUserManager lockscreenUserManager, - Context context) { - mLockscreenUserManager = lockscreenUserManager; + public NotificationRemoteInputManager(Context context) { mContext = context; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); @@ -273,16 +276,18 @@ public class NotificationRemoteInputManager implements Dumpable { } public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager, Callback callback, RemoteInputController.Delegate delegate) { mPresenter = presenter; + mEntryManager = entryManager; mCallback = callback; mRemoteInputController = new RemoteInputController(delegate); mRemoteInputController.addCallback(new RemoteInputController.Callback() { @Override public void onRemoteInputSent(NotificationData.Entry entry) { if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) { - mPresenter.removeNotification(entry.key, null); + mEntryManager.removeNotification(entry.key, null); } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { // We're currently holding onto this notification, but from the apps point of // view it is already canceled, so we'll need to cancel it on the apps behalf @@ -290,7 +295,7 @@ public class NotificationRemoteInputManager implements Dumpable { // bit. mPresenter.getHandler().postDelayed(() -> { if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { - mPresenter.removeNotification(entry.key, null); + mEntryManager.removeNotification(entry.key, null); } }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); } @@ -336,7 +341,7 @@ public class NotificationRemoteInputManager implements Dumpable { for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); mRemoteInputController.removeRemoteInput(entry, null); - mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap()); + mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap()); } mRemoteInputEntriesToRemoveOnCollapse.clear(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 1cbb44025906..8325df7faddb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -25,6 +25,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.os.SystemProperties; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -56,6 +57,7 @@ public class NotificationShelf extends ActivatableNotificationView implements private static final boolean ICON_ANMATIONS_WHILE_SCROLLING = SystemProperties.getBoolean("debug.icon_scroll_animations", true); private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag; + private static final String TAG = "NotificationShelf"; private ViewInvertHelper mViewInvertHelper; private boolean mDark; private NotificationIconContainer mShelfIcons; @@ -294,10 +296,15 @@ public class NotificationShelf extends ActivatableNotificationView implements if (notGoneIndex == 0) { StatusBarIconView icon = row.getEntry().expandedIcon; NotificationIconContainer.IconState iconState = getIconState(icon); - if (iconState.clampedAppearAmount == 1.0f) { + if (iconState != null && iconState.clampedAppearAmount == 1.0f) { // only if the first icon is fully in the shelf we want to clip to it! backgroundTop = (int) (row.getTranslationY() - getTranslationY()); firstElementRoundness = row.getCurrentTopRoundness(); + } else if (iconState == null) { + Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon + + (row.getEntry().expandedIcon != null + ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "") + + " \n number of notifications: " + mHostLayout.getChildCount() ); } } notGoneIndex++; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java new file mode 100644 index 000000000000..266c09bc6c8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +/** + * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based + * on their group structure. For example, if a notification becomes bundled with another, + * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will + * tell NotificationListContainer which notifications to display, and inform it of changes to those + * notifications that might affect their display. + */ +public class NotificationViewHierarchyManager { + private static final String TAG = "NotificationViewHierarchyManager"; + + private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> + mTmpChildOrderMap = new HashMap<>(); + + // Dependencies: + protected final NotificationLockscreenUserManager mLockscreenUserManager = + Dependency.get(NotificationLockscreenUserManager.class); + protected final NotificationGroupManager mGroupManager = + Dependency.get(NotificationGroupManager.class); + protected final VisualStabilityManager mVisualStabilityManager = + Dependency.get(VisualStabilityManager.class); + + /** + * {@code true} if notifications not part of a group should by default be rendered in their + * expanded state. If {@code false}, then only the first notification will be expanded if + * possible. + */ + private final boolean mAlwaysExpandNonGroupedNotification; + + private NotificationPresenter mPresenter; + private NotificationEntryManager mEntryManager; + private NotificationListContainer mListContainer; + + public NotificationViewHierarchyManager(Context context) { + Resources res = context.getResources(); + mAlwaysExpandNonGroupedNotification = + res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationEntryManager entryManager, NotificationListContainer listContainer) { + mPresenter = presenter; + mEntryManager = entryManager; + mListContainer = listContainer; + } + + /** + * Updates the visual representation of the notifications. + */ + public void updateNotificationViews() { + ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData() + .getActiveNotifications(); + ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); + final int N = activeNotifications.size(); + for (int i = 0; i < N; i++) { + NotificationData.Entry ent = activeNotifications.get(i); + if (ent.row.isDismissed() || ent.row.isRemoved()) { + // we don't want to update removed notifications because they could + // temporarily become children if they were isolated before. + continue; + } + int userId = ent.notification.getUserId(); + + // Display public version of the notification if we need to redact. + // TODO: This area uses a lot of calls into NotificationLockscreenUserManager. + // We can probably move some of this code there. + boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode( + mLockscreenUserManager.getCurrentUserId()); + boolean userPublic = devicePublic + || mLockscreenUserManager.isLockscreenPublicMode(userId); + boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent); + boolean sensitive = userPublic && needsRedaction; + boolean deviceSensitive = devicePublic + && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( + mLockscreenUserManager.getCurrentUserId()); + ent.row.setSensitive(sensitive, deviceSensitive); + ent.row.setNeedsRedaction(needsRedaction); + if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { + ExpandableNotificationRow summary = mGroupManager.getGroupSummary( + ent.row.getStatusBarNotification()); + List<ExpandableNotificationRow> orderedChildren = + mTmpChildOrderMap.get(summary); + if (orderedChildren == null) { + orderedChildren = new ArrayList<>(); + mTmpChildOrderMap.put(summary, orderedChildren); + } + orderedChildren.add(ent.row); + } else { + toShow.add(ent.row); + } + + } + + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + for (int i=0; i< mListContainer.getContainerChildCount(); i++) { + View child = mListContainer.getContainerChildAt(i); + if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { + toRemove.add((ExpandableNotificationRow) child); + } + } + + for (ExpandableNotificationRow remove : toRemove) { + if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) { + // we are only transferring this notification to its parent, don't generate an + // animation + mListContainer.setChildTransferInProgress(true); + } + if (remove.isSummaryWithChildren()) { + remove.removeAllChildren(); + } + mListContainer.removeContainerView(remove); + mListContainer.setChildTransferInProgress(false); + } + + removeNotificationChildren(); + + for (int i = 0; i < toShow.size(); i++) { + View v = toShow.get(i); + if (v.getParent() == null) { + mVisualStabilityManager.notifyViewAddition(v); + mListContainer.addContainerView(v); + } + } + + addNotificationChildrenAndSort(); + + // So after all this work notifications still aren't sorted correctly. + // Let's do that now by advancing through toShow and mListContainer in + // lock-step, making sure mListContainer matches what we see in toShow. + int j = 0; + for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { + View child = mListContainer.getContainerChildAt(i); + if (!(child instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow targetChild = toShow.get(j); + if (child != targetChild) { + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + if (mVisualStabilityManager.canReorderNotification(targetChild)) { + mListContainer.changeViewPosition(targetChild, i); + } else { + mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager); + } + } + j++; + + } + + mVisualStabilityManager.onReorderingFinished(); + // clear the map again for the next usage + mTmpChildOrderMap.clear(); + + updateRowStates(); + + mListContainer.onNotificationViewUpdateFinished(); + } + + private void addNotificationChildrenAndSort() { + // Let's now add all notification children which are missing + boolean orderChanged = false; + for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { + View view = mListContainer.getContainerChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); + + for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); + childIndex++) { + ExpandableNotificationRow childView = orderedChildren.get(childIndex); + if (children == null || !children.contains(childView)) { + if (childView.getParent() != null) { + Log.wtf(TAG, "trying to add a notification child that already has " + + "a parent. class:" + childView.getParent().getClass() + + "\n child: " + childView); + // This shouldn't happen. We can recover by removing it though. + ((ViewGroup) childView.getParent()).removeView(childView); + } + mVisualStabilityManager.notifyViewAddition(childView); + parent.addChildNotification(childView, childIndex); + mListContainer.notifyGroupChildAdded(childView); + } + } + + // Finally after removing and adding has been performed we can apply the order. + orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, + mEntryManager); + } + if (orderChanged) { + mListContainer.generateChildOrderChangedEvent(); + } + } + + private void removeNotificationChildren() { + // First let's remove all children which don't belong in the parents + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { + View view = mListContainer.getContainerChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); + + if (children != null) { + toRemove.clear(); + for (ExpandableNotificationRow childRow : children) { + if ((orderedChildren == null + || !orderedChildren.contains(childRow)) + && !childRow.keepInParent()) { + toRemove.add(childRow); + } + } + for (ExpandableNotificationRow remove : toRemove) { + parent.removeChildNotification(remove); + if (mEntryManager.getNotificationData().get( + remove.getStatusBarNotification().getKey()) == null) { + // We only want to add an animation if the view is completely removed + // otherwise it's just a transfer + mListContainer.notifyGroupChildRemoved(remove, + parent.getChildrenContainer()); + } + } + } + } + } + + /** + * Updates expanded, dimmed and locked states of notification rows. + */ + public void updateRowStates() { + final int N = mListContainer.getContainerChildCount(); + + int visibleNotifications = 0; + boolean isLocked = mPresenter.isPresenterLocked(); + int maxNotifications = -1; + if (isLocked) { + maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */); + } + mListContainer.setMaxDisplayedNotifications(maxNotifications); + Stack<ExpandableNotificationRow> stack = new Stack<>(); + for (int i = N - 1; i >= 0; i--) { + View child = mListContainer.getContainerChildAt(i); + if (!(child instanceof ExpandableNotificationRow)) { + continue; + } + stack.push((ExpandableNotificationRow) child); + } + while(!stack.isEmpty()) { + ExpandableNotificationRow row = stack.pop(); + NotificationData.Entry entry = row.getEntry(); + boolean isChildNotification = + mGroupManager.isChildInGroupWithSummary(entry.notification); + + row.setOnKeyguard(isLocked); + + if (!isLocked) { + // If mAlwaysExpandNonGroupedNotification is false, then only expand the + // very first notification and if it's not a child of grouped notifications. + row.setSystemExpanded(mAlwaysExpandNonGroupedNotification + || (visibleNotifications == 0 && !isChildNotification + && !row.isLowPriority())); + } + + entry.row.setShowAmbient(mPresenter.isDozing()); + int userId = entry.notification.getUserId(); + boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup( + entry.notification) && !entry.row.isRemoved(); + boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry + .notification); + if (suppressedSummary + || (mLockscreenUserManager.isLockscreenPublicMode(userId) + && !mLockscreenUserManager.shouldShowLockscreenNotifications()) + || (isLocked && !showOnKeyguard)) { + entry.row.setVisibility(View.GONE); + } else { + boolean wasGone = entry.row.getVisibility() == View.GONE; + if (wasGone) { + entry.row.setVisibility(View.VISIBLE); + } + if (!isChildNotification && !entry.row.isRemoved()) { + if (wasGone) { + // notify the scroller of a child addition + mListContainer.generateAddAnimation(entry.row, + !showOnKeyguard /* fromMoreCard */); + } + visibleNotifications++; + } + } + if (row.isSummaryWithChildren()) { + List<ExpandableNotificationRow> notificationChildren = + row.getNotificationChildren(); + int size = notificationChildren.size(); + for (int i = size - 1; i >= 0; i--) { + stack.push(notificationChildren.get(i)); + } + } + } + + mPresenter.onUpdateRowStates(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 5ba6f6af05ab..3ebeb4d45c26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -247,19 +247,6 @@ public class CarStatusBar extends StatusBar implements return null; } - /** - * Returns the - * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will - * be triggered when a notification card is long-pressed. - */ - @Override - protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { - // For the automative use case, we do not want to the user to be able to interact with - // a notification other than a regular click. As a result, just return null for the - // long click listener. - return null; - } - @Override public void showBatteryView() { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -388,18 +375,6 @@ public class CarStatusBar extends StatusBar implements } @Override - protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { - // Because space is usually constrained in the auto use-case, there should not be a - // pinned notification when the shade has been expanded. Ensure this by not pinning any - // notification if the shade is already opened. - if (mPanelExpanded) { - return false; - } - - return super.shouldPeek(entry, sbn); - } - - @Override public void animateExpandNotificationsPanel() { // Because space is usually constrained in the auto use-case, there should not be a // pinned notification when the shade has been expanded. Ensure this by removing all heads- diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 61dd22fc4571..f0bd1f94ec3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -455,7 +455,7 @@ public class NotificationPanelView extends PanelView implements mTopPaddingAdjustment = 0; } else { mClockPositionAlgorithm.setup( - mStatusBar.getMaxKeyguardNotifications(), + mStatusBar.getMaxNotificationsWhileLocked(), getMaxPanelHeight(), getExpandedHeight(), mNotificationStackScroller.getNotGoneChildCount(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index c5349d186b86..2da1e4d108b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -28,10 +28,6 @@ import static com.android.systemui.statusbar.NotificationLockscreenUserManager .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA; -import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; -import static com.android.systemui.statusbar.NotificationRemoteInputManager - .FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; @@ -66,14 +62,12 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; @@ -88,7 +82,6 @@ import android.media.MediaMetadata; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -103,12 +96,9 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; -import android.service.notification.NotificationListenerService.RankingMap; -import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; -import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -139,7 +129,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.MessagingGroup; import com.android.internal.widget.MessagingMessage; @@ -149,11 +138,9 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.ActivityStarterDelegate; import com.android.systemui.AutoReinflateContainer; -import com.android.systemui.DejankUtils; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; -import com.android.systemui.ForegroundServiceController; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; @@ -200,6 +187,7 @@ import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; +import com.android.systemui.statusbar.NotificationEntryManager; import com.android.systemui.statusbar.NotificationGutsManager; import com.android.systemui.statusbar.NotificationInfo; import com.android.systemui.statusbar.NotificationListener; @@ -209,13 +197,12 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AboveShelfObserver; -import com.android.systemui.statusbar.notification.InflationException; -import com.android.systemui.statusbar.notification.RowInflaterTask; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; @@ -239,7 +226,6 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.util.NotificationChannels; -import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -247,17 +233,12 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Stack; public class StatusBar extends SystemUI implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, - OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks, - ActivatableNotificationView.OnActivatedListener, - ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment, - ExpandableNotificationRow.OnExpandClickListener, InflationCallback, + OnHeadsUpChangedListener, CommandQueue.Callbacks, ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter { public static final boolean MULTIUSER_DEBUG = false; @@ -270,10 +251,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026; protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027; - protected static final boolean ENABLE_HEADS_UP = true; - protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; - - // Should match the values in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; @@ -309,7 +286,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Time after we abort the launch transition. private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000; - private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; + protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; private static final int STATUS_OR_NAV_TRANSIENT = View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; @@ -392,13 +369,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window private TextView mNotificationPanelDebugText; - /** - * {@code true} if notifications not part of a group should by default be rendered in their - * expanded state. If {@code false}, then only the first notification will be expanded if - * possible. - */ - private boolean mAlwaysExpandNonGroupedNotification; - // settings private QSPanel mQSPanel; @@ -427,6 +397,8 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationGutsManager mGutsManager; protected NotificationLogger mNotificationLogger; + protected NotificationEntryManager mEntryManager; + protected NotificationViewHierarchyManager mViewHierarchyManager; // for disabling the status bar private int mDisabled1 = 0; @@ -478,23 +450,6 @@ public class StatusBar extends SystemUI implements DemoMode, }; protected final H mHandler = createHandler(); - final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - boolean wasUsing = mUseHeadsUp; - mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts - && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, - Settings.Global.HEADS_UP_OFF); - Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); - if (wasUsing != mUseHeadsUp) { - if (!mUseHeadsUp) { - Log.d(TAG, "dismissing any existing heads up notification on disable event"); - mHeadsUpManager.releaseAllImmediately(); - } - } - } - }; private int mInteractingWindows; private boolean mAutohideSuspended; @@ -588,7 +543,6 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private NotificationMessagingUtil mMessagingUtil; private KeyguardUserSwitcher mKeyguardUserSwitcher; private UserSwitcherController mUserSwitcherController; private NetworkController mNetworkController; @@ -603,11 +557,9 @@ public class StatusBar extends SystemUI implements DemoMode, private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); protected NotificationIconAreaController mNotificationIconAreaController; private boolean mReinflateNotificationsOnUserSwitched; - private final HashMap<String, Entry> mPendingNotifications = new HashMap<>(); private boolean mClearAllEnabled; @Nullable private View mAmbientIndicationContainer; private SysuiColorExtractor mColorExtractor; - private ForegroundServiceController mForegroundServiceController; private ScreenLifecycle mScreenLifecycle; @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle; @@ -617,9 +569,6 @@ public class StatusBar extends SystemUI implements DemoMode, goToLockedShade(null); } }; - private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> - mTmpChildOrderMap = new HashMap<>(); - private RankingMap mLatestRankingMap; private boolean mNoAnimationOnNextBarModeChange; private FalsingManager mFalsingManager; @@ -639,8 +588,11 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void start() { mGroupManager = Dependency.get(NotificationGroupManager.class); + mVisualStabilityManager = Dependency.get(VisualStabilityManager.class); mNotificationLogger = Dependency.get(NotificationLogger.class); mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); + mNotificationListener = Dependency.get(NotificationListener.class); + mGroupManager = Dependency.get(NotificationGroupManager.class); mNetworkController = Dependency.get(NetworkController.class); mUserSwitcherController = Dependency.get(UserSwitcherController.class); mScreenLifecycle = Dependency.get(ScreenLifecycle.class); @@ -649,27 +601,25 @@ public class StatusBar extends SystemUI implements DemoMode, mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mBatteryController = Dependency.get(BatteryController.class); mAssistManager = Dependency.get(AssistManager.class); - mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); mOverlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class); mGutsManager = Dependency.get(NotificationGutsManager.class); + mMediaManager = Dependency.get(NotificationMediaManager.class); + mEntryManager = Dependency.get(NotificationEntryManager.class); + mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class); mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mForegroundServiceController = Dependency.get(ForegroundServiceController.class); - mDisplay = mWindowManager.getDefaultDisplay(); updateDisplaySize(); Resources res = mContext.getResources(); mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src); mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); - mAlwaysExpandNonGroupedNotification = - res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER)); putComponent(StatusBar.class, this); @@ -679,16 +629,12 @@ public class StatusBar extends SystemUI implements DemoMode, mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( Context.DEVICE_POLICY_SERVICE); - mNotificationData = new NotificationData(this); - mMessagingUtil = new NotificationMessagingUtil(mContext); - mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); @@ -698,8 +644,7 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mLockPatternUtils = new LockPatternUtils(mContext); - mMediaManager = new NotificationMediaManager(this, mContext); - mLockscreenUserManager.setUpWithPresenter(this); + mMediaManager.setUpWithPresenter(this, mEntryManager); // Connect in to the status bar manager service mCommandQueue = getComponent(CommandQueue.class); @@ -725,6 +670,7 @@ public class StatusBar extends SystemUI implements DemoMode, mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter); mWallpaperChangedReceiver.onReceive(mContext, null); + mLockscreenUserManager.setUpWithPresenter(this, mEntryManager); mCommandQueue.disable(switches[0], switches[6], false /* animate */); setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff, fullscreenStackBounds, dockedStackBounds); @@ -739,8 +685,7 @@ public class StatusBar extends SystemUI implements DemoMode, } // Set up the initial notification state. - mNotificationListener = Dependency.get(NotificationListener.class); - mNotificationListener.setUpWithPresenter(this); + mNotificationListener.setUpWithPresenter(this, mEntryManager); if (DEBUG) { Log.d(TAG, String.format( @@ -774,15 +719,6 @@ public class StatusBar extends SystemUI implements DemoMode, // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController); - mHeadsUpObserver.onChange(true); // set up - if (ENABLE_HEADS_UP) { - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true, - mHeadsUpObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, - mHeadsUpObserver); - } mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); mUnlockMethodCache.addListener(this); startKeyguard(); @@ -817,7 +753,7 @@ public class StatusBar extends SystemUI implements DemoMode, // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot. mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel); mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller); - mGutsManager.setUp(this, mStackScroller, mCheckSaveListener, + mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener, key -> { try { mBarService.onNotificationSettingsViewed(key); @@ -825,7 +761,7 @@ public class StatusBar extends SystemUI implements DemoMode, // if we're here we're dead } }); - mNotificationLogger.setUpWithPresenter(this, mStackScroller); + mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller); mNotificationPanel.setStatusBar(this); mNotificationPanel.setGroupManager(mGroupManager); mAboveShelfObserver = new AboveShelfObserver(mStackScroller); @@ -864,11 +800,13 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager.addListener(mGroupManager); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanel.setHeadsUpManager(mHeadsUpManager); - mNotificationData.setHeadsUpManager(mHeadsUpManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); putComponent(HeadsUpManager.class, mHeadsUpManager); + mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager); + mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller); + if (MULTIUSER_DEBUG) { mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info); mNotificationPanelDebugText.setVisibility(View.VISIBLE); @@ -884,7 +822,7 @@ public class StatusBar extends SystemUI implements DemoMode, // no window manager? good luck with that } - mStackScroller.setLongPressListener(getNotificationLongClicker()); + mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker()); mStackScroller.setStatusBar(this); mStackScroller.setGroupManager(mGroupManager); mStackScroller.setHeadsUpManager(mHeadsUpManager); @@ -1107,7 +1045,7 @@ public class StatusBar extends SystemUI implements DemoMode, MessagingGroup.dropCache(); // start old BaseStatusBar.onDensityOrFontScaleChanged(). if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { - updateNotificationsOnDensityOrFontScaleChanged(); + mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); } else { mReinflateNotificationsOnUserSwitched = true; } @@ -1171,20 +1109,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - private void updateNotificationsOnDensityOrFontScaleChanged() { - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - for (int i = 0; i < activeNotifications.size(); i++) { - Entry entry = activeNotifications.get(i); - boolean exposedGuts = mGutsManager.getExposedGuts() != null - && entry.row.getGuts() == mGutsManager.getExposedGuts(); - entry.row.onDensityOrFontScaleChanged(); - if (exposedGuts) { - mGutsManager.setExposedGuts(entry.row.getGuts()); - mGutsManager.bindGuts(entry.row); - } - } - } - private void inflateSignalClusters() { if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar); } @@ -1298,7 +1222,7 @@ public class StatusBar extends SystemUI implements DemoMode, mStackScroller.setDismissAllInProgress(false); for (ExpandableNotificationRow rowToRemove : viewsToRemove) { if (mStackScroller.canChildBeDismissed(rowToRemove)) { - removeNotification(rowToRemove.getEntry().key, null); + mEntryManager.removeNotification(rowToRemove.getEntry().key, null); } else { rowToRemove.resetTranslation(); } @@ -1406,213 +1330,53 @@ public class StatusBar extends SystemUI implements DemoMode, return true; } - void awakenDreams() { - SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); + @Override + public void onPerformRemoveNotification(StatusBarNotification n) { + if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { + // We were showing a pulse for a notification, but no notifications are pulsing anymore. + // Finish the pulse. + mDozeScrimController.pulseOutNow(); + } } @Override - public void addNotification(StatusBarNotification notification, RankingMap ranking) { - String key = notification.getKey(); - if (DEBUG) Log.d(TAG, "addNotification key=" + key); + public void updateNotificationViews() { + // The function updateRowStates depends on both of these being non-null, so check them here. + // We may be called before they are set from DeviceProvisionedController's callback. + if (mStackScroller == null || mScrimController == null) return; - mNotificationData.updateRanking(ranking); - Entry shadeEntry = null; - try { - shadeEntry = createNotificationViews(notification); - } catch (InflationException e) { - handleInflationException(notification, e); + // Do not modify the notifications during collapse. + if (isCollapsing()) { + addPostCollapseAction(this::updateNotificationViews); return; } - boolean isHeadsUped = shouldPeek(shadeEntry); - if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { - if (shouldSuppressFullScreenIntent(key)) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); - } - } else if (mNotificationData.getImportance(key) - < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: not important enough: " - + key); - } - } else { - // Stop screensaver if the notification has a fullscreen intent. - // (like an incoming phone call) - awakenDreams(); - // not immersive & a fullscreen alert should be shown - if (DEBUG) - Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); - try { - EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, - key); - notification.getNotification().fullScreenIntent.send(); - shadeEntry.notifyFullScreenIntentLaunched(); - mMetricsLogger.count("note_fullscreen", 1); - } catch (PendingIntent.CanceledException e) { - } - } - } - abortExistingInflation(key); + mViewHierarchyManager.updateNotificationViews(); - mForegroundServiceController.addNotification(notification, - mNotificationData.getImportance(key)); + updateSpeedBumpIndex(); + updateClearAll(); + updateEmptyShadeView(); - mPendingNotifications.put(key, shadeEntry); - } + updateQsExpansionEnabled(); - private void abortExistingInflation(String key) { - if (mPendingNotifications.containsKey(key)) { - Entry entry = mPendingNotifications.get(key); - entry.abortTask(); - mPendingNotifications.remove(key); - } - Entry addedEntry = mNotificationData.get(key); - if (addedEntry != null) { - addedEntry.abortTask(); - } + // Let's also update the icons + mNotificationIconAreaController.updateNotificationIcons( + mEntryManager.getNotificationData()); } - private void addEntry(Entry shadeEntry) { - boolean isHeadsUped = shouldPeek(shadeEntry); - if (isHeadsUped) { - mHeadsUpManager.showNotification(shadeEntry); - // Mark as seen immediately - setNotificationShown(shadeEntry.notification); - } - addNotificationViews(shadeEntry); + @Override + public void onNotificationAdded(Entry shadeEntry) { // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); } @Override - public void handleInflationException(StatusBarNotification notification, Exception e) { - handleNotificationError(notification, e.getMessage()); - } - - @Override - public void onAsyncInflationFinished(Entry entry) { - mPendingNotifications.remove(entry.key); - // If there was an async task started after the removal, we don't want to add it back to - // the list, otherwise we might get leaks. - boolean isNew = mNotificationData.get(entry.key) == null; - if (isNew && !entry.row.isRemoved()) { - addEntry(entry); - } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { - mVisualStabilityManager.onLowPriorityUpdated(entry); - updateNotificationShade(); - } - entry.row.setLowPriorityStateUpdated(false); - } - - private boolean shouldSuppressFullScreenIntent(String key) { - if (isDeviceInVrMode()) { - return true; - } - - if (mPowerManager.isInteractive()) { - return mNotificationData.shouldSuppressScreenOn(key); - } else { - return mNotificationData.shouldSuppressScreenOff(key); - } - } - - @Override - public void updateNotificationRanking(RankingMap ranking) { - mNotificationData.updateRanking(ranking); - updateNotifications(); + public void onNotificationUpdated(StatusBarNotification notification) { + setAreThereNotifications(); } @Override - public void removeNotification(String key, RankingMap ranking) { - boolean deferRemoval = false; - abortExistingInflation(key); - if (mHeadsUpManager.isHeadsUp(key)) { - // A cancel() in response to a remote input shouldn't be delayed, as it makes the - // sending look longer than it takes. - // Also we should not defer the removal if reordering isn't allowed since otherwise - // some notifications can't disappear before the panel is closed. - boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) - && !FORCE_REMOTE_INPUT_HISTORY - || !mVisualStabilityManager.isReorderingAllowed(); - deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); - } - mMediaManager.onNotificationRemoved(key); - - Entry entry = mNotificationData.get(key); - if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key) - && entry.row != null && !entry.row.isDismissed()) { - StatusBarNotification sbn = entry.notification; - - Notification.Builder b = Notification.Builder - .recoverBuilder(mContext, sbn.getNotification().clone()); - CharSequence[] oldHistory = sbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - CharSequence[] newHistory; - if (oldHistory == null) { - newHistory = new CharSequence[1]; - } else { - newHistory = new CharSequence[oldHistory.length + 1]; - System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); - } - newHistory[0] = String.valueOf(entry.remoteInputText); - b.setRemoteInputHistory(newHistory); - - Notification newNotification = b.build(); - - // Undo any compatibility view inflation - newNotification.contentView = sbn.getNotification().contentView; - newNotification.bigContentView = sbn.getNotification().bigContentView; - newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; - - StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); - boolean updated = false; - try { - updateNotificationInternal(newSbn, null); - updated = true; - } catch (InflationException e) { - deferRemoval = false; - } - if (updated) { - Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); - mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key); - return; - } - } - if (deferRemoval) { - mLatestRankingMap = ranking; - mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); - return; - } - - if (mRemoteInputManager.onRemoveNotification(entry)) { - mLatestRankingMap = ranking; - return; - } - - if (entry != null && mGutsManager.getExposedGuts() != null - && mGutsManager.getExposedGuts() == entry.row.getGuts() - && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { - Log.w(TAG, "Keeping notification because it's showing guts. " + key); - mLatestRankingMap = ranking; - mGutsManager.setKeyToRemoveOnGutsClosed(key); - return; - } - - if (entry != null) { - mForegroundServiceController.removeNotification(entry.notification); - } - - if (entry != null && entry.row != null) { - entry.row.setRemoved(); - mStackScroller.cleanUpViewState(entry.row); - } - // Let's remove the children if this was a summary - handleGroupSummaryRemoved(key); - StatusBarNotification old = removeNotificationViews(key, ranking); + public void onNotificationRemoved(String key, StatusBarNotification old) { if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { @@ -1629,195 +1393,6 @@ public class StatusBar extends SystemUI implements DemoMode, } /** - * Ensures that the group children are cancelled immediately when the group summary is cancelled - * instead of waiting for the notification manager to send all cancels. Otherwise this could - * lead to flickers. - * - * This also ensures that the animation looks nice and only consists of a single disappear - * animation instead of multiple. - * @param key the key of the notification was removed - * - */ - private void handleGroupSummaryRemoved(String key) { - Entry entry = mNotificationData.get(key); - if (entry != null && entry.row != null - && entry.row.isSummaryWithChildren()) { - if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { - // We don't want to remove children for autobundled notifications as they are not - // always cancelled. We only remove them if they were dismissed by the user. - return; - } - List<ExpandableNotificationRow> notificationChildren = - entry.row.getNotificationChildren(); - for (int i = 0; i < notificationChildren.size(); i++) { - ExpandableNotificationRow row = notificationChildren.get(i); - if ((row.getStatusBarNotification().getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - // the child is a foreground service notification which we can't remove! - continue; - } - row.setKeepInParent(true); - // we need to set this state earlier as otherwise we might generate some weird - // animations - row.setRemoved(); - } - } - } - - protected void performRemoveNotification(StatusBarNotification n) { - Entry entry = mNotificationData.get(n.getKey()); - mRemoteInputManager.onPerformRemoveNotification(n, entry); - // start old BaseStatusBar.performRemoveNotification. - final String pkg = n.getPackageName(); - final String tag = n.getTag(); - final int id = n.getId(); - final int userId = n.getUserId(); - try { - int dismissalSurface = NotificationStats.DISMISSAL_SHADE; - if (isHeadsUp(n.getKey())) { - dismissalSurface = NotificationStats.DISMISSAL_PEEK; - } else if (mStackScroller.hasPulsingNotifications()) { - dismissalSurface = NotificationStats.DISMISSAL_AOD; - } - mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface); - removeNotification(n.getKey(), null); - - } catch (RemoteException ex) { - // system process is dead if we're here. - } - if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { - // We were showing a pulse for a notification, but no notifications are pulsing anymore. - // Finish the pulse. - mDozeScrimController.pulseOutNow(); - } - // end old BaseStatusBar.performRemoveNotification. - } - - private void updateNotificationShade() { - if (mStackScroller == null) return; - - // Do not modify the notifications during collapse. - if (isCollapsing()) { - addPostCollapseAction(this::updateNotificationShade); - return; - } - - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); - final int N = activeNotifications.size(); - for (int i = 0; i < N; i++) { - Entry ent = activeNotifications.get(i); - if (ent.row.isDismissed() || ent.row.isRemoved()) { - // we don't want to update removed notifications because they could - // temporarily become children if they were isolated before. - continue; - } - int userId = ent.notification.getUserId(); - - // Display public version of the notification if we need to redact. - // TODO: This area uses a lot of calls into NotificationLockscreenUserManager. - // We can probably move some of this code there. - boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode( - mLockscreenUserManager.getCurrentUserId()); - boolean userPublic = devicePublic - || mLockscreenUserManager.isLockscreenPublicMode(userId); - boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent); - boolean sensitive = userPublic && needsRedaction; - boolean deviceSensitive = devicePublic - && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( - mLockscreenUserManager.getCurrentUserId()); - ent.row.setSensitive(sensitive, deviceSensitive); - ent.row.setNeedsRedaction(needsRedaction); - if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { - ExpandableNotificationRow summary = mGroupManager.getGroupSummary( - ent.row.getStatusBarNotification()); - List<ExpandableNotificationRow> orderedChildren = - mTmpChildOrderMap.get(summary); - if (orderedChildren == null) { - orderedChildren = new ArrayList<>(); - mTmpChildOrderMap.put(summary, orderedChildren); - } - orderedChildren.add(ent.row); - } else { - toShow.add(ent.row); - } - - } - - ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); - for (int i=0; i< mStackScroller.getChildCount(); i++) { - View child = mStackScroller.getChildAt(i); - if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { - toRemove.add((ExpandableNotificationRow) child); - } - } - - for (ExpandableNotificationRow remove : toRemove) { - if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) { - // we are only transferring this notification to its parent, don't generate an - // animation - mStackScroller.setChildTransferInProgress(true); - } - if (remove.isSummaryWithChildren()) { - remove.removeAllChildren(); - } - mStackScroller.removeView(remove); - mStackScroller.setChildTransferInProgress(false); - } - - removeNotificationChildren(); - - for (int i = 0; i < toShow.size(); i++) { - View v = toShow.get(i); - if (v.getParent() == null) { - mVisualStabilityManager.notifyViewAddition(v); - mStackScroller.addView(v); - } - } - - addNotificationChildrenAndSort(); - - // So after all this work notifications still aren't sorted correctly. - // Let's do that now by advancing through toShow and mStackScroller in - // lock-step, making sure mStackScroller matches what we see in toShow. - int j = 0; - for (int i = 0; i < mStackScroller.getChildCount(); i++) { - View child = mStackScroller.getChildAt(i); - if (!(child instanceof ExpandableNotificationRow)) { - // We don't care about non-notification views. - continue; - } - - ExpandableNotificationRow targetChild = toShow.get(j); - if (child != targetChild) { - // Oops, wrong notification at this position. Put the right one - // here and advance both lists. - if (mVisualStabilityManager.canReorderNotification(targetChild)) { - mStackScroller.changeViewPosition(targetChild, i); - } else { - mVisualStabilityManager.addReorderingAllowedCallback(this); - } - } - j++; - - } - - mVisualStabilityManager.onReorderingFinished(); - // clear the map again for the next usage - mTmpChildOrderMap.clear(); - - updateRowStates(); - updateSpeedBumpIndex(); - updateClearAll(); - updateEmptyShadeView(); - - updateQsExpansionEnabled(); - - // Let's also update the icons - mNotificationIconAreaController.updateNotificationIcons(mNotificationData); - } - - /** * Disable QS if device not provisioned. * If the user switcher is simple then disable QS during setup because * the user intends to use the lock screen user switcher, QS in not needed. @@ -1832,81 +1407,6 @@ public class StatusBar extends SystemUI implements DemoMode, && !ONLY_CORE_APPS); } - private void addNotificationChildrenAndSort() { - // Let's now add all notification children which are missing - boolean orderChanged = false; - for (int i = 0; i < mStackScroller.getChildCount(); i++) { - View view = mStackScroller.getChildAt(i); - if (!(view instanceof ExpandableNotificationRow)) { - // We don't care about non-notification views. - continue; - } - - ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - List<ExpandableNotificationRow> children = parent.getNotificationChildren(); - List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - - for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); - childIndex++) { - ExpandableNotificationRow childView = orderedChildren.get(childIndex); - if (children == null || !children.contains(childView)) { - if (childView.getParent() != null) { - Log.wtf(TAG, "trying to add a notification child that already has " + - "a parent. class:" + childView.getParent().getClass() + - "\n child: " + childView); - // This shouldn't happen. We can recover by removing it though. - ((ViewGroup) childView.getParent()).removeView(childView); - } - mVisualStabilityManager.notifyViewAddition(childView); - parent.addChildNotification(childView, childIndex); - mStackScroller.notifyGroupChildAdded(childView); - } - } - - // Finally after removing and adding has been performed we can apply the order. - orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this); - } - if (orderChanged) { - mStackScroller.generateChildOrderChangedEvent(); - } - } - - private void removeNotificationChildren() { - // First let's remove all children which don't belong in the parents - ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); - for (int i = 0; i < mStackScroller.getChildCount(); i++) { - View view = mStackScroller.getChildAt(i); - if (!(view instanceof ExpandableNotificationRow)) { - // We don't care about non-notification views. - continue; - } - - ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - List<ExpandableNotificationRow> children = parent.getNotificationChildren(); - List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - - if (children != null) { - toRemove.clear(); - for (ExpandableNotificationRow childRow : children) { - if ((orderedChildren == null - || !orderedChildren.contains(childRow)) - && !childRow.keepInParent()) { - toRemove.add(childRow); - } - } - for (ExpandableNotificationRow remove : toRemove) { - parent.removeChildNotification(remove); - if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) { - // We only want to add an animation if the view is completely removed - // otherwise it's just a transfer - mStackScroller.notifyGroupChildRemoved(remove, - parent.getChildrenContainer()); - } - } - } - } - } - public void addQsTile(ComponentName tile) { mQSPanel.getHost().addTile(tile); } @@ -1949,7 +1449,7 @@ public class StatusBar extends SystemUI implements DemoMode, private void updateEmptyShadeView() { boolean showEmptyShadeView = mState != StatusBarState.KEYGUARD && - mNotificationData.getActiveNotifications().size() == 0; + mEntryManager.getNotificationData().getActiveNotifications().size() == 0; mNotificationPanel.showEmptyShadeView(showEmptyShadeView); } @@ -1964,7 +1464,8 @@ public class StatusBar extends SystemUI implements DemoMode, } ExpandableNotificationRow row = (ExpandableNotificationRow) view; currentIndex++; - if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) { + if (!mEntryManager.getNotificationData().isAmbient( + row.getStatusBarNotification().getKey())) { speedBumpIndex = currentIndex; } } @@ -1976,15 +1477,9 @@ public class StatusBar extends SystemUI implements DemoMode, return entry.row.getParent() instanceof NotificationStackScrollLayout; } - @Override - public void updateNotifications() { - mNotificationData.filterAndSort(); - - updateNotificationShade(); - } public void requestNotificationUpdate() { - updateNotifications(); + mEntryManager.updateNotifications(); } protected void setAreThereNotifications() { @@ -1993,7 +1488,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean clearable = hasActiveNotifications() && hasActiveClearableNotifications(); Log.d(TAG, "setAreThereNotifications: N=" + - mNotificationData.getActiveNotifications().size() + " any=" + + mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" + hasActiveNotifications() + " clearable=" + clearable); } @@ -2278,9 +1773,8 @@ public class StatusBar extends SystemUI implements DemoMode, } if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { - mDisableNotificationAlerts = - (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; - mHeadsUpObserver.onChange(true); + mEntryManager.setDisableNotificationAlerts( + (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0); } if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) { @@ -2343,10 +1837,40 @@ public class StatusBar extends SystemUI implements DemoMode, return getBarState() == StatusBarState.KEYGUARD; } + @Override public boolean isDozing() { return mDozing; } + @Override + public boolean shouldPeek(Entry entry, StatusBarNotification sbn) { + if (mIsOccluded && !isDozing()) { + boolean devicePublic = mLockscreenUserManager. + isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); + boolean userPublic = devicePublic + || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); + boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); + if (userPublic && needsRedaction) { + return false; + } + } + + if (sbn.getNotification().fullScreenIntent != null) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { + if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); + return false; + } else if (isDozing()) { + // We never want heads up when we are dozing. + return false; + } else { + // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent + return !mStatusBarKeyguardViewManager.isShowing() + || mStatusBarKeyguardViewManager.isOccluded(); + } + } + return true; + } + @Override // NotificationData.Environment public String getCurrentMediaNotificationKey() { return mMediaManager.getMediaNotificationKey(); @@ -2415,34 +1939,10 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { - if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { - removeNotification(entry.key, mLatestRankingMap); - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { - mLatestRankingMap = null; - } - } else { - updateNotificationRanking(null); - if (isHeadsUp) { - mDozeServiceHost.fireNotificationHeadsUp(); - } - } - - } + mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp); - protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek, - boolean alertAgain) { - final boolean wasHeadsUp = isHeadsUp(key); - if (wasHeadsUp) { - if (!shouldPeek) { - // We don't want this to be interrupting anymore, lets remove it - mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); - } else { - mHeadsUpManager.updateNotification(entry, alertAgain); - } - } else if (shouldPeek && alertAgain) { - // This notification was updated to be a heads-up, show it! - mHeadsUpManager.showNotification(entry); + if (isHeadsUp) { + mDozeServiceHost.fireNotificationHeadsUp(); } } @@ -2452,14 +1952,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - public boolean isHeadsUp(String key) { - return mHeadsUpManager.isHeadsUp(key); - } - - protected boolean isSnoozedPackage(StatusBarNotification sbn) { - return mHeadsUpManager.isSnoozed(sbn.getPackageName()); - } - public boolean isKeyguardCurrentlySecure() { return !mUnlockMethodCache.canSkipBouncer(); } @@ -2489,11 +1981,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mDozeScrimController != null && mDozeScrimController.isPulsing(); } - @Override - public void onReorderingAllowed() { - updateNotifications(); - } - public boolean isLaunchTransitionFadingAway() { return mLaunchTransitionFadingAway; } @@ -3127,14 +2614,6 @@ public class StatusBar extends SystemUI implements DemoMode, + " scroll " + mStackScroller.getScrollX() + "," + mStackScroller.getScrollY()); } - pw.print(" mPendingNotifications="); - if (mPendingNotifications.size() == 0) { - pw.println("null"); - } else { - for (Entry entry : mPendingNotifications.values()) { - pw.println(entry.notification); - } - } pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); pw.print(" mStatusBarWindowState="); @@ -3146,8 +2625,7 @@ public class StatusBar extends SystemUI implements DemoMode, pw.println(Settings.Global.zenModeToString(Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF))); - pw.print(" mUseHeadsUp="); - pw.println(mUseHeadsUp); + if (mStatusBarView != null) { dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); } @@ -3189,8 +2667,8 @@ public class StatusBar extends SystemUI implements DemoMode, } if (DUMPTRUCK) { - synchronized (mNotificationData) { - mNotificationData.dump(pw, " "); + synchronized (mEntryManager.getNotificationData()) { + mEntryManager.getNotificationData().dump(pw, " "); } if (false) { @@ -3250,19 +2728,20 @@ public class StatusBar extends SystemUI implements DemoMode, private void addStatusBarWindow() { makeStatusBarView(); mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); - mRemoteInputManager.setUpWithPresenter(this, this, new RemoteInputController.Delegate() { - public void setRemoteInputActive(NotificationData.Entry entry, - boolean remoteInputActive) { - mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); - } - public void lockScrollTo(NotificationData.Entry entry) { - mStackScroller.lockScrollTo(entry.row); - } - public void requestDisallowLongPressAndDismiss() { - mStackScroller.requestDisallowLongPress(); - mStackScroller.requestDisallowDismiss(); - } - }); + mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this, + new RemoteInputController.Delegate() { + public void setRemoteInputActive(NotificationData.Entry entry, + boolean remoteInputActive) { + mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); + } + public void lockScrollTo(NotificationData.Entry entry) { + mStackScroller.lockScrollTo(entry.row); + } + public void requestDisallowLongPressAndDismiss() { + mStackScroller.requestDisallowLongPress(); + mStackScroller.requestDisallowDismiss(); + } + }); mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); } @@ -3429,7 +2908,8 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); + ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData() + .getActiveNotifications(); final int notificationCount = activeNotifications.size(); for (int i = 0; i < notificationCount; i++) { NotificationData.Entry entry = activeNotifications.get(i); @@ -3472,7 +2952,7 @@ public class StatusBar extends SystemUI implements DemoMode, Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration()); } - updateRowStates(); + mViewHierarchyManager.updateRowStates(); mScreenPinningRequest.onConfigurationChanged(); } @@ -3484,12 +2964,12 @@ public class StatusBar extends SystemUI implements DemoMode, if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); updatePublicMode(); - mNotificationData.filterAndSort(); + mEntryManager.getNotificationData().filterAndSort(); if (mReinflateNotificationsOnUserSwitched) { - updateNotificationsOnDensityOrFontScaleChanged(); + mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); mReinflateNotificationsOnUserSwitched = false; } - updateNotificationShade(); + updateNotificationViews(); mMediaManager.clearCurrentMediaNotification(); setLockscreenUser(newUserId); } @@ -3499,6 +2979,13 @@ public class StatusBar extends SystemUI implements DemoMode, return mLockscreenUserManager; } + @Override + public void onBindRow(Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setAboveShelfChangedListener(mAboveShelfObserver); + row.setSecureStateProvider(this::isKeyguardCurrentlySecure); + } + protected void setLockscreenUser(int newUserId) { mLockscreenWallpaper.setCurrentUser(newUserId); mScrimController.setCurrentUser(newUserId); @@ -3559,7 +3046,8 @@ public class StatusBar extends SystemUI implements DemoMode, try { // consider the transition from peek to expanded to be a panel open, // but not one that clears notification effects. - int notificationLoad = mNotificationData.getActiveNotifications().size(); + int notificationLoad = mEntryManager.getNotificationData() + .getActiveNotifications().size(); mBarService.onPanelRevealed(false, notificationLoad); } catch (RemoteException ex) { // Won't fail unless the world has ended. @@ -3577,7 +3065,8 @@ public class StatusBar extends SystemUI implements DemoMode, !isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationData.getActiveNotifications().size(); + int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications() + .size(); if (pinnedHeadsUp && isPresenterFullyCollapsed()) { notificationLoad = 1; } @@ -3712,7 +3201,7 @@ public class StatusBar extends SystemUI implements DemoMode, } catch (RemoteException e) { // Ignore. } - mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); + mEntryManager.destroy(); // End old BaseStatusBar.destroy(). if (mStatusBarWindow != null) { mWindowManager.removeViewImmediate(mStatusBarWindow); @@ -4165,7 +3654,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateDozingState(); updatePublicMode(); updateStackScrollerState(goingToFullShade, fromShadeLocked); - updateNotifications(); + mEntryManager.updateNotifications(); checkBarModes(); updateScrimController(); updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); @@ -4236,7 +3725,7 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardIndicationController.setDozing(mDozing); mNotificationPanel.setDark(mDozing, animate); updateQsExpansionEnabled(); - updateRowStates(); + mViewHierarchyManager.updateRowStates(); Trace.endSection(); } @@ -4440,7 +3929,8 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected int getMaxKeyguardNotifications(boolean recompute) { + @Override + public int getMaxNotificationsWhileLocked(boolean recompute) { if (recompute) { mMaxKeyguardNotifications = Math.max(1, mNotificationPanel.computeMaxKeyguardNotifications( @@ -4450,8 +3940,8 @@ public class StatusBar extends SystemUI implements DemoMode, return mMaxKeyguardNotifications; } - public int getMaxKeyguardNotifications() { - return getMaxKeyguardNotifications(false /* recompute */); + public int getMaxNotificationsWhileLocked() { + return getMaxNotificationsWhileLocked(false /* recompute */); } // TODO: Figure out way to remove these. @@ -4679,7 +4169,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onWorkChallengeChanged() { updatePublicMode(); - updateNotifications(); + mEntryManager.updateNotifications(); if (mPendingWorkRemoteInputView != null && !mLockscreenUserManager.isAnyProfilePublicMode()) { // Expand notification panel and the notification row, then click on remote input view @@ -4889,7 +4379,7 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean hasActiveNotifications() { - return !mNotificationData.getActiveNotifications().isEmpty(); + return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); } @Override @@ -5268,7 +4758,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected IStatusBarService mBarService; // all notifications - protected NotificationData mNotificationData; protected NotificationStackScrollLayout mStackScroller; protected NotificationGroupManager mGroupManager; @@ -5280,22 +4769,17 @@ public class StatusBar extends SystemUI implements DemoMode, private AboveShelfObserver mAboveShelfObserver; // handling reordering - protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager(); - + protected VisualStabilityManager mVisualStabilityManager; protected AccessibilityManager mAccessibilityManager; protected boolean mDeviceInteractive; protected boolean mVisible; - protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>(); // mScreenOnFromKeyguard && mVisible. private boolean mVisibleToUser; - protected boolean mUseHeadsUp = false; - protected boolean mDisableNotificationAlerts = false; - protected DevicePolicyManager mDevicePolicyManager; protected PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -5304,7 +4788,6 @@ public class StatusBar extends SystemUI implements DemoMode, private LockPatternUtils mLockPatternUtils; private DeviceProvisionedController mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - protected SystemServicesProxy mSystemServicesProxy; // UI-specific methods @@ -5319,8 +4802,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected DismissView mDismissView; protected EmptyShadeView mEmptyShadeView; - private final NotificationClicker mNotificationClicker = new NotificationClicker(); - protected AssistManager mAssistManager; protected boolean mVrMode; @@ -5345,14 +4826,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mVrMode; } - private final DeviceProvisionedListener mDeviceProvisionedListener = - new DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - updateNotifications(); - } - }; - private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -5377,6 +4850,121 @@ public class StatusBar extends SystemUI implements DemoMode, } }; + @Override + public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) { + Notification notification = sbn.getNotification(); + final PendingIntent intent = notification.contentIntent != null + ? notification.contentIntent + : notification.fullScreenIntent; + final String notificationKey = sbn.getKey(); + + final boolean afterKeyguardGone = intent.isActivity() + && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + dismissKeyguardThenExecute(() -> { + // TODO: Some of this code may be able to move to NotificationEntryManager. + if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { + // Release the HUN notification to the shade. + + if (isPresenterFullyCollapsed()) { + HeadsUpManager.setIsClickedNotification(row, true); + } + // + // In most cases, when FLAG_AUTO_CANCEL is set, the notification will + // become canceled shortly by NoMan, but we can't assume that. + mHeadsUpManager.releaseImmediately(notificationKey); + } + StatusBarNotification parentToCancel = null; + if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { + StatusBarNotification summarySbn = + mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification(); + if (shouldAutoCancel(summarySbn)) { + parentToCancel = summarySbn; + } + } + final StatusBarNotification parentToCancelFinal = parentToCancel; + final Runnable runnable = () -> { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + if (intent != null) { + // If we are launching a work activity and require to launch + // separate work challenge, we defer the activity action and cancel + // notification until work challenge is unlocked. + if (intent.isActivity()) { + final int userId = intent.getCreatorUserHandle().getIdentifier(); + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) + && mKeyguardManager.isDeviceLocked(userId)) { + // TODO(b/28935539): should allow certain activities to + // bypass work challenge + if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(), + notificationKey)) { + // Show work challenge, do not run PendingIntent and + // remove notification + return; + } + } + } + try { + intent.send(null, 0, null, null, null, null, getActivityOptions()); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending contentIntent failed: " + e); + + // TODO: Dismiss Keyguard. + } + if (intent.isActivity()) { + mAssistManager.hideAssist(); + } + } + + try { + mBarService.onNotificationClick(notificationKey); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + if (parentToCancelFinal != null) { + // We have to post it to the UI thread for synchronization + mHandler.post(() -> { + Runnable removeRunnable = + () -> mEntryManager.performRemoveNotification(parentToCancelFinal); + if (isCollapsing()) { + // To avoid lags we're only performing the remove + // after the shade was collapsed + addPostCollapseAction(removeRunnable); + } else { + removeRunnable.run(); + } + }); + } + }; + + if (mStatusBarKeyguardViewManager.isShowing() + && mStatusBarKeyguardViewManager.isOccluded()) { + mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); + } else { + new Thread(runnable).start(); + } + + if (!mNotificationPanel.isFullyCollapsed()) { + // close the shade if it was open + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, + true /* delayed */); + visibilityChanged(false); + + return true; + } else { + return false; + } + }, afterKeyguardGone); + } + protected NotificationListener mNotificationListener; protected void notifyUserAboutHiddenNotifications() { @@ -5438,14 +5026,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mLockscreenUserManager.isCurrentProfile(notificationUserId); } - protected void setNotificationShown(StatusBarNotification n) { - try { - mNotificationListener.setNotificationsShown(new String[]{n.getKey()}); - } catch (RuntimeException e) { - Log.d(TAG, "failed setNotificationsShown: ", e); - } - } - @Override public NotificationGroupManager getGroupManager() { return mGroupManager; @@ -5475,15 +5055,15 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { - return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item); - } - @Override public void toggleSplitScreen() { toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */); } + void awakenDreams() { + SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); + } + @Override public void preloadRecentApps() { int msg = MSG_PRELOAD_RECENT_APPS; @@ -5561,102 +5141,12 @@ public class StatusBar extends SystemUI implements DemoMode, if (mState == StatusBarState.KEYGUARD) { // Since the number of notifications is determined based on the height of the view, we // need to update them. - int maxBefore = getMaxKeyguardNotifications(false /* recompute */); - int maxNotifications = getMaxKeyguardNotifications(true /* recompute */); + int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */); + int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */); if (maxBefore != maxNotifications) { - updateRowStates(); - } - } - } - - protected void inflateViews(Entry entry, ViewGroup parent) { - PackageManager pmUser = getPackageManagerForUser(mContext, - entry.notification.getUser().getIdentifier()); - - final StatusBarNotification sbn = entry.notification; - if (entry.row != null) { - entry.reset(); - updateNotification(entry, pmUser, sbn, entry.row); - } else { - new RowInflaterTask().inflate(mContext, parent, entry, - row -> { - bindRow(entry, pmUser, sbn, row); - updateNotification(entry, pmUser, sbn, row); - }); - } - - } - - private void bindRow(Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setExpansionLogger(this, entry.notification.getKey()); - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setAboveShelfChangedListener(mAboveShelfObserver); - row.setOnExpandClickListener(this); - row.setInflationCallback(this); - row.setSecureStateProvider(this::isKeyguardCurrentlySecure); - row.setLongPressListener(getNotificationLongClicker()); - mRemoteInputManager.bindRow(row); - - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); + mViewHierarchyManager.updateRowStates(); } - } catch (NameNotFoundException e) { - // Do nothing - } - row.setAppName(appname); - row.setOnDismissRunnable(() -> - performRemoveNotification(row.getStatusBarNotification())); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - } - - private void updateNotification(Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry)); - boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); - boolean isUpdate = mNotificationData.get(entry.key) != null; - boolean wasLowPriority = row.isLowPriority(); - row.setIsLowPriority(isLowPriority); - row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); - // bind the click event to the content area - mNotificationClicker.register(row, sbn); - - // Extract target SDK version. - try { - ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); - entry.targetSdk = info.targetSdkVersion; - } catch (NameNotFoundException ex) { - Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); } - row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD - && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.autoRedacted = entry.notification.getNotification().publicVersion == null; - - entry.row = row; - entry.row.setOnActivatedListener(this); - - boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, - mNotificationData.getImportance(sbn.getKey())); - boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded; - row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.updateNotification(entry); } public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { @@ -5702,166 +5192,15 @@ public class StatusBar extends SystemUI implements DemoMode, }, afterKeyguardGone); } - - private final class NotificationClicker implements View.OnClickListener { - - @Override - public void onClick(final View v) { - if (!(v instanceof ExpandableNotificationRow)) { - Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); - return; - } - - wakeUpIfDozing(SystemClock.uptimeMillis(), v); - - final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final StatusBarNotification sbn = row.getStatusBarNotification(); - if (sbn == null) { - Log.e(TAG, "NotificationClicker called on an unclickable notification,"); - return; - } - - // Check if the notification is displaying the menu, if so slide notification back - if (row.getProvider() != null && row.getProvider().isMenuVisible()) { - row.animateTranslateNotification(0); - return; - } - - Notification notification = sbn.getNotification(); - final PendingIntent intent = notification.contentIntent != null - ? notification.contentIntent - : notification.fullScreenIntent; - final String notificationKey = sbn.getKey(); - - // Mark notification for one frame. - row.setJustClicked(true); - DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); - - final boolean afterKeyguardGone = intent.isActivity() - && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), - mLockscreenUserManager.getCurrentUserId()); - dismissKeyguardThenExecute(() -> { - if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { - // Release the HUN notification to the shade. - - if (isPresenterFullyCollapsed()) { - HeadsUpManager.setIsClickedNotification(row, true); - } - // - // In most cases, when FLAG_AUTO_CANCEL is set, the notification will - // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpManager.releaseImmediately(notificationKey); - } - StatusBarNotification parentToCancel = null; - if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { - StatusBarNotification summarySbn = - mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification(); - if (shouldAutoCancel(summarySbn)) { - parentToCancel = summarySbn; - } - } - final StatusBarNotification parentToCancelFinal = parentToCancel; - final Runnable runnable = () -> { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - if (intent != null) { - // If we are launching a work activity and require to launch - // separate work challenge, we defer the activity action and cancel - // notification until work challenge is unlocked. - if (intent.isActivity()) { - final int userId = intent.getCreatorUserHandle().getIdentifier(); - if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) - && mKeyguardManager.isDeviceLocked(userId)) { - // TODO(b/28935539): should allow certain activities to - // bypass work challenge - if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(), - notificationKey)) { - // Show work challenge, do not run PendingIntent and - // remove notification - return; - } - } - } - try { - intent.send(null, 0, null, null, null, null, getActivityOptions()); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. - // Just log the exception message. - Log.w(TAG, "Sending contentIntent failed: " + e); - - // TODO: Dismiss Keyguard. - } - if (intent.isActivity()) { - mAssistManager.hideAssist(); - } - } - - try { - mBarService.onNotificationClick(notificationKey); - } catch (RemoteException ex) { - // system process is dead if we're here. - } - if (parentToCancelFinal != null) { - // We have to post it to the UI thread for synchronization - mHandler.post(() -> { - Runnable removeRunnable = - () -> performRemoveNotification(parentToCancelFinal); - if (isCollapsing()) { - // To avoid lags we're only performing the remove - // after the shade was collapsed - addPostCollapseAction(removeRunnable); - } else { - removeRunnable.run(); - } - }); - } - }; - - if (mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isOccluded()) { - mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - } else { - new Thread(runnable).start(); - } - - if (!mNotificationPanel.isFullyCollapsed()) { - // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, - true /* delayed */); - visibilityChanged(false); - - return true; - } else { - return false; - } - }, afterKeyguardGone); - } - - private boolean shouldAutoCancel(StatusBarNotification sbn) { - int flags = sbn.getNotification().flags; - if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { - return false; - } - if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - return false; - } - return true; + private boolean shouldAutoCancel(StatusBarNotification sbn) { + int flags = sbn.getNotification().flags; + if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { + return false; } - - public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); - if (notification.contentIntent != null || notification.fullScreenIntent != null) { - row.setOnClickListener(this); - } else { - row.setOnClickListener(null); - } + if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + return false; } + return true; } protected Bundle getActivityOptions() { @@ -5904,127 +5243,10 @@ public class StatusBar extends SystemUI implements DemoMode, } /** - * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService - * about the failure. - * - * WARNING: this will call back into us. Don't hold any locks. - */ - void handleNotificationError(StatusBarNotification n, String message) { - removeNotification(n.getKey(), null); - try { - mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), - n.getInitialPid(), message, n.getUserId()); - } catch (RemoteException ex) { - // The end is nigh. - } - } - - protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { - NotificationData.Entry entry = mNotificationData.remove(key, ranking); - if (entry == null) { - Log.w(TAG, "removeNotification for unknown key: " + key); - return null; - } - updateNotifications(); - Dependency.get(LeakDetector.class).trackGarbage(entry); - return entry.notification; - } - - protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) - throws InflationException { - if (DEBUG) { - Log.d(TAG, "createNotificationViews(notification=" + sbn); - } - NotificationData.Entry entry = new NotificationData.Entry(sbn); - Dependency.get(LeakDetector.class).trackInstance(entry); - entry.createIcons(mContext, sbn); - // Construct the expanded view. - inflateViews(entry, mStackScroller); - return entry; - } - - protected void addNotificationViews(Entry entry) { - if (entry == null) { - return; - } - // Add the expanded view and icon. - mNotificationData.add(entry); - updateNotifications(); - } - - /** * Updates expanded, dimmed and locked states of notification rows. */ - protected void updateRowStates() { - final int N = mStackScroller.getChildCount(); - - int visibleNotifications = 0; - boolean onKeyguard = mState == StatusBarState.KEYGUARD; - int maxNotifications = -1; - if (onKeyguard) { - maxNotifications = getMaxKeyguardNotifications(true /* recompute */); - } - mStackScroller.setMaxDisplayedNotifications(maxNotifications); - Stack<ExpandableNotificationRow> stack = new Stack<>(); - for (int i = N - 1; i >= 0; i--) { - View child = mStackScroller.getChildAt(i); - if (!(child instanceof ExpandableNotificationRow)) { - continue; - } - stack.push((ExpandableNotificationRow) child); - } - while(!stack.isEmpty()) { - ExpandableNotificationRow row = stack.pop(); - NotificationData.Entry entry = row.getEntry(); - boolean isChildNotification = - mGroupManager.isChildInGroupWithSummary(entry.notification); - - row.setOnKeyguard(onKeyguard); - - if (!onKeyguard) { - // If mAlwaysExpandNonGroupedNotification is false, then only expand the - // very first notification and if it's not a child of grouped notifications. - row.setSystemExpanded(mAlwaysExpandNonGroupedNotification - || (visibleNotifications == 0 && !isChildNotification - && !row.isLowPriority())); - } - - entry.row.setShowAmbient(isDozing()); - int userId = entry.notification.getUserId(); - boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup( - entry.notification) && !entry.row.isRemoved(); - boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry - .notification); - if (suppressedSummary - || (mLockscreenUserManager.isLockscreenPublicMode(userId) - && !mLockscreenUserManager.shouldShowLockscreenNotifications()) - || (onKeyguard && !showOnKeyguard)) { - entry.row.setVisibility(View.GONE); - } else { - boolean wasGone = entry.row.getVisibility() == View.GONE; - if (wasGone) { - entry.row.setVisibility(View.VISIBLE); - } - if (!isChildNotification && !entry.row.isRemoved()) { - if (wasGone) { - // notify the scroller of a child addition - mStackScroller.generateAddAnimation(entry.row, - !showOnKeyguard /* fromMoreCard */); - } - visibleNotifications++; - } - } - if (row.isSummaryWithChildren()) { - List<ExpandableNotificationRow> notificationChildren = - row.getNotificationChildren(); - int size = notificationChildren.size(); - for (int i = size - 1; i >= 0; i--) { - stack.push(notificationChildren.get(i)); - } - } - } - mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0); - + @Override + public void onUpdateRowStates() { // The following views will be moved to the end of mStackScroller. This counter represents // the offset from the last child. Initialized to 1 for the very last position. It is post- // incremented in the following "changeViewPosition" calls so that its value is correct for @@ -6047,163 +5269,10 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount()); } - // TODO: Move this to NotificationEntryManager once it is created. - private void updateNotificationInternal(StatusBarNotification notification, - RankingMap ranking) throws InflationException { - if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); - - final String key = notification.getKey(); - abortExistingInflation(key); - Entry entry = mNotificationData.get(key); - if (entry == null) { - return; - } - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - mRemoteInputManager.onUpdateNotification(entry); - - if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { - mGutsManager.setKeyToRemoveOnGutsClosed(null); - Log.w(TAG, "Notification that was kept for guts was updated. " + key); - } - - Notification n = notification.getNotification(); - mNotificationData.updateRanking(ranking); - - final StatusBarNotification oldNotification = entry.notification; - entry.notification = notification; - mGroupManager.onEntryUpdated(entry, oldNotification); - - entry.updateIcons(mContext, notification); - inflateViews(entry, mStackScroller); - - mForegroundServiceController.updateNotification(notification, - mNotificationData.getImportance(key)); - - boolean shouldPeek = shouldPeek(entry, notification); - boolean alertAgain = alertAgain(entry, n); - - updateHeadsUp(key, entry, shouldPeek, alertAgain); - updateNotifications(); - - if (!notification.isClearable()) { - // The user may have performed a dismiss action on the notification, since it's - // not clearable we should snap it back. - mStackScroller.snapViewIfNeeded(entry.row); - } - - if (DEBUG) { - // Is this for you? - boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); - Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); - } - - setAreThereNotifications(); - } - - @Override - public void updateNotification(StatusBarNotification notification, RankingMap ranking) { - try { - updateNotificationInternal(notification, ranking); - } catch (InflationException e) { - handleInflationException(notification, e); - } - } - protected void notifyHeadsUpGoingToSleep() { maybeEscalateHeadsUp(); } - private boolean alertAgain(Entry oldEntry, Notification newNotification) { - return oldEntry == null || !oldEntry.hasInterrupted() - || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; - } - - protected boolean shouldPeek(Entry entry) { - return shouldPeek(entry, entry.notification); - } - - protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) { - if (!mUseHeadsUp || isDeviceInVrMode()) { - if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode"); - return false; - } - - if (mNotificationData.shouldFilterOut(sbn)) { - if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); - return false; - } - - boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming(); - - if (!inUse && !isDozing()) { - if (DEBUG) { - Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); - } - return false; - } - - if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); - return false; - } - - if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); - return false; - } - - if (entry.hasJustLaunchedFullScreenIntent()) { - if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); - return false; - } - - if (isSnoozedPackage(sbn)) { - if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); - return false; - } - - // Allow peeking for DEFAULT notifications only if we're on Ambient Display. - int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT - : NotificationManager.IMPORTANCE_HIGH; - if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { - if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); - return false; - } - - if (mIsOccluded && !isDozing()) { - boolean devicePublic = mLockscreenUserManager. - isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); - boolean userPublic = devicePublic - || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); - boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); - if (userPublic && needsRedaction) { - return false; - } - } - - if (sbn.getNotification().fullScreenIntent != null) { - if (mAccessibilityManager.isTouchExplorationEnabled()) { - if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); - return false; - } else if (mDozing) { - // We never want heads up when we are dozing. - return false; - } else { - // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent - return !mStatusBarKeyguardViewManager.isShowing() - || mStatusBarKeyguardViewManager.isOccluded(); - } - } - - // Don't peek notifications that are suppressed due to group alert behavior - if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior"); - return false; - } - - return true; - } - /** * @return Whether the security bouncer from Keyguard is showing. */ @@ -6233,17 +5302,6 @@ public class StatusBar extends SystemUI implements DemoMode, return contextForUser.getPackageManager(); } - @Override - public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mUiOffloadThread.submit(() -> { - try { - mBarService.onNotificationExpansionChanged(key, userAction, expanded); - } catch (RemoteException e) { - // Ignore. - } - }); - } - public boolean isKeyguardSecure() { if (mStatusBarKeyguardViewManager == null) { // startKeyguard() hasn't been called yet, so we don't know. @@ -6291,20 +5349,10 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public NotificationData getNotificationData() { - return mNotificationData; - } - - @Override public Handler getHandler() { return mHandler; } - @Override - public RankingMap getLatestRankingMap() { - return mLatestRankingMap; - } - private final NotificationInfo.CheckSaveListener mCheckSaveListener = (Runnable saveImportance, StatusBarNotification sbn) -> { // If the user has security enabled, show challenge if the setting is changed. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index fe39a894a094..369e7ffa991c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -80,6 +80,8 @@ import com.android.systemui.statusbar.ExpandableOutlineView; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationGuts; +import com.android.systemui.statusbar.NotificationListContainer; +import com.android.systemui.statusbar.NotificationLogger; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationSnooze; import com.android.systemui.statusbar.StackScrollerDecorView; @@ -112,7 +114,8 @@ import java.util.List; public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener, - NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider { + NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider, + NotificationListContainer { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; @@ -207,7 +210,7 @@ public class NotificationStackScrollLayout extends ViewGroup * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; - private OnChildLocationsChangedListener mListener; + private NotificationLogger.OnChildLocationsChangedListener mListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; private OnEmptySpaceClickListener mOnEmptySpaceClickListener; @@ -447,6 +450,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override public NotificationSwipeActionHelper getSwipeActionHelper() { return mSwipeHelper; } @@ -614,7 +618,9 @@ public class NotificationStackScrollLayout extends ViewGroup mNoAmbient = noAmbient; } - public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { + @Override + public void setChildLocationsChangedListener( + NotificationLogger.OnChildLocationsChangedListener listener) { mListener = listener; } @@ -1325,6 +1331,7 @@ public class NotificationStackScrollLayout extends ViewGroup true /* isDismissAll */); } + @Override public void snapViewIfNeeded(ExpandableNotificationRow child) { boolean animate = mIsExpanded || isPinnedHeadsUp(child); // If the child is showing the notification menu snap to that @@ -1333,6 +1340,11 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override + public ViewGroup getViewParentForNotification(NotificationData.Entry entry) { + return this; + } + + @Override public boolean onTouchEvent(MotionEvent ev) { boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL || ev.getActionMasked()== MotionEvent.ACTION_UP; @@ -2053,6 +2065,7 @@ public class NotificationStackScrollLayout extends ViewGroup return mAmbientState.isPulsing(entry); } + @Override public boolean hasPulsingNotifications() { return mPulsing != null; } @@ -2610,10 +2623,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } - /** - * Called when a notification is removed from the shade. This cleans up the state for a given - * view. - */ + @Override public void cleanUpViewState(View child) { if (child == mTranslatingParentView) { mTranslatingParentView = null; @@ -2922,10 +2932,12 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) { onViewRemovedInternal(row, childrenContainer); } + @Override public void notifyGroupChildAdded(View row) { onViewAddedInternal(row); } @@ -2963,12 +2975,8 @@ public class NotificationStackScrollLayout extends ViewGroup return mNeedsAnimation && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); } - /** - * Generate an animation for an added child view. - * - * @param child The view to be added. - * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. - */ + + @Override public void generateAddAnimation(View child, boolean fromMoreCard) { if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) { // Generate Animations @@ -2984,12 +2992,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } - /** - * Change the position of child to a new location - * - * @param child the view to change the position for - * @param newIndex the new index - */ + @Override public void changeViewPosition(View child, int newIndex) { int currentIndex = indexOfChild(child); if (child != null && child.getParent() == this && currentIndex != newIndex) { @@ -3705,7 +3708,7 @@ public class NotificationStackScrollLayout extends ViewGroup private void applyCurrentState() { mCurrentStackScrollState.apply(); if (mListener != null) { - mListener.onChildLocationsChanged(this); + mListener.onChildLocationsChanged(); } runAnimationFinishedRunnables(); setAnimationRunning(false); @@ -4189,6 +4192,26 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override + public int getContainerChildCount() { + return getChildCount(); + } + + @Override + public View getContainerChildAt(int i) { + return getChildAt(i); + } + + @Override + public void removeContainerView(View v) { + removeView(v); + } + + @Override + public void addContainerView(View v) { + addView(v); + } + public void runAfterAnimationFinished(Runnable runnable) { mAnimationFinishedRunnables.add(runnable); } @@ -4445,13 +4468,6 @@ public class NotificationStackScrollLayout extends ViewGroup } /** - * A listener that is notified when some child locations might have changed. - */ - public interface OnChildLocationsChangedListener { - void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); - } - - /** * A listener that is notified when the empty space below the notifications is clicked on */ public interface OnEmptySpaceClickListener { @@ -4706,6 +4722,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override public void resetExposedMenuView(boolean animate, boolean force) { mSwipeHelper.resetExposedMenuView(animate, force); } diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 9d44895dbe8d..066cfe5862ef 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -46,7 +46,12 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-v7-mediarouter \ android-support-v7-palette \ android-support-v14-preference \ - android-support-v17-leanback + android-support-v17-leanback \ + android-slices-core \ + android-slices-view \ + android-slices-builders \ + apptoolkit-arch-core-runtime \ + apptoolkit-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ metrics-helper-lib \ diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index 4eae3426f815..be28569ef629 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -16,11 +16,10 @@ package com.android.systemui.keyguard; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; +import androidx.app.slice.Slice; import android.content.Intent; import android.net.Uri; +import android.os.Debug; import android.os.Handler; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -34,6 +33,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import androidx.app.slice.SliceItem; +import androidx.app.slice.core.SliceQuery; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -63,7 +65,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void returnsValidSlice() { Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI)); - SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, + SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT, + android.app.slice.Slice.HINT_TITLE, null /* nonHints */); Assert.assertNotNull("Slice must provide a title.", text); } @@ -78,9 +81,10 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void updatesClock() { + mProvider.mUpdateClockInvokations = 0; mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK)); TestableLooper.get(this).processAllMessages(); - Assert.assertEquals("Clock should have been updated.", 2 /* expected */, + Assert.assertEquals("Clock should have been updated.", 1 /* expected */, mProvider.mUpdateClockInvokations); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java new file mode 100644 index 000000000000..0a683891e66f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.Notification; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.widget.FrameLayout; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.UiOffloadThread; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.HeadsUpManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationEntryManagerTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + + @Mock private NotificationPresenter mPresenter; + @Mock private ExpandableNotificationRow mRow; + @Mock private NotificationListContainer mListContainer; + @Mock private NotificationEntryManager.Callback mCallback; + @Mock private HeadsUpManager mHeadsUpManager; + @Mock private NotificationListenerService.RankingMap mRankingMap; + @Mock private RemoteInputController mRemoteInputController; + @Mock private IStatusBarService mBarService; + + // Dependency mocks: + @Mock private ForegroundServiceController mForegroundServiceController; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock private NotificationGroupManager mGroupManager; + @Mock private NotificationGutsManager mGutsManager; + @Mock private NotificationRemoteInputManager mRemoteInputManager; + @Mock private NotificationMediaManager mMediaManager; + @Mock private NotificationListener mNotificationListener; + @Mock private DeviceProvisionedController mDeviceProvisionedController; + @Mock private VisualStabilityManager mVisualStabilityManager; + @Mock private MetricsLogger mMetricsLogger; + + private NotificationData.Entry mEntry; + private StatusBarNotification mSbn; + private Handler mHandler; + private TestableNotificationEntryManager mEntryManager; + private CountDownLatch mCountDownLatch; + + private class TestableNotificationEntryManager extends NotificationEntryManager { + private final CountDownLatch mCountDownLatch; + + public TestableNotificationEntryManager(Context context, IStatusBarService barService) { + super(context); + mBarService = barService; + mCountDownLatch = new CountDownLatch(1); + mUseHeadsUp = true; + } + + @Override + public void onAsyncInflationFinished(NotificationData.Entry entry) { + super.onAsyncInflationFinished(entry); + + mCountDownLatch.countDown(); + } + + public CountDownLatch getCountDownLatch() { + return mCountDownLatch; + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(ForegroundServiceController.class, + mForegroundServiceController); + mDependency.injectTestDependency(NotificationLockscreenUserManager.class, + mLockscreenUserManager); + mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); + mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager); + mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager); + mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager); + mDependency.injectTestDependency(NotificationListener.class, mNotificationListener); + mDependency.injectTestDependency(DeviceProvisionedController.class, + mDeviceProvisionedController); + mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); + mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); + + mHandler = new Handler(Looper.getMainLooper()); + mCountDownLatch = new CountDownLatch(1); + + when(mPresenter.getHandler()).thenReturn(mHandler); + when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(mLockscreenUserManager); + when(mPresenter.getGroupManager()).thenReturn(mGroupManager); + when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); + when(mListContainer.getViewParentForNotification(any())).thenReturn( + new FrameLayout(mContext)); + + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0); + mEntry = new NotificationData.Entry(mSbn); + mEntry.expandedIcon = mock(StatusBarIconView.class); + + mEntryManager = new TestableNotificationEntryManager(mContext, mBarService); + mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager); + } + + @Test + public void testAddNotification() throws Exception { + com.android.systemui.util.Assert.isNotMainThread(); + + doAnswer(invocation -> { + mCountDownLatch.countDown(); + return null; + }).when(mCallback).onBindRow(any(), any(), any(), any()); + + // Post on main thread, otherwise we will be stuck waiting here for the inflation finished + // callback forever, since it won't execute until the tests ends. + mHandler.post(() -> { + mEntryManager.addNotification(mSbn, mRankingMap); + }); + assertTrue(mCountDownLatch.await(1, TimeUnit.MINUTES)); + assertTrue(mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES)); + waitForIdleSync(mHandler); + + // Check that no inflation error occurred. + verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(), + any(), anyInt()); + verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt()); + + // Row inflation: + ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass( + NotificationData.Entry.class); + verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any()); + NotificationData.Entry entry = entryCaptor.getValue(); + verify(mRemoteInputManager).bindRow(entry.row); + + // Row content inflation: + verify(mCallback).onNotificationAdded(entry); + verify(mPresenter).updateNotificationViews(); + + assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry); + assertNotNull(entry.row); + } + + @Test + public void testUpdateNotification() throws Exception { + com.android.systemui.util.Assert.isNotMainThread(); + + mEntryManager.getNotificationData().add(mEntry); + + mHandler.post(() -> { + mEntryManager.updateNotification(mSbn, mRankingMap); + }); + // Wait for content update. + mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES); + waitForIdleSync(mHandler); + + verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(), + any(), anyInt()); + + verify(mRemoteInputManager).onUpdateNotification(mEntry); + verify(mPresenter).updateNotificationViews(); + verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt()); + verify(mCallback).onNotificationUpdated(mSbn); + assertNotNull(mEntry.row); + } + + @Test + public void testRemoveNotification() throws Exception { + com.android.systemui.util.Assert.isNotMainThread(); + + mEntry.row = mRow; + mEntryManager.getNotificationData().add(mEntry); + + mHandler.post(() -> { + mEntryManager.removeNotification(mSbn.getKey(), mRankingMap); + }); + waitForIdleSync(mHandler); + + verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(), + any(), anyInt()); + + verify(mMediaManager).onNotificationRemoved(mSbn.getKey()); + verify(mRemoteInputManager).onRemoveNotification(mEntry); + verify(mForegroundServiceController).removeNotification(mSbn); + verify(mListContainer).cleanUpViewState(mRow); + verify(mPresenter).updateNotificationViews(); + verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn); + verify(mRow).setRemoved(); + + assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index ccc300625c8b..ef5f071d11fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,6 +37,8 @@ import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.HashSet; import java.util.Set; @@ -49,40 +50,45 @@ public class NotificationListenerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; - private NotificationPresenter mPresenter; - private Handler mHandler; + @Mock private NotificationPresenter mPresenter; + @Mock private NotificationListenerService.RankingMap mRanking; + @Mock private NotificationData mNotificationData; + + // Dependency mocks: + @Mock private NotificationEntryManager mEntryManager; + @Mock private NotificationRemoteInputManager mRemoteInputManager; + private NotificationListener mListener; + private Handler mHandler; private StatusBarNotification mSbn; - private NotificationListenerService.RankingMap mRanking; private Set<String> mKeysKeptForRemoteInput; - private NotificationData mNotificationData; - private NotificationRemoteInputManager mRemoteInputManager; @Before public void setUp() { + MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); + mDependency.injectTestDependency(NotificationRemoteInputManager.class, + mRemoteInputManager); + mHandler = new Handler(Looper.getMainLooper()); - mPresenter = mock(NotificationPresenter.class); - mNotificationData = mock(NotificationData.class); - mRanking = mock(NotificationListenerService.RankingMap.class); - mRemoteInputManager = mock(NotificationRemoteInputManager.class); mKeysKeptForRemoteInput = new HashSet<>(); when(mPresenter.getHandler()).thenReturn(mHandler); - when(mPresenter.getNotificationData()).thenReturn(mNotificationData); + when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput); - mListener = new NotificationListener(mRemoteInputManager, mContext); + mListener = new NotificationListener(mContext); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); - mListener.setUpWithPresenter(mPresenter); + mListener.setUpWithPresenter(mPresenter, mEntryManager); } @Test public void testNotificationAddCallsAddNotification() { mListener.onNotificationPosted(mSbn, mRanking); waitForIdleSync(mHandler); - verify(mPresenter).addNotification(mSbn, mRanking); + verify(mEntryManager).addNotification(mSbn, mRanking); } @Test @@ -98,14 +104,14 @@ public class NotificationListenerTest extends SysuiTestCase { when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn)); mListener.onNotificationPosted(mSbn, mRanking); waitForIdleSync(mHandler); - verify(mPresenter).updateNotification(mSbn, mRanking); + verify(mEntryManager).updateNotification(mSbn, mRanking); } @Test public void testNotificationRemovalCallsRemoveNotification() { mListener.onNotificationRemoved(mSbn, mRanking); waitForIdleSync(mHandler); - verify(mPresenter).removeNotification(mSbn.getKey(), mRanking); + verify(mEntryManager).removeNotification(mSbn.getKey(), mRanking); } @Test @@ -113,6 +119,6 @@ public class NotificationListenerTest extends SysuiTestCase { mListener.onNotificationRankingUpdate(mRanking); waitForIdleSync(mHandler); // RankingMap may be modified by plugins. - verify(mPresenter).updateNotificationRanking(any()); + verify(mEntryManager).updateNotificationRanking(any()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 40459952dc95..cb8f7ce1a92b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -22,7 +22,6 @@ import static android.content.Intent.ACTION_USER_SWITCHED; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -49,42 +48,47 @@ import com.google.android.collect.Lists; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class NotificationLockscreenUserManagerTest extends SysuiTestCase { - private NotificationPresenter mPresenter; - private TestNotificationLockscreenUserManager mLockscreenUserManager; - private DeviceProvisionedController mDeviceProvisionedController; + @Mock private NotificationPresenter mPresenter; + @Mock private UserManager mUserManager; + + // Dependency mocks: + @Mock private NotificationEntryManager mEntryManager; + @Mock private DeviceProvisionedController mDeviceProvisionedController; + private int mCurrentUserId; private Handler mHandler; - private UserManager mUserManager; + private TestNotificationLockscreenUserManager mLockscreenUserManager; @Before public void setUp() { - mUserManager = mock(UserManager.class); - mContext.addMockSystemService(UserManager.class, mUserManager); + MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); + mDependency.injectTestDependency(DeviceProvisionedController.class, + mDeviceProvisionedController); + mHandler = new Handler(Looper.getMainLooper()); - mDependency.injectMockDependency(DeviceProvisionedController.class); - mDeviceProvisionedController = mDependency.get(DeviceProvisionedController.class); - mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); - mDependency.injectTestDependency(NotificationLockscreenUserManager.class, - mLockscreenUserManager); + mContext.addMockSystemService(UserManager.class, mUserManager); + mCurrentUserId = ActivityManager.getCurrentUser(); when(mUserManager.getProfiles(mCurrentUserId)).thenReturn(Lists.newArrayList( new UserInfo(mCurrentUserId, "", 0), new UserInfo(mCurrentUserId + 1, "", 0))); - - mPresenter = mock(NotificationPresenter.class); when(mPresenter.getHandler()).thenReturn(mHandler); - mLockscreenUserManager.setUpWithPresenter(mPresenter); - mCurrentUserId = ActivityManager.getCurrentUser(); + + mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); + mLockscreenUserManager.setUpWithPresenter(mPresenter, mEntryManager); } @Test public void testLockScreenShowNotificationsChangeUpdatesNotifications() { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); - verify(mPresenter, times(1)).updateNotifications(); + verify(mEntryManager, times(1)).updateNotifications(); } @Test @@ -123,7 +127,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testSettingsObserverUpdatesNotifications() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); mLockscreenUserManager.getSettingsObserverForTest().onChange(false); - verify(mPresenter, times(1)).updateNotifications(); + verify(mEntryManager, times(1)).updateNotifications(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java index 142ce63afbd8..726810e3e177 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java @@ -36,7 +36,6 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.SysuiTestCase; import com.android.systemui.UiOffloadThread; -import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.google.android.collect.Lists; @@ -55,12 +54,15 @@ public class NotificationLoggerTest extends SysuiTestCase { private static final int TEST_UID = 0; @Mock private NotificationPresenter mPresenter; - @Mock private NotificationListener mListener; - @Mock private NotificationStackScrollLayout mStackScroller; + @Mock private NotificationListContainer mListContainer; @Mock private IStatusBarService mBarService; @Mock private NotificationData mNotificationData; @Mock private ExpandableNotificationRow mRow; + // Dependency mocks: + @Mock private NotificationEntryManager mEntryManager; + @Mock private NotificationListener mListener; + private NotificationData.Entry mEntry; private StatusBarNotification mSbn; private TestableNotificationLogger mLogger; @@ -68,24 +70,25 @@ public class NotificationLoggerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); + mDependency.injectTestDependency(NotificationListener.class, mListener); - when(mPresenter.getNotificationData()).thenReturn(mNotificationData); + when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); mEntry = new NotificationData.Entry(mSbn); mEntry.row = mRow; - mLogger = new TestableNotificationLogger(mListener, mDependency.get(UiOffloadThread.class), - mBarService); - mLogger.setUpWithPresenter(mPresenter, mStackScroller); + mLogger = new TestableNotificationLogger(mBarService); + mLogger.setUpWithEntryManager(mEntryManager, mListContainer); } @Test public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception { - when(mStackScroller.isInVisibleLocation(any())).thenReturn(true); + when(mListContainer.isInVisibleLocation(any())).thenReturn(true); when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry)); - mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller); + mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); waitForIdleSync(mLogger.getHandlerForTest()); waitForUiOffloadThread(); @@ -97,7 +100,7 @@ public class NotificationLoggerTest extends SysuiTestCase { // |mEntry| won't change visibility, so it shouldn't be reported again: Mockito.reset(mBarService); - mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller); + mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); waitForIdleSync(mLogger.getHandlerForTest()); waitForUiOffloadThread(); @@ -107,9 +110,9 @@ public class NotificationLoggerTest extends SysuiTestCase { @Test public void testStoppingNotificationLoggingReportsCurrentNotifications() throws Exception { - when(mStackScroller.isInVisibleLocation(any())).thenReturn(true); + when(mListContainer.isInVisibleLocation(any())).thenReturn(true); when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry)); - mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller); + mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); waitForIdleSync(mLogger.getHandlerForTest()); waitForUiOffloadThread(); Mockito.reset(mBarService); @@ -123,17 +126,13 @@ public class NotificationLoggerTest extends SysuiTestCase { private class TestableNotificationLogger extends NotificationLogger { - public TestableNotificationLogger( - NotificationListenerService notificationListener, - UiOffloadThread uiOffloadThread, - IStatusBarService barService) { - super(notificationListener, uiOffloadThread); + public TestableNotificationLogger(IStatusBarService barService) { mBarService = barService; // Make this on the main thread so we can wait for it during tests. mHandler = new Handler(Looper.getMainLooper()); } - public NotificationStackScrollLayout.OnChildLocationsChangedListener + public OnChildLocationsChangedListener getChildLocationsChangedListenerForTest() { return mNotificationLocationsChangedListener; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index b881c098a1a3..4829cb256e9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -34,36 +34,41 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; - private Handler mHandler; - private TestableNotificationRemoteInputManager mRemoteInputManager; - private StatusBarNotification mSbn; - private NotificationData.Entry mEntry; - @Mock private NotificationPresenter mPresenter; @Mock private RemoteInputController.Delegate mDelegate; - @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationRemoteInputManager.Callback mCallback; @Mock private RemoteInputController mController; @Mock private NotificationListenerService.RankingMap mRanking; @Mock private ExpandableNotificationRow mRow; + // Dependency mocks: + @Mock private NotificationEntryManager mEntryManager; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + + private Handler mHandler; + private TestableNotificationRemoteInputManager mRemoteInputManager; + private StatusBarNotification mSbn; + private NotificationData.Entry mEntry; + @Before public void setUp() { MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); + mDependency.injectTestDependency(NotificationLockscreenUserManager.class, + mLockscreenUserManager); mHandler = new Handler(Looper.getMainLooper()); when(mPresenter.getHandler()).thenReturn(mHandler); - when(mPresenter.getLatestRankingMap()).thenReturn(mRanking); + when(mEntryManager.getLatestRankingMap()).thenReturn(mRanking); - mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager, - mContext); + mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); mEntry = new NotificationData.Entry(mSbn); mEntry.row = mRow; - mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mCallback, mDelegate, - mController); + mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mEntryManager, mCallback, + mDelegate, mController); } @Test @@ -97,21 +102,21 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty()); verify(mController).removeRemoteInput(mEntry, null); - verify(mPresenter).removeNotification(mEntry.key, mRanking); + verify(mEntryManager).removeNotification(mEntry.key, mRanking); } private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager { - public TestableNotificationRemoteInputManager( - NotificationLockscreenUserManager lockscreenUserManager, Context context) { - super(lockscreenUserManager, context); + public TestableNotificationRemoteInputManager(Context context) { + super(context); } public void setUpWithPresenterForTest(NotificationPresenter presenter, + NotificationEntryManager entryManager, Callback callback, RemoteInputController.Delegate delegate, RemoteInputController controller) { - super.setUpWithPresenter(presenter, callback, delegate); + super.setUpWithPresenter(presenter, entryManager, callback, delegate); mRemoteInputController = controller; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java new file mode 100644 index 000000000000..fbe730a64c6f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationViewHierarchyManagerTest extends SysuiTestCase { + @Mock private NotificationPresenter mPresenter; + @Mock private NotificationData mNotificationData; + @Spy private FakeListContainer mListContainer = new FakeListContainer(); + + // Dependency mocks: + @Mock private NotificationEntryManager mEntryManager; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock private NotificationGroupManager mGroupManager; + @Mock private VisualStabilityManager mVisualStabilityManager; + + private NotificationViewHierarchyManager mViewHierarchyManager; + private NotificationTestHelper mHelper = new NotificationTestHelper(mContext);; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); + mDependency.injectTestDependency(NotificationLockscreenUserManager.class, + mLockscreenUserManager); + mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); + mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); + + when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); + + mViewHierarchyManager = new NotificationViewHierarchyManager(mContext); + mViewHierarchyManager.setUpWithPresenter(mPresenter, mEntryManager, mListContainer); + } + + private NotificationData.Entry createEntry() throws Exception { + ExpandableNotificationRow row = mHelper.createRow(); + NotificationData.Entry entry = new NotificationData.Entry(row.getStatusBarNotification()); + entry.row = row; + return entry; + } + + @Test + public void testNotificationsBecomingBundled() throws Exception { + // Tests 3 top level notifications becoming a single bundled notification with |entry0| as + // the summary. + NotificationData.Entry entry0 = createEntry(); + NotificationData.Entry entry1 = createEntry(); + NotificationData.Entry entry2 = createEntry(); + + // Set up the prior state to look like three top level notifications. + mListContainer.addContainerView(entry0.row); + mListContainer.addContainerView(entry1.row); + mListContainer.addContainerView(entry2.row); + when(mNotificationData.getActiveNotifications()).thenReturn( + Lists.newArrayList(entry0, entry1, entry2)); + + // Set up group manager to report that they should be bundled now. + when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false); + when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(true); + when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(true); + when(mGroupManager.getGroupSummary(entry1.notification)).thenReturn(entry0.row); + when(mGroupManager.getGroupSummary(entry2.notification)).thenReturn(entry0.row); + + // Run updateNotifications - the view hierarchy should be reorganized. + mViewHierarchyManager.updateNotificationViews(); + + verify(mListContainer).notifyGroupChildAdded(entry1.row); + verify(mListContainer).notifyGroupChildAdded(entry2.row); + assertTrue(Lists.newArrayList(entry0.row).equals(mListContainer.mRows)); + } + + @Test + public void testNotificationsBecomingUnbundled() throws Exception { + // Tests a bundled notification becoming three top level notifications. + NotificationData.Entry entry0 = createEntry(); + NotificationData.Entry entry1 = createEntry(); + NotificationData.Entry entry2 = createEntry(); + entry0.row.addChildNotification(entry1.row); + entry0.row.addChildNotification(entry2.row); + + // Set up the prior state to look like one top level notification. + mListContainer.addContainerView(entry0.row); + when(mNotificationData.getActiveNotifications()).thenReturn( + Lists.newArrayList(entry0, entry1, entry2)); + + // Set up group manager to report that they should not be bundled now. + when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false); + when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false); + when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(false); + + // Run updateNotifications - the view hierarchy should be reorganized. + mViewHierarchyManager.updateNotificationViews(); + + verify(mListContainer).notifyGroupChildRemoved( + entry1.row, entry0.row.getChildrenContainer()); + verify(mListContainer).notifyGroupChildRemoved( + entry2.row, entry0.row.getChildrenContainer()); + assertTrue(Lists.newArrayList(entry0.row, entry1.row, entry2.row).equals(mListContainer.mRows)); + } + + @Test + public void testNotificationsBecomingSuppressed() throws Exception { + // Tests two top level notifications becoming a suppressed summary and a child. + NotificationData.Entry entry0 = createEntry(); + NotificationData.Entry entry1 = createEntry(); + entry0.row.addChildNotification(entry1.row); + + // Set up the prior state to look like a top level notification. + mListContainer.addContainerView(entry0.row); + when(mNotificationData.getActiveNotifications()).thenReturn( + Lists.newArrayList(entry0, entry1)); + + // Set up group manager to report a suppressed summary now. + when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false); + when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false); + when(mGroupManager.isSummaryOfSuppressedGroup(entry0.notification)).thenReturn(true); + + // Run updateNotifications - the view hierarchy should be reorganized. + mViewHierarchyManager.updateNotificationViews(); + + verify(mListContainer).notifyGroupChildRemoved( + entry1.row, entry0.row.getChildrenContainer()); + assertTrue(Lists.newArrayList(entry0.row, entry1.row).equals(mListContainer.mRows)); + assertEquals(View.GONE, entry0.row.getVisibility()); + assertEquals(View.VISIBLE, entry1.row.getVisibility()); + } + + private class FakeListContainer implements NotificationListContainer { + final LinearLayout mLayout = new LinearLayout(mContext); + final List<View> mRows = Lists.newArrayList(); + + @Override + public void setChildTransferInProgress(boolean childTransferInProgress) {} + + @Override + public void changeViewPosition(View child, int newIndex) { + mRows.remove(child); + mRows.add(newIndex, child); + } + + @Override + public void notifyGroupChildAdded(View row) {} + + @Override + public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {} + + @Override + public void generateAddAnimation(View child, boolean fromMoreCard) {} + + @Override + public void generateChildOrderChangedEvent() {} + + @Override + public int getContainerChildCount() { + return mRows.size(); + } + + @Override + public View getContainerChildAt(int i) { + return mRows.get(i); + } + + @Override + public void removeContainerView(View v) { + mLayout.removeView(v); + mRows.remove(v); + } + + @Override + public void addContainerView(View v) { + mLayout.addView(v); + mRows.add(v); + } + + @Override + public void setMaxDisplayedNotifications(int maxNotifications) {} + + @Override + public void snapViewIfNeeded(ExpandableNotificationRow row) {} + + @Override + public ViewGroup getViewParentForNotification(NotificationData.Entry entry) { + return null; + } + + @Override + public void onHeightChanged(ExpandableView view, boolean animate) {} + + @Override + public void resetExposedMenuView(boolean animate, boolean force) {} + + @Override + public NotificationSwipeActionHelper getSwipeActionHelper() { + return null; + } + + @Override + public void cleanUpViewState(View view) {} + + @Override + public boolean isInVisibleLocation(ExpandableNotificationRow row) { + return true; + } + + @Override + public void setChildLocationsChangedListener( + NotificationLogger.OnChildLocationsChangedListener listener) {} + + @Override + public boolean hasPulsingNotifications() { + return false; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 073286617979..99202f4ac10c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.StatusBarManager; import android.app.trust.TrustManager; +import android.content.Context; import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Binder; @@ -52,7 +53,6 @@ import android.support.test.metricshelper.MetricsAsserts; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.ViewGroup.LayoutParams; @@ -61,6 +61,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.keyguard.KeyguardHostView.OnDismissAction; +import com.android.systemui.ForegroundServiceController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.UiOffloadThread; @@ -72,10 +73,18 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; +import com.android.systemui.statusbar.NotificationEntryManager; +import com.android.systemui.statusbar.NotificationGutsManager; +import com.android.systemui.statusbar.NotificationListContainer; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLogger; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -85,6 +94,8 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; @@ -94,70 +105,71 @@ import java.util.ArrayList; @RunWith(AndroidTestingRunner.class) @RunWithLooper public class StatusBarTest extends SysuiTestCase { - - StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - UnlockMethodCache mUnlockMethodCache; - KeyguardIndicationController mKeyguardIndicationController; - NotificationStackScrollLayout mStackScroller; - TestableStatusBar mStatusBar; - FakeMetricsLogger mMetricsLogger; - HeadsUpManager mHeadsUpManager; - NotificationData mNotificationData; - PowerManager mPowerManager; - SystemServicesProxy mSystemServicesProxy; - NotificationPanelView mNotificationPanelView; - ScrimController mScrimController; - IStatusBarService mBarService; - NotificationListener mNotificationListener; - NotificationLogger mNotificationLogger; - ArrayList<Entry> mNotificationList; - FingerprintUnlockController mFingerprintUnlockController; - private DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + @Mock private UnlockMethodCache mUnlockMethodCache; + @Mock private KeyguardIndicationController mKeyguardIndicationController; + @Mock private NotificationStackScrollLayout mStackScroller; + @Mock private HeadsUpManager mHeadsUpManager; + @Mock private SystemServicesProxy mSystemServicesProxy; + @Mock private NotificationPanelView mNotificationPanelView; + @Mock private IStatusBarService mBarService; + @Mock private ScrimController mScrimController; + @Mock private ArrayList<Entry> mNotificationList; + @Mock private FingerprintUnlockController mFingerprintUnlockController; + @Mock private NotificationData mNotificationData; + + // Mock dependencies: + @Mock private NotificationViewHierarchyManager mViewHierarchyManager; + @Mock private VisualStabilityManager mVisualStabilityManager; + @Mock private NotificationListener mNotificationListener; + + private TestableStatusBar mStatusBar; + private FakeMetricsLogger mMetricsLogger; + private PowerManager mPowerManager; + private TestableNotificationEntryManager mEntryManager; + private NotificationLogger mNotificationLogger; @Before public void setup() throws Exception { - mContext.setTheme(R.style.Theme_SystemUI_Light); + MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(AssistManager.class); mDependency.injectMockDependency(DeviceProvisionedController.class); + mDependency.injectMockDependency(NotificationGroupManager.class); + mDependency.injectMockDependency(NotificationGutsManager.class); + mDependency.injectMockDependency(NotificationRemoteInputManager.class); + mDependency.injectMockDependency(NotificationMediaManager.class); + mDependency.injectMockDependency(ForegroundServiceController.class); + mDependency.injectTestDependency(NotificationViewHierarchyManager.class, + mViewHierarchyManager); + mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); + mDependency.injectTestDependency(NotificationListener.class, mNotificationListener); mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class)); - CommandQueue commandQueue = mock(CommandQueue.class); - when(commandQueue.asBinder()).thenReturn(new Binder()); - mContext.putComponent(CommandQueue.class, commandQueue); + mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); - mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class); - mUnlockMethodCache = mock(UnlockMethodCache.class); - mKeyguardIndicationController = mock(KeyguardIndicationController.class); - mStackScroller = mock(NotificationStackScrollLayout.class); - when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0)); + mMetricsLogger = new FakeMetricsLogger(); - mHeadsUpManager = mock(HeadsUpManager.class); - mNotificationData = mock(NotificationData.class); - mSystemServicesProxy = mock(SystemServicesProxy.class); - mNotificationPanelView = mock(NotificationPanelView.class); - when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0)); - mNotificationList = mock(ArrayList.class); - mScrimController = mock(ScrimController.class); - mFingerprintUnlockController = mock(FingerprintUnlockController.class); + mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); + mNotificationLogger = new NotificationLogger(); + mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger); + IPowerManager powerManagerService = mock(IPowerManager.class); HandlerThread handlerThread = new HandlerThread("TestThread"); handlerThread.start(); mPowerManager = new PowerManager(mContext, powerManagerService, new Handler(handlerThread.getLooper())); + + CommandQueue commandQueue = mock(CommandQueue.class); + when(commandQueue.asBinder()).thenReturn(new Binder()); + mContext.putComponent(CommandQueue.class, commandQueue); + + mContext.setTheme(R.style.Theme_SystemUI_Light); + + when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0)); + when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0)); when(powerManagerService.isInteractive()).thenReturn(true); - mBarService = mock(IStatusBarService.class); - mNotificationListener = mock(NotificationListener.class); - mNotificationLogger = new NotificationLogger(mNotificationListener, mDependency.get( - UiOffloadThread.class)); + when(mStackScroller.getActivatedChild()).thenReturn(null); - mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); - mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, - mKeyguardIndicationController, mStackScroller, mHeadsUpManager, - mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView, - mBarService, mNotificationListener, mNotificationLogger, mScrimController, - mFingerprintUnlockController); - mStatusBar.mContext = mContext; - mStatusBar.mComponents = mContext.getComponents(); doAnswer(invocation -> { OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0]; onDismissAction.onDismiss(); @@ -170,9 +182,19 @@ public class StatusBarTest extends SysuiTestCase { return null; }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); - mNotificationLogger.setUpWithPresenter(mStatusBar, mStackScroller); + mEntryManager = new TestableNotificationEntryManager(mSystemServicesProxy, mPowerManager, + mContext); + mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, + mKeyguardIndicationController, mStackScroller, mHeadsUpManager, + mPowerManager, mNotificationPanelView, mBarService, mNotificationListener, + mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager, + mEntryManager, mScrimController, mFingerprintUnlockController); + mStatusBar.mContext = mContext; + mStatusBar.mComponents = mContext.getComponents(); + mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar, mHeadsUpManager, + mNotificationData); + mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller); - when(mStackScroller.getActivatedChild()).thenReturn(null); TestableLooper.get(this).setMessageHandler(m -> { if (m.getCallback() == mStatusBar.mNotificationLogger.getVisibilityReporter()) { return false; @@ -334,7 +356,7 @@ public class StatusBarTest extends SysuiTestCase { UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertTrue(mStatusBar.shouldPeek(entry, sbn)); + assertTrue(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -355,7 +377,7 @@ public class StatusBarTest extends SysuiTestCase { UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertFalse(mStatusBar.shouldPeek(entry, sbn)); + assertFalse(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -375,7 +397,7 @@ public class StatusBarTest extends SysuiTestCase { UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertTrue(mStatusBar.shouldPeek(entry, sbn)); + assertTrue(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -394,7 +416,7 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertFalse(mStatusBar.shouldPeek(entry, sbn)); + assertFalse(mEntryManager.shouldPeek(entry, sbn)); } @Test public void testShouldPeek_suppressedScreenOff_dozing() { @@ -412,7 +434,7 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertFalse(mStatusBar.shouldPeek(entry, sbn)); + assertFalse(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -431,7 +453,7 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertTrue(mStatusBar.shouldPeek(entry, sbn)); + assertTrue(mEntryManager.shouldPeek(entry, sbn)); } @@ -564,25 +586,28 @@ public class StatusBarTest extends SysuiTestCase { static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, - NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd, - PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView, + NotificationStackScrollLayout stack, HeadsUpManager hum, + PowerManager pm, NotificationPanelView panelView, IStatusBarService barService, NotificationListener notificationListener, - NotificationLogger notificationLogger, ScrimController scrimController, + NotificationLogger notificationLogger, + VisualStabilityManager visualStabilityManager, + NotificationViewHierarchyManager viewHierarchyManager, + TestableNotificationEntryManager entryManager, ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController) { mStatusBarKeyguardViewManager = man; mUnlockMethodCache = unlock; mKeyguardIndicationController = key; mStackScroller = stack; mHeadsUpManager = hum; - mNotificationData = nd; - mUseHeadsUp = true; mPowerManager = pm; - mSystemServicesProxy = ssp; mNotificationPanel = panelView; mBarService = barService; mNotificationListener = notificationListener; mNotificationLogger = notificationLogger; mWakefulnessLifecycle = createAwakeWakefulnessLifecycle(); + mVisualStabilityManager = visualStabilityManager; + mViewHierarchyManager = viewHierarchyManager; + mEntryManager = entryManager; mScrimController = scrimController; mFingerprintUnlockController = fingerprintUnlockController; } @@ -606,5 +631,26 @@ public class StatusBarTest extends SysuiTestCase { public void setUserSetupForTest(boolean userSetup) { mUserSetup = userSetup; } + + } + + private class TestableNotificationEntryManager extends NotificationEntryManager { + + public TestableNotificationEntryManager(SystemServicesProxy systemServicesProxy, + PowerManager powerManager, Context context) { + super(context); + mSystemServicesProxy = systemServicesProxy; + mPowerManager = powerManager; + } + + public void setUpForTest(NotificationPresenter presenter, + NotificationListContainer listContainer, + Callback callback, + HeadsUpManager headsUpManager, + NotificationData notificationData) { + super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager); + mNotificationData = notificationData; + mUseHeadsUp = true; + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java index f1965a223dbc..56de32d3a401 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java @@ -33,6 +33,8 @@ import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.Consumer; @SmallTest @RunWith(AndroidJUnit4.class) @@ -40,6 +42,30 @@ public class LeakDetectorTest extends SysuiTestCase { private LeakDetector mLeakDetector; + // The references for which collection is observed are stored in fields. The allocation and + // of these references happens in separate methods (trackObjectWith/trackCollectionWith) + // from where they are set to null. The generated code might keep the allocated reference + // alive in a dex register when compiling in release mode. As R8 is used to compile this + // test the --dontoptimize flag is also required to ensure that these methods are not + // inlined, as that would defeat the purpose of having the mutation in methods. + private Object mObject; + private Collection<?> mCollection; + + private CollectionWaiter trackObjectWith(Consumer<Object> tracker) { + mObject = new Object(); + CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(mObject); + tracker.accept(mObject); + return collectionWaiter; + } + + private CollectionWaiter trackCollectionWith( + BiConsumer<? super Collection<?>, String> tracker) { + mCollection = new ArrayList<>(); + CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(mCollection); + tracker.accept(mCollection, "tag"); + return collectionWaiter; + } + @Before public void setup() { mLeakDetector = LeakDetector.create(); @@ -51,31 +77,22 @@ public class LeakDetectorTest extends SysuiTestCase { @Test public void trackInstance_doesNotLeakTrackedObject() { - Object object = new Object(); - CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object); - - mLeakDetector.trackInstance(object); - object = null; + CollectionWaiter collectionWaiter = trackObjectWith(mLeakDetector::trackInstance); + mObject = null; collectionWaiter.waitForCollection(); } @Test public void trackCollection_doesNotLeakTrackedObject() { - Collection<?> object = new ArrayList<>(); - CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object); - - mLeakDetector.trackCollection(object, "tag"); - object = null; + CollectionWaiter collectionWaiter = trackCollectionWith(mLeakDetector::trackCollection); + mCollection = null; collectionWaiter.waitForCollection(); } @Test public void trackGarbage_doesNotLeakTrackedObject() { - Object object = new Object(); - CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object); - - mLeakDetector.trackGarbage(object); - object = null; + CollectionWaiter collectionWaiter = trackObjectWith(mLeakDetector::trackGarbage); + mObject = null; collectionWaiter.waitForCollection(); } @@ -108,4 +125,4 @@ public class LeakDetectorTest extends SysuiTestCase { FileOutputStream fos = new FileOutputStream("/dev/null"); mLeakDetector.dump(fos.getFD(), new PrintWriter(fos), new String[0]); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java index 9787df91f5de..ce6212ef5aae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java @@ -41,6 +41,13 @@ public class WeakIdentityHashMapTest extends SysuiTestCase { mMap = new WeakIdentityHashMap<>(); } + private CollectionWaiter addObjectToMap(WeakIdentityHashMap<Object, Object> map) { + Object object = new Object(); + CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object); + map.put(object, "value"); + return collectionWaiter; + } + @Test public void testUsesIdentity() { String a1 = new String("a"); @@ -56,11 +63,12 @@ public class WeakIdentityHashMapTest extends SysuiTestCase { @Test public void testWeaklyReferences() { - Object object = new Object(); - CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object); - - mMap.put(object, "value"); - object = null; + // Allocate and add an object to the weak map in a separate method to avoid a live + // reference to the allocated object in a dex register. As R8 is used to compile this + // test the --dontoptimize flag is also required to ensure that the method is not + // inlined, as that would defeat the purpose of having the allocation in a separate + // method. + CollectionWaiter collectionWaiter = addObjectToMap(mMap); // Wait until object has been collected. We'll also need to wait for mMap to become empty, // because our collection waiter may be told about the collection earlier than mMap. @@ -70,4 +78,4 @@ public class WeakIdentityHashMapTest extends SysuiTestCase { assertEquals(0, mMap.size()); assertTrue(mMap.isEmpty()); } -}
\ No newline at end of file +} diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 935b787250fb..1aaa53837bf9 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5094,6 +5094,36 @@ message MetricsEvent { // Tag used to report autofill field classification scores FIELD_AUTOFILL_MATCH_SCORE = 1274; + // ACTION: Usb config has been changed to charging + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_CHARGING = 1275; + + // ACTION: Usb config has been changed to mtp (file transfer) + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_MTP = 1276; + + // ACTION: Usb config has been changed to ptp (photo transfer) + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_PTP = 1277; + + // ACTION: Usb config has been changed to rndis (usb tethering) + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_RNDIS = 1278; + + // ACTION: Usb config has been changed to midi + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_MIDI = 1279; + + // ACTION: Usb config has been changed to accessory + // CATEGORY: SETTINGS + // OS: P + ACTION_USB_CONFIG_ACCESSORY = 1280; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 3d7d6b7e5f3f..ed068b931bad 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -30,6 +30,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.graphics.Region; import android.os.Binder; @@ -740,6 +741,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean isFingerprintGestureDetectionAvailable() { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + return false; + } if (isCapturingFingerprintGestures()) { FingerprintGestureDispatcher dispatcher = mSystemSupport.getFingerprintGestureDispatcher(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d83f6ae0425e..ba8ce59803f7 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -17,6 +17,7 @@ package com.android.server.accessibility; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; +import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS; @@ -64,7 +65,9 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -299,6 +302,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return state; } + boolean getBindInstantServiceAllowed(int userId) { + return mSecurityPolicy.getBindInstantServiceAllowed(userId); + } + + void setBindInstantServiceAllowed(int userId, boolean allowed) { + mSecurityPolicy.setBindInstantServiceAllowed(userId, allowed); + } + private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { @Override @@ -752,7 +763,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mPictureInPictureActionReplacingConnection = wrapper; wrapper.linkToDeath(); } - mSecurityPolicy.notifyWindowsChanged(); } } @@ -1218,14 +1228,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private boolean readInstalledAccessibilityServiceLocked(UserState userState) { mTempAccessibilityServiceInfoList.clear(); + int flags = PackageManager.GET_SERVICES + | PackageManager.GET_META_DATA + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + + if (userState.mBindInstantServiceAllowed) { + flags |= PackageManager.MATCH_INSTANT; + } + List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( - new Intent(AccessibilityService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES - | PackageManager.GET_META_DATA - | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - mCurrentUserId); + new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId); for (int i = 0, count = installedServices.size(); i < count; i++) { ResolveInfo resolveInfo = installedServices.get(i); @@ -2269,6 +2283,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + private void sendAccessibilityEventLocked(AccessibilityEvent event, int userId) { + // Resync to avoid calling out with the lock held + event.setEventTime(SystemClock.uptimeMillis()); + mMainHandler.obtainMessage( + MainHandler.MSG_SEND_ACCESSIBILITY_EVENT, userId, 0 /* unused */, event) + .sendToTarget(); + } + /** * AIDL-exposed method. System only. * Inform accessibility that a fingerprint gesture was performed @@ -2405,6 +2427,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13; public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14; public static final int MSG_INIT_SERVICE = 15; + public static final int MSG_SEND_ACCESSIBILITY_EVENT = 16; public MainHandler(Looper looper) { super(looper); @@ -2505,6 +2528,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub (AccessibilityServiceConnection) msg.obj; service.initializeService(); } break; + + case MSG_SEND_ACCESSIBILITY_EVENT: { + final AccessibilityEvent event = (AccessibilityEvent) msg.obj; + final int userId = msg.arg1; + sendAccessibilityEvent(event, userId); + } } } @@ -2519,7 +2548,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_ANNOUNCEMENT); event.getText().add(message); - sendAccessibilityEvent(event, mCurrentUserId); + sendAccessibilityEventLocked(event, mCurrentUserId); } } } @@ -2709,6 +2738,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + new AccessibilityShellCommand(this).exec(this, in, out, err, args, + callback, resultReceiver); + } + final class WindowsForAccessibilityCallback implements WindowManagerInternal.WindowsForAccessibilityCallback { @@ -2939,21 +2976,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public class SecurityPolicy { public static final int INVALID_WINDOW_ID = -1; - private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = - AccessibilityEvent.TYPE_VIEW_CLICKED - | AccessibilityEvent.TYPE_VIEW_FOCUSED - | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER - | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT - | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED - | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED - | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED - | AccessibilityEvent.TYPE_VIEW_SELECTED - | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED - | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED - | AccessibilityEvent.TYPE_VIEW_SCROLLED - | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED - | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED - | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; + private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED + | AccessibilityEvent.TYPE_VIEW_FOCUSED + | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER + | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT + | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED + | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED + | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + | AccessibilityEvent.TYPE_WINDOWS_CHANGED + | AccessibilityEvent.TYPE_VIEW_SELECTED + | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED + | AccessibilityEvent.TYPE_VIEW_SCROLLED + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED + | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; // In Z order public List<AccessibilityWindowInfo> mWindows; @@ -3076,6 +3113,32 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return uidPackages; } + private boolean getBindInstantServiceAllowed(int userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_BIND_INSTANT_SERVICE, + "getBindInstantServiceAllowed"); + UserState state = mUserStates.get(userId); + return (state != null) && state.mBindInstantServiceAllowed; + } + + private void setBindInstantServiceAllowed(int userId, boolean allowed) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_BIND_INSTANT_SERVICE, + "setBindInstantServiceAllowed"); + UserState state = mUserStates.get(userId); + if (state == null) { + if (!allowed) { + return; + } + state = new UserState(userId); + mUserStates.put(userId, state); + } + if (state.mBindInstantServiceAllowed != allowed) { + state.mBindInstantServiceAllowed = allowed; + onUserStateChangedLocked(state); + } + } + public void clearWindowsLocked() { List<WindowInfo> windows = Collections.emptyList(); final int activeWindowId = mActiveWindowId; @@ -3089,10 +3152,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mWindows = new ArrayList<>(); } - final int oldWindowCount = mWindows.size(); - for (int i = oldWindowCount - 1; i >= 0; i--) { - mWindows.remove(i).recycle(); - } + List<AccessibilityWindowInfo> oldWindowList = new ArrayList<>(mWindows); + SparseArray<AccessibilityWindowInfo> oldWindowsById = mA11yWindowInfoById.clone(); + + mWindows.clear(); mA11yWindowInfoById.clear(); for (int i = 0; i < mWindowInfoById.size(); i++) { @@ -3154,7 +3217,49 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - notifyWindowsChanged(); + sendEventsForChangedWindowsLocked(oldWindowList, oldWindowsById); + + final int oldWindowCount = oldWindowList.size(); + for (int i = oldWindowCount - 1; i >= 0; i--) { + oldWindowList.remove(i).recycle(); + } + } + + private void sendEventsForChangedWindowsLocked(List<AccessibilityWindowInfo> oldWindows, + SparseArray<AccessibilityWindowInfo> oldWindowsById) { + List<AccessibilityEvent> events = new ArrayList<>(); + // Send events for all removed windows + final int oldWindowsCount = oldWindows.size(); + for (int i = 0; i < oldWindowsCount; i++) { + final AccessibilityWindowInfo window = oldWindows.get(i); + if (mA11yWindowInfoById.get(window.getId()) == null) { + events.add(AccessibilityEvent.obtainWindowsChangedEvent( + window.getId(), AccessibilityEvent.WINDOWS_CHANGE_REMOVED)); + } + } + + // Look for other changes + int oldWindowIndex = 0; + final int newWindowCount = mWindows.size(); + for (int i = 0; i < newWindowCount; i++) { + final AccessibilityWindowInfo newWindow = mWindows.get(i); + final AccessibilityWindowInfo oldWindow = oldWindowsById.get(newWindow.getId()); + if (oldWindow == null) { + events.add(AccessibilityEvent.obtainWindowsChangedEvent( + newWindow.getId(), AccessibilityEvent.WINDOWS_CHANGE_ADDED)); + } else { + int changes = newWindow.differenceFrom(oldWindow); + if (changes != 0) { + events.add(AccessibilityEvent.obtainWindowsChangedEvent( + newWindow.getId(), changes)); + } + } + } + + final int numEvents = events.size(); + for (int i = 0; i < numEvents; i++) { + sendAccessibilityEventLocked(events.get(i), mCurrentUserId); + } } public boolean computePartialInteractiveRegionForWindowLocked(int windowId, @@ -3195,7 +3300,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } public void updateEventSourceLocked(AccessibilityEvent event) { - if ((event.getEventType() & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) { + if ((event.getEventType() & KEEP_SOURCE_EVENT_TYPES) == 0) { event.setSource((View) null); } } @@ -3309,46 +3414,55 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void setActiveWindowLocked(int windowId) { if (mActiveWindowId != windowId) { + sendAccessibilityEventLocked( + AccessibilityEvent.obtainWindowsChangedEvent( + mActiveWindowId, AccessibilityEvent.WINDOWS_CHANGE_ACTIVE), + mCurrentUserId); + mActiveWindowId = windowId; if (mWindows != null) { final int windowCount = mWindows.size(); for (int i = 0; i < windowCount; i++) { AccessibilityWindowInfo window = mWindows.get(i); - window.setActive(window.getId() == windowId); + if (window.getId() == windowId) { + window.setActive(true); + sendAccessibilityEventLocked( + AccessibilityEvent.obtainWindowsChangedEvent(windowId, + AccessibilityEvent.WINDOWS_CHANGE_ACTIVE), + mCurrentUserId); + } else { + window.setActive(false); + } } } - notifyWindowsChanged(); } } private void setAccessibilityFocusedWindowLocked(int windowId) { if (mAccessibilityFocusedWindowId != windowId) { + sendAccessibilityEventLocked( + AccessibilityEvent.obtainWindowsChangedEvent( + mAccessibilityFocusedWindowId, + WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED), + mCurrentUserId); + mAccessibilityFocusedWindowId = windowId; if (mWindows != null) { final int windowCount = mWindows.size(); for (int i = 0; i < windowCount; i++) { AccessibilityWindowInfo window = mWindows.get(i); - window.setAccessibilityFocused(window.getId() == windowId); + if (window.getId() == windowId) { + window.setAccessibilityFocused(true); + sendAccessibilityEventLocked( + AccessibilityEvent.obtainWindowsChangedEvent( + windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED), + mCurrentUserId); + + } else { + window.setAccessibilityFocused(false); + } } } - - notifyWindowsChanged(); - } - } - - public void notifyWindowsChanged() { - if (mWindowsForAccessibilityCallback == null) { - return; - } - final long identity = Binder.clearCallingIdentity(); - try { - // Let the client know the windows changed. - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_WINDOWS_CHANGED); - event.setEventTime(SystemClock.uptimeMillis()); - sendAccessibilityEvent(event, mCurrentUserId); - } finally { - Binder.restoreCallingIdentity(identity); } } @@ -3558,6 +3672,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public boolean mIsFilterKeyEventsEnabled; public boolean mAccessibilityFocusOnlyInActiveWindow; + public boolean mBindInstantServiceAllowed; + public UserState(int userId) { mUserId = userId; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 5f6efb613be7..96b897926136 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -91,10 +91,12 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (userState == null) return; final long identity = Binder.clearCallingIdentity(); try { + int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE; + if (userState.mBindInstantServiceAllowed) { + flags |= Context.BIND_ALLOW_INSTANT; + } if (mService == null && mContext.bindServiceAsUser( - mIntent, this, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - new UserHandle(userState.mUserId))) { + mIntent, this, flags, new UserHandle(userState.mUserId))) { userState.getBindingServicesLocked().add(mComponentName); } } finally { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java new file mode 100644 index 000000000000..ff59c24a7ca2 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import android.annotation.NonNull; +import android.os.ShellCommand; +import android.os.UserHandle; + +import java.io.PrintWriter; + +/** + * Shell command implementation for the accessibility manager service + */ +final class AccessibilityShellCommand extends ShellCommand { + final @NonNull AccessibilityManagerService mService; + + AccessibilityShellCommand(@NonNull AccessibilityManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + switch (cmd) { + case "get-bind-instant-service-allowed": { + return runGetBindInstantServiceAllowed(); + } + case "set-bind-instant-service-allowed": { + return runSetBindInstantServiceAllowed(); + } + } + return -1; + } + + private int runGetBindInstantServiceAllowed() { + final Integer userId = parseUserId(); + if (userId == null) { + return -1; + } + getOutPrintWriter().println(Boolean.toString( + mService.getBindInstantServiceAllowed(userId))); + return 0; + } + + private int runSetBindInstantServiceAllowed() { + final Integer userId = parseUserId(); + if (userId == null) { + return -1; + } + final String allowed = getNextArgRequired(); + if (allowed == null) { + getErrPrintWriter().println("Error: no true/false specified"); + return -1; + } + mService.setBindInstantServiceAllowed(userId, + Boolean.parseBoolean(allowed)); + return 0; + } + + private Integer parseUserId() { + final String option = getNextOption(); + if (option != null) { + if (option.equals("--user")) { + return UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Unknown option: " + option); + return null; + } + } + return UserHandle.USER_SYSTEM; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Accessibility service (accessibility) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" set-bind-instant-service-allowed [--user <USER_ID>] true|false "); + pw.println(" Set whether binding to services provided by instant apps is allowed."); + pw.println(" get-bind-instant-service-allowed [--user <USER_ID>]"); + pw.println(" Get whether binding to services provided by instant apps is allowed."); + } +}
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java index abfdb683c04c..d5b53bc686da 100644 --- a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java +++ b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java @@ -40,12 +40,6 @@ final class GestureUtils { return (deltaTime >= timeout); } - public static boolean isSamePointerContext(MotionEvent first, MotionEvent second) { - return (first.getPointerIdBits() == second.getPointerIdBits() - && first.getPointerId(first.getActionIndex()) - == second.getPointerId(second.getActionIndex())); - } - /** * Determines whether a two pointer gesture is a dragging one. * diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 9b2b4eb7ebee..74d2dddcdfb3 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -229,7 +229,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } void clearAndTransitionToStateDetecting() { - mCurrentState = mDelegatingState; + mCurrentState = mDetectingState; mDetectingState.clear(); mViewportDraggingState.clear(); mPanningScalingState.clear(); @@ -649,14 +649,19 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { break; case ACTION_MOVE: { if (isFingerDown() - && distance(mLastDown, /* move */ event) > mSwipeMinDistance - // For convenience, viewport dragging on 3tap&hold takes precedence - // over insta-delegating on 3tap&swipe - // (which is a rare combo to be used aside from magnification) - && !isMultiTapTriggered(2 /* taps */)) { - - // Swipe detected - delegate skipping timeout - transitionToDelegatingStateAndClear(); + && distance(mLastDown, /* move */ event) > mSwipeMinDistance) { + + // Swipe detected - transition immediately + + // For convenience, viewport dragging takes precedence + // over insta-delegating on 3tap&swipe + // (which is a rare combo to be used aside from magnification) + if (isMultiTapTriggered(2 /* taps */)) { + transitionTo(mViewportDraggingState); + clear(); + } else { + transitionToDelegatingStateAndClear(); + } } } break; @@ -755,10 +760,10 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { int policyFlags) { if (event.getActionMasked() == ACTION_DOWN) { mPreLastDown = mLastDown; - mLastDown = event; + mLastDown = MotionEvent.obtain(event); } else if (event.getActionMasked() == ACTION_UP) { mPreLastUp = mLastUp; - mLastUp = event; + mLastUp = MotionEvent.obtain(event); } MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent, diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 581407b6c160..96296907b2c5 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -65,7 +65,6 @@ import android.service.autofill.SaveInfo; import android.service.autofill.SaveRequest; import android.service.autofill.UserData; import android.service.autofill.ValueFinder; -import android.service.autofill.EditDistanceScorer; import android.service.autofill.FieldClassification; import android.util.ArrayMap; import android.util.ArraySet; @@ -1581,7 +1580,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * <p>A new request will be started in 2 scenarios: * <ol> - * <li>If the user manually requested autofill after the view was already filled. + * <li>If the user manually requested autofill. * <li>If the view is part of a new partition. * </ol> * @@ -1589,14 +1588,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @param viewState The view that is entered. * @param flags The flag that was passed by the AutofillManager. */ - private void requestNewFillResponseIfNecessaryLocked(@NonNull AutofillId id, + private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id, @NonNull ViewState viewState, int flags) { - // First check if this is a manual request after view was autofilled. - final int state = viewState.getState(); - final boolean restart = (state & STATE_AUTOFILLED) != 0 - && (flags & FLAG_MANUAL_REQUEST) != 0; - if (restart) { - if (sDebug) Slog.d(TAG, "Re-starting session on view " + id); + if ((flags & FLAG_MANUAL_REQUEST) != 0) { + if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); viewState.setState(STATE_RESTARTED_SESSION); requestNewFillResponseLocked(flags); return; @@ -1751,7 +1746,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose && virtualBounds != null) { Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds); } - requestNewFillResponseIfNecessaryLocked(id, viewState, flags); + requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); // Remove the UI if the ViewState has changed. if (mCurrentViewId != viewState.id) { diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java index 245241cb1f4c..b17f79480036 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java +++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java @@ -24,6 +24,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Slog; @@ -51,6 +52,8 @@ class BackupManagerConstants extends ContentObserver { "full_backup_require_charging"; private static final String FULL_BACKUP_REQUIRED_NETWORK_TYPE = "full_backup_required_network_type"; + private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS = + "backup_finished_notification_receivers"; // Hard coded default values. private static final long DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS = @@ -63,6 +66,7 @@ class BackupManagerConstants extends ContentObserver { 24 * AlarmManager.INTERVAL_HOUR; private static final boolean DEFAULT_FULL_BACKUP_REQUIRE_CHARGING = true; private static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE = 2; + private static final String DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS = ""; // Backup manager constants. private long mKeyValueBackupIntervalMilliseconds; @@ -72,6 +76,7 @@ class BackupManagerConstants extends ContentObserver { private long mFullBackupIntervalMilliseconds; private boolean mFullBackupRequireCharging; private int mFullBackupRequiredNetworkType; + private String[] mBackupFinishedNotificationReceivers; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -116,6 +121,14 @@ class BackupManagerConstants extends ContentObserver { DEFAULT_FULL_BACKUP_REQUIRE_CHARGING); mFullBackupRequiredNetworkType = mParser.getInt(FULL_BACKUP_REQUIRED_NETWORK_TYPE, DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE); + String backupFinishedNotificationReceivers = mParser.getString( + BACKUP_FINISHED_NOTIFICATION_RECEIVERS, + DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS); + if (backupFinishedNotificationReceivers.isEmpty()) { + mBackupFinishedNotificationReceivers = new String[] {}; + } else { + mBackupFinishedNotificationReceivers = backupFinishedNotificationReceivers.split(":"); + } } // The following are access methods for the individual parameters. @@ -167,7 +180,6 @@ class BackupManagerConstants extends ContentObserver { Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging); } return mFullBackupRequireCharging; - } public synchronized int getFullBackupRequiredNetworkType() { @@ -177,4 +189,15 @@ class BackupManagerConstants extends ContentObserver { } return mFullBackupRequiredNetworkType; } + + /** + * Returns an array of package names that should be notified whenever a backup finishes. + */ + public synchronized String[] getBackupFinishedNotificationReceivers() { + if (RefactoredBackupManagerService.DEBUG_SCHEDULING) { + Slog.v(TAG, "getBackupFinishedNotificationReceivers(...) returns " + + TextUtils.join(", ", mBackupFinishedNotificationReceivers)); + } + return mBackupFinishedNotificationReceivers; + } } diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index 94b06b67ff87..51c44e103cc4 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -203,6 +203,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN"; public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT"; + public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; + public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; // Timeout interval for deciding that a bind or clear-data has taken too long private static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -1084,34 +1086,34 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } /** - * Maintain persistent state around whether need to do an initialize operation. - * Must be called with the queue lock held. + * Maintain persistent state around whether need to do an initialize operation. This will lock + * on {@link #getQueueLock()}. */ - @GuardedBy("mQueueLock") - public void recordInitPendingLocked( + public void recordInitPending( boolean isPending, String transportName, String transportDirName) { - if (MORE_DEBUG) { - Slog.i(TAG, "recordInitPendingLocked: " + isPending - + " on transport " + transportName); - } + synchronized (mQueueLock) { + if (MORE_DEBUG) { + Slog.i(TAG, "recordInitPending(" + isPending + ") on transport " + transportName); + } - File stateDir = new File(mBaseStateDir, transportDirName); - File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); + File stateDir = new File(mBaseStateDir, transportDirName); + File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); - if (isPending) { - // We need an init before we can proceed with sending backup data. - // Record that with an entry in our set of pending inits, as well as - // journaling it via creation of a sentinel file. - mPendingInits.add(transportName); - try { - (new FileOutputStream(initPendingFile)).close(); - } catch (IOException ioe) { - // Something is badly wrong with our permissions; just try to move on + if (isPending) { + // We need an init before we can proceed with sending backup data. + // Record that with an entry in our set of pending inits, as well as + // journaling it via creation of a sentinel file. + mPendingInits.add(transportName); + try { + (new FileOutputStream(initPendingFile)).close(); + } catch (IOException ioe) { + // Something is badly wrong with our permissions; just try to move on + } + } else { + // No more initialization needed; wipe the journal and reset our state. + initPendingFile.delete(); + mPendingInits.remove(transportName); } - } else { - // No more initialization needed; wipe the journal and reset our state. - initPendingFile.delete(); - mPendingInits.remove(transportName); } } @@ -1418,6 +1420,16 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public void logBackupComplete(String packageName) { if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return; + for (String receiver : mConstants.getBackupFinishedNotificationReceivers()) { + final Intent notification = new Intent(); + notification.setAction(BACKUP_FINISHED_ACTION); + notification.setPackage(receiver); + notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES | + Intent.FLAG_RECEIVER_FOREGROUND); + notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName); + mContext.sendBroadcastAsUser(notification, UserHandle.OWNER); + } + mProcessedPackagesJournal.addPackage(packageName); } @@ -2311,7 +2323,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter final long oldId = Binder.clearCallingIdentity(); try { mWakelock.acquire(); - mBackupHandler.post(new PerformInitializeTask(this, transportNames, observer)); + OnTaskFinishedListener listener = caller -> mWakelock.release(); + mBackupHandler.post( + new PerformInitializeTask(this, transportNames, observer, listener)); } finally { Binder.restoreCallingIdentity(oldId); } @@ -2802,7 +2816,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // build the set of transports for which we are posting an init for (int i = 0; i < transportNames.size(); i++) { - recordInitPendingLocked( + recordInitPending( true, transportNames.get(i), transportDirNames.get(i)); diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java index b21b0724acc2..c6246981ef44 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java @@ -18,6 +18,7 @@ package com.android.server.backup.internal; import static com.android.server.backup.RefactoredBackupManagerService.TAG; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.backup.BackupTransport; import android.app.backup.IBackupObserver; @@ -26,23 +27,63 @@ import android.os.SystemClock; import android.util.EventLog; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.TransportManager; +import com.android.server.backup.transport.TransportClient; import java.io.File; +import java.util.ArrayList; +import java.util.List; +/** + * Attempts to call {@link BackupTransport#initializeDevice()} followed by + * {@link BackupTransport#finishBackup()} for the transport names passed in with the intent of + * wiping backup data from the transport. + * + * If the transport returns error, it will record the operation as pending and schedule it to run in + * a future time according to {@link BackupTransport#requestBackupTime()}. The result status + * reported to observers will be the last unsuccessful status reported by the transports. If every + * operation was successful then it's {@link BackupTransport#TRANSPORT_OK}. + */ public class PerformInitializeTask implements Runnable { + private final RefactoredBackupManagerService mBackupManagerService; + private final TransportManager mTransportManager; + private final String[] mQueue; + private final File mBaseStateDir; + private final OnTaskFinishedListener mListener; + @Nullable private IBackupObserver mObserver; - private RefactoredBackupManagerService backupManagerService; - String[] mQueue; - IBackupObserver mObserver; + public PerformInitializeTask( + RefactoredBackupManagerService backupManagerService, + String[] transportNames, + @Nullable IBackupObserver observer, + OnTaskFinishedListener listener) { + this( + backupManagerService, + backupManagerService.getTransportManager(), + transportNames, + observer, + listener, + backupManagerService.getBaseStateDir()); + } - public PerformInitializeTask(RefactoredBackupManagerService backupManagerService, - String[] transportNames, IBackupObserver observer) { - this.backupManagerService = backupManagerService; + @VisibleForTesting + PerformInitializeTask( + RefactoredBackupManagerService backupManagerService, + TransportManager transportManager, + String[] transportNames, + @Nullable IBackupObserver observer, + OnTaskFinishedListener listener, + File baseStateDir) { + mBackupManagerService = backupManagerService; + mTransportManager = transportManager; mQueue = transportNames; mObserver = observer; + mListener = listener; + mBaseStateDir = baseStateDir; } private void notifyResult(String target, int status) { @@ -67,21 +108,25 @@ public class PerformInitializeTask implements Runnable { public void run() { // mWakelock is *acquired* when execution begins here + String callerLogString = "PerformInitializeTask.run()"; + List<TransportClient> transportClientsToDisposeOf = new ArrayList<>(mQueue.length); int result = BackupTransport.TRANSPORT_OK; try { for (String transportName : mQueue) { - IBackupTransport transport = - backupManagerService.getTransportManager().getTransportBinder( - transportName); - if (transport == null) { + TransportClient transportClient = + mTransportManager.getTransportClient(transportName, callerLogString); + if (transportClient == null) { Slog.e(TAG, "Requested init for " + transportName + " but not found"); continue; } + transportClientsToDisposeOf.add(transportClient); Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); - String transportDirName = transport.transportDirName(); + String transportDirName = transportClient.getTransportDirName(); EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName); long startRealtime = SystemClock.elapsedRealtime(); + + IBackupTransport transport = transportClient.connectOrThrow(callerLogString); int status = transport.initializeDevice(); if (status == BackupTransport.TRANSPORT_OK) { @@ -93,42 +138,38 @@ public class PerformInitializeTask implements Runnable { Slog.i(TAG, "Device init successful"); int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); - backupManagerService - .resetBackupState(new File(backupManagerService.getBaseStateDir(), - transportDirName)); + File stateFileDir = new File(mBaseStateDir, transportDirName); + mBackupManagerService.resetBackupState(stateFileDir); EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis); - synchronized (backupManagerService.getQueueLock()) { - backupManagerService.recordInitPendingLocked( - false, transportName, transportDirName); - } + mBackupManagerService.recordInitPending(false, transportName, transportDirName); notifyResult(transportName, BackupTransport.TRANSPORT_OK); } else { // If this didn't work, requeue this one and try again // after a suitable interval Slog.e(TAG, "Transport error in initializeDevice()"); EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)"); - synchronized (backupManagerService.getQueueLock()) { - backupManagerService.recordInitPendingLocked( - true, transportName, transportDirName); - } + mBackupManagerService.recordInitPending(true, transportName, transportDirName); notifyResult(transportName, status); result = status; // do this via another alarm to make sure of the wakelock states long delay = transport.requestBackupTime(); Slog.w(TAG, "Init failed on " + transportName + " resched in " + delay); - backupManagerService.getAlarmManager().set(AlarmManager.RTC_WAKEUP, + mBackupManagerService.getAlarmManager().set( + AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, - backupManagerService.getRunInitIntent()); + mBackupManagerService.getRunInitIntent()); } } } catch (Exception e) { Slog.e(TAG, "Unexpected error performing init", e); result = BackupTransport.TRANSPORT_ERROR; } finally { - // Done; release the wakelock + for (TransportClient transportClient : transportClientsToDisposeOf) { + mTransportManager.disposeOfTransportClient(transportClient, callerLogString); + } notifyFinished(result); - backupManagerService.getWakelock().release(); + mListener.onFinished(callerLogString); } } } diff --git a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java index 1df0bf0c6e08..6c160a332096 100644 --- a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java +++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java @@ -23,37 +23,41 @@ import static com.android.server.backup.RefactoredBackupManagerService.TAG; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.PowerManager; import android.util.ArraySet; import android.util.Slog; import com.android.server.backup.RefactoredBackupManagerService; public class RunInitializeReceiver extends BroadcastReceiver { - - private RefactoredBackupManagerService backupManagerService; + private final RefactoredBackupManagerService mBackupManagerService; public RunInitializeReceiver(RefactoredBackupManagerService backupManagerService) { - this.backupManagerService = backupManagerService; + mBackupManagerService = backupManagerService; } public void onReceive(Context context, Intent intent) { if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) { - synchronized (backupManagerService.getQueueLock()) { - final ArraySet<String> pendingInits = backupManagerService.getPendingInits(); + synchronized (mBackupManagerService.getQueueLock()) { + final ArraySet<String> pendingInits = mBackupManagerService.getPendingInits(); if (DEBUG) { Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending"); } if (pendingInits.size() > 0) { - final String[] transports = pendingInits.toArray(new String[pendingInits.size()]); - PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService, - transports, null); - - // Acquire the wakelock and pass it to the init thread. it will - // be released once init concludes. - backupManagerService.clearPendingInits(); - backupManagerService.getWakelock().acquire(); - backupManagerService.getBackupHandler().post(initTask); + final String[] transports = + pendingInits.toArray(new String[pendingInits.size()]); + + mBackupManagerService.clearPendingInits(); + + PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock(); + wakelock.acquire(); + OnTaskFinishedListener listener = caller -> wakelock.release(); + + Runnable task = + new PerformInitializeTask( + mBackupManagerService, transports, null, listener); + mBackupManagerService.getBackupHandler().post(task); } } } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 06cf9820ca5f..472723dfa909 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -3035,15 +3035,8 @@ class AlarmManagerService extends SystemService { Slog.v(TAG, "sending alarm " + alarm); } if (RECORD_ALARMS_IN_HISTORY) { - if (alarm.workSource != null && alarm.workSource.size() > 0) { - for (int wi=0; wi<alarm.workSource.size(); wi++) { - ActivityManager.noteAlarmStart( - alarm.operation, alarm.workSource.get(wi), alarm.statsTag); - } - } else { - ActivityManager.noteAlarmStart( - alarm.operation, alarm.uid, alarm.statsTag); - } + ActivityManager.noteAlarmStart(alarm.operation, alarm.workSource, alarm.uid, + alarm.statsTag); } mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle); } catch (RuntimeException e) { @@ -3553,15 +3546,8 @@ class AlarmManagerService extends SystemService { fs.aggregateTime += nowELAPSED - fs.startTime; } if (RECORD_ALARMS_IN_HISTORY) { - if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) { - for (int wi=0; wi<inflight.mWorkSource.size(); wi++) { - ActivityManager.noteAlarmFinish( - inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag); - } - } else { - ActivityManager.noteAlarmFinish( - inflight.mPendingIntent, inflight.mUid, inflight.mTag); - } + ActivityManager.noteAlarmFinish(inflight.mPendingIntent, inflight.mWorkSource, + inflight.mUid, inflight.mTag); } } @@ -3771,18 +3757,9 @@ class AlarmManagerService extends SystemService { || alarm.type == RTC_WAKEUP) { bs.numWakeup++; fs.numWakeup++; - if (alarm.workSource != null && alarm.workSource.size() > 0) { - for (int wi=0; wi<alarm.workSource.size(); wi++) { - final String wsName = alarm.workSource.getName(wi); - ActivityManager.noteWakeupAlarm( - alarm.operation, alarm.workSource.get(wi), - (wsName != null) ? wsName : alarm.packageName, - alarm.statsTag); - } - } else { - ActivityManager.noteWakeupAlarm( - alarm.operation, alarm.uid, alarm.packageName, alarm.statsTag); - } + ActivityManager.noteWakeupAlarm( + alarm.operation, alarm.workSource, alarm.uid, alarm.packageName, + alarm.statsTag); } } } diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java index 9877717943df..5e6e9d34dc25 100644 --- a/services/core/java/com/android/server/EntropyMixer.java +++ b/services/core/java/com/android/server/EntropyMixer.java @@ -196,11 +196,14 @@ public class EntropyMixer extends Binder { * Mixes in the output from HW RNG (if present) into the Linux RNG. */ private void addHwRandomEntropy() { + if (!new File(hwRandomDevice).exists()) { + // HW RNG not present/exposed -- ignore + return; + } + try { RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false); Slog.i(TAG, "Added HW RNG output to entropy pool"); - } catch (FileNotFoundException ignored) { - // HW RNG not present/exposed -- ignore } catch (IOException e) { Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e); } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 0a862812e027..57c992fdcf48 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -1144,7 +1144,7 @@ public class LocationManagerService extends ILocationManager.Stub { } /** - * Returns the system information of the GNSS hardware. + * Returns the year of the GNSS hardware. */ @Override public int getGnssYearOfHardware() { @@ -1155,6 +1155,19 @@ public class LocationManagerService extends ILocationManager.Stub { } } + + /** + * Returns the model name of the GNSS hardware. + */ + @Override + public String getGnssHardwareModelName() { + if (mGnssSystemInfoProvider != null) { + return mGnssSystemInfoProvider.getGnssHardwareModelName(); + } else { + return LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN; + } + } + /** * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly * (try to) access GNSS information at this layer. diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 31aea63875ea..46eea78c7f8b 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5595,24 +5595,25 @@ public class AccountManagerService long ident = Binder.clearCallingIdentity(); try { packages = mPackageManager.getPackagesForUid(callingUid); - } finally { - Binder.restoreCallingIdentity(ident); - } - if (packages != null) { - for (String name : packages) { - try { - PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */); - if (packageInfo != null - && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) - != 0) { - return true; + if (packages != null) { + for (String name : packages) { + try { + PackageInfo packageInfo = + mPackageManager.getPackageInfo(name, 0 /* flags */); + if (packageInfo != null + && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) + != 0) { + return true; + } + } catch (NameNotFoundException e) { + Log.w(TAG, String.format("Could not find package [%s]", name), e); } - } catch (NameNotFoundException e) { - Log.w(TAG, String.format("Could not find package [%s]", name), e); } + } else { + Log.w(TAG, "No known packages with uid " + callingUid); } - } else { - Log.w(TAG, "No known packages with uid " + callingUid); + } finally { + Binder.restoreCallingIdentity(ident); } return false; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 088ddeaab820..2f7d4c1ec634 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -348,7 +348,7 @@ public final class ActiveServices { ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPackage, - callingPid, callingUid, userId, true, callerFg, false); + callingPid, callingUid, userId, true, callerFg, false, false); if (res == null) { return null; } @@ -597,7 +597,7 @@ public final class ActiveServices { // If this service is active, make sure it is stopped. ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null, - Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false); + Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false); if (r != null) { if (r.record != null) { final long origId = Binder.clearCallingIdentity(); @@ -658,7 +658,7 @@ public final class ActiveServices { IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) { ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(), Binder.getCallingUid(), - UserHandle.getCallingUserId(), false, false, false); + UserHandle.getCallingUserId(), false, false, false, false); IBinder ret = null; if (r != null) { @@ -1282,12 +1282,19 @@ public final class ActiveServices { + ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service); } + if ((flags & Context.BIND_ALLOW_INSTANT) != 0 && !isCallerSystem) { + throw new SecurityException( + "Non-system caller " + caller + " (pid=" + Binder.getCallingPid() + + ") set BIND_ALLOW_INSTANT when binding service " + service); + } + final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND; final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0; + final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(), - Binder.getCallingUid(), userId, true, callerFg, isBindExternal); + Binder.getCallingUid(), userId, true, callerFg, isBindExternal, allowInstant); if (res == null) { return 0; } @@ -1657,7 +1664,8 @@ public final class ActiveServices { private ServiceLookupResult retrieveServiceLocked(Intent service, String resolvedType, String callingPackage, int callingPid, int callingUid, int userId, - boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal) { + boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, + boolean allowInstant) { ServiceRecord r = null; if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service + " type=" + resolvedType + " callingUid=" + callingUid); @@ -1685,11 +1693,14 @@ public final class ActiveServices { } if (r == null) { try { + int flags = ActivityManagerService.STOCK_PM_FLAGS + | PackageManager.MATCH_DEBUG_TRIAGED_MISSING; + if (allowInstant) { + flags |= PackageManager.MATCH_INSTANT; + } // TODO: come back and remove this assumption to triage all services ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service, - resolvedType, ActivityManagerService.STOCK_PM_FLAGS - | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - userId, callingUid); + resolvedType, flags, userId, callingUid); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; if (sInfo == null) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7dfde56c07ee..5936ce1f65de 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -10364,26 +10364,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void cancelTaskThumbnailTransition(int taskId) { - enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, - "cancelTaskThumbnailTransition()"); - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (this) { - final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, - MATCH_TASK_IN_STACKS_ONLY); - if (task == null) { - Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found"); - return; - } - task.cancelThumbnailTransition(); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @Override public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) { enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()"); final long ident = Binder.clearCallingIdentity(); @@ -13878,68 +13858,100 @@ public class ActivityManagerService extends IActivityManager.Stub Context.WINDOW_SERVICE)).addView(v, lp); } - public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) { - if (sender != null && !(sender instanceof PendingIntentRecord)) { - return; + @Override + public void noteWakeupAlarm(IIntentSender sender, WorkSource workSource, int sourceUid, + String sourcePkg, String tag) { + if (workSource != null && workSource.isEmpty()) { + workSource = null; } - final PendingIntentRecord rec = (PendingIntentRecord)sender; - final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - if (mBatteryStatsService.isOnBattery()) { - mBatteryStatsService.enforceCallingPermission(); - int MY_UID = Binder.getCallingUid(); - final int uid; - if (sender == null) { - uid = sourceUid; - } else { - uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid; + + if (sourceUid <= 0 && workSource == null) { + // Try and derive a UID to attribute things to based on the caller. + if (sender != null) { + if (!(sender instanceof PendingIntentRecord)) { + return; } - BatteryStatsImpl.Uid.Pkg pkg = - stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid, - sourcePkg != null ? sourcePkg : rec.key.packageName); - pkg.noteWakeupAlarmLocked(tag); - StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid, - tag); + + final PendingIntentRecord rec = (PendingIntentRecord) sender; + final int callerUid = Binder.getCallingUid(); + sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid; + } else { + // TODO(narayan): Should we throw an exception in this case ? It means that we + // haven't been able to derive a UID to attribute things to. + return; } } + + if (DEBUG_POWER) { + Slog.w(TAG, "noteWakupAlarm[ sourcePkg=" + sourcePkg + ", sourceUid=" + sourceUid + + ", workSource=" + workSource + ", tag=" + tag + "]"); + } + + mBatteryStatsService.noteWakupAlarm(sourcePkg, sourceUid, workSource, tag); } - public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) { - if (sender != null && !(sender instanceof PendingIntentRecord)) { - return; + @Override + public void noteAlarmStart(IIntentSender sender, WorkSource workSource, int sourceUid, + String tag) { + if (workSource != null && workSource.isEmpty()) { + workSource = null; } - final PendingIntentRecord rec = (PendingIntentRecord)sender; - final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - mBatteryStatsService.enforceCallingPermission(); - int MY_UID = Binder.getCallingUid(); - final int uid; - if (sender == null) { - uid = sourceUid; + + if (sourceUid <= 0 && workSource == null) { + // Try and derive a UID to attribute things to based on the caller. + if (sender != null) { + if (!(sender instanceof PendingIntentRecord)) { + return; + } + + final PendingIntentRecord rec = (PendingIntentRecord) sender; + final int callerUid = Binder.getCallingUid(); + sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid; } else { - uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid; + // TODO(narayan): Should we throw an exception in this case ? It means that we + // haven't been able to derive a UID to attribute things to. + return; } - mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid); } + + if (DEBUG_POWER) { + Slog.w(TAG, "noteAlarmStart[sourceUid=" + sourceUid + ", workSource=" + workSource + + ", tag=" + tag + "]"); + } + + mBatteryStatsService.noteAlarmStart(tag, workSource, sourceUid); } - public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) { - if (sender != null && !(sender instanceof PendingIntentRecord)) { - return; + @Override + public void noteAlarmFinish(IIntentSender sender, WorkSource workSource, int sourceUid, + String tag) { + if (workSource != null && workSource.isEmpty()) { + workSource = null; } - final PendingIntentRecord rec = (PendingIntentRecord)sender; - final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - mBatteryStatsService.enforceCallingPermission(); - int MY_UID = Binder.getCallingUid(); - final int uid; - if (sender == null) { - uid = sourceUid; + + if (sourceUid <= 0 && workSource == null) { + // Try and derive a UID to attribute things to based on the caller. + if (sender != null) { + if (!(sender instanceof PendingIntentRecord)) { + return; + } + + final PendingIntentRecord rec = (PendingIntentRecord) sender; + final int callerUid = Binder.getCallingUid(); + sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid; } else { - uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid; + // TODO(narayan): Should we throw an exception in this case ? It means that we + // haven't been able to derive a UID to attribute things to. + return; } - mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid); } + + if (DEBUG_POWER) { + Slog.w(TAG, "noteAlarmFinish[sourceUid=" + sourceUid + ", workSource=" + workSource + + ", tag=" + tag + "]"); + } + + mBatteryStatsService.noteAlarmFinish(tag, workSource, sourceUid); } public boolean killPids(int[] pids, String pReason, boolean secure) { @@ -19900,16 +19912,14 @@ public class ActivityManagerService extends IActivityManager.Stub userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL, "broadcast", callerPackage); - // Make sure that the user who is receiving this broadcast is running. - // If not, we will just skip it. Make an exception for shutdown broadcasts - // and upgrade steps. - - if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) { + // Make sure that the user who is receiving this broadcast or its parent is running. + // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps. + if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) { if ((callingUid != SYSTEM_UID || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) { Slog.w(TAG, "Skipping broadcast of " + intent - + ": user " + userId + " is stopped"); + + ": user " + userId + " and its parent (if any) are stopped"); return ActivityManager.BROADCAST_FAILED_USER_STOPPED; } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 69f6d5eef275..8eb519797641 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -23,6 +23,7 @@ import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; import static android.app.ActivityOptions.ANIM_CUSTOM; import static android.app.ActivityOptions.ANIM_SCALE_UP; import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; +import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN; import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; @@ -1476,6 +1477,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } break; + case ANIM_OPEN_CROSS_PROFILE_APPS: + service.mWindowManager.overridePendingAppTransitionStartCrossProfileApps(); + break; default: Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType); break; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 0a42aa9cce63..21085fa2f717 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3116,6 +3116,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Need to make sure the pinned stack exist so we can resize it below... stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP); + // Calculate the target bounds here before the task is reparented back into pinned windowing + // mode (which will reset the saved bounds) + final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio); + try { final TaskRecord task = r.getTask(); // Resize the pinned stack to match the current size of the task the activity we are @@ -3154,11 +3158,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mWindowManager.continueSurfaceLayout(); } - // Calculate the default bounds (don't use existing stack bounds as we may have just created - // the stack, and schedule the start of the animation into PiP (the bounds animator that - // is triggered by this is posted on another thread) - final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio); - stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */, true /* fromFullscreen */); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index b920b572b4b5..430320a58e9c 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -38,6 +38,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManagerInternal; import android.os.WorkSource; +import android.os.WorkSource.WorkChain; import android.os.connectivity.CellularBatteryStats; import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; @@ -66,6 +67,7 @@ import java.nio.CharBuffer; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; @@ -446,17 +448,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - public void noteAlarmStart(String name, int uid) { + public void noteWakupAlarm(String name, int uid, WorkSource workSource, String tag) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteAlarmStartLocked(name, uid); + mStats.noteWakupAlarmLocked(name, uid, workSource, tag); } } - public void noteAlarmFinish(String name, int uid) { + public void noteAlarmStart(String name, WorkSource workSource, int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteAlarmFinishLocked(name, uid); + mStats.noteAlarmStartLocked(name, workSource, uid); + } + } + + public void noteAlarmFinish(String name, WorkSource workSource, int uid) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteAlarmFinishLocked(name, workSource, uid); } } @@ -464,15 +473,16 @@ public final class BatteryStatsService extends IBatteryStats.Stub boolean unimportantForLogging) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteStartWakeLocked(uid, pid, name, historyName, type, unimportantForLogging, - SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); + mStats.noteStartWakeLocked(uid, pid, null, name, historyName, type, + unimportantForLogging, SystemClock.elapsedRealtime(), + SystemClock.uptimeMillis()); } } public void noteStopWakelock(int uid, int pid, String name, String historyName, int type) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteStopWakeLocked(uid, pid, name, historyName, type, + mStats.noteStopWakeLocked(uid, pid, null, name, historyName, type, SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 91b3315e4c0d..4aef95d23ce9 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -772,10 +772,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mWindowContainerController.cancelWindowTransition(); } - void cancelThumbnailTransition() { - mWindowContainerController.cancelThumbnailTransition(); - } - /** * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD! */ diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 14260c5fb105..34621e0331e4 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1737,6 +1737,19 @@ class UserController implements Handler.Callback { } } + boolean isUserOrItsParentRunning(int userId) { + synchronized (mLock) { + if (isUserRunning(userId, 0)) { + return true; + } + final int parentUserId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID); + if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) { + return false; + } + return isUserRunning(parentUserId, 0); + } + } + boolean isCurrentProfile(int userId) { synchronized (mLock) { return ArrayUtils.contains(mCurrentProfileIds, userId); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6e7b43ef00f3..799f2a92bc33 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2251,12 +2251,15 @@ public class AudioService extends IAudioService.Stub if (DEBUG_VOL) { Log.d(TAG, String.format("Mic mute %s, user=%d", on, userId)); } - // If mute is for current user actually mute, else just persist the setting - // which will be loaded on user switch. + // only mute for the current user if (getCurrentUserId() == userId) { + final boolean currentMute = AudioSystem.isMicrophoneMuted(); AudioSystem.muteMicrophone(on); + if (on != currentMute) { + mContext.sendBroadcast(new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); + } } - // Post a persist microphone msg. } @Override diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java index 3064144072ae..ef5166579cb3 100644 --- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java @@ -25,6 +25,7 @@ import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.os.ParcelableException; +import android.util.Slog; import com.android.server.SystemService; @@ -33,6 +34,8 @@ import java.util.Objects; import java.util.OptionalInt; public class BroadcastRadioService extends SystemService { + private static final String TAG = "BcRadioSrv"; + private final ServiceImpl mServiceImpl = new ServiceImpl(); private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1 = @@ -84,13 +87,14 @@ public class BroadcastRadioService extends SystemService { @Override public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, boolean withAudio, ITunerCallback callback) { + Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)"); enforcePolicyAccess(); if (callback == null) { throw new IllegalArgumentException("Callback must not be empty"); } synchronized (mLock) { if (mHal2.hasModule(moduleId)) { - throw new RuntimeException("Not implemented"); + return mHal2.openSession(moduleId, callback); } else { return mHal1.openTuner(moduleId, bandConfig, withAudio, callback); } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 7629477438e4..413a27ce9af0 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -17,6 +17,8 @@ package com.android.server.broadcastradio.hal2; import android.annotation.NonNull; +import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.hardware.broadcastradio.V2_0.IBroadcastRadio; import android.hidl.manager.V1_0.IServiceManager; @@ -28,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; public class BroadcastRadioService { @@ -76,4 +79,15 @@ public class BroadcastRadioService { public boolean hasModule(int id) { return mModules.containsKey(id); } + + public ITuner openSession(int moduleId, @NonNull ITunerCallback callback) { + Objects.requireNonNull(callback); + + RadioModule module = mModules.get(moduleId); + if (module == null) { + throw new IllegalArgumentException("Invalid module ID"); + } + + return module.openSession(callback); + } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java index c3394e9e0df5..434e2620a324 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java @@ -18,12 +18,17 @@ package com.android.server.broadcastradio.hal2; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.broadcastradio.V2_0.AmFmBandRange; +import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; import android.hardware.broadcastradio.V2_0.Properties; +import android.hardware.broadcastradio.V2_0.Result; import android.hardware.broadcastradio.V2_0.VendorKeyValue; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; +import android.os.ParcelableException; import android.util.Slog; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -35,6 +40,28 @@ import java.util.Set; class Convert { private static final String TAG = "BcRadio2Srv.convert"; + static void throwOnError(String action, int result) { + switch (result) { + case Result.OK: + return; + case Result.UNKNOWN_ERROR: + throw new ParcelableException(new RuntimeException(action + ": UNKNOWN_ERROR")); + case Result.INTERNAL_ERROR: + throw new ParcelableException(new RuntimeException(action + ": INTERNAL_ERROR")); + case Result.INVALID_ARGUMENTS: + throw new IllegalArgumentException(action + ": INVALID_ARGUMENTS"); + case Result.INVALID_STATE: + throw new IllegalStateException(action + ": INVALID_STATE"); + case Result.NOT_SUPPORTED: + throw new UnsupportedOperationException(action + ": NOT_SUPPORTED"); + case Result.TIMEOUT: + throw new ParcelableException(new RuntimeException(action + ": TIMEOUT")); + default: + throw new ParcelableException(new RuntimeException( + action + ": unknown error (" + result + ")")); + } + } + private static @NonNull Map<String, String> vendorInfoFromHal(@Nullable List<VendorKeyValue> info) { if (info == null) return Collections.emptyMap(); @@ -94,13 +121,48 @@ class Convert { return pTypes.stream().mapToInt(Integer::intValue).toArray(); } + private static @NonNull RadioManager.BandDescriptor[] + amfmConfigToBands(@Nullable AmFmRegionConfig config) { + if (config == null) return new RadioManager.BandDescriptor[0]; + + int len = config.ranges.size(); + List<RadioManager.BandDescriptor> bands = new ArrayList<>(len); + + // Just a dummy value. + int region = RadioManager.REGION_ITU_1; + + for (AmFmBandRange range : config.ranges) { + FrequencyBand bandType = Utils.getBand(range.lowerBound); + if (bandType == FrequencyBand.UNKNOWN) { + Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz"); + continue; + } + if (bandType == FrequencyBand.FM) { + bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM, + range.lowerBound, range.upperBound, range.spacing, + + // TODO(b/69958777): stereo, rds, ta, af, ea + true, true, true, true, true + )); + } else { // AM + bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM, + range.lowerBound, range.upperBound, range.spacing, + + // TODO(b/69958777): stereo + true + )); + } + } + + return bands.toArray(new RadioManager.BandDescriptor[bands.size()]); + } + static @NonNull RadioManager.ModuleProperties - propertiesFromHal(int id, @NonNull String serviceName, Properties prop) { + propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop, + @Nullable AmFmRegionConfig amfmConfig) { + Objects.requireNonNull(serviceName); Objects.requireNonNull(prop); - // TODO(b/69958423): implement region info - RadioManager.BandDescriptor[] bands = new RadioManager.BandDescriptor[0]; - int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream(). mapToInt(Integer::intValue).toArray(); int[] supportedProgramTypes = identifierTypesToProgramTypes(supportedIdentifierTypes); @@ -123,7 +185,7 @@ class Convert { 1, // numAudioSources false, // isCaptureSupported - bands, + amfmConfigToBands(amfmConfig), true, // isBgScanSupported is deprecated supportedProgramTypes, supportedIdentifierTypes, diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java new file mode 100644 index 000000000000..a9d80549f963 --- /dev/null +++ b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio.hal2; + +/** + * A wrapper class for mutable objects to be used in non-mutable contexts + * (i.e. final variables catched in lambda closures). + * + * @param <E> type of boxed value. + */ +final class Mutable<E> { + /** + * A mutable value. + */ + public E value; + + /** + * Initialize value with null pointer. + */ + public Mutable() { + value = null; + } + + /** + * Initialize value with specific value. + * + * @param value initial value. + */ + public Mutable(E value) { + this.value = value; + } +} diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 34c1b0ce7d93..8a7ac7355b7e 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -18,9 +18,15 @@ package com.android.server.broadcastradio.hal2; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.radio.ITuner; import android.hardware.radio.RadioManager; +import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; import android.hardware.broadcastradio.V2_0.IBroadcastRadio; +import android.hardware.broadcastradio.V2_0.ITunerSession; +import android.hardware.broadcastradio.V2_0.Result; +import android.os.ParcelableException; import android.os.RemoteException; +import android.util.MutableInt; import android.util.Slog; import java.util.Objects; @@ -42,8 +48,13 @@ class RadioModule { IBroadcastRadio service = IBroadcastRadio.getService(); if (service == null) return null; + Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>(); + service.getAmFmRegionConfig(false, (int result, AmFmRegionConfig config) -> { + if (result == Result.OK) amfmConfig.value = config; + }); + RadioManager.ModuleProperties prop = - Convert.propertiesFromHal(idx, fqName, service.getProperties()); + Convert.propertiesFromHal(idx, fqName, service.getProperties(), amfmConfig.value); return new RadioModule(service, prop); } catch (RemoteException ex) { @@ -51,4 +62,44 @@ class RadioModule { return null; } } + + public @NonNull ITuner openSession(@NonNull android.hardware.radio.ITunerCallback userCb) { + TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb)); + Mutable<ITunerSession> hwSession = new Mutable<>(); + MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); + + try { + mService.openSession(cb, (int result, ITunerSession session) -> { + hwSession.value = session; + halResult.value = result; + }); + } catch (RemoteException ex) { + Slog.e(TAG, "failed to open session", ex); + throw new ParcelableException(ex); + } + + Convert.throwOnError("openSession", halResult.value); + Objects.requireNonNull(hwSession.value); + + TunerSession session = new TunerSession(hwSession.value, cb); + + // send out legacy callback about band configuration + RadioManager.BandDescriptor[] bands = mProperties.getBands(); + if (bands != null && bands.length > 0) { + RadioManager.BandDescriptor descr = bands[0]; // just pick first + Mutable<RadioManager.BandConfig> config = new Mutable<>(); + if (descr instanceof RadioManager.FmBandDescriptor) { + config.value = new RadioManager.FmBandConfig((RadioManager.FmBandDescriptor)descr); + } else if (descr instanceof RadioManager.AmBandDescriptor) { + config.value = new RadioManager.AmBandConfig((RadioManager.AmBandDescriptor)descr); + } else { + Slog.w(TAG, "Descriptor is neither AM nor FM"); + } + if (config.value != null) { + TunerCallback.dispatch(() -> userCb.onConfigurationChanged(config.value)); + } + } + + return session; + } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java new file mode 100644 index 000000000000..5ee6a4c693cd --- /dev/null +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio.hal2; + +import android.annotation.NonNull; +import android.hardware.broadcastradio.V2_0.ITunerCallback; +import android.hardware.broadcastradio.V2_0.ProgramInfo; +import android.hardware.broadcastradio.V2_0.ProgramListChunk; +import android.hardware.broadcastradio.V2_0.ProgramSelector; +import android.hardware.broadcastradio.V2_0.VendorKeyValue; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Objects; + +class TunerCallback extends ITunerCallback.Stub { + private static final String TAG = "BcRadio2Srv.cb"; + + final android.hardware.radio.ITunerCallback mCb; + + interface RunnableThrowingRemoteException { + void run() throws RemoteException; + } + + TunerCallback(@NonNull android.hardware.radio.ITunerCallback clientCallback) { + mCb = Objects.requireNonNull(clientCallback); + } + + static void dispatch(RunnableThrowingRemoteException func) { + try { + func.run(); + } catch (RemoteException ex) { + Slog.e(TAG, "callback call failed", ex); + } + } + + @Override + public void onTuneFailed(int result, ProgramSelector selector) {} + + @Override + public void onCurrentProgramInfoChanged(ProgramInfo info) {} + + @Override + public void onProgramListUpdated(ProgramListChunk chunk) {} + + @Override + public void onAntennaStateChange(boolean connected) {} + + @Override + public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {} +} diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java new file mode 100644 index 000000000000..e8faf3dfa63f --- /dev/null +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio.hal2; + +import android.annotation.NonNull; +import android.graphics.Bitmap; +import android.hardware.broadcastradio.V2_0.ITunerSession; +import android.hardware.radio.ITuner; +import android.hardware.radio.ProgramSelector; +import android.hardware.radio.RadioManager; +import android.util.Slog; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +class TunerSession extends ITuner.Stub { + private static final String TAG = "BcRadio2Srv.session"; + + private final Object mLock = new Object(); + + private final ITunerSession mHwSession; + private final TunerCallback mCallback; + private boolean mIsClosed = false; + + TunerSession(@NonNull ITunerSession hwSession, @NonNull TunerCallback callback) { + mHwSession = Objects.requireNonNull(hwSession); + mCallback = Objects.requireNonNull(callback); + } + + @Override + public void close() { + synchronized (mLock) { + if (mIsClosed) return; + mIsClosed = true; + } + } + + @Override + public boolean isClosed() { + return mIsClosed; + } + + private void checkNotClosedLocked() { + if (mIsClosed) { + throw new IllegalStateException("Tuner is closed, no further operations are allowed"); + } + } + + @Override + public void setConfiguration(RadioManager.BandConfig config) {} + + @Override + public RadioManager.BandConfig getConfiguration() { + return null; + } + + @Override + public void setMuted(boolean mute) {} + + @Override + public boolean isMuted() { + return false; + } + + @Override + public void step(boolean directionDown, boolean skipSubChannel) {} + + @Override + public void scan(boolean directionDown, boolean skipSubChannel) {} + + @Override + public void tune(ProgramSelector selector) {} + + @Override + public void cancel() {} + + @Override + public void cancelAnnouncement() {} + + @Override + public RadioManager.ProgramInfo getProgramInformation() { + return null; + } + + @Override + public Bitmap getImage(int id) { + return null; + } + + @Override + public boolean startBackgroundScan() { + return false; + } + + @Override + public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { + return null; + } + + @Override + public boolean isAnalogForced() { + return false; + } + + @Override + public void setAnalogForced(boolean isForced) {} + + @Override + public Map setParameters(Map parameters) { + return null; + } + + @Override + public Map getParameters(List<String> keys) { + return null; + } + + @Override + public boolean isAntennaConnected() { + return true; + } +} diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java new file mode 100644 index 000000000000..3520f37880c5 --- /dev/null +++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio.hal2; + +enum FrequencyBand { + UNKNOWN, + FM, + AM_LW, + AM_MW, + AM_SW, +}; + +class Utils { + private static final String TAG = "BcRadio2Srv.utils"; + + static FrequencyBand getBand(int freq) { + // keep in sync with hardware/interfaces/broadcastradio/common/utils2x/Utils.cpp + if (freq < 30) return FrequencyBand.UNKNOWN; + if (freq < 500) return FrequencyBand.AM_LW; + if (freq < 1705) return FrequencyBand.AM_MW; + if (freq < 30000) return FrequencyBand.AM_SW; + if (freq < 60000) return FrequencyBand.UNKNOWN; + if (freq < 110000) return FrequencyBand.FM; + return FrequencyBand.UNKNOWN; + } +} diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 7715727f6d73..c7a43153c0aa 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -305,6 +305,7 @@ public class Vpn { } else { for (Network underlying : underlyingNetworks) { final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying); + if (underlyingCaps == null) continue; for (int underlyingType : underlyingCaps.getTransportTypes()) { transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType); } diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 3b9d40fa0825..0b62907a0d0c 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -17,6 +17,8 @@ package com.android.server.display; import android.annotation.Nullable; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.hardware.display.BrightnessConfiguration; import android.os.PowerManager; import android.util.MathUtils; @@ -41,11 +43,30 @@ public abstract class BrightnessMappingStrategy { private static final boolean DEBUG = false; @Nullable - public static BrightnessMappingStrategy create( - float[] luxLevels, int[] brightnessLevelsBacklight, float[] brightnessLevelsNits, - float[] nitsRange, int[] backlightRange) { + public static BrightnessMappingStrategy create(Resources resources) { + float[] luxLevels = getLuxLevels(resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevels)); + int[] brightnessLevelsBacklight = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); + float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( + com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)); + + float[] nitsRange = getFloatArray(resources.obtainTypedArray( + com.android.internal.R.array.config_screenBrightnessNits)); + int[] backlightRange = resources.getIntArray( + com.android.internal.R.array.config_screenBrightnessBacklight); + if (isValidMapping(nitsRange, backlightRange) && isValidMapping(luxLevels, brightnessLevelsNits)) { + int minimumBacklight = resources.getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingMinimum); + int maximumBacklight = resources.getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingMaximum); + if (backlightRange[0] > minimumBacklight + || backlightRange[backlightRange.length - 1] < maximumBacklight) { + Slog.w(TAG, "Screen brightness mapping does not cover whole range of available" + + " backlight values, autobrightness functionality may be impaired."); + } BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(); builder.setCurve(luxLevels, brightnessLevelsNits); return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange); @@ -56,6 +77,25 @@ public abstract class BrightnessMappingStrategy { } } + private static float[] getLuxLevels(int[] lux) { + // The first control point is implicit and always at 0 lux. + float[] levels = new float[lux.length + 1]; + for (int i = 0; i < lux.length; i++) { + levels[i + 1] = (float) lux[i]; + } + return levels; + } + + private static float[] getFloatArray(TypedArray array) { + final int N = array.length(); + float[] vals = new float[N]; + for (int i = 0; i < N; i++) { + vals[i] = array.getFloat(i, -1.0f); + } + array.recycle(); + return vals; + } + private static boolean isValidMapping(float[] x, float[] y) { if (x == null || y == null || x.length == 0 || y.length == 0) { return false; @@ -124,10 +164,17 @@ public abstract class BrightnessMappingStrategy { * brightness and 0 is the display at minimum brightness. * * @param lux The current ambient brightness in lux. - * @return The desired brightness of the display compressed to the range [0, 1.0]. + * @return The desired brightness of the display normalized to the range [0, 1.0]. */ public abstract float getBrightness(float lux); + /** + * Gets the display's brightness in nits for the given backlight value. + * + * Returns -1.0f if there's no available mapping for the backlight to nits. + */ + public abstract float getNits(int backlight); + public abstract void dump(PrintWriter pw); private static float normalizeAbsoluteBrightness(int brightness) { @@ -186,6 +233,11 @@ public abstract class BrightnessMappingStrategy { } @Override + public float getNits(int backlight) { + return -1.0f; + } + + @Override public void dump(PrintWriter pw) { pw.println("SimpleMappingStrategy"); pw.println(" mSpline=" + mSpline); @@ -209,7 +261,11 @@ public abstract class BrightnessMappingStrategy { // A spline mapping from nits to the corresponding backlight value, normalized to the range // [0, 1.0]. - private final Spline mBacklightSpline; + private final Spline mNitsToBacklightSpline; + + // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to + // a brightness in nits. + private final Spline mBacklightToNitsSpline; // The default brightness configuration. private final BrightnessConfiguration mDefaultConfig; @@ -227,19 +283,18 @@ public abstract class BrightnessMappingStrategy { // Setup the backlight spline final int N = nits.length; - float[] x = new float[N]; - float[] y = new float[N]; + float[] normalizedBacklight = new float[N]; for (int i = 0; i < N; i++) { - x[i] = nits[i]; - y[i] = normalizeAbsoluteBrightness(backlight[i]); + normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]); } - mBacklightSpline = Spline.createSpline(x, y); + mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight); + mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); if (DEBUG) { - Slog.d(TAG, "Backlight spline: " + mBacklightSpline); + Slog.d(TAG, "Backlight spline: " + mNitsToBacklightSpline); for (float v = 1f; v < nits[nits.length - 1] * 1.25f; v *= 1.25f) { Slog.d(TAG, String.format( - " %7.1f: %7.1f", v, mBacklightSpline.interpolate(v))); + " %7.1f: %7.1f", v, mNitsToBacklightSpline.interpolate(v))); } } @@ -275,7 +330,12 @@ public abstract class BrightnessMappingStrategy { @Override public float getBrightness(float lux) { - return mBacklightSpline.interpolate(mBrightnessSpline.interpolate(lux)); + return mNitsToBacklightSpline.interpolate(mBrightnessSpline.interpolate(lux)); + } + + @Override + public float getNits(int backlight) { + return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight)); } @Override @@ -283,7 +343,7 @@ public abstract class BrightnessMappingStrategy { pw.println("PhysicalMappingStrategy"); pw.println(" mConfig=" + mConfig); pw.println(" mBrightnessSpline=" + mBrightnessSpline); - pw.println(" mBacklightSpline=" + mBacklightSpline); + pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline); } } } diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 42247f94e69f..cbb1c0139bca 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ParceledListSlice; -import android.database.ContentObserver; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -34,6 +33,8 @@ import android.net.Uri; import android.os.BatteryManager; import android.os.Environment; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; @@ -88,7 +89,7 @@ public class BrightnessTracker { private static final String TAG_EVENTS = "events"; private static final String TAG_EVENT = "event"; - private static final String ATTR_BRIGHTNESS = "brightness"; + private static final String ATTR_NITS = "nits"; private static final String ATTR_TIMESTAMP = "timestamp"; private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_USER = "user"; @@ -97,7 +98,10 @@ public class BrightnessTracker { private static final String ATTR_BATTERY_LEVEL = "batteryLevel"; private static final String ATTR_NIGHT_MODE = "nightMode"; private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature"; - private static final String ATTR_LAST_BRIGHTNESS = "lastBrightness"; + private static final String ATTR_LAST_NITS = "lastNits"; + + private static final int MSG_BACKGROUND_START = 0; + private static final int MSG_BRIGHTNESS_CHANGED = 1; // Lock held while accessing mEvents, is held while writing events to flash. private final Object mEventsLock = new Object(); @@ -113,9 +117,7 @@ public class BrightnessTracker { private final Context mContext; private final ContentResolver mContentResolver; private Handler mBgHandler; - // mSettingsObserver, mBroadcastReceiver and mSensorListener should only be used on - // the mBgHandler thread. - private SettingsObserver mSettingsObserver; + // mBroadcastReceiver and mSensorListener should only be used on the mBgHandler thread. private BroadcastReceiver mBroadcastReceiver; private SensorListener mSensorListener; @@ -126,9 +128,9 @@ public class BrightnessTracker { @GuardedBy("mDataCollectionLock") private float mLastBatteryLevel = Float.NaN; @GuardedBy("mDataCollectionLock") - private int mIgnoreBrightness = -1; + private float mLastBrightness = -1; @GuardedBy("mDataCollectionLock") - private int mLastBrightness = -1; + private boolean mStarted; private final Injector mInjector; @@ -144,33 +146,31 @@ public class BrightnessTracker { } } - /** Start listening for brightness slider events */ - public void start() { + /** + * Start listening for brightness slider events + * + * @param brightness the initial screen brightness + */ + public void start(float initialBrightness) { if (DEBUG) { Slog.d(TAG, "Start"); } - mBgHandler = mInjector.getBackgroundHandler(); + mBgHandler = new TrackerHandler(mInjector.getBackgroundHandler().getLooper()); mUserManager = mContext.getSystemService(UserManager.class); - mBgHandler.post(() -> backgroundStart()); + mBgHandler.obtainMessage(MSG_BACKGROUND_START, (Float) initialBrightness).sendToTarget(); } - private void backgroundStart() { + private void backgroundStart(float initialBrightness) { readEvents(); - mLastBrightness = mInjector.getSystemIntForUser(mContentResolver, - Settings.System.SCREEN_BRIGHTNESS, -1, - UserHandle.USER_CURRENT); - mSensorListener = new SensorListener(); + if (mInjector.isInteractive(mContext)) { mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler); } - mSettingsObserver = new SettingsObserver(mBgHandler); - mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver); - final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SHUTDOWN); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); @@ -180,6 +180,10 @@ public class BrightnessTracker { mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter); mInjector.scheduleIdleJob(mContext); + synchronized (mDataCollectionLock) { + mLastBrightness = initialBrightness; + mStarted = true; + } } /** Stop listening for events */ @@ -188,10 +192,14 @@ public class BrightnessTracker { if (DEBUG) { Slog.d(TAG, "Stop"); } + mBgHandler.removeMessages(MSG_BACKGROUND_START); mInjector.unregisterSensorListener(mContext, mSensorListener); mInjector.unregisterReceiver(mContext, mBroadcastReceiver); - mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver); mInjector.cancelIdleJob(mContext); + + synchronized (mDataCollectionLock) { + mStarted = false; + } } /** @@ -220,40 +228,45 @@ public class BrightnessTracker { return new ParceledListSlice<>(out); } - /** Sets brightness without logging the brightness change event */ - public void setBrightness(int brightness, int userId) { - synchronized (mDataCollectionLock) { - mIgnoreBrightness = brightness; - } - mInjector.putSystemIntForUser(mContentResolver, Settings.System.SCREEN_BRIGHTNESS, - brightness, userId); - } - public void persistEvents() { scheduleWriteEvents(); } - private void handleBrightnessChanged() { + /** + * Notify the BrightnessTracker that the user has changed the brightness of the display. + */ + public void notifyBrightnessChanged(float brightness, boolean userInitiated) { if (DEBUG) { - Slog.d(TAG, "Brightness change"); + Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)", + brightness, userInitiated)); } - final BrightnessChangeEvent event = new BrightnessChangeEvent(); - event.timeStamp = mInjector.currentTimeMillis(); - - int brightness = mInjector.getSystemIntForUser(mContentResolver, - Settings.System.SCREEN_BRIGHTNESS, -1, - UserHandle.USER_CURRENT); + Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED, + userInitiated ? 1 : 0, 0 /*unused*/, (Float) brightness); + m.sendToTarget(); + } + private void handleBrightnessChanged(float brightness, boolean userInitiated) { + final BrightnessChangeEvent event; synchronized (mDataCollectionLock) { - int previousBrightness = mLastBrightness; + if (!mStarted) { + // Not currently gathering brightness change information + return; + } + + float previousBrightness = mLastBrightness; mLastBrightness = brightness; - if (brightness == -1 || brightness == mIgnoreBrightness) { - // Notified of brightness change but no setting or self change so ignore. - mIgnoreBrightness = -1; + if (!userInitiated) { + // We want to record what current brightness is so that we know what the user + // changed it from, but if it wasn't user initiated then we don't want to record it + // as a BrightnessChangeEvent. return; } + + event = new BrightnessChangeEvent(); + event.timeStamp = mInjector.currentTimeMillis(); + final int readingCount = mLastSensorReadings.size(); if (readingCount == 0) { // No sensor data so ignore this. @@ -386,7 +399,7 @@ public class BrightnessTracker { if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) { mEvents.append(toWrite[i]); out.startTag(null, TAG_EVENT); - out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness)); + out.attribute(null, ATTR_NITS, Float.toString(toWrite[i].brightness)); out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp)); out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName); out.attribute(null, ATTR_USER, Integer.toString(userSerialNo)); @@ -394,8 +407,8 @@ public class BrightnessTracker { out.attribute(null, ATTR_NIGHT_MODE, Boolean.toString(toWrite[i].nightMode)); out.attribute(null, ATTR_COLOR_TEMPERATURE, Integer.toString( toWrite[i].colorTemperature)); - out.attribute(null, ATTR_LAST_BRIGHTNESS, - Integer.toString(toWrite[i].lastBrightness)); + out.attribute(null, ATTR_LAST_NITS, + Float.toString(toWrite[i].lastBrightness)); StringBuilder luxValues = new StringBuilder(); StringBuilder luxTimestamps = new StringBuilder(); for (int j = 0; j < toWrite[i].luxValues.length; ++j) { @@ -446,8 +459,8 @@ public class BrightnessTracker { if (TAG_EVENT.equals(tag)) { BrightnessChangeEvent event = new BrightnessChangeEvent(); - String brightness = parser.getAttributeValue(null, ATTR_BRIGHTNESS); - event.brightness = Integer.parseInt(brightness); + String brightness = parser.getAttributeValue(null, ATTR_NITS); + event.brightness = Float.parseFloat(brightness); String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP); event.timeStamp = Long.parseLong(timestamp); event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); @@ -460,8 +473,8 @@ public class BrightnessTracker { String colorTemperature = parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE); event.colorTemperature = Integer.parseInt(colorTemperature); - String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_BRIGHTNESS); - event.lastBrightness = Integer.parseInt(lastBrightness); + String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS); + event.lastBrightness = Float.parseFloat(lastBrightness); String luxValue = parser.getAttributeValue(null, ATTR_LUX); String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS); @@ -582,22 +595,6 @@ public class BrightnessTracker { } } - private final class SettingsObserver extends ContentObserver { - public SettingsObserver(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (DEBUG) { - Slog.v(TAG, "settings change " + uri); - } - // Self change is based on observer passed to notifyObserver, SettingsProvider - // passes null so no changes are self changes. - handleBrightnessChanged(); - } - } - private final class Receiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -623,6 +620,24 @@ public class BrightnessTracker { } } + private final class TrackerHandler extends Handler { + public TrackerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_BACKGROUND_START: + backgroundStart((float)msg.obj /*initial brightness*/); + break; + case MSG_BRIGHTNESS_CHANGED: + float newBrightness = (float) msg.obj; + boolean userInitiatedChange = (msg.arg1 == 1); + handleBrightnessChanged(newBrightness, userInitiatedChange); + break; + } + } + } + @VisibleForTesting static class Injector { public void registerSensorListener(Context context, @@ -638,18 +653,6 @@ public class BrightnessTracker { sensorManager.unregisterListener(sensorListener); } - public void registerBrightnessObserver(ContentResolver resolver, - ContentObserver settingsObserver) { - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS), - false, settingsObserver, UserHandle.USER_ALL); - } - - public void unregisterBrightnessObserver(Context context, - ContentObserver settingsObserver) { - context.getContentResolver().unregisterContentObserver(settingsObserver); - } - public void registerReceiver(Context context, BroadcastReceiver receiver, IntentFilter filter) { context.registerReceiver(receiver, filter); @@ -664,16 +667,6 @@ public class BrightnessTracker { return BackgroundThread.getHandler(); } - public int getSystemIntForUser(ContentResolver resolver, String setting, int defaultValue, - int userId) { - return Settings.System.getIntForUser(resolver, setting, defaultValue, userId); - } - - public void putSystemIntForUser(ContentResolver resolver, String setting, int value, - int userId) { - Settings.System.putIntForUser(resolver, setting, value, userId); - } - public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue, int userId) { return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 9b97934cfc3b..02e4fe00f893 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -270,8 +270,6 @@ public final class DisplayManagerService extends SystemService { private final Injector mInjector; - private final BrightnessTracker mBrightnessTracker; - public DisplayManagerService(Context context) { this(context, new Injector()); } @@ -290,7 +288,6 @@ public final class DisplayManagerService extends SystemService { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting(); - mBrightnessTracker = new BrightnessTracker(context, null); mCurrentUserId = UserHandle.USER_SYSTEM; } @@ -1339,9 +1336,6 @@ public final class DisplayManagerService extends SystemService { pw.println(); mPersistentDataStore.dump(pw); - - pw.println(); - mBrightnessTracker.dump(pw); } } @@ -1418,10 +1412,6 @@ public final class DisplayManagerService extends SystemService { break; } - case MSG_REGISTER_BRIGHTNESS_TRACKER: - mBrightnessTracker.start(); - break; - case MSG_LOAD_BRIGHTNESS_CONFIGURATION: loadBrightnessConfiguration(); break; @@ -1833,22 +1823,9 @@ public final class DisplayManagerService extends SystemService { final int userId = UserHandle.getUserId(callingUid); final long token = Binder.clearCallingIdentity(); try { - return mBrightnessTracker.getEvents(userId, hasUsageStats); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override // Binder call - public void setBrightness(int brightness) { - // STOPSHIP - remove when adaptive brightness controller accepts curves. - mContext.enforceCallingOrSelfPermission( - Manifest.permission.BRIGHTNESS_SLIDER_USAGE, - "Permission to set brightness."); - int userId = UserHandle.getUserId(Binder.getCallingUid()); - final long token = Binder.clearCallingIdentity(); - try { - mBrightnessTracker.setBrightness(brightness, userId); + synchronized (mSyncRoot) { + return mDisplayPowerController.getBrightnessEvents(userId, hasUsageStats); + } } finally { Binder.restoreCallingIdentity(token); } @@ -2028,7 +2005,9 @@ public final class DisplayManagerService extends SystemService { @Override public void persistBrightnessSliderEvents() { - mBrightnessTracker.persistEvents(); + synchronized (mSyncRoot) { + mDisplayPowerController.persistBrightnessSliderEvents(); + } } } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index a2d954822e64..e5a4b0a78a65 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -24,13 +24,16 @@ import com.android.server.policy.WindowManagerPolicy; import android.animation.Animator; import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.Context; +import android.content.pm.ParceledListSlice; import android.content.res.Resources; -import android.content.res.TypedArray; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; @@ -96,7 +99,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_SCREEN_ON_UNBLOCKED = 3; private static final int MSG_SCREEN_OFF_UNBLOCKED = 4; private static final int MSG_CONFIGURE_BRIGHTNESS = 5; - private static final int MSG_USER_SWITCH = 6; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -151,9 +153,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The dim screen brightness. private final int mScreenBrightnessDimConfig; - // The minimum screen brightness to use in a very dark room. - private final int mScreenBrightnessDarkConfig; - // The minimum allowed brightness. private final int mScreenBrightnessRangeMinimum; @@ -261,6 +260,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private long mScreenOnBlockStartRealTime; private long mScreenOffBlockStartRealTime; + // The last brightness that was set by the user and not temporary. Set to -1 when a brightness + // has yet to be recorded. + private int mLastBrightness; + // The last auto brightness adjustment that was set by the user and not temporary. Set to + // Float.NaN when an auto-brightness adjustment hasn't been recorded yet. + private float mLastAutoBrightnessAdjustment; + // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_SCREEN_* fields. private int mReportedScreenStateToPolicy; @@ -289,6 +295,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The controller for the automatic brightness level. private AutomaticBrightnessController mAutomaticBrightnessController; + // The mapper between ambient lux, display backlight values, and display brightness. + @Nullable + private BrightnessMappingStrategy mBrightnessMapper; + // The default brightness configuration. Used for whenever we don't have a valid brightness // configuration set. This is typically seen with users that don't have a brightness // configuration that's different from the default. @@ -302,6 +312,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private ObjectAnimator mColorFadeOffAnimator; private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; + // Tracker for brightness changes + private final BrightnessTracker mBrightnessTracker; + /** * Creates the display power controller. */ @@ -309,6 +322,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager, DisplayBlanker blanker) { mHandler = new DisplayControllerHandler(handler.getLooper()); + mBrightnessTracker = new BrightnessTracker(context, null); mCallbacks = callbacks; mBatteryStats = BatteryStatsService.getService(); @@ -327,23 +341,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessDimConfig = clampAbsoluteBrightness(resources.getInteger( com.android.internal.R.integer.config_screenBrightnessDim)); - mScreenBrightnessDarkConfig = clampAbsoluteBrightness(resources.getInteger( - com.android.internal.R.integer.config_screenBrightnessDark)); - if (mScreenBrightnessDarkConfig > mScreenBrightnessDimConfig) { - Slog.w(TAG, "Expected config_screenBrightnessDark (" - + mScreenBrightnessDarkConfig + ") to be less than or equal to " - + "config_screenBrightnessDim (" + mScreenBrightnessDimConfig + ")."); - } - if (mScreenBrightnessDarkConfig > screenBrightnessSettingMinimum) { - Slog.w(TAG, "Expected config_screenBrightnessDark (" - + mScreenBrightnessDarkConfig + ") to be less than or equal to " - + "config_screenBrightnessSettingMinimum (" - + screenBrightnessSettingMinimum + ")."); - } - - int screenBrightnessRangeMinimum = Math.min(Math.min( - screenBrightnessSettingMinimum, mScreenBrightnessDimConfig), - mScreenBrightnessDarkConfig); + mScreenBrightnessRangeMinimum = + Math.min(screenBrightnessSettingMinimum, mScreenBrightnessDimConfig); mScreenBrightnessRangeMaximum = clampAbsoluteBrightness(resources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMaximum)); @@ -362,18 +361,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.bool.config_skipScreenOnBrightnessRamp); if (mUseSoftwareAutoBrightnessConfig) { - float[] luxLevels = getLuxLevels(resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLevels)); - int[] backlightValues = resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); - float[] brightnessValuesNits = getFloatArray(resources.obtainTypedArray( - com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)); - - final float screenMinimumNits = resources.getFloat( - com.android.internal.R.dimen.config_screenBrightnessMinimumNits); - final float screenMaximumNits = resources.getFloat( - com.android.internal.R.dimen.config_screenBrightnessMaximumNits); - final float dozeScaleFactor = resources.getFraction( com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor, 1, 1); @@ -413,31 +400,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ")."); } - if (backlightValues != null && backlightValues.length > 0) { - final int bottom = backlightValues[0]; - if (mScreenBrightnessDarkConfig > bottom) { - Slog.w(TAG, "config_screenBrightnessDark (" + mScreenBrightnessDarkConfig - + ") should be less than or equal to the first value of " - + "config_autoBrightnessLcdBacklightValues (" - + bottom + ")."); - } - if (bottom < screenBrightnessRangeMinimum) { - screenBrightnessRangeMinimum = bottom; - } - } - - float[] nitsRange = { screenMinimumNits, screenMaximumNits }; - int[] backlightRange = { screenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum }; - - BrightnessMappingStrategy mapper = BrightnessMappingStrategy.create( - luxLevels, backlightValues, brightnessValuesNits, - nitsRange, backlightRange); - if (mapper != null) { + mBrightnessMapper = BrightnessMappingStrategy.create(resources); + if (mBrightnessMapper != null) { mAutomaticBrightnessController = new AutomaticBrightnessController(this, - handler.getLooper(), sensorManager, mapper, lightSensorWarmUpTimeConfig, - screenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum, - dozeScaleFactor, lightSensorRate, initialLightSensorRate, - brighteningLightDebounce, darkeningLightDebounce, + handler.getLooper(), sensorManager, mBrightnessMapper, + lightSensorWarmUpTimeConfig, mScreenBrightnessRangeMinimum, + mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, + initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientLightHorizon, autoBrightnessAdjustmentMaxGamma, dynamicHysteresis); } else { @@ -445,9 +414,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - mScreenBrightnessRangeMinimum = screenBrightnessRangeMinimum; - - mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic(); mColorFadeFadesConfig = resources.getBoolean( com.android.internal.R.bool.config_animateScreenLights); @@ -466,32 +432,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + mLastBrightness = -1; + mLastAutoBrightnessAdjustment = Float.NaN; } - private static float[] getLuxLevels(int[] lux) { - // The first control point is implicit and always at 0 lux. - float[] levels = new float[lux.length + 1]; - for (int i = 0; i < lux.length; i++) { - levels[i + 1] = (float) lux[i]; - } - return levels; + /** + * Returns true if the proximity sensor screen-off function is available. + */ + public boolean isProximitySensorAvailable() { + return mProximitySensor != null; } - private static float[] getFloatArray(TypedArray array) { - final int N = array.length(); - float[] vals = new float[N]; - for (int i = 0; i < N; i++) { - vals[i] = array.getFloat(i, -1.0f); - } - array.recycle(); - return vals; + /** + * Get the {@link BrightnessChangeEvent}s for the specified user. + * @param userId userId to fetch data for + * @param includePackage if false will null out the package name in events + */ + public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents( + @UserIdInt int userId, boolean includePackage) { + return mBrightnessTracker.getEvents(userId, includePackage); } /** - * Returns true if the proximity sensor screen-off function is available. + * Persist the brightness slider events to disk. */ - public boolean isProximitySensorAvailable() { - return mProximitySensor != null; + public void persistBrightnessSliderEvents() { + mBrightnessTracker.persistEvents(); } /** @@ -588,6 +554,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } catch (RemoteException ex) { // same process } + + // Initialize all of the brightness tracking state + final float brightness = getNits(mPowerState.getScreenBrightness()); + if (brightness >= 0.0f) { + mBrightnessTracker.start(brightness); + } } private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { @@ -722,16 +694,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brightness = PowerManager.BRIGHTNESS_OFF; } + + final boolean autoBrightnessEnabledInDoze = + mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state); + final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness + && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) + && brightness < 0 + && mAutomaticBrightnessController != null; + final boolean brightnessAdjustmentChanged = + !Float.isNaN(mLastAutoBrightnessAdjustment) + && mPowerRequest.screenAutoBrightnessAdjustment != mLastAutoBrightnessAdjustment; + final boolean brightnessChanged = mLastBrightness >= 0 + && mPowerRequest.screenBrightness != mLastBrightness; + + // Update the last set brightness values. + final boolean userInitiatedChange; + if (mPowerRequest.brightnessSetByUser && !mPowerRequest.brightnessIsTemporary) { + userInitiatedChange = autoBrightnessEnabled && brightnessAdjustmentChanged + || !autoBrightnessEnabled && brightnessChanged; + mLastBrightness = mPowerRequest.screenBrightness; + mLastAutoBrightnessAdjustment = mPowerRequest.screenAutoBrightnessAdjustment; + } else { + userInitiatedChange = false; + } + // Configure auto-brightness. - boolean autoBrightnessEnabled = false; if (mAutomaticBrightnessController != null) { - final boolean autoBrightnessEnabledInDoze = - mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state); - autoBrightnessEnabled = mPowerRequest.useAutoBrightness - && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) - && brightness < 0; - final boolean userInitiatedChange = autoBrightnessAdjustmentChanged - && mPowerRequest.brightnessSetByUser; mAutomaticBrightnessController.configure(autoBrightnessEnabled, mBrightnessConfiguration, mPowerRequest.screenAutoBrightnessAdjustment, state != Display.STATE_ON, userInitiatedChange); @@ -854,6 +842,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call animateScreenBrightness(brightness, slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast); } + + final float brightnessInNits = getNits(brightness); + if (!mPowerRequest.brightnessIsTemporary && brightnessInNits >= 0.0f) { + // We only want to track changes made by the user and on devices that can actually + // map the display backlight values into a physical brightness unit. + mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiatedChange); + } } // Determine whether the display is ready for use in the newly requested state. @@ -1312,6 +1307,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHandler.post(mOnStateChangedRunnable); } + private float getNits(int backlight) { + if (mBrightnessMapper != null) { + return mBrightnessMapper.getNits(backlight); + } else { + return -1.0f; + } + } + private final Runnable mOnStateChangedRunnable = new Runnable() { @Override public void run() { @@ -1362,7 +1365,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println("Display Power Controller Configuration:"); pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig); pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); - pw.println(" mScreenBrightnessDarkConfig=" + mScreenBrightnessDarkConfig); pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); @@ -1383,7 +1385,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println("Display Power Controller Thread State:"); pw.println(" mPowerRequest=" + mPowerRequest); pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity); - pw.println(" mProximitySensor=" + mProximitySensor); pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled); pw.println(" mProximityThreshold=" + mProximityThreshold); @@ -1392,6 +1393,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mPendingProximityDebounceTime=" + TimeUtils.formatUptime(mPendingProximityDebounceTime)); pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity); + pw.println(" mLastBrightness=" + mLastBrightness); + pw.println(" mLastAutoBrightnessAdjustment=" + mLastAutoBrightnessAdjustment); pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness); pw.println(" mAppliedDimming=" + mAppliedDimming); pw.println(" mAppliedLowPower=" + mAppliedLowPower); @@ -1420,6 +1423,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.dump(pw); } + if (mBrightnessTracker != null) { + pw.println(); + mBrightnessTracker.dump(pw); + } } private static String proximityToString(int state) { diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java index c356b639deba..033437a53891 100644 --- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java +++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java @@ -221,7 +221,7 @@ import java.util.ArrayList; case Result.NOT_INIT: return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED; case Result.TRANSACTION_PENDING: - return ContextHubTransaction.RESULT_FAILED_PENDING; + return ContextHubTransaction.RESULT_FAILED_BUSY; case Result.TRANSACTION_FAILED: case Result.UNKNOWN_FAILURE: default: /* fall through */ diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 3bd64467bca6..e6de07d209a1 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -414,16 +414,16 @@ public class GnssLocationProvider implements LocationProviderInterface { private WorkSource mClientSource = new WorkSource(); private GeofenceHardwareImpl mGeofenceHardwareImpl; - private int mYearOfHardware = 0; + + // Volatile for simple inter-thread sync on these values. + private volatile int mHardwareYear = 0; + private volatile String mHardwareModelName = LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN; // Set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL // stops output right at 600m/s, depriving this of the information of a device that reaches // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases. private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F; - // TODO: improve comment - // Volatile to ensure that potentially near-concurrent outputs from HAL - // react to this value change promptly private volatile boolean mItarSpeedLimitExceeded = false; // GNSS Metrics @@ -1833,33 +1833,53 @@ public class GnssLocationProvider implements LocationProviderInterface { /** * called from native code to inform us what the GPS engine capabilities are */ - private void setEngineCapabilities(int capabilities) { - mEngineCapabilities = capabilities; + private void setEngineCapabilities(final int capabilities) { + // send to handler thread for fast native return, and in-order handling + mHandler.post(new Runnable() { + @Override + public void run() { + mEngineCapabilities = capabilities; - if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { - mOnDemandTimeInjection = true; - requestUtcTime(); - } + if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { + mOnDemandTimeInjection = true; + requestUtcTime(); + } - mGnssMeasurementsProvider.onCapabilitiesUpdated( - (capabilities & GPS_CAPABILITY_MEASUREMENTS) == GPS_CAPABILITY_MEASUREMENTS); - mGnssNavigationMessageProvider.onCapabilitiesUpdated( - (capabilities & GPS_CAPABILITY_NAV_MESSAGES) == GPS_CAPABILITY_NAV_MESSAGES); - } + mGnssMeasurementsProvider.onCapabilitiesUpdated(hasCapability( + GPS_CAPABILITY_MEASUREMENTS)); + mGnssNavigationMessageProvider.onCapabilitiesUpdated(hasCapability( + GPS_CAPABILITY_NAV_MESSAGES)); + } + }); + } /** - * Called from native code to inform us the hardware information. + * Called from native code to inform us the hardware year. */ - private void setGnssYearOfHardware(int yearOfHardware) { + private void setGnssYearOfHardware(final int yearOfHardware) { + // mHardwareYear is simply set here, to be read elsewhere, and is volatile for safe sync if (DEBUG) Log.d(TAG, "setGnssYearOfHardware called with " + yearOfHardware); - mYearOfHardware = yearOfHardware; + mHardwareYear = yearOfHardware; + } + + /** + * Called from native code to inform us the hardware model name. + */ + private void setGnssHardwareModelName(final String modelName) { + // mHardwareModelName is simply set here, to be read elsewhere, and volatile for safe sync + if (DEBUG) Log.d(TAG, "setGnssModelName called with " + modelName); + mHardwareModelName = modelName; } public interface GnssSystemInfoProvider { /** - * Returns the year of GPS hardware. + * Returns the year of underlying GPS hardware. */ int getGnssYearOfHardware(); + /** + * Returns the model name of underlying GPS hardware. + */ + String getGnssHardwareModelName(); } /** @@ -1869,7 +1889,11 @@ public class GnssLocationProvider implements LocationProviderInterface { return new GnssSystemInfoProvider() { @Override public int getGnssYearOfHardware() { - return mYearOfHardware; + return mHardwareYear; + } + @Override + public String getGnssHardwareModelName() { + return mHardwareModelName; } }; } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index eef4d9be4b56..02218ffc14ea 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1581,6 +1581,8 @@ public class LockSettingsService extends ILockSettings.Stub { userId, progressCallback); // The user employs synthetic password based credential. if (response != null) { + mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential, + userId); return response; } @@ -1705,6 +1707,9 @@ public class LockSettingsService extends ILockSettings.Stub { /* TODO(roosa): keep the same password quality */, userId); if (!hasChallenge) { notifyActivePasswordMetricsAvailable(credential, userId); + // Use credentials to create recoverable keystore snapshot. + mRecoverableKeyStoreManager.lockScreenSecretAvailable( + storedHash.type, credential, userId); return VerifyCredentialResponse.OK; } // Fall through to get the auth token. Technically this should never happen, @@ -2021,11 +2026,17 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, - @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId) + public Map<String, byte[]> recoverKeys(@NonNull String sessionId, + @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys, + @UserIdInt int userId) throws RemoteException { - mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys, - userId); + return mRecoverableKeyStoreManager.recoverKeys( + sessionId, recoveryKeyBlob, applicationKeys, userId); + } + + @Override + public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { + return mRecoverableKeyStoreManager.generateAndStoreKey(alias); } private static final String[] VALID_SETTINGS = new String[] { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java new file mode 100644 index 000000000000..e385833bac63 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings.recoverablekeystore; + +import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN; + +import android.annotation.NonNull; +import android.content.Context; +import android.security.recoverablekeystore.KeyDerivationParameters; +import android.security.recoverablekeystore.KeyEntryRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockPatternUtils; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +/** + * Task to sync application keys to a remote vault service. + * + * @hide + */ +public class KeySyncTask implements Runnable { + private static final String TAG = "KeySyncTask"; + + private static final String RECOVERY_KEY_ALGORITHM = "AES"; + private static final int RECOVERY_KEY_SIZE_BITS = 256; + private static final int SALT_LENGTH_BYTES = 16; + private static final int LENGTH_PREFIX_BYTES = Integer.BYTES; + private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256"; + private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10; + + private final RecoverableKeyStoreDb mRecoverableKeyStoreDb; + private final int mUserId; + private final int mCredentialType; + private final String mCredential; + private final PlatformKeyManager.Factory mPlatformKeyManagerFactory; + private final RecoverySnapshotStorage mRecoverySnapshotStorage; + private final RecoverySnapshotListenersStorage mSnapshotListenersStorage; + + public static KeySyncTask newInstance( + Context context, + RecoverableKeyStoreDb recoverableKeyStoreDb, + RecoverySnapshotStorage snapshotStorage, + RecoverySnapshotListenersStorage recoverySnapshotListenersStorage, + int userId, + int credentialType, + String credential + ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException { + return new KeySyncTask( + recoverableKeyStoreDb, + snapshotStorage, + recoverySnapshotListenersStorage, + userId, + credentialType, + credential, + () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb, userId)); + } + + /** + * A new task. + * + * @param recoverableKeyStoreDb Database where the keys are stored. + * @param userId The uid of the user whose profile has been unlocked. + * @param credentialType The type of credential - i.e., pattern or password. + * @param credential The credential, encoded as a {@link String}. + * @param platformKeyManagerFactory Instantiates a {@link PlatformKeyManager} for the user. + * This is a factory to enable unit testing, as otherwise it would be impossible to test + * without a screen unlock occurring! + */ + @VisibleForTesting + KeySyncTask( + RecoverableKeyStoreDb recoverableKeyStoreDb, + RecoverySnapshotStorage snapshotStorage, + RecoverySnapshotListenersStorage recoverySnapshotListenersStorage, + int userId, + int credentialType, + String credential, + PlatformKeyManager.Factory platformKeyManagerFactory) { + mSnapshotListenersStorage = recoverySnapshotListenersStorage; + mRecoverableKeyStoreDb = recoverableKeyStoreDb; + mUserId = userId; + mCredentialType = credentialType; + mCredential = credential; + mPlatformKeyManagerFactory = platformKeyManagerFactory; + mRecoverySnapshotStorage = snapshotStorage; + } + + @Override + public void run() { + try { + syncKeys(); + } catch (Exception e) { + Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e); + } + } + + private void syncKeys() { + if (!isSyncPending()) { + Log.d(TAG, "Key sync not needed."); + return; + } + + int recoveryAgentUid = mRecoverableKeyStoreDb.getRecoveryAgentUid(mUserId); + if (recoveryAgentUid == -1) { + Log.w(TAG, "No recovery agent initialized for user " + mUserId); + return; + } + if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) { + Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid); + return; + } + + PublicKey publicKey = getVaultPublicKey(); + if (publicKey == null) { + Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task."); + return; + } + + Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid); + if (deviceId == null) { + Log.w(TAG, "No device ID set for user " + mUserId); + return; + } + + byte[] salt = generateSalt(); + byte[] localLskfHash = hashCredentials(salt, mCredential); + + Map<String, SecretKey> rawKeys; + try { + rawKeys = getKeysToSync(); + } catch (GeneralSecurityException e) { + Log.e(TAG, "Failed to load recoverable keys for sync", e); + return; + } catch (InsecureUserException e) { + Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have " + + "lock screen. This should be impossible.", e); + return; + } catch (BadPlatformKeyException e) { + Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so " + + "BadPlatformKeyException should be impossible.", e); + return; + } + + SecretKey recoveryKey; + try { + recoveryKey = generateRecoveryKey(); + } catch (NoSuchAlgorithmException e) { + Log.wtf("AES should never be unavailable", e); + return; + } + + Map<String, byte[]> encryptedApplicationKeys; + try { + encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey( + recoveryKey, rawKeys); + } catch (InvalidKeyException | NoSuchAlgorithmException e) { + Log.wtf(TAG, + "Should be impossible: could not encrypt application keys with random key", + e); + return; + } + + // TODO: where do we get counter_id from here? + byte[] vaultParams = KeySyncUtils.packVaultParams( + publicKey, + /*counterId=*/ 1, + TRUSTED_HARDWARE_MAX_ATTEMPTS, + deviceId); + + byte[] encryptedRecoveryKey; + try { + encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey( + publicKey, + localLskfHash, + vaultParams, + recoveryKey); + } catch (NoSuchAlgorithmException e) { + Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e); + return; + } catch (InvalidKeyException e) { + Log.e(TAG,"Could not encrypt with recovery key", e); + return; + } + + KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata( + /*userSecretType=*/ TYPE_LOCKSCREEN, + /*lockScreenUiFormat=*/ mCredentialType, + /*keyDerivationParameters=*/ KeyDerivationParameters.createSHA256Parameters(salt), + /*secret=*/ new byte[0]); + ArrayList<KeyStoreRecoveryMetadata> metadataList = new ArrayList<>(); + metadataList.add(metadata); + + // TODO: implement snapshot version + mRecoverySnapshotStorage.put(mUserId, new KeyStoreRecoveryData( + /*snapshotVersion=*/ 1, + /*recoveryMetadata=*/ metadataList, + /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys), + /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey)); + mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid); + } + + private PublicKey getVaultPublicKey() { + return mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId); + } + + /** + * Returns all of the recoverable keys for the user. + */ + private Map<String, SecretKey> getKeysToSync() + throws InsecureUserException, KeyStoreException, UnrecoverableKeyException, + NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException { + PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance(); + PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(); + Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys( + mUserId, decryptKey.getGenerationId()); + return WrappedKey.unwrapKeys(decryptKey, wrappedKeys); + } + + /** + * Returns {@code true} if a sync is pending. + */ + private boolean isSyncPending() { + // TODO: implement properly. For now just always syncing if the user has any recoverable + // keys. We need to keep track of when the store's state actually changes. + return !mRecoverableKeyStoreDb.getAllKeys( + mUserId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(mUserId)).isEmpty(); + } + + /** + * The UI best suited to entering the given lock screen. This is synced with the vault so the + * user can be shown the same UI when recovering the vault on another device. + * + * @return The format - either pattern, pin, or password. + */ + @VisibleForTesting + @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat( + int credentialType, String credential) { + if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { + return KeyStoreRecoveryMetadata.TYPE_PATTERN; + } else if (isPin(credential)) { + return KeyStoreRecoveryMetadata.TYPE_PIN; + } else { + return KeyStoreRecoveryMetadata.TYPE_PASSWORD; + } + } + + /** + * Generates a salt to include with the lock screen hash. + * + * @return The salt. + */ + private byte[] generateSalt() { + byte[] salt = new byte[SALT_LENGTH_BYTES]; + new SecureRandom().nextBytes(salt); + return salt; + } + + /** + * Returns {@code true} if {@code credential} looks like a pin. + */ + @VisibleForTesting + static boolean isPin(@NonNull String credential) { + int length = credential.length(); + for (int i = 0; i < length; i++) { + if (!Character.isDigit(credential.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Hashes {@code credentials} with the given {@code salt}. + * + * @return The SHA-256 hash. + */ + @VisibleForTesting + static byte[] hashCredentials(byte[] salt, String credentials) { + byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8); + ByteBuffer byteBuffer = ByteBuffer.allocate( + salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.putInt(salt.length); + byteBuffer.put(salt); + byteBuffer.putInt(credentialsBytes.length); + byteBuffer.put(credentialsBytes); + byte[] bytes = byteBuffer.array(); + + try { + return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes); + } catch (NoSuchAlgorithmException e) { + // Impossible, SHA-256 must be supported on Android. + throw new RuntimeException(e); + } + } + + private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException { + KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM); + keyGenerator.init(RECOVERY_KEY_SIZE_BITS); + return keyGenerator.generateKey(); + } + + private static List<KeyEntryRecoveryData> createApplicationKeyEntries( + Map<String, byte[]> encryptedApplicationKeys) { + ArrayList<KeyEntryRecoveryData> keyEntries = new ArrayList<>(); + for (String alias : encryptedApplicationKeys.keySet()) { + keyEntries.add( + new KeyEntryRecoveryData( + alias.getBytes(StandardCharsets.UTF_8), + encryptedApplicationKeys.get(alias))); + } + return keyEntries; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java index 74e25e4dcbd8..e851d8cf21b3 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java @@ -18,6 +18,8 @@ package com.android.server.locksettings.recoverablekeystore; import com.android.internal.annotations.VisibleForTesting; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -54,10 +56,13 @@ public class KeySyncUtils { "V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8); private static final byte[] RECOVERY_CLAIM_HEADER = "V1 KF_claim".getBytes(StandardCharsets.UTF_8); + private static final byte[] RECOVERY_RESPONSE_HEADER = + "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8); private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; + private static final int VAULT_PARAMS_LENGTH_BYTES = 85; /** * Encrypts the recovery key using both the lock screen hash and the remote storage's public @@ -74,7 +79,7 @@ public class KeySyncUtils { * * @hide */ - public byte[] thmEncryptRecoveryKey( + public static byte[] thmEncryptRecoveryKey( PublicKey publicKey, byte[] lockScreenHash, byte[] vaultParams, @@ -204,6 +209,28 @@ public class KeySyncUtils { } /** + * Decrypts response from recovery claim, returning the locally encrypted key. + * + * @param keyClaimant The key claimant, used by the remote service to encrypt the response. + * @param vaultParams Vault params associated with the claim. + * @param encryptedResponse The encrypted response. + * @return The locally encrypted recovery key. + * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present. + * @throws InvalidKeyException if the {@code keyClaimant} could not be used to decrypt. + * @throws AEADBadTagException if the message has been tampered with or was encrypted with a + * different key. + */ + public static byte[] decryptRecoveryClaimResponse( + byte[] keyClaimant, byte[] vaultParams, byte[] encryptedResponse) + throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { + return SecureBox.decrypt( + /*ourPrivateKey=*/ null, + /*sharedSecret=*/ keyClaimant, + /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*encryptedPayload=*/ encryptedResponse); + } + + /** * Decrypts a recovery key, after having retrieved it from a remote server. * * @param lskfHash The lock screen hash associated with the key. @@ -257,6 +284,26 @@ public class KeySyncUtils { } /** + * Packs vault params into a binary format. + * + * @param thmPublicKey Public key of the trusted hardware module. + * @param counterId ID referring to the specific counter in the hardware module. + * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key. + * @param deviceId ID of the device. + * @return The binary vault params, ready for sync. + */ + public static byte[] packVaultParams( + PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) { + return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES) + .order(ByteOrder.LITTLE_ENDIAN) + .put(SecureBox.encodePublicKey(thmPublicKey)) + .putLong(counterId) + .putLong(deviceId) + .putInt(maxAttempts) + .array(); + } + + /** * Returns the concatenation of all the given {@code arrays}. */ @VisibleForTesting diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java deleted file mode 100644 index 0f17294b461f..000000000000 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.locksettings.recoverablekeystore; - -import android.annotation.Nullable; -import android.app.PendingIntent; - -import com.android.internal.annotations.VisibleForTesting; - -import java.util.Map; -import java.util.HashMap; - -/** - * In memory storage for listeners to be notified when new recovery snapshot is available. - * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent} - * class. - * - * @hide - */ -public class ListenersStorage { - private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>(); - - private static final ListenersStorage mInstance = new ListenersStorage(); - public static ListenersStorage getInstance() { - return mInstance; - } - - /** - * Sets new listener for the recovery agent, identified by {@code uid} - * - * @param recoveryAgentUid uid - * @param intent PendingIntent which will be triggered than new snapshot is available. - */ - public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) { - mAgentIntents.put(recoveryAgentUid, intent); - } - - /** - * Notifies recovery agent, that new snapshot is available. - * Does nothing if a listener was not registered. - * - * @param recoveryAgentUid uid. - */ - public void recoverySnapshotAvailable(int recoveryAgentUid) { - PendingIntent intent = mAgentIntents.get(recoveryAgentUid); - if (intent != null) { - try { - intent.send(); - } catch (PendingIntent.CanceledException e) { - // Ignore - sending intent is not allowed. - } - } - } -} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java index 24f3f65eec15..a8b8361b4f03 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -88,7 +88,8 @@ public class PlatformKeyManager { * * @hide */ - public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId) + public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, + int userId) throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException { context = context.getApplicationContext(); PlatformKeyManager keyManager = new PlatformKeyManager( @@ -115,16 +116,12 @@ public class PlatformKeyManager { /** * Returns the current generation ID of the platform key. This increments whenever a platform * key has to be replaced. (e.g., because the user has removed and then re-added their lock - * screen). + * screen). Returns -1 if no key has been generated yet. * * @hide */ public int getGenerationId() { - int generationId = mDatabase.getPlatformKeyGenerationId(mUserId); - if (generationId == -1) { - return 1; - } - return generationId; + return mDatabase.getPlatformKeyGenerationId(mUserId); } /** @@ -149,7 +146,6 @@ public class PlatformKeyManager { public void regenerate() throws NoSuchAlgorithmException, KeyStoreException { int nextId = getGenerationId() + 1; generateAndLoadKey(nextId); - setGenerationId(nextId); } /** @@ -207,13 +203,20 @@ public class PlatformKeyManager { Locale.US, "Platform key generation %d exists already.", generationId)); return; } - if (generationId == 1) { + if (generationId == -1) { Log.i(TAG, "Generating initial platform ID."); } else { Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no " + "entry was present in AndroidKeyStore. Generating fresh key.", generationId)); } + if (generationId == -1) { + generationId = 1; + } else { + // Had to generate a fresh key, bump the generation id + generationId++; + } + generateAndLoadKey(generationId); } @@ -296,6 +299,8 @@ public class PlatformKeyManager { .setBoundToSpecificSecureUserId(mUserId) .build()); + setGenerationId(generationId); + try { secretKey.destroy(); } catch (DestroyFailedException e) { @@ -332,4 +337,17 @@ public class PlatformKeyManager { } return keyStore; } + + /** + * @hide + */ + public interface Factory { + /** + * New PlatformKeyManager instance. + * + * @hide + */ + PlatformKeyManager newInstance() + throws NoSuchAlgorithmException, InsecureUserException, KeyStoreException; + } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java index bb40fd8c2134..8c23d9b4f8e1 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java @@ -16,22 +16,15 @@ package com.android.server.locksettings.recoverablekeystore; -import android.security.keystore.KeyProperties; -import android.security.keystore.KeyProtection; -import android.util.Log; - import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import java.security.InvalidKeyException; -import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.util.Locale; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; -import javax.security.auth.DestroyFailedException; /** * Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form. @@ -43,8 +36,6 @@ import javax.security.auth.DestroyFailedException; * @hide */ public class RecoverableKeyGenerator { - private static final String TAG = "RecoverableKeyGenerator"; - private static final int RESULT_CANNOT_INSERT_ROW = -1; private static final String KEY_GENERATOR_ALGORITHM = "AES"; private static final int KEY_SIZE_BITS = 256; @@ -62,20 +53,16 @@ public class RecoverableKeyGenerator { // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key // material, so that it can be synced to disk in encrypted form. KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM); - return new RecoverableKeyGenerator( - keyGenerator, database, new AndroidKeyStoreFactory.Impl()); + return new RecoverableKeyGenerator(keyGenerator, database); } private final KeyGenerator mKeyGenerator; private final RecoverableKeyStoreDb mDatabase; - private final AndroidKeyStoreFactory mAndroidKeyStoreFactory; private RecoverableKeyGenerator( KeyGenerator keyGenerator, - RecoverableKeyStoreDb recoverableKeyStoreDb, - AndroidKeyStoreFactory androidKeyStoreFactory) { + RecoverableKeyStoreDb recoverableKeyStoreDb) { mKeyGenerator = keyGenerator; - mAndroidKeyStoreFactory = androidKeyStoreFactory; mDatabase = recoverableKeyStoreDb; } @@ -89,69 +76,29 @@ public class RecoverableKeyGenerator { * @param platformKey The user's platform key, with which to wrap the generated key. * @param userId The user ID of the profile to which the calling app belongs. * @param uid The uid of the application that will own the key. - * @param alias The alias by which the key will be known in AndroidKeyStore. + * @param alias The alias by which the key will be known in the recoverable key store. * @throws RecoverableKeyStorageException if there is some error persisting the key either to - * the AndroidKeyStore or the database. + * the database. * @throws KeyStoreException if there is a KeyStore error wrapping the generated key. * @throws InvalidKeyException if the platform key cannot be used to wrap keys. * * @hide */ - public void generateAndStoreKey( + public byte[] generateAndStoreKey( PlatformEncryptionKey platformKey, int userId, int uid, String alias) throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException { mKeyGenerator.init(KEY_SIZE_BITS); SecretKey key = mKeyGenerator.generateKey(); - KeyStoreProxy keyStore; - - try { - keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid); - } catch (NoSuchProviderException e) { - throw new RecoverableKeyStorageException( - "Impossible: AndroidKeyStore provider did not exist", e); - } catch (KeyStoreException e) { - throw new RecoverableKeyStorageException( - "Could not load AndroidKeyStore for " + uid, e); - } - - try { - keyStore.setEntry( - alias, - new KeyStore.SecretKeyEntry(key), - new KeyProtection.Builder( - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .build()); - } catch (KeyStoreException e) { - throw new RecoverableKeyStorageException( - "Failed to load (%d, %s) into AndroidKeyStore", e); - } - WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key); - try { - // Keep raw key material in memory for minimum possible time. - key.destroy(); - } catch (DestroyFailedException e) { - Log.w(TAG, "Could not destroy SecretKey."); - } long result = mDatabase.insertKey(userId, uid, alias, wrappedKey); if (result == RESULT_CANNOT_INSERT_ROW) { - // Attempt to clean up - try { - keyStore.deleteEntry(alias); - } catch (KeyStoreException e) { - Log.e(TAG, String.format(Locale.US, - "Could not delete recoverable key (%d, %s) from " - + "AndroidKeyStore after error writing to database.", uid, alias), - e); - } - throw new RecoverableKeyStorageException( String.format( Locale.US, "Failed writing (%d, %s) to database.", uid, alias)); } + + return key.getEncoded(); } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index a50b425e52b1..eccf241dd47f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -29,19 +29,30 @@ import android.security.recoverablekeystore.KeyEntryRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import android.security.recoverablekeystore.RecoverableKeyStoreLoader; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; +import java.security.KeyStoreException; +import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; +import java.security.UnrecoverableKeyException; import java.security.spec.InvalidKeySpecException; -import java.util.ArrayList; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.crypto.AEADBadTagException; /** * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact @@ -50,12 +61,21 @@ import java.util.Map; * @hide */ public class RecoverableKeyStoreManager { - private static final String TAG = "RecoverableKeyStoreManager"; + private static final String TAG = "RecoverableKeyStoreMgr"; + + private static final int ERROR_INSECURE_USER = 1; + private static final int ERROR_KEYSTORE_INTERNAL_ERROR = 2; + private static final int ERROR_DATABASE_ERROR = 3; + private static RecoverableKeyStoreManager mInstance; private final Context mContext; private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; + private final ExecutorService mExecutorService; + private final RecoverySnapshotListenersStorage mListenersStorage; + private final RecoverableKeyGenerator mRecoverableKeyGenerator; + private final RecoverySnapshotStorage mSnapshotStorage; /** * Returns a new or existing instance. @@ -68,7 +88,10 @@ public class RecoverableKeyStoreManager { mInstance = new RecoverableKeyStoreManager( mContext.getApplicationContext(), db, - new RecoverySessionStorage()); + new RecoverySessionStorage(), + Executors.newSingleThreadExecutor(), + new RecoverySnapshotStorage(), + new RecoverySnapshotListenersStorage()); } return mInstance; } @@ -77,18 +100,42 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreManager( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, - RecoverySessionStorage recoverySessionStorage) { + RecoverySessionStorage recoverySessionStorage, + ExecutorService executorService, + RecoverySnapshotStorage snapshotStorage, + RecoverySnapshotListenersStorage listenersStorage) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; + mExecutorService = executorService; + mListenersStorage = listenersStorage; + mSnapshotStorage = snapshotStorage; + try { + mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); + } catch (NoSuchAlgorithmException e) { + // Impossible: all AOSP implementations must support AES. + throw new RuntimeException(e); + } } - public int initRecoveryService( + public void initRecoveryService( @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId) throws RemoteException { checkRecoverKeyStorePermission(); - // TODO open /system/etc/security/... cert file - throw new UnsupportedOperationException(); + // TODO: open /system/etc/security/... cert file, and check the signature on the public keys + PublicKey publicKey; + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + // TODO: Randomly choose a key from the list -- right now we just use the whole input + X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList); + publicKey = kf.generatePublic(pkSpec); + } catch (NoSuchAlgorithmException e) { + // Should never happen + throw new RuntimeException(e); + } catch (InvalidKeySpecException e) { + throw new RemoteException("Invalid public key for the recovery service"); + } + mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey); } /** @@ -100,30 +147,19 @@ public class RecoverableKeyStoreManager { public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId) throws RemoteException { checkRecoverKeyStorePermission(); - final int callingUid = Binder.getCallingUid(); // Recovery agent uid. - final int callingUserId = UserHandle.getCallingUserId(); - final long callingIdentiy = Binder.clearCallingIdentity(); - try { - // TODO: Return the latest snapshot for the calling recovery agent. - } finally { - Binder.restoreCallingIdentity(callingIdentiy); - } - // KeyStoreRecoveryData without application keys and empty recovery blob. - KeyStoreRecoveryData recoveryData = - new KeyStoreRecoveryData( - /*snapshotVersion=*/ 1, - new ArrayList<KeyStoreRecoveryMetadata>(), - new ArrayList<KeyEntryRecoveryData>(), - /*encryptedRecoveryKeyBlob=*/ new byte[] {}); - throw new ServiceSpecificException( - RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY); + KeyStoreRecoveryData snapshot = mSnapshotStorage.get(UserHandle.getCallingUserId()); + if (snapshot == null) { + throw new ServiceSpecificException(RecoverableKeyStoreLoader.NO_SNAPSHOT_PENDING_ERROR); + } + return snapshot; } public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + final int recoveryAgentUid = Binder.getCallingUid(); + mListenersStorage.setSnapshotListener(recoveryAgentUid, intent); } /** @@ -140,29 +176,47 @@ public class RecoverableKeyStoreManager { public void setServerParameters(long serverParameters, int userId) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters); } + /** + * Updates recovery status for the application given its {@code packageName}. + * + * @param packageName which recoverable key statuses will be returned + * @param aliases - KeyStore aliases or {@code null} for all aliases of the app + * @param status - new status + */ public void setRecoveryStatus( @NonNull String packageName, @Nullable String[] aliases, int status, int userId) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + int uid = Binder.getCallingUid(); + if (packageName != null) { + // TODO: get uid for package name, when many apps are supported. + } + if (aliases == null) { + // Get all keys for the app. + Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid); + aliases = new String[allKeys.size()]; + allKeys.keySet().toArray(aliases); + } + for (String alias: aliases) { + mDatabase.setRecoveryStatus(uid, alias, status); + } } /** - * Gets recovery status for keys {@code packageName}. + * Gets recovery status for caller or other application {@code packageName}. + * @param packageName which recoverable keys statuses will be returned. * - * @param packageName which recoverable keys statuses will be returned - * @return Map from KeyStore alias to recovery status + * @return {@code Map} from KeyStore alias to recovery status. */ public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException { // Any application should be able to check status for its own keys. // If caller is a recovery agent it can check statuses for other packages, but // only for recoverable keys it manages. - checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + return mDatabase.getStatusForAllKeys(Binder.getCallingUid()); } /** @@ -174,7 +228,8 @@ public class RecoverableKeyStoreManager { @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(), + secretTypes); } /** @@ -185,7 +240,8 @@ public class RecoverableKeyStoreManager { */ public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(), + Binder.getCallingUid()); } /** @@ -218,7 +274,7 @@ public class RecoverableKeyStoreManager { * @param verifierPublicKey X509-encoded public key. * @param vaultParams Additional params associated with vault. * @param vaultChallenge Challenge issued by vault service. - * @param secrets Lock-screen hashes. Should have a single element. TODO: why is this a list? + * @param secrets Lock-screen hashes. For now only a single secret is supported. * @return Encrypted bytes of recovery claim. This can then be issued to the vault service. * * @hide @@ -241,7 +297,8 @@ public class RecoverableKeyStoreManager { byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); byte[] kfHash = secrets.get(0).getSecret(); mRecoverySessionStorage.add( - userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant)); + userId, + new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams)); try { byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash); @@ -268,20 +325,144 @@ public class RecoverableKeyStoreManager { } } - public void recoverKeys( + /** + * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault + * service. + * + * @param sessionId The session ID used to generate the claim. See + * {@link #startRecoverySession(String, byte[], byte[], byte[], List, int)}. + * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault + * service. + * @param applicationKeys The encrypted key blobs returned by the remote vault service. These + * were wrapped with the recovery key. + * @param uid The uid of the recovery agent. + * @return Map from alias to raw key material. + * @throws RemoteException if an error occurred recovering the keys. + */ + public Map<String, byte[]> recoverKeys( @NonNull String sessionId, - @NonNull byte[] recoveryKeyBlob, + @NonNull byte[] encryptedRecoveryKey, @NonNull List<KeyEntryRecoveryData> applicationKeys, - int userId) + int uid) throws RemoteException { checkRecoverKeyStorePermission(); - throw new UnsupportedOperationException(); + + RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId); + if (sessionEntry == null) { + throw new RemoteException(String.format(Locale.US, + "User %d does not have pending session '%s'", uid, sessionId)); + } + + try { + byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey); + return recoverApplicationKeys(recoveryKey, applicationKeys); + } finally { + sessionEntry.destroy(); + mRecoverySessionStorage.remove(uid); + } + } + + /** + * Generates a key named {@code alias} in the recoverable store for the calling uid. Then + * returns the raw key material. + * + * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes. + * + * @hide + */ + public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException { + int uid = Binder.getCallingUid(); + int userId = Binder.getCallingUserHandle().getIdentifier(); + + PlatformEncryptionKey encryptionKey; + + try { + PlatformKeyManager platformKeyManager = PlatformKeyManager.getInstance( + mContext, mDatabase, userId); + encryptionKey = platformKeyManager.getEncryptKey(); + } catch (NoSuchAlgorithmException e) { + // Impossible: all algorithms must be supported by AOSP + throw new RuntimeException(e); + } catch (KeyStoreException | UnrecoverableKeyException e) { + throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage()); + } catch (InsecureUserException e) { + throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); + } + + try { + return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias); + } catch (KeyStoreException | InvalidKeyException e) { + throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage()); + } catch (RecoverableKeyStorageException e) { + throw new ServiceSpecificException(ERROR_DATABASE_ERROR, e.getMessage()); + } + } + + private byte[] decryptRecoveryKey( + RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse) + throws RemoteException { + try { + byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( + sessionEntry.getKeyClaimant(), + sessionEntry.getVaultParams(), + encryptedClaimResponse); + return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); + } catch (InvalidKeyException | AEADBadTagException e) { + throw new RemoteException( + "Failed to decrypt recovery key", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } catch (NoSuchAlgorithmException e) { + // Should never happen: all the algorithms used are required by AOSP implementations + throw new RemoteException( + "Missing required algorithm", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } + } + + /** + * Uses {@code recoveryKey} to decrypt {@code applicationKeys}. + * + * @return Map from alias to raw key material. + * @throws RemoteException if an error occurred decrypting the keys. + */ + private Map<String, byte[]> recoverApplicationKeys( + @NonNull byte[] recoveryKey, + @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException { + HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>(); + for (KeyEntryRecoveryData applicationKey : applicationKeys) { + String alias = new String(applicationKey.getAlias(), StandardCharsets.UTF_8); + byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial(); + + try { + byte[] keyMaterial = + KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial); + keyMaterialByAlias.put(alias, keyMaterial); + } catch (NoSuchAlgorithmException e) { + // Should never happen: all the algorithms used are required by AOSP implementations + throw new RemoteException( + "Missing required algorithm", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } catch (InvalidKeyException | AEADBadTagException e) { + throw new RemoteException( + "Failed to recover key with alias '" + alias + "'", + e, + /*enableSuppression=*/ true, + /*writeableStackTrace=*/ true); + } + } + return keyMaterialByAlias; } /** * This function can only be used inside LockSettingsService. * - * @param storedHashType from {@Code CredentialHash} + * @param storedHashType from {@code CredentialHash} * @param credential - unencrypted String. Password length should be at most 16 symbols {@code * mPasswordMaxLength} * @param userId for user who just unlocked the device. @@ -289,17 +470,23 @@ public class RecoverableKeyStoreManager { */ public void lockScreenSecretAvailable( int storedHashType, @NonNull String credential, int userId) { - // Notify RecoverableKeystoreLoader about unlock - @KeyStoreRecoveryMetadata.LockScreenUiFormat int uiFormat; - if (storedHashType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { - uiFormat = KeyStoreRecoveryMetadata.TYPE_PATTERN; - } else if (isPin(credential)) { - uiFormat = KeyStoreRecoveryMetadata.TYPE_PIN; - } else { - uiFormat = KeyStoreRecoveryMetadata.TYPE_PASSWORD; + // So as not to block the critical path unlocking the phone, defer to another thread. + try { + mExecutorService.execute(KeySyncTask.newInstance( + mContext, + mDatabase, + mSnapshotStorage, + mListenersStorage, + userId, + storedHashType, + credential)); + } catch (NoSuchAlgorithmException e) { + Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); + } catch (KeyStoreException e) { + Log.e(TAG, "Key store error encountered during recoverable key sync", e); + } catch (InsecureUserException e) { + Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e); } - // TODO: check getPendingRecoverySecretTypes. - // TODO: compute SHA256 or Argon2id depending on secret type. } /** This function can only be used inside LockSettingsService. */ @@ -310,17 +497,6 @@ public class RecoverableKeyStoreManager { throw new UnsupportedOperationException(); } - @VisibleForTesting - boolean isPin(@NonNull String credential) { - for (int i = 0; i < credential.length(); i++) { - char c = credential.charAt(i); - if (c < '0' || c > '9') { - return false; - } - } - return true; - } - private void checkRecoverKeyStorePermission() { mContext.enforceCallingOrSelfPermission( RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE, diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java new file mode 100644 index 000000000000..c925329ed81c --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings.recoverablekeystore; + +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +/** + * In memory storage for listeners to be notified when new recovery snapshot is available. This + * class is thread-safe. It is used on two threads - the service thread and the thread that runs the + * {@link KeySyncTask}. + * + * @hide + */ +public class RecoverySnapshotListenersStorage { + private static final String TAG = "RecoverySnapshotLstnrs"; + + @GuardedBy("this") + private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>(); + + /** + * Sets new listener for the recovery agent, identified by {@code uid}. + * + * @param recoveryAgentUid uid of the recovery agent. + * @param intent PendingIntent which will be triggered when new snapshot is available. + */ + public synchronized void setSnapshotListener( + int recoveryAgentUid, @Nullable PendingIntent intent) { + Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid); + mAgentIntents.put(recoveryAgentUid, intent); + } + + /** + * Returns {@code true} if a listener has been set for the recovery agent. + */ + public synchronized boolean hasListener(int recoveryAgentUid) { + return mAgentIntents.get(recoveryAgentUid) != null; + } + + /** + * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not + * registered. + * + * @param recoveryAgentUid uid of recovery agent. + */ + public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) { + PendingIntent intent = mAgentIntents.get(recoveryAgentUid); + if (intent != null) { + try { + intent.send(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, + "Failed to trigger PendingIntent for " + recoveryAgentUid, + e); + } + } + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java index d8a2d31f6703..807ee034e269 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java @@ -230,7 +230,7 @@ public class SecureBox { * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload} - * cannot be validated + * cannot be validated, or if the payload is not a valid SecureBox V2 payload. * @hide */ public static byte[] decrypt( @@ -244,12 +244,14 @@ public class SecureBox { throw new IllegalArgumentException("Both the private key and shared secret are empty"); } header = emptyByteArrayIfNull(header); - encryptedPayload = emptyByteArrayIfNull(encryptedPayload); + if (encryptedPayload == null) { + throw new NullPointerException("Encrypted payload must not be null."); + } ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload); byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length); if (!Arrays.equals(version, VERSION)) { - throw new IllegalArgumentException("The payload was not encrypted by SecureBox v2"); + throw new AEADBadTagException("The payload was not encrypted by SecureBox v2"); } byte[] senderPublicKeyBytes; @@ -271,12 +273,13 @@ public class SecureBox { return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header); } - private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) { + private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) + throws AEADBadTagException { byte[] output = new byte[length]; try { buffer.get(output); } catch (BufferUnderflowException ex) { - throw new IllegalArgumentException("The encrypted payload is too short"); + throw new AEADBadTagException("The encrypted payload is too short"); } return output; } @@ -369,7 +372,13 @@ public class SecureBox { } } - @VisibleForTesting + /** + * Encodes public key in format expected by the secure hardware module. This is used as part + * of the vault params. + * + * @param publicKey The public key. + * @return The key packed into a 65-byte array. + */ static byte[] encodePublicKey(PublicKey publicKey) { ECPoint point = ((ECPublicKey) publicKey).getW(); byte[] x = point.getAffineX().toByteArray(); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java index dfa173c8d463..54aa9f082ad9 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java @@ -17,6 +17,7 @@ package com.android.server.locksettings.recoverablekeystore; import android.util.Log; +import android.security.recoverablekeystore.RecoverableKeyStoreLoader; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -45,6 +46,7 @@ public class WrappedKey { private static final int GCM_TAG_LENGTH_BITS = 128; private final int mPlatformKeyGenerationId; + private final int mRecoveryStatus; private final byte[] mNonce; private final byte[] mKeyMaterial; @@ -94,22 +96,43 @@ public class WrappedKey { return new WrappedKey( /*nonce=*/ cipher.getIV(), /*keyMaterial=*/ encryptedKeyMaterial, - /*platformKeyGenerationId=*/ wrappingKey.getGenerationId()); + /*platformKeyGenerationId=*/ wrappingKey.getGenerationId(), + RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS); } /** - * A new instance. + * A new instance with default recovery status. * * @param nonce The nonce with which the key material was encrypted. * @param keyMaterial The encrypted bytes of the key material. * @param platformKeyGenerationId The generation ID of the key used to wrap this key. * + * @see RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS * @hide */ public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) { mNonce = nonce; mKeyMaterial = keyMaterial; mPlatformKeyGenerationId = platformKeyGenerationId; + mRecoveryStatus = RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS; + } + + /** + * A new instance. + * + * @param nonce The nonce with which the key material was encrypted. + * @param keyMaterial The encrypted bytes of the key material. + * @param platformKeyGenerationId The generation ID of the key used to wrap this key. + * @param recoveryStatus recovery status of the key. + * + * @hide + */ + public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId, + int recoveryStatus) { + mNonce = nonce; + mKeyMaterial = keyMaterial; + mPlatformKeyGenerationId = platformKeyGenerationId; + mRecoveryStatus = recoveryStatus; } /** @@ -130,7 +153,6 @@ public class WrappedKey { return mKeyMaterial; } - /** * Returns the generation ID of the platform key, with which this key was wrapped. * @@ -141,6 +163,15 @@ public class WrappedKey { } /** + * Returns recovery status of the key. + * + * @hide + */ + public int getRecoveryStatus() { + return mRecoveryStatus; + } + + /** * Unwraps the {@code wrappedKeys} with the {@code platformKey}. * * @return The unwrapped keys, indexed by alias. diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java index ed570c3b660c..5ca5da4ead56 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java @@ -16,20 +16,30 @@ package com.android.server.locksettings.recoverablekeystore.storage; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; import android.util.Log; import com.android.server.locksettings.recoverablekeystore.WrappedKey; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.StringJoiner; /** * Database of recoverable key information. @@ -80,6 +90,7 @@ public class RecoverableKeyStoreDb { values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial()); values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED); values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId()); + values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus()); return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); } @@ -94,7 +105,8 @@ public class RecoverableKeyStoreDb { KeysEntry._ID, KeysEntry.COLUMN_NAME_NONCE, KeysEntry.COLUMN_NAME_WRAPPED_KEY, - KeysEntry.COLUMN_NAME_GENERATION_ID}; + KeysEntry.COLUMN_NAME_GENERATION_ID, + KeysEntry.COLUMN_NAME_RECOVERY_STATUS}; String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; @@ -128,11 +140,73 @@ public class RecoverableKeyStoreDb { cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); int generationId = cursor.getInt( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID)); - return new WrappedKey(nonce, keyMaterial, generationId); + int recoveryStatus = cursor.getInt( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); + return new WrappedKey(nonce, keyMaterial, generationId, recoveryStatus); } } /** + * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}. + * + * @param uid of the application + * + * @return Map from Aliases to status. + * + * @hide + */ + public @NonNull Map<String, Integer> getStatusForAllKeys(int uid) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + String[] projection = { + KeysEntry._ID, + KeysEntry.COLUMN_NAME_ALIAS, + KeysEntry.COLUMN_NAME_RECOVERY_STATUS}; + String selection = + KeysEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(uid)}; + + try ( + Cursor cursor = db.query( + KeysEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + HashMap<String, Integer> statuses = new HashMap<>(); + while (cursor.moveToNext()) { + String alias = cursor.getString( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); + int recoveryStatus = cursor.getInt( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); + statuses.put(alias, recoveryStatus); + } + return statuses; + } + } + + /** + * Updates status for given key. + * @param uid of the application + * @param alias of the key + * @param status - new status + * @return number of updated entries. + * @hide + **/ + public int setRecoveryStatus(int uid, String alias, int status) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, status); + String selection = + KeysEntry.COLUMN_NAME_UID + " = ? AND " + + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; + return db.update(KeysEntry.TABLE_NAME, values, selection, + new String[] {String.valueOf(uid), alias}); + } + + /** * Returns all keys for the given {@code userId} and {@code platformKeyGenerationId}. * * @param userId User id of the profile to which all the keys are associated. @@ -148,7 +222,8 @@ public class RecoverableKeyStoreDb { KeysEntry._ID, KeysEntry.COLUMN_NAME_NONCE, KeysEntry.COLUMN_NAME_WRAPPED_KEY, - KeysEntry.COLUMN_NAME_ALIAS}; + KeysEntry.COLUMN_NAME_ALIAS, + KeysEntry.COLUMN_NAME_RECOVERY_STATUS}; String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ? AND " + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?"; @@ -173,7 +248,10 @@ public class RecoverableKeyStoreDb { cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); String alias = cursor.getString( cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); - keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId)); + int recoveryStatus = cursor.getInt( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS)); + keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId, + recoveryStatus)); } return keys; } @@ -226,11 +304,366 @@ public class RecoverableKeyStoreDb { } /** + * Updates the public key of the recovery service into the database. + * + * @param userId The uid of the profile the application is running under. + * @param uid The uid of the application to whom the key belongs. + * @param publicKey The public key of the recovery service. + * @return The primary key of the inserted row, or -1 if failed. + * + * @hide + */ + public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded()); + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; + + ensureRecoveryServiceMetadataEntryExists(userId, uid); + return db.update( + RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); + } + + /** + * Returns the uid of the recovery agent for the given user, or -1 if none is set. + */ + public int getRecoveryAgentUid(int userId) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + + String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID }; + String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; + String[] selectionArguments = { Integer.toString(userId) }; + + try ( + Cursor cursor = db.query( + RecoveryServiceMetadataEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + int count = cursor.getCount(); + if (count == 0) { + return -1; + } + cursor.moveToFirst(); + return cursor.getInt( + cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID)); + } + } + + /** + * Returns the public key of the recovery service. + * + * @param userId The uid of the profile the application is running under. + * @param uid The uid of the application who initializes the local recovery components. + * + * @hide + */ + @Nullable + public PublicKey getRecoveryServicePublicKey(int userId, int uid) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + + String[] projection = { + RecoveryServiceMetadataEntry._ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_UID, + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY}; + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; + + try ( + Cursor cursor = db.query( + RecoveryServiceMetadataEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, + String.format(Locale.US, + "%d PublicKey entries found for userId=%d uid=%d. " + + "Should only ever be 0 or 1.", count, userId, uid)); + return null; + } + cursor.moveToFirst(); + int idx = cursor.getColumnIndexOrThrow( + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY); + if (cursor.isNull(idx)) { + return null; + } + byte[] keyBytes = cursor.getBlob(idx); + try { + return decodeX509Key(keyBytes); + } catch (InvalidKeySpecException e) { + Log.wtf(TAG, + String.format(Locale.US, + "Recovery service public key entry cannot be decoded for " + + "userId=%d uid=%d.", + userId, uid)); + return null; + } + } + } + + /** + * Updates the list of user secret types used for end-to-end encryption. + * If no secret types are set, recovery snapshot will not be created. + * See {@code KeyStoreRecoveryMetadata} + * + * @param userId The uid of the profile the application is running under. + * @param uid The uid of the application. + * @param secretTypes list of secret types + * @return The primary key of the updated row, or -1 if failed. + * + * @hide + */ + public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + StringJoiner joiner = new StringJoiner(","); + Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i))); + String typesAsCsv = joiner.toString(); + values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv); + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + ensureRecoveryServiceMetadataEntryExists(userId, uid); + return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, + new String[] {String.valueOf(userId), String.valueOf(uid)}); + } + + /** + * Returns the list of secret types used for end-to-end encryption. + * + * @param userId The uid of the profile the application is running under. + * @param uid The uid of the application who initialized the local recovery components. + * @return Secret types or empty array, if types were not set. + * + * @hide + */ + public @NonNull int[] getRecoverySecretTypes(int userId, int uid) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + + String[] projection = { + RecoveryServiceMetadataEntry._ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_UID, + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES}; + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; + + try ( + Cursor cursor = db.query( + RecoveryServiceMetadataEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + int count = cursor.getCount(); + if (count == 0) { + return new int[]{}; + } + if (count > 1) { + Log.wtf(TAG, + String.format(Locale.US, + "%d deviceId entries found for userId=%d uid=%d. " + + "Should only ever be 0 or 1.", count, userId, uid)); + return new int[]{}; + } + cursor.moveToFirst(); + int idx = cursor.getColumnIndexOrThrow( + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES); + if (cursor.isNull(idx)) { + return new int[]{}; + } + String csv = cursor.getString(idx); + if (TextUtils.isEmpty(csv)) { + return new int[]{}; + } + String[] types = csv.split(","); + int[] result = new int[types.length]; + for (int i = 0; i < types.length; i++) { + try { + result[i] = Integer.parseInt(types[i]); + } catch (NumberFormatException e) { + Log.wtf(TAG, "String format error " + e); + } + } + return result; + } + } + + /** + * Returns the first (and only?) public key for {@code userId}. + * + * @param userId The uid of the profile whose keys are to be synced. + * @return The public key, or null if none exists. + */ + @Nullable + public PublicKey getRecoveryServicePublicKey(int userId) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + + String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY }; + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; + String[] selectionArguments = { Integer.toString(userId) }; + + try ( + Cursor cursor = db.query( + RecoveryServiceMetadataEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + if (cursor.getCount() < 1) { + return null; + } + + cursor.moveToFirst(); + byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow( + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY)); + + try { + return decodeX509Key(keyBytes); + } catch (InvalidKeySpecException e) { + Log.wtf(TAG, "Could not decode public key for " + userId); + return null; + } + } + } + + /** + * Updates the server parameters given by the application initializing the local recovery + * components. + * + * @param userId The uid of the profile the application is running under. + * @param uid The uid of the application. + * @param serverParameters The server parameters. + * @return The primary key of the inserted row, or -1 if failed. + * + * @hide + */ + public long setServerParameters(int userId, int uid, long serverParameters) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters); + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; + + ensureRecoveryServiceMetadataEntryExists(userId, uid); + return db.update( + RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); + } + + /** + * Returns the server paramters that was previously set by the application who initialized the + * local recovery service components. + * + * @param userId The uid of the profile the application is running under. + * @param uid The uid of the application who initialized the local recovery components. + * @return The server parameters that were previously set, or null if there's none. + * + * @hide + */ + @Nullable + public Long getServerParameters(int userId, int uid) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + + String[] projection = { + RecoveryServiceMetadataEntry._ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_UID, + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS}; + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; + + try ( + Cursor cursor = db.query( + RecoveryServiceMetadataEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, + String.format(Locale.US, + "%d deviceId entries found for userId=%d uid=%d. " + + "Should only ever be 0 or 1.", count, userId, uid)); + return null; + } + cursor.moveToFirst(); + int idx = cursor.getColumnIndexOrThrow( + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS); + if (cursor.isNull(idx)) { + return null; + } else { + return cursor.getLong(idx); + } + } + } + + /** + * Creates an empty row in the recovery service metadata table if such a row doesn't exist for + * the given userId and uid, so db.update will succeed. + */ + private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId); + values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid); + db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, + values, SQLiteDatabase.CONFLICT_IGNORE); + } + + /** * Closes all open connections to the database. */ public void close() { mKeyStoreDbHelper.close(); } - // TODO: Add method for updating the 'last synced' time. + @Nullable + private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException { + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes); + try { + return KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } catch (NoSuchAlgorithmException e) { + // Should never happen + throw new RuntimeException(e); + } + } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java index dcd1827d8576..8f773ddd4598 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java @@ -62,6 +62,11 @@ class RecoverableKeyStoreDbContract { * Timestamp of when this key was last synced with remote storage, or -1 if never synced. */ static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at"; + + /** + * Status of the key sync {@code RecoverableKeyStoreLoader#setRecoveryStatus} + */ + static final String COLUMN_NAME_RECOVERY_STATUS = "recovery_status"; } /** @@ -81,4 +86,36 @@ class RecoverableKeyStoreDbContract { */ static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id"; } + + /** + * Table holding metadata of the recovery service. + */ + static class RecoveryServiceMetadataEntry implements BaseColumns { + static final String TABLE_NAME = "recovery_service_metadata"; + + /** + * The user id of the profile the application is running under. + */ + static final String COLUMN_NAME_USER_ID = "user_id"; + + /** + * The uid of the application that initializes the local recovery components. + */ + static final String COLUMN_NAME_UID = "uid"; + + /** + * The public key of the recovery service. + */ + static final String COLUMN_NAME_PUBLIC_KEY = "public_key"; + + /** + * Secret types used for end-to-end encryption. + */ + static final String COLUMN_NAME_SECRET_TYPES = "secret_types"; + + /** + * The server parameters of the recovery service. + */ + static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters"; + } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java index b017fbb59a65..5b07f3e28e83 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.server.locksettings.recoverablekeystore.storage; import android.content.Context; @@ -5,6 +21,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry; /** @@ -24,6 +41,7 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB," + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER," + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER," + + KeysEntry.COLUMN_NAME_RECOVERY_STATUS + " INTEGER," + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + "," + KeysEntry.COLUMN_NAME_ALIAS + "))"; @@ -33,12 +51,27 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE," + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)"; + private static final String SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY = + "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " (" + + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY," + + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER," + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER," + + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB," + + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT," + + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER," + + "UNIQUE(" + + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + "," + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))"; + private static final String SQL_DELETE_KEYS_ENTRY = "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME; private static final String SQL_DELETE_USER_METADATA_ENTRY = "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME; + private static final String SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY = + "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME; + RecoverableKeyStoreDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @@ -47,12 +80,14 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_KEYS_ENTRY); db.execSQL(SQL_CREATE_USER_METADATA_ENTRY); + db.execSQL(SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DELETE_KEYS_ENTRY); db.execSQL(SQL_DELETE_USER_METADATA_ENTRY); + db.execSQL(SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY); onCreate(db); } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java index bc56ae1dc181..f7633e4cee8d 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java @@ -129,15 +129,17 @@ public class RecoverySessionStorage implements Destroyable { public static class Entry implements Destroyable { private final byte[] mLskfHash; private final byte[] mKeyClaimant; + private final byte[] mVaultParams; private final String mSessionId; /** * @hide */ - public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant) { - this.mLskfHash = lskfHash; - this.mSessionId = sessionId; - this.mKeyClaimant = keyClaimant; + public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant, byte[] vaultParams) { + mLskfHash = lskfHash; + mSessionId = sessionId; + mKeyClaimant = keyClaimant; + mVaultParams = vaultParams; } /** @@ -160,6 +162,15 @@ public class RecoverySessionStorage implements Destroyable { } /** + * Returns the vault params associated with the session. + * + * @hide + */ + public byte[] getVaultParams() { + return mVaultParams; + } + + /** * Overwrites the memory for the lskf hash and key claimant. * * @hide diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java new file mode 100644 index 000000000000..d1a1629d101e --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings.recoverablekeystore.storage; + +import android.annotation.Nullable; +import android.security.recoverablekeystore.KeyStoreRecoveryData; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +/** + * In-memory storage for recovery snapshots. + * + * <p>Recovery snapshots are generated after a successful screen unlock. They are only generated if + * the recoverable keystore has been mutated since the previous snapshot. This class stores only the + * latest snapshot for each user. + * + * <p>This class is thread-safe. It is used both on the service thread and the + * {@link com.android.server.locksettings.recoverablekeystore.KeySyncTask} thread. + */ +public class RecoverySnapshotStorage { + @GuardedBy("this") + private final SparseArray<KeyStoreRecoveryData> mSnapshotByUserId = new SparseArray<>(); + + /** + * Sets the latest {@code snapshot} for the user {@code userId}. + */ + public synchronized void put(int userId, KeyStoreRecoveryData snapshot) { + mSnapshotByUserId.put(userId, snapshot); + } + + /** + * Returns the latest snapshot for user {@code userId}, or null if none exists. + */ + @Nullable + public synchronized KeyStoreRecoveryData get(int userId) { + return mSnapshotByUserId.get(userId); + } + + /** + * Removes any (if any) snapshot associated with user {@code userId}. + */ + public synchronized void remove(int userId) { + mSnapshotByUserId.remove(userId); + } +} diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java index 171703ac8933..f35e6ec92dae 100644 --- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java @@ -33,6 +33,7 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.net.INetworkWatchlistManager; @@ -92,6 +93,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { } } + @GuardedBy("mLoggingSwitchLock") private volatile boolean mIsLoggingEnabled = false; private final Object mLoggingSwitchLock = new Object(); @@ -220,36 +222,11 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { } } - /** - * Set a new network watchlist. - * This method should be called by ConfigUpdater only. - * - * @return True if network watchlist is updated. - */ - public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests, - List<byte[]> domainsSha256Digests, - List<byte[]> ipAddressesCrc32Digests, - List<byte[]> ipAddressesSha256Digests) { - Slog.i(TAG, "Setting network watchlist"); - if (domainsCrc32Digests == null || domainsSha256Digests == null - || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) { - Slog.e(TAG, "Parameters cannot be null"); - return false; - } - if (domainsCrc32Digests.size() != domainsSha256Digests.size() - || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) { - Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests"); - return false; - } - if (domainsSha256Digests.size() + ipAddressesSha256Digests.size() - > MAX_NUM_OF_WATCHLIST_DIGESTS) { - Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS); - return false; - } - mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests, - ipAddressesCrc32Digests, ipAddressesSha256Digests); - Slog.i(TAG, "Set network watchlist: Success"); - return true; + @Override + public void reloadWatchlist() throws RemoteException { + enforceWatchlistLoggingPermission(); + Slog.i(TAG, "Reloading watchlist"); + mSettings.reloadSettings(); } @Override diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java index f48463f5ae63..838aa53938fa 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java @@ -21,10 +21,12 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; import android.util.Pair; import com.android.internal.util.HexDump; +import java.io.File; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.HashMap; @@ -83,9 +85,12 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { HashMap<String, String> appDigestCNCList; } + static File getSystemWatchlistDbFile() { + return new File(Environment.getDataSystemDirectory(), NAME); + } + private WatchlistReportDbHelper(Context context) { - super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(), - null, VERSION); + super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION); // Memory optimization - close idle connections after 30s of inactivity setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS); } diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java index c50f0d56c992..70002ea21aff 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java @@ -19,8 +19,10 @@ package com.android.server.net.watchlist; import android.os.Environment; import android.util.AtomicFile; import android.util.Log; +import android.util.Slog; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.HexDump; @@ -51,10 +53,9 @@ import java.util.zip.CRC32; class WatchlistSettings { private static final String TAG = "WatchlistSettings"; - // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml - static final String SYSTEM_WATCHLIST_DIR = "network_watchlist"; - - private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml"; + // Watchlist config that pushed by ConfigUpdater. + private static final String NETWORK_WATCHLIST_DB_PATH = + "/data/misc/network_watchlist/network_watchlist.xml"; private static class XmlTags { private static final String WATCHLIST_SETTINGS = "watchlist-settings"; @@ -65,86 +66,74 @@ class WatchlistSettings { private static final String HASH = "hash"; } - private static WatchlistSettings sInstance = new WatchlistSettings(); + private static class CrcShaDigests { + final HarmfulDigests crc32Digests; + final HarmfulDigests sha256Digests; + + public CrcShaDigests(HarmfulDigests crc32Digests, HarmfulDigests sha256Digests) { + this.crc32Digests = crc32Digests; + this.sha256Digests = sha256Digests; + } + } + + private final static WatchlistSettings sInstance = new WatchlistSettings(); private final AtomicFile mXmlFile; - private final Object mLock = new Object(); - private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>()); - private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>()); - private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>()); - private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>()); - public static synchronized WatchlistSettings getInstance() { + private volatile CrcShaDigests mDomainDigests; + private volatile CrcShaDigests mIpDigests; + + public static WatchlistSettings getInstance() { return sInstance; } private WatchlistSettings() { - this(getSystemWatchlistFile(WATCHLIST_XML_FILE)); + this(new File(NETWORK_WATCHLIST_DB_PATH)); } @VisibleForTesting protected WatchlistSettings(File xmlFile) { mXmlFile = new AtomicFile(xmlFile); - readSettingsLocked(); - } - - static File getSystemWatchlistFile(String filename) { - final File dataSystemDir = Environment.getDataSystemDirectory(); - final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR); - systemWatchlistDir.mkdirs(); - return new File(systemWatchlistDir, filename); + reloadSettings(); } - private void readSettingsLocked() { - synchronized (mLock) { - FileInputStream stream; - try { - stream = mXmlFile.openRead(); - } catch (FileNotFoundException e) { - Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath()); - return; - } + public void reloadSettings() { + try (FileInputStream stream = mXmlFile.openRead()){ final List<byte[]> crc32DomainList = new ArrayList<>(); final List<byte[]> sha256DomainList = new ArrayList<>(); final List<byte[]> crc32IpList = new ArrayList<>(); final List<byte[]> sha256IpList = new ArrayList<>(); - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(stream, StandardCharsets.UTF_8.name()); - parser.nextTag(); - parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS); - while (parser.nextTag() == XmlPullParser.START_TAG) { - String tagName = parser.getName(); - switch (tagName) { - case XmlTags.CRC32_DOMAIN: - parseHash(parser, tagName, crc32DomainList); - break; - case XmlTags.CRC32_IP: - parseHash(parser, tagName, crc32IpList); - break; - case XmlTags.SHA256_DOMAIN: - parseHash(parser, tagName, sha256DomainList); - break; - case XmlTags.SHA256_IP: - parseHash(parser, tagName, sha256IpList); - break; - default: - Log.w(TAG, "Unknown element: " + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS); - writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList); - } catch (IllegalStateException | NullPointerException | NumberFormatException | - XmlPullParserException | IOException | IndexOutOfBoundsException e) { - Log.w(TAG, "Failed parsing " + e); - } finally { - try { - stream.close(); - } catch (IOException e) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, StandardCharsets.UTF_8.name()); + parser.nextTag(); + parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS); + while (parser.nextTag() == XmlPullParser.START_TAG) { + String tagName = parser.getName(); + switch (tagName) { + case XmlTags.CRC32_DOMAIN: + parseHash(parser, tagName, crc32DomainList); + break; + case XmlTags.CRC32_IP: + parseHash(parser, tagName, crc32IpList); + break; + case XmlTags.SHA256_DOMAIN: + parseHash(parser, tagName, sha256DomainList); + break; + case XmlTags.SHA256_IP: + parseHash(parser, tagName, sha256IpList); + break; + default: + Log.w(TAG, "Unknown element: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); } } + parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS); + writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList); + Log.i(TAG, "Reload watchlist done"); + } catch (IllegalStateException | NullPointerException | NumberFormatException | + XmlPullParserException | IOException | IndexOutOfBoundsException e) { + Slog.e(TAG, "Failed parsing xml", e); } } @@ -161,101 +150,61 @@ class WatchlistSettings { } /** - * Write network watchlist settings to disk. - * Adb should not use it, should use writeSettingsToMemory directly instead. - */ - public void writeSettingsToDisk(List<byte[]> newCrc32DomainList, - List<byte[]> newSha256DomainList, - List<byte[]> newCrc32IpList, - List<byte[]> newSha256IpList) { - synchronized (mLock) { - FileOutputStream stream; - try { - stream = mXmlFile.startWrite(); - } catch (IOException e) { - Log.w(TAG, "Failed to write display settings: " + e); - return; - } - - try { - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(stream, StandardCharsets.UTF_8.name()); - out.startDocument(null, true); - out.startTag(null, XmlTags.WATCHLIST_SETTINGS); - - writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList); - writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList); - writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList); - writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList); - - out.endTag(null, XmlTags.WATCHLIST_SETTINGS); - out.endDocument(); - mXmlFile.finishWrite(stream); - writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList, - newSha256IpList); - } catch (IOException e) { - Log.w(TAG, "Failed to write display settings, restoring backup.", e); - mXmlFile.failWrite(stream); - } - } - } - - /** * Write network watchlist settings to memory. */ public void writeSettingsToMemory(List<byte[]> newCrc32DomainList, List<byte[]> newSha256DomainList, List<byte[]> newCrc32IpList, List<byte[]> newSha256IpList) { - synchronized (mLock) { - mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList); - mCrc32IpDigests = new HarmfulDigests(newCrc32IpList); - mSha256DomainDigests = new HarmfulDigests(newSha256DomainList); - mSha256IpDigests = new HarmfulDigests(newSha256IpList); - } - } - - private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet) - throws IOException { - out.startTag(null, tagName); - for (byte[] hash : hashSet) { - out.startTag(null, XmlTags.HASH); - out.text(HexDump.toHexString(hash)); - out.endTag(null, XmlTags.HASH); - } - out.endTag(null, tagName); + mDomainDigests = new CrcShaDigests(new HarmfulDigests(newCrc32DomainList), + new HarmfulDigests(newSha256DomainList)); + mIpDigests = new CrcShaDigests(new HarmfulDigests(newCrc32IpList), + new HarmfulDigests(newSha256IpList)); } public boolean containsDomain(String domain) { + final CrcShaDigests domainDigests = mDomainDigests; + if (domainDigests == null) { + Slog.wtf(TAG, "domainDigests should not be null"); + return false; + } // First it does a quick CRC32 check. final byte[] crc32 = getCrc32(domain); - if (!mCrc32DomainDigests.contains(crc32)) { + if (!domainDigests.crc32Digests.contains(crc32)) { return false; } // Now we do a slow SHA256 check. final byte[] sha256 = getSha256(domain); - return mSha256DomainDigests.contains(sha256); + return domainDigests.sha256Digests.contains(sha256); } public boolean containsIp(String ip) { + final CrcShaDigests ipDigests = mIpDigests; + if (ipDigests == null) { + Slog.wtf(TAG, "ipDigests should not be null"); + return false; + } // First it does a quick CRC32 check. final byte[] crc32 = getCrc32(ip); - if (!mCrc32IpDigests.contains(crc32)) { + if (!ipDigests.crc32Digests.contains(crc32)) { return false; } // Now we do a slow SHA256 check. final byte[] sha256 = getSha256(ip); - return mSha256IpDigests.contains(sha256); + return ipDigests.sha256Digests.contains(sha256); } - /** Get CRC32 of a string */ + /** Get CRC32 of a string + * + * TODO: Review if we should use CRC32 or other algorithms + */ private byte[] getCrc32(String str) { final CRC32 crc = new CRC32(); crc.update(str.getBytes()); final long tmp = crc.getValue(); - return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255), - (byte)(tmp >> 8 & 255), (byte)(tmp & 255)}; + return new byte[]{(byte) (tmp >> 24 & 255), (byte) (tmp >> 16 & 255), + (byte) (tmp >> 8 & 255), (byte) (tmp & 255)}; } /** Get SHA256 of a string */ @@ -273,12 +222,12 @@ class WatchlistSettings { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Domain CRC32 digest list:"); - mCrc32DomainDigests.dump(fd, pw, args); + mDomainDigests.crc32Digests.dump(fd, pw, args); pw.println("Domain SHA256 digest list:"); - mSha256DomainDigests.dump(fd, pw, args); + mDomainDigests.sha256Digests.dump(fd, pw, args); pw.println("Ip CRC32 digest list:"); - mCrc32IpDigests.dump(fd, pw, args); + mIpDigests.crc32Digests.dump(fd, pw, args); pw.println("Ip SHA256 digest list:"); - mSha256IpDigests.dump(fd, pw, args); + mIpDigests.sha256Digests.dump(fd, pw, args); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 696d89575e2d..1157af41ccec 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -163,10 +163,12 @@ import android.content.pm.PackageCleanItem; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; +import android.content.pm.PackageList; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageManager.PackageInfoFlags; +import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ActivityIntentInfo; import android.content.pm.PackageParser.Package; @@ -757,6 +759,9 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mPackages") final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>(); + @GuardedBy("mPackages") + final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>(); + class PackageParserCallback implements PackageParser.Callback { @Override public final boolean hasFeature(String feature) { return PackageManagerService.this.hasSystemFeature(feature, 0); @@ -2095,6 +2100,10 @@ public class PackageManagerService extends IPackageManager.Stub } } + if (allNewUsers && !update) { + notifyPackageAdded(packageName); + } + // Log current value of "unknown sources" setting EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, getUnknownSourcesSettings()); @@ -12983,6 +12992,34 @@ public class PackageManagerService extends IPackageManager.Stub }); } + @Override + public void notifyPackageAdded(String packageName) { + final PackageListObserver[] observers; + synchronized (mPackages) { + if (mPackageListObservers.size() == 0) { + return; + } + observers = (PackageListObserver[]) mPackageListObservers.toArray(); + } + for (int i = observers.length - 1; i >= 0; --i) { + observers[i].onPackageAdded(packageName); + } + } + + @Override + public void notifyPackageRemoved(String packageName) { + final PackageListObserver[] observers; + synchronized (mPackages) { + if (mPackageListObservers.size() == 0) { + return; + } + observers = (PackageListObserver[]) mPackageListObservers.toArray(); + } + for (int i = observers.length - 1; i >= 0; --i) { + observers[i].onPackageRemoved(packageName); + } + } + /** * Sends a broadcast for the given action. * <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with @@ -17640,6 +17677,7 @@ public class PackageManagerService extends IPackageManager.Stub removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null, null, broadcastUsers, instantUserIds); + packageSender.notifyPackageRemoved(removedPackage); } } if (removedAppId >= 0) { @@ -20395,10 +20433,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } } sUserManager.systemReady(); - // If we upgraded grant all default permissions before kicking off. for (int userId : grantPermissionsUserIds) { - mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId); + mDefaultPermissionPolicy.grantDefaultPermissions(userId); } if (grantPermissionsUserIds == EMPTY_INT_ARRAY) { @@ -22445,8 +22482,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } void onNewUserCreated(final int userId) { + mDefaultPermissionPolicy.grantDefaultPermissions(userId); synchronized(mPackages) { - mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId); // If permission review for legacy apps is required, we represent // dagerous permissions for such apps as always granted runtime // permissions to keep per user flag state whether review is needed. @@ -22933,6 +22970,29 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public PackageList getPackageList(PackageListObserver observer) { + synchronized (mPackages) { + final int N = mPackages.size(); + final ArrayList<String> list = new ArrayList<>(N); + for (int i = 0; i < N; i++) { + list.add(mPackages.keyAt(i)); + } + final PackageList packageList = new PackageList(list, observer); + if (observer != null) { + mPackageListObservers.add(packageList); + } + return packageList; + } + } + + @Override + public void removePackageListObserver(PackageListObserver observer) { + synchronized (mPackages) { + mPackageListObservers.remove(observer); + } + } + + @Override public PackageParser.Package getDisabledPackage(String packageName) { synchronized (mPackages) { final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName); @@ -23594,4 +23654,6 @@ interface PackageSender { final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds); void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted, boolean includeStopped, int appId, int[] userIds, int[] instantUserIds); + void notifyPackageAdded(String packageName); + void notifyPackageRemoved(String packageName); } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 648f847ac3c5..4cf18149d853 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3122,7 +3122,7 @@ public final class Settings { ATTR_VOLUME_UUID); final VersionInfo ver = findOrCreateVersion(volumeUuid); ver.sdkVersion = XmlUtils.readIntAttribute(parser, ATTR_SDK_VERSION); - ver.databaseVersion = XmlUtils.readIntAttribute(parser, ATTR_SDK_VERSION); + ver.databaseVersion = XmlUtils.readIntAttribute(parser, ATTR_DATABASE_VERSION); ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 768eb8f37549..c3dce3133026 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -27,7 +27,6 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; -import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IStopUserCallback; import android.app.KeyguardManager; @@ -795,12 +794,7 @@ public class UserManagerService extends IUserManager.Stub { "target should only be specified when we are disabling quiet mode."); } - if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) { - throw new SecurityException("Not allowed to call trySetQuietModeEnabled, " - + "caller is foreground default launcher " - + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission"); - } - + ensureCanModifyQuietMode(callingPackage, Binder.getCallingUid(), target != null); final long identity = Binder.clearCallingIdentity(); try { if (enableQuietMode) { @@ -824,35 +818,44 @@ public class UserManagerService extends IUserManager.Stub { } /** - * An app can modify quiet mode if the caller meets one of the condition: + * The caller can modify quiet mode if it meets one of these conditions: * <ul> * <li>Has system UID or root UID</li> * <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li> * <li>Has {@link Manifest.permission#MANAGE_USERS}</li> * </ul> + * <p> + * If caller wants to start an intent after disabling the quiet mode, it must has + * {@link Manifest.permission#MANAGE_USERS}. */ - private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) { + private void ensureCanModifyQuietMode(String callingPackage, int callingUid, + boolean startIntent) { if (hasManageUsersPermission()) { - return true; + return; + } + if (startIntent) { + throw new SecurityException("MANAGE_USERS permission is required to start intent " + + "after disabling quiet mode."); } - final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission( Manifest.permission.MODIFY_QUIET_MODE, callingUid, -1, true) == PackageManager.PERMISSION_GRANTED; if (hasModifyQuietModePermission) { - return true; + return; } + verifyCallingPackage(callingPackage, callingUid); final ShortcutServiceInternal shortcutInternal = LocalServices.getService(ShortcutServiceInternal.class); if (shortcutInternal != null) { boolean isForegroundLauncher = shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid); if (isForegroundLauncher) { - return true; + return; } } - return false; + throw new SecurityException("Can't modify quiet mode, caller is neither foreground " + + "default launcher nor has MANAGE_USERS/MODIFY_QUIET_MODE permission"); } private void setQuietModeEnabled( @@ -3932,4 +3935,16 @@ public class UserManagerService extends IUserManager.Stub { return false; } } + + /** + * Check if the calling package name matches with the calling UID, throw + * {@link SecurityException} if not. + */ + private void verifyCallingPackage(String callingPackage, int callingUid) { + int packageUid = mPm.getPackageUid(callingPackage, 0, UserHandle.getUserId(callingUid)); + if (packageUid != callingUid) { + throw new SecurityException("Specified package " + callingPackage + + " does not match the calling uid " + callingUid); + } + } } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 45649880854a..924084325cd5 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -115,6 +115,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_AUTOFILL, UserManager.DISALLOW_USER_SWITCH, UserManager.DISALLOW_UNIFIED_PASSWORD, + UserManager.DISALLOW_CONFIG_LOCATION_MODE, + UserManager.DISALLOW_AIRPLANE_MODE }); /** @@ -142,7 +144,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_FUN, UserManager.DISALLOW_SAFE_BOOT, UserManager.DISALLOW_CREATE_WINDOWS, - UserManager.DISALLOW_DATA_ROAMING + UserManager.DISALLOW_DATA_ROAMING, + UserManager.DISALLOW_AIRPLANE_MODE ); /** @@ -197,7 +200,8 @@ public class UserRestrictionsUtils { * Special user restrictions that are always applied to all users no matter who sets them. */ private static final Set<String> PROFILE_GLOBAL_RESTRICTIONS = Sets.newArraySet( - UserManager.ENSURE_VERIFY_APPS + UserManager.ENSURE_VERIFY_APPS, + UserManager.DISALLOW_AIRPLANE_MODE ); /** diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java index 854b70439d56..d6281c51b3fa 100644 --- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java @@ -19,6 +19,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import android.annotation.UserIdInt; +import android.app.ActivityOptions; import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; @@ -74,8 +75,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { public void startActivityAsUser( String callingPackage, ComponentName component, - Rect sourceBounds, - Bundle startActivityOptions, UserHandle user) throws RemoteException { Preconditions.checkNotNull(callingPackage); Preconditions.checkNotNull(component); @@ -103,7 +102,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present. final Intent launchIntent = new Intent(Intent.ACTION_MAIN); launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); - launchIntent.setSourceBounds(sourceBounds); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); // Only package name is set here, as opposed to component name, because intent action and @@ -114,7 +112,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { final long ident = mInjector.clearCallingIdentity(); try { launchIntent.setComponent(component); - mContext.startActivityAsUser(launchIntent, startActivityOptions, user); + mContext.startActivityAsUser(launchIntent, + ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), user); } finally { mInjector.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 01f3c576f72b..d38dc9ae55f0 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; +import android.content.pm.PackageList; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; @@ -252,11 +253,11 @@ public final class DefaultPermissionGrantPolicy { } } - public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) { + public void grantDefaultPermissions(int userId) { if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) { - grantAllRuntimePermissions(packages, userId); + grantAllRuntimePermissions(userId); } else { - grantPermissionsToSysComponentsAndPrivApps(packages, userId); + grantPermissionsToSysComponentsAndPrivApps(userId); grantDefaultSystemHandlerPermissions(userId); grantDefaultPermissionExceptions(userId); } @@ -278,10 +279,14 @@ public final class DefaultPermissionGrantPolicy { } } - private void grantAllRuntimePermissions( - Collection<PackageParser.Package> packages, int userId) { + private void grantAllRuntimePermissions(int userId) { Log.i(TAG, "Granting all runtime permissions for user " + userId); - for (PackageParser.Package pkg : packages) { + final PackageList packageList = mServiceInternal.getPackageList(); + for (String packageName : packageList.getPackageNames()) { + final PackageParser.Package pkg = mServiceInternal.getPackage(packageName); + if (pkg == null) { + continue; + } grantRuntimePermissionsForPackage(userId, pkg); } } @@ -290,10 +295,14 @@ public final class DefaultPermissionGrantPolicy { mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS); } - private void grantPermissionsToSysComponentsAndPrivApps( - Collection<PackageParser.Package> packages, int userId) { + private void grantPermissionsToSysComponentsAndPrivApps(int userId) { Log.i(TAG, "Granting permissions to platform components for user " + userId); - for (PackageParser.Package pkg : packages) { + final PackageList packageList = mServiceInternal.getPackageList(); + for (String packageName : packageList.getPackageNames()) { + final PackageParser.Package pkg = mServiceInternal.getPackage(packageName); + if (pkg == null) { + continue; + } if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg) || !doesPackageSupportRuntimePermissions(pkg) || pkg.requestedPermissions.isEmpty()) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9752a57e2423..076c0e4d2d4a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2126,14 +2126,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { @Override public int onAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, - Animation openAnimation, Animation closeAnimation) { - return handleStartTransitionForKeyguardLw(transit, openAnimation); + IBinder closeToken, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { + return handleStartTransitionForKeyguardLw(transit, duration); } @Override public void onAppTransitionCancelledLocked(int transit) { - handleStartTransitionForKeyguardLw(transit, null /* transit */); + handleStartTransitionForKeyguardLw(transit, 0 /* duration */); } }); mKeyguardDelegate = new KeyguardServiceDelegate(mContext, @@ -3989,7 +3989,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private int handleStartTransitionForKeyguardLw(int transit, @Nullable Animation anim) { + private int handleStartTransitionForKeyguardLw(int transit, long duration) { if (mKeyguardOccludedChanged) { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded=" + mPendingKeyguardOccluded); @@ -4000,13 +4000,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (AppTransition.isKeyguardGoingAwayTransit(transit)) { if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation"); - final long startTime = anim != null - ? SystemClock.uptimeMillis() + anim.getStartOffset() - : SystemClock.uptimeMillis(); - final long duration = anim != null - ? anim.getDuration() - : 0; - startKeyguardExitAnimation(startTime, duration); + startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration); } return 0; } diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java index af7e91c5bfa6..e6e4d7f98250 100644 --- a/services/core/java/com/android/server/policy/StatusBarController.java +++ b/services/core/java/com/android/server/policy/StatusBarController.java @@ -37,8 +37,6 @@ import com.android.server.statusbar.StatusBarManagerInternal; */ public class StatusBarController extends BarController { - private static final long TRANSITION_DURATION = 120L; - private final AppTransitionListener mAppTransitionListener = new AppTransitionListener() { @@ -57,17 +55,15 @@ public class StatusBarController extends BarController { @Override public int onAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, final Animation openAnimation, final Animation closeAnimation) { + IBinder closeToken, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { mHandler.post(new Runnable() { @Override public void run() { StatusBarManagerInternal statusbar = getStatusBarInternal(); if (statusbar != null) { - long startTime = calculateStatusBarTransitionStartTime(openAnimation, - closeAnimation); - long duration = closeAnimation != null || openAnimation != null - ? TRANSITION_DURATION : 0; - statusbar.appTransitionStarting(startTime, duration); + statusbar.appTransitionStarting(statusBarAnimationStartTime, + statusBarAnimationDuration); } } }); @@ -128,72 +124,4 @@ public class StatusBarController extends BarController { public AppTransitionListener getAppTransitionListener() { return mAppTransitionListener; } - - /** - * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this - * calculates the timings for the corresponding status bar transition. - * - * @return the desired start time of the status bar transition, in uptime millis - */ - private static long calculateStatusBarTransitionStartTime(Animation openAnimation, - Animation closeAnimation) { - if (openAnimation != null && closeAnimation != null) { - TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation); - TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation); - if (openTranslateAnimation != null) { - - // Some interpolators are extremely quickly mostly finished, but not completely. For - // our purposes, we need to find the fraction for which ther interpolator is mostly - // there, and use that value for the calculation. - float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator()); - return SystemClock.uptimeMillis() - + openTranslateAnimation.getStartOffset() - + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION; - } else if (closeTranslateAnimation != null) { - return SystemClock.uptimeMillis(); - } else { - return SystemClock.uptimeMillis(); - } - } else { - return SystemClock.uptimeMillis(); - } - } - - /** - * Tries to find a {@link TranslateAnimation} inside the {@code animation}. - * - * @return the found animation, {@code null} otherwise - */ - private static TranslateAnimation findTranslateAnimation(Animation animation) { - if (animation instanceof TranslateAnimation) { - return (TranslateAnimation) animation; - } else if (animation instanceof AnimationSet) { - AnimationSet set = (AnimationSet) animation; - for (int i = 0; i < set.getAnimations().size(); i++) { - Animation a = set.getAnimations().get(i); - if (a instanceof TranslateAnimation) { - return (TranslateAnimation) a; - } - } - } - return null; - } - - /** - * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which - * {@code interpolator(t + eps) > 0.99}. - */ - private static float findAlmostThereFraction(Interpolator interpolator) { - float val = 0.5f; - float adj = 0.25f; - while (adj >= 0.01f) { - if (interpolator.getInterpolation(val) < 0.99f) { - val += adj; - } else { - val -= adj; - } - adj /= 2; - } - return val; - } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 0b590bc4702b..86b22bb9c219 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -57,6 +57,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; +import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; @@ -1976,6 +1977,16 @@ public final class PowerManagerService extends SystemService return true; } } + + final ArrayList<WorkChain> workChains = wakeLock.mWorkSource.getWorkChains(); + if (workChains != null) { + for (int k = 0; k < workChains.size(); k++) { + final int uid = workChains.get(k).getAttributionUid(); + if (userId == UserHandle.getUserId(uid)) { + return true; + } + } + } } return userId == UserHandle.getUserId(wakeLock.mOwnerUid); } @@ -2441,6 +2452,7 @@ public final class PowerManagerService extends SystemService float screenAutoBrightnessAdjustment = 0.0f; boolean autoBrightness = (mScreenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + boolean brightnessIsTemporary = false; if (!mBootCompleted) { // Keep the brightness steady during boot. This requires the // bootloader brightness and the default brightness to be identical. @@ -2455,6 +2467,7 @@ public final class PowerManagerService extends SystemService brightnessSetByUser = false; } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) { screenBrightness = mTemporaryScreenBrightnessSettingOverride; + brightnessIsTemporary = true; } else if (isValidBrightness(mScreenBrightnessSetting)) { screenBrightness = mScreenBrightnessSetting; } @@ -2464,6 +2477,7 @@ public final class PowerManagerService extends SystemService mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) { screenAutoBrightnessAdjustment = mTemporaryScreenAutoBrightnessAdjustmentSettingOverride; + brightnessIsTemporary = true; } else if (isValidAutoBrightnessAdjustment( mScreenAutoBrightnessAdjustmentSetting)) { screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting; @@ -2479,6 +2493,7 @@ public final class PowerManagerService extends SystemService mDisplayPowerRequest.screenAutoBrightnessAdjustment = screenAutoBrightnessAdjustment; mDisplayPowerRequest.brightnessSetByUser = brightnessSetByUser; + mDisplayPowerRequest.brightnessIsTemporary = brightnessIsTemporary; mDisplayPowerRequest.useAutoBrightness = autoBrightness; mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness(); @@ -4324,7 +4339,7 @@ public final class PowerManagerService extends SystemService mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); } - if (ws != null && ws.size() != 0) { + if (ws != null && !ws.isEmpty()) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.UPDATE_DEVICE_STATS, null); } else { @@ -4379,7 +4394,7 @@ public final class PowerManagerService extends SystemService } mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); - if (ws != null && ws.size() != 0) { + if (ws != null && !ws.isEmpty()) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.UPDATE_DEVICE_STATS, null); } else { diff --git a/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java new file mode 100644 index 000000000000..3b7ddc2e803f --- /dev/null +++ b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.updates; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkWatchlistManager; +import android.os.RemoteException; +import android.util.Slog; + +public class NetworkWatchlistInstallReceiver extends ConfigUpdateInstallReceiver { + + public NetworkWatchlistInstallReceiver() { + super("/data/misc/network_watchlist/", "network_watchlist.xml", "metadata/", "version"); + } + + @Override + protected void postInstall(Context context, Intent intent) { + try { + context.getSystemService(NetworkWatchlistManager.class).reloadWatchlist(); + } catch (Exception e) { + // Network Watchlist is not available + Slog.wtf("NetworkWatchlistInstallReceiver", "Unable to reload watchlist"); + } + } +} diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index 84d47b4d66b5..ed4543ef82fe 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -30,6 +30,8 @@ import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; */ interface AnimationAdapter { + long STATUS_BAR_TRANSITION_DURATION = 120L; + /** * @return Whether we should detach the wallpaper during the animation. * @see Animation#setDetachWallpaper @@ -66,4 +68,13 @@ interface AnimationAdapter { * @return The approximate duration of the animation, in milliseconds. */ long getDurationHint(); + + /** + * If this animation is run as an app opening animation, this calculates the start time for all + * status bar transitions that happen as part of the app opening animation, which will be + * forwarded to SystemUI. + * + * @return the desired start time of the status bar transition, in uptime millis + */ + long getStatusBarTransitionsStartTime(); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index c2cbced2311c..2ac758344f52 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -38,23 +38,22 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; -import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import static com.android.server.wm.proto.AppTransitionProto.APP_TRANSITION_STATE; import static com.android.server.wm.proto.AppTransitionProto.LAST_USED_APP_TRANSITION; import android.annotation.Nullable; import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.graphics.GraphicBuffer; import android.graphics.Path; import android.graphics.Rect; @@ -63,7 +62,9 @@ import android.os.Debug; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; +import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -199,6 +200,13 @@ public class AppTransition implements Dump { private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6; private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7; private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8; + + /** + * Refers to the transition to activity started by using {@link + * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle) + * }. + */ + private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9; private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE; // These are the possible states for the enter/exit activities during a thumbnail transition @@ -407,17 +415,23 @@ public class AppTransition implements Dump { * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another * layout pass needs to be done */ - int goodToGo(int transit, AppWindowAnimator topOpeningAppAnimator, - AppWindowAnimator topClosingAppAnimator, ArraySet<AppWindowToken> openingApps, + int goodToGo(int transit, AppWindowToken topOpeningApp, + AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps, ArraySet<AppWindowToken> closingApps) { mNextAppTransition = TRANSIT_UNSET; mNextAppTransitionFlags = 0; setAppTransitionState(APP_STATE_RUNNING); + final AnimationAdapter topOpeningAnim = topOpeningApp != null + ? topOpeningApp.getAnimation() + : null; int redoLayout = notifyAppTransitionStartingLocked(transit, - topOpeningAppAnimator != null ? topOpeningAppAnimator.mAppToken.token : null, - topClosingAppAnimator != null ? topClosingAppAnimator.mAppToken.token : null, - topOpeningAppAnimator != null ? topOpeningAppAnimator.animation : null, - topClosingAppAnimator != null ? topClosingAppAnimator.animation : null); + topOpeningApp != null ? topOpeningApp.token : null, + topClosingApp != null ? topClosingApp.token : null, + topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, + topOpeningAnim != null + ? topOpeningAnim.getStatusBarTransitionsStartTime() + : SystemClock.uptimeMillis(), + AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); mService.getDefaultDisplayContentLocked().getDockedDividerController() .notifyAppTransitionStarting(openingApps, transit); @@ -425,8 +439,8 @@ public class AppTransition implements Dump { // ended it already then we don't need to wait. if (transit == TRANSIT_DOCK_TASK_FROM_RECENTS && !mProlongedAnimationsEnded) { for (int i = openingApps.size() - 1; i >= 0; i--) { - final AppWindowAnimator appAnimator = openingApps.valueAt(i).mAppAnimator; - appAnimator.startProlongAnimation(PROLONG_ANIMATION_AT_START); + final AppWindowToken app = openingApps.valueAt(i); + app.startDelayingAnimationStart(); } } return redoLayout; @@ -496,11 +510,12 @@ public class AppTransition implements Dump { } private int notifyAppTransitionStartingLocked(int transit, IBinder openToken, - IBinder closeToken, Animation openAnimation, Animation closeAnimation) { + IBinder closeToken, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { int redoLayout = 0; for (int i = 0; i < mListeners.size(); i++) { redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken, - closeToken, openAnimation, closeAnimation); + closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration); } return redoLayout; } @@ -1605,6 +1620,17 @@ public class AppTransition implements Dump { + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); } + } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS + && (transit == TRANSIT_ACTIVITY_OPEN + || transit == TRANSIT_TASK_OPEN + || transit == TRANSIT_TASK_TO_FRONT)) { + a = loadAnimationRes("android", enter + ? com.android.internal.R.anim.activity_open_enter + : com.android.internal.R.anim.activity_open_exit); + Slog.v(TAG, + "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:" + + " anim=" + a + " transit=" + appTransitionToString(transit) + + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); } else { int animAttr = 0; switch (transit) { @@ -1833,6 +1859,17 @@ public class AppTransition implements Dump { } /** + * @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS} + */ + void overridePendingAppTransitionStartCrossProfileApps() { + if (isTransitionSet()) { + clear(); + mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS; + postAnimationCallback(); + } + } + + /** * If a future is set for the app transition specs, fetch it in another thread. */ private void fetchAppTransitionSpecsFromFuture() { @@ -1855,9 +1892,6 @@ public class AppTransition implements Dump { mNextAppTransitionFutureCallback, null /* finishedCallback */, mNextAppTransitionScaleUp); mNextAppTransitionFutureCallback = null; - if (specs != null) { - mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp); - } } mService.requestTraversal(); }); diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java deleted file mode 100644 index 5c1d5b255d88..000000000000 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; -import static com.android.server.wm.AppTransition.TRANSIT_UNSET; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET; -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; - -import android.graphics.Matrix; -import android.util.Slog; -import android.util.TimeUtils; -import android.view.Choreographer; -import android.view.Display; -import android.view.SurfaceControl; -import android.view.animation.Animation; -import android.view.animation.Transformation; - -import java.io.PrintWriter; -import java.util.ArrayList; - -public class AppWindowAnimator { - static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowAnimator" : TAG_WM; - - private static final int PROLONG_ANIMATION_DISABLED = 0; - static final int PROLONG_ANIMATION_AT_END = 1; - static final int PROLONG_ANIMATION_AT_START = 2; - - final AppWindowToken mAppToken; - final WindowManagerService mService; - final WindowAnimator mAnimator; - - boolean animating; - boolean wasAnimating; - Animation animation; - boolean hasTransformation; - final Transformation transformation = new Transformation(); - - // Have we been asked to have this token keep the screen frozen? - // Protect with mAnimator. - boolean freezingScreen; - - /** - * How long we last kept the screen frozen. - */ - int lastFreezeDuration; - - // Offset to the window of all layers in the token, for use by - // AppWindowToken animations. - int animLayerAdjustment; - - // Propagated from AppWindowToken.allDrawn, to determine when - // the state changes. - boolean allDrawn; - - // Special surface for thumbnail animation. If deferThumbnailDestruction is enabled, then we - // will make sure that the thumbnail is destroyed after the other surface is completed. This - // requires that the duration of the two animations are the same. - SurfaceControl thumbnail; - int thumbnailTransactionSeq; - private int mThumbnailLayer; - - Animation thumbnailAnimation; - final Transformation thumbnailTransformation = new Transformation(); - // This flag indicates that the destruction of the thumbnail surface is synchronized with - // another animation, so defer the destruction of this thumbnail surface for a single frame - // after the secondary animation completes. - boolean deferThumbnailDestruction; - // This flag is set if the animator has deferThumbnailDestruction set and has reached the final - // frame of animation. It will extend the animation by one frame and then clean up afterwards. - boolean deferFinalFrameCleanup; - // If true when the animation hits the last frame, it will keep running on that last frame. - // This is used to synchronize animation with Recents and we wait for Recents to tell us to - // finish or for a new animation be set as fail-safe mechanism. - private int mProlongAnimation; - // Whether the prolong animation can be removed when animation is set. The purpose of this is - // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it - // when new animation is set. - private boolean mClearProlongedAnimation; - private int mTransit; - private int mTransitFlags; - - /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */ - ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<>(); - - /** True if the current animation was transferred from another AppWindowAnimator. - * See {@link #transferCurrentAnimation}*/ - boolean usingTransferredAnimation = false; - - private boolean mSkipFirstFrame = false; - private int mStackClip = STACK_CLIP_BEFORE_ANIM; - - static final Animation sDummyAnimation = new DummyAnimation(); - - public AppWindowAnimator(final AppWindowToken atoken, WindowManagerService service) { - mAppToken = atoken; - mService = service; - mAnimator = mService.mAnimator; - } - - public void setAnimation(Animation anim, int width, int height, int parentWidth, - int parentHeight, boolean skipFirstFrame, int stackClip, int transit, - int transitFlags) { - if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken - + ": " + anim + " wxh=" + width + "x" + height - + " hasContentToDisplay=" + mAppToken.hasContentToDisplay()); - animation = anim; - animating = false; - if (!anim.isInitialized()) { - anim.initialize(width, height, parentWidth, parentHeight); - } - anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); - anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked()); - int zorder = anim.getZAdjustment(); - int adj = 0; - if (zorder == Animation.ZORDER_TOP) { - adj = TYPE_LAYER_OFFSET; - } else if (zorder == Animation.ZORDER_BOTTOM) { - adj = -TYPE_LAYER_OFFSET; - } - - if (animLayerAdjustment != adj) { - animLayerAdjustment = adj; - updateLayers(); - } - // Start out animation gone if window is gone, or visible if window is visible. - transformation.clear(); - transformation.setAlpha(mAppToken.isVisible() ? 1 : 0); - hasTransformation = true; - mStackClip = stackClip; - - mSkipFirstFrame = skipFirstFrame; - mTransit = transit; - mTransitFlags = transitFlags; - - if (!mAppToken.fillsParent()) { - anim.setBackgroundColor(0); - } - if (mClearProlongedAnimation) { - mProlongAnimation = PROLONG_ANIMATION_DISABLED; - } else { - mClearProlongedAnimation = true; - } - } - - public void setDummyAnimation() { - if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken - + " hasContentToDisplay=" + mAppToken.hasContentToDisplay()); - animation = sDummyAnimation; - hasTransformation = true; - transformation.clear(); - transformation.setAlpha(mAppToken.isVisible() ? 1 : 0); - } - - void setNullAnimation() { - animation = null; - usingTransferredAnimation = false; - } - - public void clearAnimation() { - if (animation != null) { - animating = true; - } - clearThumbnail(); - setNullAnimation(); - if (mAppToken.deferClearAllDrawn) { - mAppToken.clearAllDrawn(); - } - mStackClip = STACK_CLIP_BEFORE_ANIM; - mTransit = TRANSIT_UNSET; - mTransitFlags = 0; - } - - public boolean isAnimating() { - return animation != null || mAppToken.inPendingTransaction; - } - - /** - * @return whether an animation is about to start, i.e. the animation is set already but we - * haven't processed the first frame yet. - */ - boolean isAnimationStarting() { - return animation != null && !animating; - } - - public int getTransit() { - return mTransit; - } - - int getTransitFlags() { - return mTransitFlags; - } - - public void clearThumbnail() { - if (thumbnail != null) { - thumbnail.hide(); - mService.mWindowPlacerLocked.destroyAfterTransaction(thumbnail); - thumbnail = null; - } - deferThumbnailDestruction = false; - } - - int getStackClip() { - return mStackClip; - } - - void transferCurrentAnimation( - AppWindowAnimator toAppAnimator, WindowStateAnimator transferWinAnimator) { - - if (animation != null) { - toAppAnimator.animation = animation; - toAppAnimator.animating = animating; - toAppAnimator.animLayerAdjustment = animLayerAdjustment; - setNullAnimation(); - animLayerAdjustment = 0; - toAppAnimator.updateLayers(); - updateLayers(); - toAppAnimator.usingTransferredAnimation = true; - toAppAnimator.mTransit = mTransit; - } - if (transferWinAnimator != null) { - mAllAppWinAnimators.remove(transferWinAnimator); - toAppAnimator.mAllAppWinAnimators.add(transferWinAnimator); - toAppAnimator.hasTransformation = transferWinAnimator.mAppAnimator.hasTransformation; - if (toAppAnimator.hasTransformation) { - toAppAnimator.transformation.set(transferWinAnimator.mAppAnimator.transformation); - } else { - toAppAnimator.transformation.clear(); - } - transferWinAnimator.mAppAnimator = toAppAnimator; - } - } - - private void updateLayers() { - mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */); - } - - private void stepThumbnailAnimation(long currentTime) { - thumbnailTransformation.clear(); - final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime); - thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation); - - ScreenRotationAnimation screenRotationAnimation = - mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); - final boolean screenAnimation = screenRotationAnimation != null - && screenRotationAnimation.isAnimating(); - if (screenAnimation) { - thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation()); - } - // cache often used attributes locally - final float tmpFloats[] = mService.mTmpFloats; - thumbnailTransformation.getMatrix().getValues(tmpFloats); - if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, - "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X] - + ", " + tmpFloats[Matrix.MTRANS_Y]); - thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]); - if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, - "thumbnail", "alpha=" + thumbnailTransformation.getAlpha() - + " layer=" + mThumbnailLayer - + " matrix=[" + tmpFloats[Matrix.MSCALE_X] - + "," + tmpFloats[Matrix.MSKEW_Y] - + "][" + tmpFloats[Matrix.MSKEW_X] - + "," + tmpFloats[Matrix.MSCALE_Y] + "]"); - thumbnail.setAlpha(thumbnailTransformation.getAlpha()); - thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], - tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); - thumbnail.setWindowCrop(thumbnailTransformation.getClipRect()); - } - - /** - * Sometimes we need to synchronize the first frame of animation with some external event, e.g. - * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton - * and keep producing the first frame of the animation. - */ - private long getAnimationFrameTime(Animation animation, long currentTime) { - if (mProlongAnimation == PROLONG_ANIMATION_AT_START) { - animation.setStartTime(currentTime); - return currentTime + 1; - } - return currentTime; - } - - private boolean stepAnimation(long currentTime) { - if (animation == null) { - return false; - } - transformation.clear(); - final long animationFrameTime = getAnimationFrameTime(animation, currentTime); - boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation); - if (!hasMoreFrames) { - if (deferThumbnailDestruction && !deferFinalFrameCleanup) { - // We are deferring the thumbnail destruction, so extend the animation for one more - // (dummy) frame before we clean up - deferFinalFrameCleanup = true; - hasMoreFrames = true; - } else { - if (false && DEBUG_ANIM) Slog.v(TAG, - "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames + - ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation); - deferFinalFrameCleanup = false; - if (mProlongAnimation == PROLONG_ANIMATION_AT_END) { - hasMoreFrames = true; - } else { - setNullAnimation(); - clearThumbnail(); - if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ " - + currentTime); - } - } - } - hasTransformation = hasMoreFrames; - return hasMoreFrames; - } - - private long getStartTimeCorrection() { - if (mSkipFirstFrame) { - - // If the transition is an animation in which the first frame doesn't change the screen - // contents at all, we can just skip it and start at the second frame. So we shift the - // start time of the animation forward by minus the frame duration. - return -Choreographer.getInstance().getFrameIntervalNanos() / TimeUtils.NANOS_PER_MS; - } else { - return 0; - } - } - - // This must be called while inside a transaction. - boolean stepAnimationLocked(long currentTime) { - if (mAppToken.okToAnimate()) { - // We will run animations as long as the display isn't frozen. - - if (animation == sDummyAnimation) { - // This guy is going to animate, but not yet. For now count - // it as not animating for purposes of scheduling transactions; - // when it is really time to animate, this will be set to - // a real animation and the next call will execute normally. - return false; - } - - if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed) - && animation != null) { - if (!animating) { - if (DEBUG_ANIM) Slog.v(TAG, - "Starting animation in " + mAppToken + - " @ " + currentTime + " scale=" - + mService.getTransitionAnimationScaleLocked() - + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating); - long correction = getStartTimeCorrection(); - animation.setStartTime(currentTime + correction); - animating = true; - if (thumbnail != null) { - thumbnail.show(); - thumbnailAnimation.setStartTime(currentTime + correction); - } - mSkipFirstFrame = false; - } - if (stepAnimation(currentTime)) { - // animation isn't over, step any thumbnail and that's - // it for now. - if (thumbnail != null) { - stepThumbnailAnimation(currentTime); - } - return true; - } - } - } else if (animation != null) { - // If the display is frozen, and there is a pending animation, - // clear it and make sure we run the cleanup code. - animating = true; - animation = null; - } - - hasTransformation = false; - - if (!animating && animation == null) { - return false; - } - - mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "AppWindowToken"); - - clearAnimation(); - animating = false; - if (animLayerAdjustment != 0) { - animLayerAdjustment = 0; - updateLayers(); - } - if (mService.mInputMethodTarget != null - && mService.mInputMethodTarget.mAppToken == mAppToken) { - mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */); - } - - if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken - + ": reportedVisible=" + mAppToken.reportedVisible - + " okToDisplay=" + mAppToken.okToDisplay() - + " okToAnimate=" + mAppToken.okToAnimate() - + " startingDisplayed=" + mAppToken.startingDisplayed); - - transformation.clear(); - - final int numAllAppWinAnimators = mAllAppWinAnimators.size(); - for (int i = 0; i < numAllAppWinAnimators; i++) { - mAllAppWinAnimators.get(i).mWin.onExitAnimationDone(); - } - mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token); - return false; - } - - // This must be called while inside a transaction. - boolean showAllWindowsLocked() { - boolean isAnimating = false; - final int NW = mAllAppWinAnimators.size(); - for (int i=0; i<NW; i++) { - WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i); - if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + winAnimator); - winAnimator.mWin.performShowLocked(); - isAnimating |= winAnimator.isAnimationSet(); - } - return isAnimating; - } - - void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken); - pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator); - pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen); - pw.print(" allDrawn="); pw.print(allDrawn); - pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment); - if (lastFreezeDuration != 0) { - pw.print(prefix); pw.print("lastFreezeDuration="); - TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println(); - } - if (animating || animation != null) { - pw.print(prefix); pw.print("animating="); pw.println(animating); - pw.print(prefix); pw.print("animation="); pw.println(animation); - pw.print(prefix); pw.print("mTransit="); pw.println(mTransit); - pw.print(prefix); pw.print("mTransitFlags="); pw.println(mTransitFlags); - } - if (hasTransformation) { - pw.print(prefix); pw.print("XForm: "); - transformation.printShortString(pw); - pw.println(); - } - if (thumbnail != null) { - pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail); - pw.print(" layer="); pw.println(mThumbnailLayer); - pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation); - pw.print(prefix); pw.print("thumbnailTransformation="); - pw.println(thumbnailTransformation.toShortString()); - } - for (int i=0; i<mAllAppWinAnimators.size(); i++) { - WindowStateAnimator wanim = mAllAppWinAnimators.get(i); - pw.print(prefix); pw.print("App Win Anim #"); pw.print(i); - pw.print(": "); pw.println(wanim); - } - } - - void startProlongAnimation(int prolongType) { - mProlongAnimation = prolongType; - mClearProlongedAnimation = false; - } - - void endProlongedAnimation() { - mProlongAnimation = PROLONG_ANIMATION_DISABLED; - } - - // This is an animation that does nothing: it just immediately finishes - // itself every time it is called. It is used as a stub animation in cases - // where we want to synchronize multiple things that may be animating. - static final class DummyAnimation extends Animation { - @Override - public boolean getTransformation(long currentTime, Transformation outTransformation) { - return false; - } - } - -} diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 00a0d3d18f9c..ae9f28bcb6a6 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS; @@ -25,7 +24,6 @@ import static com.android.server.wm.AppTransition.TRANSIT_UNSET; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; @@ -34,16 +32,13 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.ActivityManager.TaskSnapshot; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.graphics.Rect; import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.Trace; import android.util.Slog; -import android.view.DisplayInfo; import android.view.IApplicationToken; import com.android.internal.annotations.VisibleForTesting; @@ -339,7 +334,7 @@ public class AppWindowContainerController if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility(" + mToken + ", visible=" + visible + "): " + mService.mAppTransition - + " hidden=" + wtoken.hidden + " hiddenRequested=" + + " hidden=" + wtoken.isHidden() + " hiddenRequested=" + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6)); mService.mOpeningApps.remove(wtoken); @@ -364,11 +359,11 @@ public class AppWindowContainerController wtoken.startingMoved = false; // If the token is currently hidden (should be the common case), or has been // stopped, then we need to set up to wait for its windows to be ready. - if (wtoken.hidden || wtoken.mAppStopped) { + if (wtoken.isHidden() || wtoken.mAppStopped) { wtoken.clearAllDrawn(); // If the app was already visible, don't reset the waitingToShow state. - if (wtoken.hidden) { + if (wtoken.isHidden()) { wtoken.waitingToShow = true; } @@ -389,21 +384,6 @@ public class AppWindowContainerController // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. if (wtoken.okToAnimate() && mService.mAppTransition.isTransitionSet()) { - // A dummy animation is a placeholder animation which informs others that an - // animation is going on (in this case an application transition). If the animation - // was transferred from another application/animator, no dummy animator should be - // created since an animation is already in progress. - if (wtoken.mAppAnimator.usingTransferredAnimation - && wtoken.mAppAnimator.animation == null) { - Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken - + ", using null transferred animation!"); - } - if (!wtoken.mAppAnimator.usingTransferredAnimation && - (!wtoken.startingDisplayed || mService.mSkipAppTransitionAnimation)) { - if (DEBUG_APP_TRANSITIONS) Slog.v( - TAG_WM, "Setting dummy animation on: " + wtoken); - wtoken.mAppAnimator.setDummyAnimation(); - } wtoken.inPendingTransaction = true; if (visible) { mService.mOpeningApps.add(wtoken); @@ -423,7 +403,7 @@ public class AppWindowContainerController if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "TRANSIT_TASK_OPEN_BEHIND, " + " adding " + focusedToken + " to mOpeningApps"); // Force animation to be loaded. - focusedToken.hidden = true; + focusedToken.setHidden(true); mService.mOpeningApps.add(focusedToken); } } @@ -710,7 +690,7 @@ public class AppWindowContainerController return; } if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + mToken + ": hidden=" - + mContainer.hidden + " freezing=" + mContainer.mAppAnimator.freezingScreen); + + mContainer.isHidden() + " freezing=" + mContainer.isFreezingScreen()); mContainer.stopFreezingScreen(true, force); } } diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java new file mode 100644 index 000000000000..c16a5315060f --- /dev/null +++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.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 com.android.server.wm; + +import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; + +import android.graphics.GraphicBuffer; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.os.Binder; +import android.util.Slog; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Builder; +import android.view.SurfaceControl.Transaction; +import android.view.animation.Animation; + +import com.android.server.wm.SurfaceAnimator.Animatable; + +/** + * Represents a surface that is displayed over an {@link AppWindowToken} + */ +class AppWindowThumbnail implements Animatable { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowThumbnail" : TAG_WM; + + private final AppWindowToken mAppToken; + private final SurfaceControl mSurfaceControl; + private final SurfaceAnimator mSurfaceAnimator; + private final int mWidth; + private final int mHeight; + + AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) { + mAppToken = appToken; + mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, appToken.mService); + mWidth = thumbnailHeader.getWidth(); + mHeight = thumbnailHeader.getHeight(); + + // Create a new surface for the thumbnail + WindowState window = appToken.findMainWindow(); + + // TODO: This should be attached as a child to the app token, once the thumbnail animations + // use relative coordinates. Once we start animating task we can also consider attaching + // this to the task. + mSurfaceControl = appToken.makeSurface() + .setName("thumbnail anim: " + appToken.toString()) + .setSize(mWidth, mHeight) + .setFormat(PixelFormat.TRANSLUCENT) + .setMetadata(appToken.windowType, + window != null ? window.mOwnerUid : Binder.getCallingUid()) + .build(); + + if (SHOW_TRANSACTIONS) { + Slog.i(TAG, " THUMBNAIL " + mSurfaceControl + ": CREATE"); + } + + // Transfer the thumbnail to the surface + Surface drawSurface = new Surface(); + drawSurface.copyFrom(mSurfaceControl); + drawSurface.attachAndQueueBuffer(thumbnailHeader); + drawSurface.release(); + t.show(mSurfaceControl); + + // We parent the thumbnail to the task, and just place it on top of anything else in the + // task. + t.setLayer(mSurfaceControl, Integer.MAX_VALUE); + } + + void startAnimation(Transaction t, Animation anim) { + anim.restrictDuration(MAX_ANIMATION_DURATION); + anim.scaleCurrentDuration(mAppToken.mService.getTransitionAnimationScaleLocked()); + mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter( + new WindowAnimationSpec(anim, null /* position */, + mAppToken.mService.mAppTransition.canSkipFirstFrame()), + mAppToken.mService.mSurfaceAnimationRunner), false /* hidden */); + } + + private void onAnimationFinished() { + } + + void setShowing(Transaction pendingTransaction, boolean show) { + // TODO: Not needed anymore once thumbnail is attached to the app. + if (show) { + pendingTransaction.show(mSurfaceControl); + } else { + pendingTransaction.hide(mSurfaceControl); + } + } + + void destroy() { + mSurfaceAnimator.cancelAnimation(); + mSurfaceControl.destroy(); + } + + @Override + public Transaction getPendingTransaction() { + return mAppToken.getPendingTransaction(); + } + + @Override + public void commitPendingTransaction() { + mAppToken.commitPendingTransaction(); + } + + @Override + public void destroyAfterPendingTransaction(SurfaceControl surface) { + mAppToken.destroyAfterPendingTransaction(surface); + } + + @Override + public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { + t.setLayer(leash, Integer.MAX_VALUE); + } + + @Override + public void onAnimationLeashDestroyed(Transaction t) { + + // TODO: Once attached to app token, we don't need to hide it immediately if thumbnail + // became visible. + t.hide(mSurfaceControl); + } + + @Override + public Builder makeAnimationLeash() { + return mAppToken.makeSurface(); + } + + @Override + public SurfaceControl getSurfaceControl() { + return mSurfaceControl; + } + + @Override + public SurfaceControl getAnimationLeashParent() { + return mAppToken.getAppAnimationLayer(); + } + + @Override + public SurfaceControl getParentSurfaceControl() { + return mAppToken.getParentSurfaceControl(); + } + + @Override + public int getSurfaceWidth() { + return mWidth; + } + + @Override + public int getSurfaceHeight() { + return mHeight; + } +} diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 94a0cb715ec4..dcd88dd074d1 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -16,10 +16,14 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; @@ -51,22 +55,26 @@ import static com.android.server.wm.proto.AppWindowTokenProto.NAME; import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN; import android.annotation.CallSuper; -import android.annotation.NonNull; import android.app.Activity; +import android.app.WindowConfiguration.WindowingMode; import android.content.res.Configuration; +import android.graphics.GraphicBuffer; +import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; +import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; +import android.view.SurfaceControl.Transaction; +import android.view.animation.Animation; import android.view.IApplicationToken; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; -import android.view.animation.Transformation; import com.android.internal.util.ToBooleanFunction; import com.android.server.input.InputApplicationHandle; @@ -88,11 +96,14 @@ class AppTokenList extends ArrayList<AppWindowToken> { class AppWindowToken extends WindowToken implements WindowManagerService.AppFreezeListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowToken" : TAG_WM; + /** + * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k. + */ + private static final int Z_BOOST_BASE = 800570000; + // Non-null only for application tokens. final IApplicationToken appToken; - @NonNull final AppWindowAnimator mAppAnimator; - final boolean mVoiceInteraction; /** @see WindowContainer#fillsParent() */ @@ -120,6 +131,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private int mNumDrawnWindows; boolean inPendingTransaction; boolean allDrawn; + private boolean mLastAllDrawn; + // Set to true when this app creates a surface while in the middle of an animation. In that // case do not clear allDrawn until the animation completes. boolean deferClearAllDrawn; @@ -189,6 +202,32 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree */ private boolean mCanTurnScreenOn = true; + /** + * If we are running an animation, this determines the transition type. Must be one of + * AppTransition.TRANSIT_* constants. + */ + private int mTransit; + + /** + * If we are running an animation, this determines the flags during this animation. Must be a + * bitwise combination of AppTransition.TRANSIT_FLAG_* constants. + */ + private int mTransitFlags; + + /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ + private boolean mLastSurfaceShowing = true; + + private AppWindowThumbnail mThumbnail; + + /** Have we been asked to have this token keep the screen frozen? */ + private boolean mFreezingScreen; + + /** Whether this token should be boosted at the top of all app window tokens. */ + private boolean mNeedsZBoost; + + private final Point mTmpPoint = new Point(); + private final Rect mTmpRect = new Rect(); + AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint, @@ -206,7 +245,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mRotationAnimationHint = rotationAnimationHint; // Application tokens start out hidden. - hidden = true; + setHidden(true); hiddenRequested = true; } @@ -218,7 +257,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mVoiceInteraction = voiceInteraction; mFillsParent = fillsParent; mInputApplicationHandle = new InputApplicationHandle(this); - mAppAnimator = new AppWindowAnimator(this, service); } void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) { @@ -262,7 +300,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean nowGone = mReportedVisibilityResults.nowGone; boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting; - boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden; + boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !isHidden(); if (!nowGone) { // If the app is not yet gone, then it can only become visible/drawn. if (!nowDrawn) { @@ -325,19 +363,16 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // transition animation // * or this is an opening app and windows are being replaced. boolean visibilityChanged = false; - if (hidden == visible || (hidden && mIsExiting) || (visible && waitingForReplacement())) { + if (isHidden() == visible || (isHidden() && mIsExiting) || (visible && waitingForReplacement())) { final AccessibilityController accessibilityController = mService.mAccessibilityController; boolean changed = false; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, - "Changing app " + this + " hidden=" + hidden + " performLayout=" + performLayout); + "Changing app " + this + " hidden=" + isHidden() + " performLayout=" + performLayout); boolean runningAppAnimation = false; - if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) { - mAppAnimator.setNullAnimation(); - } if (transit != AppTransition.TRANSIT_UNSET) { - if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) { + if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) { delayed = runningAppAnimation = true; } final WindowState window = findMainWindow(); @@ -355,7 +390,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree changed |= win.onAppVisibilityChanged(visible, runningAppAnimation); } - hidden = hiddenRequested = !visible; + setHidden(!visible); + hiddenRequested = !visible; visibilityChanged = true; if (!visible) { stopFreezingScreen(true, true); @@ -373,7 +409,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this - + ": hidden=" + hidden + " hiddenRequested=" + hiddenRequested); + + ": hidden=" + isHidden() + " hiddenRequested=" + hiddenRequested); if (changed) { mService.mInputMonitor.setUpdateInputWindowsNeededLw(); @@ -386,7 +422,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } - if (mAppAnimator.animation != null) { + if (isReallyAnimating()) { delayed = true; } @@ -414,7 +450,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // no animation but there will still be a transition set. // We still need to delay hiding the surface such that it // can be synchronized with showing the next surface in the transition. - if (hidden && !delayed && !mService.mAppTransition.isTransitionSet()) { + if (isHidden() && !delayed && !mService.mAppTransition.isTransitionSet()) { SurfaceControl.openTransaction(); for (int i = mChildren.size() - 1; i >= 0; i--) { mChildren.get(i).mWinAnimator.hide("immediately hidden"); @@ -487,7 +523,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean isVisible() { // If the app token isn't hidden then it is considered visible and there is no need to check // its children windows to see if they are visible. - return !hidden; + return !isHidden(); } @Override @@ -533,7 +569,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + this + " delayed=" + delayed - + " animation=" + mAppAnimator.animation + " animating=" + mAppAnimator.animating); + + " animation=" + getAnimation() + " animating=" + isSelfAnimating()); if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: " + this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4)); @@ -545,7 +581,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // If this window was animating, then we need to ensure that the app transition notifies // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so // add to that list now - if (mAppAnimator.animating) { + if (isSelfAnimating()) { mService.mNoAnimationNotifyOnTransitionFinished.add(token); } @@ -561,8 +597,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } else { // Make sure there is no animation running on this token, so any windows associated // with it will be removed as soon as their animations are complete - mAppAnimator.clearAnimation(); - mAppAnimator.animating = false; + cancelAnimation(); if (stack != null) { stack.mExitingAppTokens.remove(this); } @@ -710,7 +745,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // We set the hidden state to false for the token from a transferred starting window. // We now reset it back to true since the starting window was the last window in the // token. - hidden = true; + setHidden(true); } } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) { // If this is the last window except for a starting transition window, @@ -756,14 +791,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree final WindowState w = mChildren.get(i); w.setWillReplaceWindow(animate); } - if (animate) { - // Set-up dummy animation so we can start treating windows associated with this - // token like they are in transition before the new app window is ready for us to - // run the real transition animation. - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, - "setWillReplaceWindow() Setting dummy animation on: " + this); - mAppAnimator.setDummyAnimation(); - } } void setWillReplaceChildWindows() { @@ -1007,13 +1034,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void startFreezingScreen() { if (DEBUG_ORIENTATION) logWithStack(TAG, "Set freezing of " + appToken + ": hidden=" - + hidden + " freezing=" + mAppAnimator.freezingScreen + " hiddenRequested=" + + isHidden() + " freezing=" + mFreezingScreen + " hiddenRequested=" + hiddenRequested); if (!hiddenRequested) { - if (!mAppAnimator.freezingScreen) { - mAppAnimator.freezingScreen = true; + if (!mFreezingScreen) { + mFreezingScreen = true; mService.registerAppFreezeListener(this); - mAppAnimator.lastFreezeDuration = 0; mService.mAppsFreezingScreen++; if (mService.mAppsFreezingScreen == 1) { mService.startFreezingDisplayLocked(false, 0, 0, getDisplayContent()); @@ -1030,7 +1056,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) { - if (!mAppAnimator.freezingScreen) { + if (!mFreezingScreen) { return; } if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + this + " force=" + force); @@ -1042,10 +1068,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } if (force || unfrozeWindows) { if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "No longer freezing: " + this); - mAppAnimator.freezingScreen = false; + mFreezingScreen = false; mService.unregisterAppFreezeListener(this); - mAppAnimator.lastFreezeDuration = - (int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime); mService.mAppsFreezingScreen--; mService.mLastFinishedFreezeSource = this; } @@ -1111,14 +1135,19 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (fromToken.firstWindowDrawn) { firstWindowDrawn = true; } - if (!fromToken.hidden) { - hidden = false; + if (!fromToken.isHidden()) { + setHidden(false); hiddenRequested = false; mHiddenSetFromTransferredStartingWindow = true; } setClientHidden(fromToken.mClientHidden); - fromToken.mAppAnimator.transferCurrentAnimation( - mAppAnimator, tStartingWindow.mWinAnimator); + + transferAnimation(fromToken); + + // When transferring an animation, we no longer need to apply an animation to the + // the token we transfer the animation over. Thus, remove the animation from + // pending opening apps. + mService.mOpeningApps.remove(this); mService.updateFocusedWindowLocked( UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/); @@ -1142,17 +1171,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return true; } - final AppWindowAnimator tAppAnimator = fromToken.mAppAnimator; - final AppWindowAnimator wAppAnimator = mAppAnimator; - if (tAppAnimator.thumbnail != null) { - // The old token is animating with a thumbnail, transfer that to the new token. - if (wAppAnimator.thumbnail != null) { - wAppAnimator.thumbnail.destroy(); - } - wAppAnimator.thumbnail = tAppAnimator.thumbnail; - wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation; - tAppAnimator.thumbnail = null; - } + // TODO: Transfer thumbnail + return false; } @@ -1160,16 +1180,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return mChildren.size() == 1 && mChildren.get(0) == win; } - void setAllAppWinAnimators() { - final ArrayList<WindowStateAnimator> allAppWinAnimators = mAppAnimator.mAllAppWinAnimators; - allAppWinAnimators.clear(); - - final int windowsCount = mChildren.size(); - for (int j = 0; j < windowsCount; j++) { - (mChildren.get(j)).addWinAnimatorToList(allAppWinAnimators); - } - } - @Override void onAppTransitionDone() { sendingToBottom = false; @@ -1205,19 +1215,43 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } @Override + public void onConfigurationChanged(Configuration newParentConfig) { + final int prevWinMode = getWindowingMode(); + super.onConfigurationChanged(newParentConfig); + final int winMode = getWindowingMode(); + + if (prevWinMode == winMode) { + return; + } + + if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) { + // Entering PiP from fullscreen, reset the snap fraction + mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this); + } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED) { + // Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds + // for the next re-entry into PiP (assuming the activity is not hidden or destroyed) + final TaskStack pinnedStack = mDisplayContent.getPinnedStack(); + if (pinnedStack != null) { + mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this, + pinnedStack.mPreAnimationBounds); + } + } + } + + @Override void checkAppWindowsReadyToShow() { - if (allDrawn == mAppAnimator.allDrawn) { + if (allDrawn == mLastAllDrawn) { return; } - mAppAnimator.allDrawn = allDrawn; + mLastAllDrawn = allDrawn; if (!allDrawn) { return; } // The token has now changed state to having all windows shown... what to do, what to do? - if (mAppAnimator.freezingScreen) { - mAppAnimator.showAllWindowsLocked(); + if (mFreezingScreen) { + showAllWindowsLocked(); stopFreezingScreen(false, true); if (DEBUG_ORIENTATION) Slog.i(TAG, "Setting mOrientationChangeComplete=true because wtoken " + this @@ -1230,7 +1264,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // We can now show all of the drawn windows! if (!mService.mOpeningApps.contains(this)) { - mService.mAnimator.orAnimating(mAppAnimator.showAllWindowsLocked()); + showAllWindowsLocked(); } } } @@ -1300,10 +1334,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (DEBUG_STARTING_WINDOW_VERBOSE && w == startingWindow) { Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen() - + " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen); + + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen); } - if (allDrawn && !mAppAnimator.freezingScreen) { + if (allDrawn && !mFreezingScreen) { return false; } @@ -1320,13 +1354,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (!allDrawn && w.mightAffectAllDrawn()) { if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw() - + ", isAnimationSet=" + winAnimator.isAnimationSet()); + + ", isAnimationSet=" + isSelfAnimating()); if (!w.isDrawnLw()) { Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController + " pv=" + w.mPolicyVisibility + " mDrawState=" + winAnimator.drawStateToString() + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested - + " a=" + winAnimator.isAnimationSet()); + + " a=" + isSelfAnimating()); } } @@ -1338,7 +1372,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: " + this + " w=" + w + " numInteresting=" + mNumInterestingWindows - + " freezingScreen=" + mAppAnimator.freezingScreen + + " freezingScreen=" + mFreezingScreen + " mAppFreezing=" + w.mAppFreezing); isInterestingAndDrawn = true; @@ -1356,21 +1390,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } @Override - void stepAppWindowsAnimation(long currentTime) { - mAppAnimator.wasAnimating = mAppAnimator.animating; - if (mAppAnimator.stepAnimationLocked(currentTime)) { - mAppAnimator.animating = true; - mService.mAnimator.setAnimating(true); - mService.mAnimator.mAppWindowAnimating = true; - } else if (mAppAnimator.wasAnimating) { - // stopped animating, do one more pass through the layout - setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, - DEBUG_LAYOUT_REPEATS ? "appToken " + this + " done" : null); - if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this); - } - } - - @Override boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent // before the non-exiting app tokens. So, we skip the exiting app tokens here. @@ -1521,18 +1540,269 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } @Override - int getAnimLayerAdjustment() { - return mAppAnimator.animLayerAdjustment; + public SurfaceControl getAnimationLeashParent() { + return getAppAnimationLayer(); + } + + boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter, + boolean isVoiceInteraction) { + + if (mService.mDisableTransitionAnimation) { + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { + Slog.v(TAG_WM, "applyAnimation: transition animation is disabled. atoken=" + this); + } + cancelAnimation(); + return false; + } + + // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason + // to animate and it can cause strange artifacts when we unfreeze the display if some + // different animation is running. + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked"); + if (okToAnimate()) { + final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction); + if (a != null) { + final TaskStack stack = getStack(); + mTmpPoint.set(0, 0); + mTmpRect.setEmpty(); + if (stack != null) { + stack.getRelativePosition(mTmpPoint); + stack.getBounds(mTmpRect); + mTmpRect.offsetTo(0, 0); + } + final AnimationAdapter adapter = new LocalAnimationAdapter( + new WindowAnimationSpec(a, mTmpPoint, mTmpRect, + mService.mAppTransition.canSkipFirstFrame(), + mService.mAppTransition.getAppStackClipMode()), + mService.mSurfaceAnimationRunner); + if (a.getZAdjustment() == Animation.ZORDER_TOP) { + mNeedsZBoost = true; + } + startAnimation(getPendingTransaction(), adapter, !isVisible()); + mTransit = transit; + mTransitFlags = mService.mAppTransition.getTransitFlags(); + } + } else { + cancelAnimation(); + } + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + + return isReallyAnimating(); + } + + private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, + boolean isVoiceInteraction) { + final DisplayContent displayContent = getTask().getDisplayContent(); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final int width = displayInfo.appWidth; + final int height = displayInfo.appHeight; + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM, + "applyAnimation: atoken=" + this); + + // Determine the visible rect to calculate the thumbnail clip + final WindowState win = findMainWindow(); + final Rect frame = new Rect(0, 0, width, height); + final Rect displayFrame = new Rect(0, 0, + displayInfo.logicalWidth, displayInfo.logicalHeight); + final Rect insets = new Rect(); + final Rect stableInsets = new Rect(); + Rect surfaceInsets = null; + final boolean freeform = win != null && win.inFreeformWindowingMode(); + if (win != null) { + // Containing frame will usually cover the whole screen, including dialog windows. + // For freeform workspace windows it will not cover the whole screen and it also + // won't exactly match the final freeform window frame (e.g. when overlapping with + // the status bar). In that case we need to use the final frame. + if (freeform) { + frame.set(win.mFrame); + } else { + frame.set(win.mContainingFrame); + } + surfaceInsets = win.getAttrs().surfaceInsets; + insets.set(win.mContentInsets); + stableInsets.set(win.mStableInsets); + } + + if (mLaunchTaskBehind) { + // Differentiate the two animations. This one which is briefly on the screen + // gets the !enter animation, and the other activity which remains on the + // screen gets the enter animation. Both appear in the mOpeningApps set. + enter = false; + } + if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition." + + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter + + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets); + final Configuration displayConfig = displayContent.getConfiguration(); + final Animation a = mService.mAppTransition.loadAnimation(lp, transit, enter, + displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets, + surfaceInsets, stableInsets, isVoiceInteraction, freeform, getTask().mTaskId); + if (a != null) { + if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this); + final int containingWidth = frame.width(); + final int containingHeight = frame.height(); + a.initialize(containingWidth, containingHeight, width, height); + a.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked()); + } + return a; + } + + @Override + protected void setLayer(Transaction t, int layer) { + if (!mSurfaceAnimator.hasLeash()) { + t.setLayer(mSurfaceControl, layer); + } + } + + @Override + protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { + if (!mSurfaceAnimator.hasLeash()) { + t.setRelativeLayer(mSurfaceControl, relativeTo, layer); + } + } + + @Override + protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) { + if (!mSurfaceAnimator.hasLeash()) { + t.reparent(mSurfaceControl, newParent.getHandle()); + } + } + + @Override + public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { + + // The leash is parented to the animation layer. We need to preserve the z-order by using + // the prefix order index, but we boost if necessary. + int layer = getPrefixOrderIndex(); + if (mNeedsZBoost) { + layer += Z_BOOST_BASE; + } + leash.setLayer(layer); + } + + /** + * This must be called while inside a transaction. + */ + void showAllWindowsLocked() { + forAllWindows(windowState -> { + if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + windowState); + windowState.performShowLocked(); + }, false /* traverseTopToBottom */); + } + + @Override + protected void onAnimationFinished() { + super.onAnimationFinished(); + + mTransit = TRANSIT_UNSET; + mTransitFlags = 0; + mNeedsZBoost = false; + + setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER, + "AppWindowToken"); + + clearThumbnail(); + + if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) { + getDisplayContent().computeImeTarget(true /* updateImeTarget */); + } + + if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this + + ": reportedVisible=" + reportedVisible + + " okToDisplay=" + okToDisplay() + + " okToAnimate=" + okToAnimate() + + " startingDisplayed=" + startingDisplayed); + + // WindowState.onExitAnimationDone might modify the children list, so make a copy and then + // traverse the copy. + final ArrayList<WindowState> children = new ArrayList<>(mChildren); + children.forEach(WindowState::onExitAnimationDone); + + mService.mAppTransition.notifyAppTransitionFinishedLocked(token); + scheduleAnimation(); + } + + @Override + boolean isAppAnimating() { + return isSelfAnimating(); } @Override boolean isSelfAnimating() { - return mAppAnimator.isAnimating(); + // If we are about to start a transition, we also need to be considered animating. + return isWaitingForTransitionStart() || isReallyAnimating(); + } + + /** + * @return True if and only if we are actually running an animation. Note that + * {@link #isSelfAnimating} also returns true if we are waiting for an animation to + * start. + */ + private boolean isReallyAnimating() { + return super.isSelfAnimating(); } @Override - void dump(PrintWriter pw, String prefix) { - super.dump(pw, prefix); + void cancelAnimation() { + super.cancelAnimation(); + clearThumbnail(); + } + + boolean isWaitingForTransitionStart() { + return mService.mAppTransition.isTransitionSet() + && (mService.mOpeningApps.contains(this) || mService.mClosingApps.contains(this)); + } + + public int getTransit() { + return mTransit; + } + + int getTransitFlags() { + return mTransitFlags; + } + + void attachThumbnailAnimation() { + if (!isReallyAnimating()) { + return; + } + final int taskId = getTask().mTaskId; + final GraphicBuffer thumbnailHeader = + mService.mAppTransition.getAppTransitionThumbnailHeader(taskId); + if (thumbnailHeader == null) { + if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId); + return; + } + clearThumbnail(); + mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnailHeader); + mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader)); + } + + private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) { + final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); + + // If this is a multi-window scenario, we use the windows frame as + // destination of the thumbnail header animation. If this is a full screen + // window scenario, we use the whole display as the target. + WindowState win = findMainWindow(); + Rect appRect = win != null ? win.getContentFrameLw() : + new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight); + Rect insets = win != null ? win.mContentInsets : null; + final Configuration displayConfig = mDisplayContent.getConfiguration(); + return mService.mAppTransition.createThumbnailAspectScaleAnimationLocked( + appRect, insets, thumbnailHeader, getTask().mTaskId, displayConfig.uiMode, + displayConfig.orientation); + } + + private void clearThumbnail() { + if (mThumbnail == null) { + return; + } + mThumbnail.destroy(); + mThumbnail = null; + } + + @Override + void dump(PrintWriter pw, String prefix, boolean dumpAll) { + super.dump(pw, prefix, dumpAll); if (appToken != null) { pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction); } @@ -1549,13 +1819,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree pw.print(prefix); pw.print("mAppStopped="); pw.println(mAppStopped); } if (mNumInterestingWindows != 0 || mNumDrawnWindows != 0 - || allDrawn || mAppAnimator.allDrawn) { + || allDrawn || mLastAllDrawn) { pw.print(prefix); pw.print("mNumInterestingWindows="); pw.print(mNumInterestingWindows); pw.print(" mNumDrawnWindows="); pw.print(mNumDrawnWindows); pw.print(" inPendingTransaction="); pw.print(inPendingTransaction); pw.print(" allDrawn="); pw.print(allDrawn); - pw.print(" (animator="); pw.print(mAppAnimator.allDrawn); + pw.print(" lastAllDrawn="); pw.print(mLastAllDrawn); pw.println(")"); } if (inPendingTransaction) { @@ -1590,9 +1860,44 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (mRemovingFromDisplay) { pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay); } - if (mAppAnimator.isAnimating()) { - mAppAnimator.dump(pw, prefix + " "); + } + + @Override + void setHidden(boolean hidden) { + super.setHidden(hidden); + + if (hidden) { + // Once the app window is hidden, reset the last saved PiP snap fraction + mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this); + } + scheduleAnimation(); + } + + @Override + void prepareSurfaces() { + // isSelfAnimating also returns true when we are about to start a transition, so we need + // to check super here. + final boolean reallyAnimating = super.isSelfAnimating(); + final boolean show = !isHidden() || reallyAnimating; + if (show && !mLastSurfaceShowing) { + mPendingTransaction.show(mSurfaceControl); + } else if (!show && mLastSurfaceShowing) { + mPendingTransaction.hide(mSurfaceControl); + } + if (mThumbnail != null) { + mThumbnail.setShowing(mPendingTransaction, show); } + mLastSurfaceShowing = show; + super.prepareSurfaces(); + } + + boolean isFreezingScreen() { + return mFreezingScreen; + } + + @Override + boolean needsZBoost() { + return mNeedsZBoost || super.needsZBoost(); } @CallSuper diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f45845752054..63dfbc2c25b6 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -69,7 +69,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; @@ -91,8 +90,6 @@ import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; -import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT; @@ -123,7 +120,6 @@ import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; @@ -350,7 +346,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private boolean mDisplayReady = false; WallpaperController mWallpaperController; - int mInputMethodAnimLayerAdjustment; private final SurfaceSession mSession = new SurfaceSession(); @@ -398,14 +393,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } } - final AppWindowAnimator appAnimator = winAnimator.mAppAnimator; - if (appAnimator != null && appAnimator.thumbnail != null) { - if (appAnimator.thumbnailTransactionSeq - != mTmpWindowAnimator.mAnimTransactionSequence) { - appAnimator.thumbnailTransactionSeq = - mTmpWindowAnimator.mAnimTransactionSequence; - } - } }; private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> { @@ -436,15 +423,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If this window's app token is running a detached wallpaper animation, make a note so // we can ensure the wallpaper is displayed behind it. - final AppWindowAnimator appAnimator = winAnimator.mAppAnimator; - if (appAnimator != null && appAnimator.animation != null - && appAnimator.animating) { - if ((flags & FLAG_SHOW_WALLPAPER) != 0 - && appAnimator.animation.getDetachWallpaper()) { + final AppWindowToken atoken = winAnimator.mWin.mAppToken; + final AnimationAdapter animation = atoken != null ? atoken.getAnimation() : null; + if (animation != null) { + if ((flags & FLAG_SHOW_WALLPAPER) != 0 && animation.getDetachWallpaper()) { mTmpWindow = w; } - final int color = appAnimator.animation.getBackgroundColor(); + final int color = animation.getBackgroundColor(); if (color != 0) { final TaskStack stack = w.getStack(); if (stack != null) { @@ -527,11 +513,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo + " screen changed=" + w.isConfigChanged()); final AppWindowToken atoken = w.mAppToken; if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility - + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden + + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden() + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested) + " parentHidden=" + w.isParentWindowHidden()); else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility - + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden + + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden() + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested) + " parentHidden=" + w.isParentWindowHidden()); } @@ -706,7 +692,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } final TaskStack stack = w.getStack(); - if ((!winAnimator.isWaitingForOpening()) + if (!winAnimator.isWaitingForOpening() || (stack != null && stack.isAnimatingBounds())) { // Updates the shown frame before we set up the surface. This is needed // because the resizing could change the top-left position (in addition to @@ -724,9 +710,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */); // Since setSurfaceBoundariesLocked applies the clipping, we need to apply the position - // to the surface of the window container as well. Use mTmpTransaction instead of - // mPendingTransaction to avoid committing any existing changes in there. + // to the surface of the window container and also the position of the stack window + // container as well. Use mTmpTransaction instead of mPendingTransaction to avoid + // committing any existing changes in there. w.updateSurfacePosition(mTmpTransaction); + if (stack != null) { + stack.updateSurfaceBounds(mTmpTransaction); + } SurfaceControl.mergeToGlobalTransaction(mTmpTransaction); } @@ -1933,6 +1923,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mService.unregisterPointerEventListener(mService.mMousePositionTracker); } } + mService.mAnimator.removeDisplayLocked(mDisplayId); + // The pending transaction won't be applied so we should // just clean up any surfaces pending destruction. onPendingTransactionApplied(); @@ -2056,12 +2048,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight); } - void setInputMethodAnimLayerAdjustment(int adj) { - if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj); - mInputMethodAnimLayerAdjustment = adj; - assignWindowLayers(false /* relayoutNeeded */); - } - /** * If a window that has an animation specifying a colored background and the current wallpaper * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to @@ -2168,7 +2154,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo proto.end(token); } - public void dump(String prefix, PrintWriter pw) { + @Override + public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + super.dump(pw, prefix, dumpAll); pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId); final String subPrefix = " " + prefix; pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x"); @@ -2202,7 +2190,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.println(prefix + "Application tokens in top down Z order:"); for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); - stack.dump(prefix + " ", pw); + stack.dump(pw, prefix + " ", dumpAll); } pw.println(); @@ -2214,7 +2202,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.print(" Exiting #"); pw.print(i); pw.print(' '); pw.print(token); pw.println(':'); - token.dump(pw, " "); + token.dump(pw, " ", dumpAll); } } @@ -2239,11 +2227,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.println(); mPinnedStackControllerLocked.dump(prefix, pw); - if (mInputMethodAnimLayerAdjustment != 0) { - pw.println(subPrefix - + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment); - } - pw.println(); mDisplayFrames.dump(prefix, pw); } @@ -2403,7 +2386,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (updateImeTarget) { if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + mService.mInputMethodTarget + " to null since mInputMethodWindow is null"); - setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0); + setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim); } return null; } @@ -2452,7 +2435,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to null." + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : "")); - setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0); + setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim); } return null; @@ -2466,7 +2449,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // to look at all windows below the current target that are in this app, finding the // highest visible one in layering. WindowState highestTarget = null; - if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) { + if (token.isSelfAnimating()) { highestTarget = token.getHighestAnimLayerWindow(curTarget); } @@ -2480,14 +2463,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (appTransition.isTransitionSet()) { // If we are currently setting up for an animation, hold everything until we // can find out what will happen. - setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment); + setInputMethodTarget(highestTarget, true); return highestTarget; } else if (highestTarget.mWinAnimator.isAnimationSet() && highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) { // If the window we are currently targeting is involved with an animation, // and it is on top of the next target we will be over, then hold off on // moving until that is done. - setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment); + setInputMethodTarget(highestTarget, true); return highestTarget; } } @@ -2495,23 +2478,20 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to " + target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : "")); - setInputMethodTarget(target, false, target.mAppToken != null - ? target.mAppToken.getAnimLayerAdjustment() : 0); + setInputMethodTarget(target, false); } return target; } - private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim, int layerAdj) { + private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) { if (target == mService.mInputMethodTarget - && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim - && mInputMethodAnimLayerAdjustment == layerAdj) { + && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) { return; } mService.mInputMethodTarget = target; mService.mInputMethodTargetWaitingAnim = targetWaitingAnim; - setInputMethodAnimLayerAdjustment(layerAdj); assignWindowLayers(false /* setLayoutNeeded */); } @@ -2573,7 +2553,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.print(token); if (dumpAll) { pw.println(':'); - token.dump(pw, " "); + token.dump(pw, " ", dumpAll); } else { pw.println(); } @@ -3197,7 +3177,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** * A control placed at the appropriate level for transitions to occur. */ - SurfaceControl mAnimationLayer = null; + SurfaceControl mAppAnimationLayer = null; // Cached reference to some special stacks we tend to get a lot so we don't need to loop // through the list to find them. @@ -3462,8 +3442,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Make sure there is no animation running on this token, so any windows // associated with it will be removed as soon as their animations are // complete. - token.mAppAnimator.clearAnimation(); - token.mAppAnimator.animating = false; + cancelAnimation(); if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "performLayout: App token exiting now removed" + token); token.removeIfPossible(); @@ -3544,19 +3523,28 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // The appropriate place for App-Transitions to occur is right // above all other animations but still below things in the Picture-and-Picture // windowing mode. - if (mAnimationLayer != null) { - t.setLayer(mAnimationLayer, layer++); + if (mAppAnimationLayer != null) { + t.setLayer(mAppAnimationLayer, layer++); } } @Override + SurfaceControl getAppAnimationLayer() { + return mAppAnimationLayer; + } + + @Override void onParentSet() { super.onParentSet(); if (getParent() != null) { - mAnimationLayer = makeSurface().build(); + mAppAnimationLayer = makeChildSurface(null) + .setName("animationLayer") + .build(); + getPendingTransaction().show(mAppAnimationLayer); + scheduleAnimation(); } else { - mAnimationLayer.destroy(); - mAnimationLayer = null; + mAppAnimationLayer.destroy(); + mAppAnimationLayer = null; } } } diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index 5fe456562270..2173fa3a283d 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -16,10 +16,9 @@ package com.android.server.wm; -import android.graphics.Point; +import android.os.SystemClock; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; -import android.view.animation.Animation; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; @@ -30,6 +29,7 @@ import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; class LocalAnimationAdapter implements AnimationAdapter { private final AnimationSpec mSpec; + private final SurfaceAnimationRunner mAnimator; LocalAnimationAdapter(AnimationSpec spec, SurfaceAnimationRunner animator) { @@ -64,6 +64,11 @@ class LocalAnimationAdapter implements AnimationAdapter { return mSpec.getDuration(); } + @Override + public long getStatusBarTransitionsStartTime() { + return mSpec.calculateStatusBarTransitionStartTime(); + } + /** * Describes how to apply an animation. */ @@ -84,6 +89,13 @@ class LocalAnimationAdapter implements AnimationAdapter { } /** + * @see AnimationAdapter#getStatusBarTransitionsStartTime + */ + default long calculateStatusBarTransitionStartTime() { + return SystemClock.uptimeMillis(); + } + + /** * @return The duration of the animation. */ long getDuration(); @@ -91,10 +103,17 @@ class LocalAnimationAdapter implements AnimationAdapter { /** * Called when the spec needs to apply the current animation state to the leash. * - * @param t The transaction to use to apply a transform. - * @param leash The leash to apply the state to. + * @param t The transaction to use to apply a transform. + * @param leash The leash to apply the state to. * @param currentPlayTime The current time of the animation. */ void apply(Transaction t, SurfaceControl leash, long currentPlayTime); + + /** + * @see AppTransition#canSkipFirstFrame + */ + default boolean canSkipFirstFrame() { + return false; + } } } diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index d8726bfc2c20..69cbe4607cf1 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -48,6 +48,7 @@ import com.android.internal.policy.PipSnapAlgorithm; import com.android.server.UiThread; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -71,6 +72,7 @@ class PinnedStackController { private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM; + public static final float INVALID_SNAP_FRACTION = -1f; private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final Handler mHandler = UiThread.getHandler(); @@ -101,6 +103,8 @@ class PinnedStackController { private float mDefaultAspectRatio; private Point mScreenEdgeInsets; private int mCurrentMinSize; + private float mReentrySnapFraction = INVALID_SNAP_FRACTION; + private WeakReference<AppWindowToken> mLastPipActivity = null; // The aspect ratio bounds of the PIP. private float mMinAspectRatio; @@ -113,6 +117,7 @@ class PinnedStackController { private final Rect mTmpAnimatingBoundsRect = new Rect(); private final Point mTmpDisplaySize = new Point(); + /** * The callback object passed to listeners for them to notify the controller of state changes. */ @@ -250,9 +255,35 @@ class PinnedStackController { } /** + * Saves the current snap fraction for re-entry of the current activity into PiP. + */ + void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) { + mReentrySnapFraction = getSnapFraction(stackBounds); + mLastPipActivity = new WeakReference<>(token); + } + + /** + * Resets the last saved snap fraction so that the default bounds will be returned. + */ + void resetReentrySnapFraction(AppWindowToken token) { + if (mLastPipActivity != null && mLastPipActivity.get() == token) { + mReentrySnapFraction = INVALID_SNAP_FRACTION; + mLastPipActivity = null; + } + } + + /** * @return the default bounds to show the PIP when there is no active PIP. */ - Rect getDefaultBounds() { + Rect getDefaultOrLastSavedBounds() { + return getDefaultBounds(mReentrySnapFraction); + } + + /** + * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it + * will apply the default bounds to the provided snap fraction. + */ + Rect getDefaultBounds(float snapFraction) { synchronized (mService.mWindowMap) { final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); @@ -260,8 +291,14 @@ class PinnedStackController { final Rect defaultBounds = new Rect(); final Size size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio, mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); - Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds, - 0, mIsImeShowing ? mImeHeight : 0, defaultBounds); + if (snapFraction != INVALID_SNAP_FRACTION) { + defaultBounds.set(0, 0, size.getWidth(), size.getHeight()); + final Rect movementBounds = getMovementBounds(defaultBounds); + mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction); + } else { + Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds, + 0, mIsImeShowing ? mImeHeight : 0, defaultBounds); + } return defaultBounds; } } @@ -299,9 +336,7 @@ class PinnedStackController { final Rect postChangeStackBounds = mTmpRect; // Calculate the snap fraction of the current stack along the old movement bounds - final Rect preChangeMovementBounds = getMovementBounds(postChangeStackBounds); - final float snapFraction = mSnapAlgorithm.getSnapFraction(postChangeStackBounds, - preChangeMovementBounds); + final float snapFraction = getSnapFraction(postChangeStackBounds); mDisplayInfo.copyFrom(displayInfo); // Calculate the stack bounds in the new orientation to the same same fraction along the @@ -414,7 +449,7 @@ class PinnedStackController { try { final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); - final Rect normalBounds = getDefaultBounds(); + final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION); if (isValidPictureInPictureAspectRatio(mAspectRatio)) { transformBoundsToAspectRatio(normalBounds, mAspectRatio, false /* useCurrentMinEdgeSize */); @@ -486,6 +521,14 @@ class PinnedStackController { } /** + * @return the default snap fraction to apply instead of the default gravity when calculating + * the default stack bounds when first entering PiP. + */ + private float getSnapFraction(Rect stackBounds) { + return mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds)); + } + + /** * @return the pixels for a given dp value. */ private int dpToPx(float dpValue, DisplayMetrics dm) { @@ -494,7 +537,8 @@ class PinnedStackController { void dump(String prefix, PrintWriter pw) { pw.println(prefix + "PinnedStackController"); - pw.print(prefix + " defaultBounds="); getDefaultBounds().printShortString(pw); + pw.print(prefix + " defaultBounds="); + getDefaultBounds(INVALID_SNAP_FRACTION).printShortString(pw); pw.println(); mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect); pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw); @@ -516,7 +560,7 @@ class PinnedStackController { void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - getDefaultBounds().writeToProto(proto, DEFAULT_BOUNDS); + getDefaultBounds(INVALID_SNAP_FRACTION).writeToProto(proto, DEFAULT_BOUNDS); mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect); getMovementBounds(mTmpRect).writeToProto(proto, MOVEMENT_BOUNDS); proto.end(token); diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java index b021a7223e2e..02fbfba9d332 100644 --- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java +++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java @@ -61,7 +61,7 @@ public class PinnedStackWindowController extends StackWindowController { displayContent.getPinnedStackController(); if (stackBounds == null) { // Calculate the aspect ratio bounds from the default bounds - stackBounds = pinnedStackController.getDefaultBounds(); + stackBounds = pinnedStackController.getDefaultOrLastSavedBounds(); } if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) { @@ -173,7 +173,7 @@ public class PinnedStackWindowController extends StackWindowController { * from fullscreen to non-fullscreen bounds. */ public boolean deferScheduleMultiWindowModeChanged() { - synchronized(mWindowMap) { + synchronized (mWindowMap) { return mContainer.deferScheduleMultiWindowModeChanged(); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index e653e7d4afa2..2a77c92b5c87 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -612,7 +612,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { defaultDisplay.pendingLayoutChanges); } - if (!mService.mAnimator.mAppWindowAnimating && mService.mAppTransition.isRunning()) { + if (!isAppAnimating() && mService.mAppTransition.isRunning()) { // We have finished the animation of an app transition. To do this, we have delayed a // lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app // token list reflects the correct Z-order, but the window list may now be out of sync @@ -1035,7 +1035,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { final int count = mChildren.size(); for (int i = 0; i < count; ++i) { final DisplayContent displayContent = mChildren.get(i); - displayContent.dump(" ", pw); + displayContent.dump(pw, " ", true /* dumpAll */); } } else { pw.println(" NO DISPLAY"); diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 3ef9b3fff80a..3a41eb0e2afc 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.util.TimeUtils.NANOS_PER_MS; import static android.view.Choreographer.CALLBACK_TRAVERSAL; import static android.view.Choreographer.getSfInstance; @@ -142,6 +143,7 @@ class SurfaceAnimationRunner { // Transaction will be applied in the commit phase. scheduleApplyTransaction(); }); + anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -158,6 +160,7 @@ class SurfaceAnimationRunner { mRunningAnimations.remove(a.mLeash); synchronized (mCancelLock) { if (!a.mCancelled) { + // Post on other thread that we can push final state without jank. AnimationThread.getHandler().post(a.mFinishCallback); } @@ -166,6 +169,14 @@ class SurfaceAnimationRunner { } }); anim.start(); + if (a.mAnimSpec.canSkipFirstFrame()) { + // If we can skip the first frame, we start one frame later. + anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS); + } + + // Immediately start the animation by manually applying an animation frame. Otherwise, the + // start time would only be set in the next frame, leading to a delay. + anim.doAnimationFrame(mChoreographer.getFrameTime()); a.mAnim = anim; mRunningAnimations.put(a.mLeash, a); } @@ -189,6 +200,7 @@ class SurfaceAnimationRunner { } private void applyTransaction() { + mFrameTransaction.setAnimationTransaction(); mFrameTransaction.apply(); mApplyScheduled = false; } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index e1652111a64e..a32e711df534 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -22,10 +22,13 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.ArrayMap; import android.util.Slog; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; /** @@ -41,7 +44,9 @@ class SurfaceAnimator { private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM; private final WindowManagerService mService; private AnimationAdapter mAnimation; - private SurfaceControl mLeash; + + @VisibleForTesting + SurfaceControl mLeash; private final Animatable mAnimatable; private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback; private final Runnable mAnimationFinishedCallback; @@ -62,23 +67,34 @@ class SurfaceAnimator { private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback) { return anim -> { synchronized (mService.mWindowMap) { - if (anim != mAnimation) { - // Callback was from another animation - ignore. + final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim); + if (target != null) { + target.mInnerAnimationFinishedCallback.onAnimationFinished(anim); return; } - final Transaction t = new Transaction(); - SurfaceControl.openTransaction(); - try { - reset(t); - animationFinishedCallback.run(); - } finally { - // TODO: This should use pendingTransaction eventually, but right now things - // happening on the animation finished callback are happening on the global - // transaction. - SurfaceControl.mergeToGlobalTransaction(t); - SurfaceControl.closeTransaction(); - } + // TODO: This should use pendingTransaction eventually, but right now things + // happening on the animation finished callback are happening on the global + // transaction. + // For now we need to run this after it's guaranteed that the transaction that + // reparents the surface onto the leash is executed already. Otherwise this may be + // executed first, leading to surface loss, as the reparent operations wouldn't + // be in order. + mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { + if (anim != mAnimation) { + // Callback was from another animation - ignore. + return; + } + final Transaction t = new Transaction(); + SurfaceControl.openTransaction(); + try { + reset(t, true /* destroyLeash */); + animationFinishedCallback.run(); + } finally { + SurfaceControl.mergeToGlobalTransaction(t); + SurfaceControl.closeTransaction(); + } + }); } }; } @@ -95,7 +111,7 @@ class SurfaceAnimator { * handing it to the component that is responsible to run the animation. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) { - cancelAnimation(t, true /* restarting */); + cancelAnimation(t, true /* restarting */, true /* forwardCancel */); mAnimation = anim; final SurfaceControl surface = mAnimatable.getSurfaceControl(); if (surface == null) { @@ -158,7 +174,8 @@ class SurfaceAnimator { * Cancels any currently running animation. */ void cancelAnimation() { - cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */); + cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */, + true /* forwardCancel */); mAnimatable.commitPendingTransaction(); } @@ -197,13 +214,47 @@ class SurfaceAnimator { return mLeash != null; } - private void cancelAnimation(Transaction t, boolean restarting) { + void transferAnimation(SurfaceAnimator from) { + if (from.mLeash == null) { + return; + } + final SurfaceControl surface = mAnimatable.getSurfaceControl(); + final SurfaceControl parent = mAnimatable.getAnimationLeashParent(); + if (surface == null || parent == null) { + Slog.w(TAG, "Unable to transfer animation, surface or parent is null"); + cancelAnimation(); + return; + } + endDelayingAnimationStart(); + final Transaction t = mAnimatable.getPendingTransaction(); + cancelAnimation(t, true /* restarting */, true /* forwardCancel */); + mLeash = from.mLeash; + mAnimation = from.mAnimation; + + // Cancel source animation, but don't let animation runner cancel the animation. + from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */); + t.reparent(surface, mLeash.getHandle()); + t.reparent(mLeash, parent.getHandle()); + mAnimatable.onAnimationLeashCreated(t, mLeash); + mService.mAnimationTransferMap.put(mAnimation, this); + } + + /** + * Cancels the animation, and resets the leash. + * + * @param t The transaction to use for all cancelling surface operations. + * @param restarting Whether we are restarting the animation. + * @param forwardCancel Whether to forward the cancel signal to the adapter executing the + * animation. This will be set to false when just transferring an animation + * to another animator. + */ + private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) { if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting); final SurfaceControl leash = mLeash; final AnimationAdapter animation = mAnimation; - reset(t); + reset(t, forwardCancel); if (animation != null) { - if (!mAnimationStartDelayed) { + if (!mAnimationStartDelayed && forwardCancel) { animation.onAnimationCancelled(leash); } if (!restarting) { @@ -215,7 +266,7 @@ class SurfaceAnimator { } } - private void reset(Transaction t) { + private void reset(Transaction t, boolean destroyLeash) { final SurfaceControl surface = mAnimatable.getSurfaceControl(); final SurfaceControl parent = mAnimatable.getParentSurfaceControl(); @@ -225,7 +276,8 @@ class SurfaceAnimator { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent"); t.reparent(surface, parent.getHandle()); } - if (mLeash != null) { + mService.mAnimationTransferMap.remove(mAnimation); + if (mLeash != null && destroyLeash) { mAnimatable.destroyAfterPendingTransaction(mLeash); } mLeash = null; @@ -241,6 +293,7 @@ class SurfaceAnimator { int height, boolean hidden) { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash"); final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash() + .setParent(mAnimatable.getAnimationLeashParent()) .setName(surface + " - animation-leash") .setSize(width, height); final SurfaceControl leash = builder.build(); @@ -309,6 +362,11 @@ class SurfaceAnimator { SurfaceControl.Builder makeAnimationLeash(); /** + * @return The parent that should be used for the animation leash. + */ + @Nullable SurfaceControl getAnimationLeashParent(); + + /** * @return The surface of the object to be animated. */ @Nullable SurfaceControl getSurfaceControl(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 244eb66a7ef0..3c96ca17c187 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -536,14 +536,7 @@ class Task extends WindowContainer<AppWindowToken> { /** Cancels any running app transitions associated with the task. */ void cancelTaskWindowTransition() { for (int i = mChildren.size() - 1; i >= 0; --i) { - mChildren.get(i).mAppAnimator.clearAnimation(); - } - } - - /** Cancels any running thumbnail transitions associated with the task. */ - void cancelTaskThumbnailTransition() { - for (int i = mChildren.size() - 1; i >= 0; --i) { - mChildren.get(i).mAppAnimator.clearThumbnail(); + mChildren.get(i).cancelAnimation(); } } @@ -656,6 +649,9 @@ class Task extends WindowContainer<AppWindowToken> { mDimmer.resetDimStates(); super.prepareSurfaces(); getDimBounds(mTmpDimBoundsRect); + + // Bounds need to be relative, as the dim layer is a child. + mTmpDimBoundsRect.offsetTo(0, 0); if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { scheduleAnimation(); } @@ -677,7 +673,9 @@ class Task extends WindowContainer<AppWindowToken> { proto.end(token); } - public void dump(String prefix, PrintWriter pw) { + @Override + public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + super.dump(pw, prefix, dumpAll); final String doublePrefix = prefix + " "; pw.println(prefix + "taskId=" + mTaskId); @@ -691,7 +689,7 @@ class Task extends WindowContainer<AppWindowToken> { for (int i = mChildren.size() - 1; i >= 0; i--) { final AppWindowToken wtoken = mChildren.get(i); pw.println(triplePrefix + "Activity #" + i + " " + wtoken); - wtoken.dump(pw, triplePrefix); + wtoken.dump(pw, triplePrefix, dumpAll); } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index c09115794713..f79719c2cfdc 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -93,6 +93,8 @@ class TaskSnapshotController { private final ArraySet<Task> mTmpTasks = new ArraySet<>(); private final Handler mHandler = new Handler(); + private final Rect mTmpRect = new Rect(); + /** * Flag indicating whether we are running on an Android TV device. */ @@ -223,11 +225,11 @@ class TaskSnapshotController { final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f; - final Rect taskFrame = new Rect(); - task.getBounds(taskFrame); + task.getBounds(mTmpRect); + mTmpRect.offsetTo(0, 0); final GraphicBuffer buffer = SurfaceControl.captureLayers( - task.getSurfaceControl().getHandle(), taskFrame, scaleFraction); + task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction); if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { if (DEBUG_SCREENSHOT) { diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 9946c6a5eade..eb8eae1a95d7 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -31,9 +31,8 @@ import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; -import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING; @@ -45,6 +44,7 @@ import static com.android.server.wm.proto.StackProto.WINDOW_CONTAINER; import android.annotation.CallSuper; import android.content.res.Configuration; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.RemoteException; @@ -146,6 +146,7 @@ public class TaskStack extends WindowContainer<Task> implements * For {@link #prepareSurfaces}. */ final Rect mTmpDimBoundsRect = new Rect(); + private final Point mLastSurfaceSize = new Point(); TaskStack(WindowManagerService service, int stackId, StackWindowController controller) { super(service); @@ -220,6 +221,8 @@ public class TaskStack extends WindowContainer<Task> implements } alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds); mDisplayContent.setLayoutNeeded(); + + updateSurfaceBounds(); } private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) { @@ -241,10 +244,11 @@ public class TaskStack extends WindowContainer<Task> implements return; } getRawBounds(mTmpRect); - // TODO: Should be in relative coordinates. - getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(), - mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left, - mTmpRect.top); + final Rect stackBounds = getBounds(); + getPendingTransaction() + .setSize(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height()) + .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left, + mTmpRect.top - stackBounds.top); scheduleAnimation(); } @@ -297,6 +301,7 @@ public class TaskStack extends WindowContainer<Task> implements updateAdjustedBounds(); + updateSurfaceBounds(); return result; } @@ -317,7 +322,7 @@ public class TaskStack extends WindowContainer<Task> implements if (matchParentBounds() || !inSplitScreenSecondaryWindowingMode() || mDisplayContent == null - || mDisplayContent.getSplitScreenPrimaryStack() != null) { + || mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null) { return true; } return false; @@ -711,8 +716,12 @@ public class TaskStack extends WindowContainer<Task> implements @Override public void onConfigurationChanged(Configuration newParentConfig) { final int prevWindowingMode = getWindowingMode(); + // Only need to update surface size here since the super method will handle updating + // surface position. + updateSurfaceSize(getPendingTransaction()); super.onConfigurationChanged(newParentConfig); final int windowingMode = getWindowingMode(); + if (mDisplayContent == null || prevWindowingMode == windowingMode) { return; } @@ -720,6 +729,31 @@ public class TaskStack extends WindowContainer<Task> implements updateBoundsForWindowModeChange(); } + private void updateSurfaceBounds() { + updateSurfaceBounds(getPendingTransaction()); + scheduleAnimation(); + } + + void updateSurfaceBounds(SurfaceControl.Transaction transaction) { + updateSurfaceSize(transaction); + updateSurfacePosition(transaction); + } + + private void updateSurfaceSize(SurfaceControl.Transaction transaction) { + if (mSurfaceControl == null) { + return; + } + + final Rect stackBounds = getBounds(); + final int width = stackBounds.width(); + final int height = stackBounds.height(); + if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { + return; + } + transaction.setSize(mSurfaceControl, width, height); + mLastSurfaceSize.set(width, height); + } + @Override void onDisplayChanged(DisplayContent dc) { if (mDisplayContent != null) { @@ -1284,7 +1318,8 @@ public class TaskStack extends WindowContainer<Task> implements proto.end(token); } - public void dump(String prefix, PrintWriter pw) { + @Override + void dump(PrintWriter pw, String prefix, boolean dumpAll) { pw.println(prefix + "mStackId=" + mStackId); pw.println(prefix + "mDeferRemoval=" + mDeferRemoval); pw.println(prefix + "mBounds=" + getRawBounds().toShortString()); @@ -1300,7 +1335,7 @@ public class TaskStack extends WindowContainer<Task> implements pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString()); } for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { - mChildren.get(taskNdx).dump(prefix + " ", pw); + mChildren.get(taskNdx).dump(pw, prefix + " ", dumpAll); } if (mAnimationBackgroundSurfaceIsShown) { pw.println(prefix + "mWindowAnimationBackgroundSurface is shown"); @@ -1313,7 +1348,7 @@ public class TaskStack extends WindowContainer<Task> implements pw.print(" Exiting App #"); pw.print(i); pw.print(' '); pw.print(token); pw.println(':'); - token.dump(pw, " "); + token.dump(pw, " ", dumpAll); } } } @@ -1653,35 +1688,6 @@ public class TaskStack extends WindowContainer<Task> implements return super.checkCompleteDeferredRemoval(); } - void stepAppWindowsAnimation(long currentTime) { - super.stepAppWindowsAnimation(currentTime); - - // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set - // below but is set in the loop above. See if it really matters... - - // Clear before using. - mTmpAppTokens.clear(); - // We copy the list as things can be removed from the exiting token list while we are - // processing. - mTmpAppTokens.addAll(mExitingAppTokens); - for (int i = 0; i < mTmpAppTokens.size(); i++) { - final AppWindowAnimator appAnimator = mTmpAppTokens.get(i).mAppAnimator; - appAnimator.wasAnimating = appAnimator.animating; - if (appAnimator.stepAnimationLocked(currentTime)) { - mService.mAnimator.setAnimating(true); - mService.mAnimator.mAppWindowAnimating = true; - } else if (appAnimator.wasAnimating) { - // stopped animating, do one more pass through the layout - appAnimator.mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, - "exiting appToken " + appAnimator.mAppToken + " done"); - if (DEBUG_ANIM) Slog.v(TAG_WM, - "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken); - } - } - // Clear to avoid holding reference to tokens. - mTmpAppTokens.clear(); - } - @Override int getOrientation() { return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET; @@ -1705,6 +1711,9 @@ public class TaskStack extends WindowContainer<Task> implements mDimmer.resetDimStates(); super.prepareSurfaces(); getDimBounds(mTmpDimBoundsRect); + + // Bounds need to be relative, as the dim layer is a child. + mTmpDimBoundsRect.offsetTo(0, 0); if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { scheduleAnimation(); } diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java index 5caae32dbeb4..d83f28ccb31c 100644 --- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java +++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java @@ -199,16 +199,6 @@ public class TaskWindowContainerController } } - public void cancelThumbnailTransition() { - synchronized (mWindowMap) { - if (mContainer == null) { - Slog.w(TAG_WM, "cancelThumbnailTransition: taskId " + mTaskId + " not found."); - return; - } - mContainer.cancelTaskThumbnailTransition(); - } - } - public void setTaskDescription(TaskDescription taskDescription) { synchronized (mWindowMap) { if (mContainer == null) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3ae4549768ce..ac0919d93166 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -64,8 +64,6 @@ class WallpaperController { // to another, and this is the previous wallpaper target. private WindowState mPrevWallpaperTarget = null; - private int mWallpaperAnimLayerAdjustment; - private float mLastWallpaperX = -1; private float mLastWallpaperY = -1; private float mLastWallpaperXStep = -1; @@ -112,7 +110,7 @@ class WallpaperController { mFindResults.resetTopWallpaper = true; if (w != winAnimator.mWindowDetachedWallpaper && w.mAppToken != null) { // If this window's app token is hidden and not animating, it is of no interest to us. - if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) { + if (w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w); return false; @@ -130,10 +128,10 @@ class WallpaperController { } final boolean keyguardGoingAwayWithWallpaper = (w.mAppToken != null - && AppTransition.isKeyguardGoingAwayTransit( - w.mAppToken.mAppAnimator.getTransit()) - && (w.mAppToken.mAppAnimator.getTransitFlags() - & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0); + && w.mAppToken.isSelfAnimating() + && AppTransition.isKeyguardGoingAwayTransit(w.mAppToken.getTransit()) + && (w.mAppToken.getTransitFlags() + & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0); boolean needsShowWhenLockedWallpaper = false; if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 @@ -204,18 +202,19 @@ class WallpaperController { private boolean isWallpaperVisible(WindowState wallpaperTarget) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured=" + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") - + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) - ? wallpaperTarget.mAppToken.mAppAnimator.animation : null) + + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) + ? wallpaperTarget.mAppToken.isSelfAnimating() : null) + " prev=" + mPrevWallpaperTarget); return (wallpaperTarget != null && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null - && wallpaperTarget.mAppToken.mAppAnimator.animation != null))) + && wallpaperTarget.mAppToken.isSelfAnimating()))) || mPrevWallpaperTarget != null; } boolean isWallpaperTargetAnimating() { return mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimationSet() - && !mWallpaperTarget.mWinAnimator.isDummyAnimation(); + && (mWallpaperTarget.mAppToken == null + || !mWallpaperTarget.mAppToken.isWaitingForTransitionStart()); } void updateWallpaperVisibility() { @@ -250,7 +249,7 @@ class WallpaperController { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); token.hideWallpaperToken(wasDeferred, "hideWallpapers"); - if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token + if (DEBUG_WALLPAPER_LIGHT && !token.isHidden()) Slog.d(TAG, "Hiding wallpaper " + token + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev=" + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " ")); } @@ -441,10 +440,6 @@ class WallpaperController { } } - int getAnimLayerAdjustment() { - return mWallpaperAnimLayerAdjustment; - } - private void findWallpaperTarget(DisplayContent dc) { mFindResults.reset(); if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) { @@ -546,7 +541,7 @@ class WallpaperController { private void updateWallpaperTokens(boolean visible) { for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.updateWallpaperWindows(visible, mWallpaperAnimLayerAdjustment); + token.updateWallpaperWindows(visible); token.getDisplayContent().assignWindowLayers(false); } } @@ -565,12 +560,6 @@ class WallpaperController { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper visibility: " + visible); if (visible) { - // If the wallpaper target is animating, we may need to copy its layer adjustment. - // Only do this if we are not transferring between two wallpaper targets. - mWallpaperAnimLayerAdjustment = - (mPrevWallpaperTarget == null && mWallpaperTarget.mAppToken != null) - ? mWallpaperTarget.mAppToken.getAnimLayerAdjustment() : 0; - if (mWallpaperTarget.mWallpaperX >= 0) { mLastWallpaperX = mWallpaperTarget.mWallpaperX; mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep; @@ -681,10 +670,6 @@ class WallpaperController { pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX); pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY); } - - if (mWallpaperAnimLayerAdjustment != 0) { - pw.println(prefix + "mWallpaperAnimLayerAdjustment=" + mWallpaperAnimLayerAdjustment); - } } /** Helper class for storing the results of a wallpaper target find operation. */ diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 3389f71394bc..2ae5c7bd9c25 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -16,12 +16,9 @@ package com.android.server.wm; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -56,7 +53,7 @@ class WallpaperWindowToken extends WindowToken { final WindowState wallpaper = mChildren.get(j); wallpaper.hideWallpaperWindow(wasDeferred, reason); } - hidden = true; + setHidden(true); } void sendWindowWallpaperCommand( @@ -92,8 +89,9 @@ class WallpaperWindowToken extends WindowToken { final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; - if (hidden == visible) { - hidden = !visible; + if (isHidden() == visible) { + setHidden(!visible); + // Need to do a layout to ensure the wallpaper now has the correct size. mDisplayContent.setLayoutNeeded(); } @@ -119,12 +117,12 @@ class WallpaperWindowToken extends WindowToken { } } - void updateWallpaperWindows(boolean visible, int animLayerAdj) { + void updateWallpaperWindows(boolean visible) { - if (hidden == visible) { + if (isHidden() == visible) { if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "Wallpaper token " + token + " hidden=" + !visible); - hidden = !visible; + setHidden(!visible); // Need to do a layout to ensure the wallpaper now has the correct size. mDisplayContent.setLayoutNeeded(); } diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java index bb25297857dd..031b57b73b05 100644 --- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java @@ -16,11 +16,20 @@ package com.android.server.wm; +import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; + import android.graphics.Point; +import android.graphics.Rect; +import android.os.SystemClock; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.Interpolator; import android.view.animation.Transformation; +import android.view.animation.TranslateAnimation; import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; @@ -32,10 +41,26 @@ public class WindowAnimationSpec implements AnimationSpec { private Animation mAnimation; private final Point mPosition = new Point(); private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new); + private final boolean mCanSkipFirstFrame; + private final Rect mStackBounds = new Rect(); + private int mStackClipMode; + private final Rect mTmpRect = new Rect(); + + public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame) { + this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE); + } - public WindowAnimationSpec(Animation animation, Point position) { + public WindowAnimationSpec(Animation animation, Point position, Rect stackBounds, + boolean canSkipFirstFrame, int stackClipMode) { mAnimation = animation; - mPosition.set(position.x, position.y); + if (position != null) { + mPosition.set(position.x, position.y); + } + mCanSkipFirstFrame = canSkipFirstFrame; + mStackClipMode = stackClipMode; + if (stackBounds != null) { + mStackBounds.set(stackBounds); + } } @Override @@ -61,6 +86,77 @@ public class WindowAnimationSpec implements AnimationSpec { tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats); t.setAlpha(leash, tmp.transformation.getAlpha()); + if (mStackClipMode == STACK_CLIP_NONE) { + t.setWindowCrop(leash, tmp.transformation.getClipRect()); + } else if (mStackClipMode == STACK_CLIP_AFTER_ANIM) { + t.setFinalCrop(leash, mStackBounds); + t.setWindowCrop(leash, tmp.transformation.getClipRect()); + } else { + mTmpRect.set(mStackBounds); + mTmpRect.intersect(tmp.transformation.getClipRect()); + t.setWindowCrop(leash, mTmpRect); + } + } + + @Override + public long calculateStatusBarTransitionStartTime() { + TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation); + if (openTranslateAnimation != null) { + + // Some interpolators are extremely quickly mostly finished, but not completely. For + // our purposes, we need to find the fraction for which ther interpolator is mostly + // there, and use that value for the calculation. + float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator()); + return SystemClock.uptimeMillis() + + openTranslateAnimation.getStartOffset() + + (long)(openTranslateAnimation.getDuration() * t) + - STATUS_BAR_TRANSITION_DURATION; + } else { + return SystemClock.uptimeMillis(); + } + } + + @Override + public boolean canSkipFirstFrame() { + return mCanSkipFirstFrame; + } + + /** + * Tries to find a {@link TranslateAnimation} inside the {@code animation}. + * + * @return the found animation, {@code null} otherwise + */ + private static TranslateAnimation findTranslateAnimation(Animation animation) { + if (animation instanceof TranslateAnimation) { + return (TranslateAnimation) animation; + } else if (animation instanceof AnimationSet) { + AnimationSet set = (AnimationSet) animation; + for (int i = 0; i < set.getAnimations().size(); i++) { + Animation a = set.getAnimations().get(i); + if (a instanceof TranslateAnimation) { + return (TranslateAnimation) a; + } + } + } + return null; + } + + /** + * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which + * {@code interpolator(t + eps) > 0.99}. + */ + private static float findAlmostThereFraction(Interpolator interpolator) { + float val = 0.5f; + float adj = 0.25f; + while (adj >= 0.01f) { + if (interpolator.getInterpolation(val) < 0.99f) { + val += adj; + } else { + val -= adj; + } + adj /= 2; + } + return val; } private static class TmpValues { diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 7c56f00b64ee..729587375421 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -35,6 +35,7 @@ import com.android.server.AnimationThread; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; +import java.util.ArrayList; /** * Singleton class that carries out the animations and Surface operations in a separate task @@ -50,16 +51,14 @@ public class WindowAnimator { /** Is any window animating? */ private boolean mAnimating; - private boolean mLastAnimating; - - /** Is any app window animating? */ - boolean mAppWindowAnimating; + private boolean mLastRootAnimating; final Choreographer.FrameCallback mAnimationFrameCallback; /** Time of current animation step. Reset on each iteration */ long mCurrentTime; + boolean mAppWindowAnimating; /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */ int mAnimTransactionSequence; @@ -89,6 +88,12 @@ public class WindowAnimator { */ private boolean mAnimationFrameCallbackScheduled; + /** + * A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is + * executed and the corresponding transaction is closed and applied. + */ + private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>(); + WindowAnimator(final WindowManagerService service) { mService = service; mContext = service.mContext; @@ -142,21 +147,10 @@ public class WindowAnimator { scheduleAnimation(); } - // Simulate back-pressure by opening and closing an empty animation transaction. This makes - // sure that an animation frame is at least presented once on the screen. We do this outside - // of the regular transaction such that we can avoid holding the window manager lock in case - // we receive back-pressure from SurfaceFlinger. Since closing an animation transaction - // without the window manager locks leads to ordering issues (as the transaction will be - // processed only at the beginning of the next frame which may result in another transaction - // that was executed later in WM side gets executed first on SF side), we don't update any - // Surface properties here such that reordering doesn't cause issues. - mService.executeEmptyAnimationTransaction(); - synchronized (mService.mWindowMap) { mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS; mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE; mAnimating = false; - mAppWindowAnimating = false; if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime); } @@ -170,7 +164,6 @@ public class WindowAnimator { for (int i = 0; i < numDisplays; i++) { final int displayId = mDisplayContentsAnimators.keyAt(i); final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId); - dc.stepAppWindowsAnimation(mCurrentTime); DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); final ScreenRotationAnimation screenRotationAnimation = @@ -251,7 +244,8 @@ public class WindowAnimator { mWindowPlacerLocked.requestTraversal(); } - if (mAnimating && !mLastAnimating) { + final boolean rootAnimating = mService.mRoot.isSelfOrChildAnimating(); + if (rootAnimating && !mLastRootAnimating) { // Usually app transitions but quite a load onto the system already (with all the // things happening in app), so pause task snapshot persisting to not increase the @@ -259,13 +253,13 @@ public class WindowAnimator { mService.mTaskSnapshotController.setPersisterPaused(true); Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0); } - if (!mAnimating && mLastAnimating) { + if (!rootAnimating && mLastRootAnimating) { mWindowPlacerLocked.requestTraversal(); mService.mTaskSnapshotController.setPersisterPaused(false); Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0); } - mLastAnimating = mAnimating; + mLastRootAnimating = rootAnimating; if (mRemoveReplacedWindows) { mService.mRoot.removeReplacedWindows(); @@ -275,6 +269,7 @@ public class WindowAnimator { mService.destroyPreservedSurfaceLocked(); mService.mWindowPlacerLocked.destroyPendingSurfaces(); + executeAfterPrepareSurfacesRunnables(); if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating @@ -438,4 +433,23 @@ public class WindowAnimator { void orAnimating(boolean animating) { mAnimating |= animating; } + + /** + * Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and + * the corresponding transaction is closed and applied. + */ + void addAfterPrepareSurfacesRunnable(Runnable r) { + mAfterPrepareSurfacesRunnables.add(r); + scheduleAnimation(); + } + + private void executeAfterPrepareSurfacesRunnables() { + + // Traverse in order they were added. + final int size = mAfterPrepareSurfacesRunnables.size(); + for (int i = 0; i < size; i++) { + mAfterPrepareSurfacesRunnables.get(i).run(); + } + mAfterPrepareSurfacesRunnables.clear(); + } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index b2b6119c559c..af314101cd2b 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -29,7 +29,8 @@ import static android.view.SurfaceControl.Transaction; import android.annotation.CallSuper; import android.content.res.Configuration; -import android.graphics.PixelFormat.Opacity; +import android.graphics.Point; +import android.graphics.Rect; import android.util.Slog; import android.view.MagnificationSpec; import android.view.SurfaceControl; @@ -93,6 +94,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< protected final SurfaceAnimator mSurfaceAnimator; protected final WindowManagerService mService; + private final Point mTmpPos = new Point(); + protected final Point mLastSurfacePosition = new Point(); + + /** Total number of elements in this subtree, including our own hierarchy element. */ + private int mTreeWeight = 1; + WindowContainer(WindowManagerService service) { mService = service; mPendingTransaction = service.mTransactionFactory.make(); @@ -114,6 +121,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mChildren.get(index); } + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + super.onConfigurationChanged(newParentConfig); + updateSurfacePosition(getPendingTransaction()); + scheduleAnimation(); + } + final protected void setParent(WindowContainer<WindowContainer> parent) { mParent = parent; // Removing parent usually means that we've detached this entity to destroy it or to attach @@ -147,7 +161,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // surface animator such that hierarchy is preserved when animating, i.e. // mSurfaceControl stays attached to the leash and we just reparent the leash to the // new parent. - mSurfaceAnimator.reparent(getPendingTransaction(), mParent.mSurfaceControl); + reparentSurfaceControl(getPendingTransaction(), mParent.mSurfaceControl); } // Either way we need to ask the parent to assign us a Z-order. @@ -190,6 +204,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } else { mChildren.add(positionToAdd, child); } + onChildAdded(child); + // Set the parent after we've actually added a child in case a subclass depends on this. child.setParent(this); } @@ -203,10 +219,21 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< + " can't add to container=" + getName()); } mChildren.add(index, child); + onChildAdded(child); + // Set the parent after we've actually added a child in case a subclass depends on this. child.setParent(this); } + private void onChildAdded(WindowContainer child) { + mTreeWeight += child.mTreeWeight; + WindowContainer parent = getParent(); + while (parent != null) { + parent.mTreeWeight += child.mTreeWeight; + parent = parent.getParent(); + } + } + /** * Removes the input child container from this container which is its parent. * @@ -215,6 +242,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @CallSuper void removeChild(E child) { if (mChildren.remove(child)) { + onChildRemoved(child); child.setParent(null); } else { throw new IllegalArgumentException("removeChild: container=" + child.getName() @@ -222,6 +250,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + private void onChildRemoved(WindowContainer child) { + mTreeWeight -= child.mTreeWeight; + WindowContainer parent = getParent(); + while (parent != null) { + parent.mTreeWeight -= child.mTreeWeight; + parent = parent.getParent(); + } + } + /** * Removes this window container and its children with no regard for what else might be going on * in the system. For example, the container will be removed during animation if this method is @@ -236,7 +273,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // Need to do this after calling remove on the child because the child might try to // remove/detach itself from its parent which will cause an exception if we remove // it before calling remove on the child. - mChildren.remove(child); + if (mChildren.remove(child)) { + onChildRemoved(child); + } } if (mSurfaceControl != null) { @@ -255,6 +294,34 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * @return The index of this element in the hierarchy tree in prefix order. + */ + int getPrefixOrderIndex() { + if (mParent == null) { + return 0; + } + return mParent.getPrefixOrderIndex(this); + } + + private int getPrefixOrderIndex(WindowContainer child) { + int order = 0; + for (int i = 0; i < mChildren.size(); i++) { + final WindowContainer childI = mChildren.get(i); + if (child == childI) { + break; + } + order += childI.mTreeWeight; + } + if (mParent != null) { + order += mParent.getPrefixOrderIndex(this); + } + + // We also need to count ourselves. + order++; + return order; + } + + /** * Removes this window container and its children taking care not to remove them during a * critical stage in the system. For example, some containers will not be removed during * animation if this method is called. @@ -446,6 +513,20 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** + * @return {@code true} if in this subtree of the hierarchy we have an {@link AppWindowToken} + * that is {@link #isSelfAnimating}; {@code false} otherwise. + */ + boolean isAppAnimating() { + for (int j = mChildren.size() - 1; j >= 0; j--) { + final WindowContainer wc = mChildren.get(j); + if (wc.isAppAnimating()) { + return true; + } + } + return false; + } + + /** * @return Whether our own container running an animation at the moment. */ boolean isSelfAnimating() { @@ -532,14 +613,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - /** Step currently ongoing animation for App window containers. */ - void stepAppWindowsAnimation(long currentTime) { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer wc = mChildren.get(i); - wc.stepAppWindowsAnimation(currentTime); - } - } - void onAppTransitionDone() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); @@ -815,10 +888,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void assignLayer(Transaction t, int layer) { final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null; if (mSurfaceControl != null && changed) { - - // Route through surface animator to accommodate that our surface control might be - // attached to the leash, and leash is attached to parent container. - mSurfaceAnimator.setLayer(t, layer); + setLayer(t, layer); mLastLayer = layer; mLastRelativeToLayer = null; } @@ -827,15 +897,30 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo; if (mSurfaceControl != null && changed) { - - // Route through surface animator to accommodate that our surface control might be - // attached to the leash, and leash is attached to parent container. - mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer); + setRelativeLayer(t, relativeTo, layer); mLastLayer = layer; mLastRelativeToLayer = relativeTo; } } + protected void setLayer(Transaction t, int layer) { + + // Route through surface animator to accommodate that our surface control might be + // attached to the leash, and leash is attached to parent container. + mSurfaceAnimator.setLayer(t, layer); + } + + protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { + + // Route through surface animator to accommodate that our surface control might be + // attached to the leash, and leash is attached to parent container. + mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer); + } + + protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) { + mSurfaceAnimator.reparent(t, newParent); + } + void assignChildLayers(Transaction t) { int layer = 0; @@ -991,6 +1076,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceAnimator.startAnimation(t, anim, hidden); } + void transferAnimation(WindowContainer from) { + mSurfaceAnimator.transferAnimation(from.mSurfaceAnimator); + } + void cancelAnimation() { mSurfaceAnimator.cancelAnimation(); } @@ -1001,6 +1090,22 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } @Override + public SurfaceControl getAnimationLeashParent() { + return getParentSurfaceControl(); + } + + /** + * @return The layer on which all app animations are happening. + */ + SurfaceControl getAppAnimationLayer() { + final WindowContainer parent = getParent(); + if (parent != null) { + return parent.getAppAnimationLayer(); + } + return null; + } + + @Override public void commitPendingTransaction() { scheduleAnimation(); } @@ -1059,10 +1164,39 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mSurfaceControl.getHeight(); } + @CallSuper void dump(PrintWriter pw, String prefix, boolean dumpAll) { if (mSurfaceAnimator.isAnimating()) { pw.print(prefix); pw.println("ContainerAnimator:"); mSurfaceAnimator.dump(pw, prefix + " "); } } + + void updateSurfacePosition(SurfaceControl.Transaction transaction) { + if (mSurfaceControl == null) { + return; + } + + getRelativePosition(mTmpPos); + if (mTmpPos.equals(mLastSurfacePosition)) { + return; + } + + transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); + mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); + + for (int i = mChildren.size() - 1; i >= 0; i--) { + mChildren.get(i).updateSurfacePosition(transaction); + } + } + + void getRelativePosition(Point outPos) { + final Rect bounds = getBounds(); + outPos.set(bounds.left, bounds.top); + final WindowContainer parent = getParent(); + if (parent != null) { + final Rect parentBounds = parent.getBounds(); + outPos.offset(-parentBounds.left, -parentBounds.top); + } + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 62d2e7d8e1c5..1935a44cf243 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -118,8 +118,11 @@ public abstract class WindowManagerInternal { * of AppTransition.TRANSIT_* values * @param openToken the token for the opening app * @param closeToken the token for the closing app - * @param openAnimation the animation for the opening app - * @param closeAnimation the animation for the closing app + * @param duration the total duration of the transition + * @param statusBarAnimationStartTime the desired start time for all visual animations in + * the status bar caused by this app transition in uptime millis + * @param statusBarAnimationDuration the duration for all visual animations in the status + * bar caused by this app transition in millis * * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT}, * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG}, @@ -127,7 +130,7 @@ public abstract class WindowManagerInternal { * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}. */ public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken, - Animation openAnimation, Animation closeAnimation) { + long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { return 0; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9fc9f3c15cfc..0a2ffbc96fe5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -71,13 +71,9 @@ import static com.android.server.LockGuard.INDEX_WINDOW; import static com.android.server.LockGuard.installLock; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.AppTransition.TRANSIT_UNSET; -import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END; -import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START; import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; @@ -177,6 +173,7 @@ import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; @@ -566,12 +563,10 @@ public class WindowManagerService extends IWindowManager.Stub int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; - private final SparseIntArray mTmpTaskIds = new SparseIntArray(); - boolean mForceResizableTasks = false; boolean mSupportsPictureInPicture = false; - private boolean mDisableTransitionAnimation = false; + boolean mDisableTransitionAnimation = false; int getDragLayerLocked() { return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET; @@ -769,6 +764,11 @@ public class WindowManagerService extends IWindowManager.Stub final WindowAnimator mAnimator; final SurfaceAnimationRunner mSurfaceAnimationRunner; + /** + * Keeps track of which animations got transferred to which animators. Entries will get cleaned + * up when the animation finishes. + */ + final ArrayMap<AnimationAdapter, SurfaceAnimator> mAnimationTransferMap = new ArrayMap<>(); final BoundsAnimationController mBoundsAnimationController; private final PointerEventDispatcher mPointerEventDispatcher; @@ -852,35 +852,6 @@ public class WindowManagerService extends IWindowManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } - - /** - * Executes an empty animation transaction without holding the WM lock to simulate - * back-pressure. See {@link WindowAnimator#animate} why this is needed. - */ - void executeEmptyAnimationTransaction() { - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction"); - synchronized (mWindowMap) { - if (mRoot.mSurfaceTraceEnabled) { - mRoot.mRemoteEventTrace.openSurfaceTransaction(); - } - SurfaceControl.openTransaction(); - SurfaceControl.setAnimationTransaction(); - if (mRoot.mSurfaceTraceEnabled) { - mRoot.mRemoteEventTrace.closeSurfaceTransaction(); - } - } - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction"); - SurfaceControl.closeTransaction(); - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - } - /** Listener to notify activity manager about app transitions. */ final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier = new WindowManagerInternal.AppTransitionListener() { @@ -2279,83 +2250,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp, - int transit, boolean enter, boolean isVoiceInteraction) { - if (mDisableTransitionAnimation) { - if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { - Slog.v(TAG_WM, - "applyAnimation: transition animation is disabled. atoken=" + atoken); - } - atoken.mAppAnimator.clearAnimation(); - return false; - } - // Only apply an animation if the display isn't frozen. If it is - // frozen, there is no reason to animate and it can cause strange - // artifacts when we unfreeze the display if some different animation - // is running. - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked"); - if (atoken.okToAnimate()) { - final DisplayContent displayContent = atoken.getTask().getDisplayContent(); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - final int width = displayInfo.appWidth; - final int height = displayInfo.appHeight; - if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM, - "applyAnimation: atoken=" + atoken); - - // Determine the visible rect to calculate the thumbnail clip - final WindowState win = atoken.findMainWindow(); - final Rect frame = new Rect(0, 0, width, height); - final Rect displayFrame = new Rect(0, 0, - displayInfo.logicalWidth, displayInfo.logicalHeight); - final Rect insets = new Rect(); - final Rect stableInsets = new Rect(); - Rect surfaceInsets = null; - final boolean freeform = win != null && win.inFreeformWindowingMode(); - if (win != null) { - // Containing frame will usually cover the whole screen, including dialog windows. - // For freeform workspace windows it will not cover the whole screen and it also - // won't exactly match the final freeform window frame (e.g. when overlapping with - // the status bar). In that case we need to use the final frame. - if (freeform) { - frame.set(win.mFrame); - } else { - frame.set(win.mContainingFrame); - } - surfaceInsets = win.getAttrs().surfaceInsets; - insets.set(win.mContentInsets); - stableInsets.set(win.mStableInsets); - } - - if (atoken.mLaunchTaskBehind) { - // Differentiate the two animations. This one which is briefly on the screen - // gets the !enter animation, and the other activity which remains on the - // screen gets the enter animation. Both appear in the mOpeningApps set. - enter = false; - } - if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition." - + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter - + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets); - final Configuration displayConfig = displayContent.getConfiguration(); - Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode, - displayConfig.orientation, frame, displayFrame, insets, surfaceInsets, - stableInsets, isVoiceInteraction, freeform, atoken.getTask().mTaskId); - if (a != null) { - if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken); - final int containingWidth = frame.width(); - final int containingHeight = frame.height(); - atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, width, - height, mAppTransition.canSkipFirstFrame(), - mAppTransition.getAppStackClipMode(), - transit, mAppTransition.getTransitFlags()); - } - } else { - atoken.mAppAnimator.clearAnimation(); - } - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - - return atoken.mAppAnimator.animation != null; - } - boolean checkCallingPermission(String permission, String func) { // Quick check: if the calling permission is me, it's all okay. if (Binder.getCallingPid() == myPid()) { @@ -2701,29 +2595,13 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback, onAnimationFinishedCallback, scaleUp); - prolongAnimationsFromSpecs(specs, scaleUp); } } - void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) { - // This is used by freeform <-> recents windows transition. We need to synchronize - // the animation with the appearance of the content of recents, so we will make - // animation stay on the first or last frame a little longer. - mTmpTaskIds.clear(); - for (int i = specs.length - 1; i >= 0; i--) { - mTmpTaskIds.put(specs[i].taskId, 0); - } - for (final WindowState win : mWindowMap.values()) { - final Task task = win.getTask(); - if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1 - && task.inFreeformWindowingMode()) { - final AppWindowToken appToken = win.mAppToken; - if (appToken != null && appToken.mAppAnimator != null) { - appToken.mAppAnimator.startProlongAnimation(scaleUp ? - PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END); - } - } + public void overridePendingAppTransitionStartCrossProfileApps() { + synchronized (mWindowMap) { + mAppTransition.overridePendingAppTransitionStartCrossProfileApps(); } } @@ -2749,8 +2627,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { for (final WindowState win : mWindowMap.values()) { final AppWindowToken appToken = win.mAppToken; - if (appToken != null && appToken.mAppAnimator != null) { - appToken.mAppAnimator.endProlongedAnimation(); + if (appToken != null) { + appToken.endDelayingAnimationStart(); } } mAppTransition.notifyProlongedAnimationsEnded(); @@ -2805,15 +2683,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) { - if (transit != TRANSIT_UNSET) { - if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) { - wtoken.mAppAnimator.setNullAnimation(); - } - applyAnimationLocked(wtoken, null, transit, false, false); - } - } - public void setDockedStackCreateState(int mode, Rect bounds) { synchronized (mWindowMap) { setDockedStackCreateStateLocked(mode, bounds); @@ -5608,7 +5477,9 @@ public class WindowManagerService extends IWindowManager.Stub /** Note that Locked in this case is on mLayoutToAnim */ void scheduleAnimationLocked() { - mAnimator.scheduleAnimation(); + if (mAnimator != null) { + mAnimator.scheduleAnimation(); + } } // TODO: Move to DisplayContent diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java index 1b2eb465399a..dd89b3b29b9d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java +++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java @@ -36,6 +36,7 @@ class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster { private final Object mLock = new Object(); private final int mAnimationThreadId; + private final int mSurfaceAnimationThreadId; @GuardedBy("mLock") private boolean mAppTransitionRunning; @@ -45,14 +46,16 @@ class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster { WindowManagerThreadPriorityBooster() { super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW); mAnimationThreadId = AnimationThread.get().getThreadId(); + mSurfaceAnimationThreadId = SurfaceAnimationThread.get().getThreadId(); } @Override public void boost() { - // Do not boost the animation thread. As the animation thread is changing priorities, + // Do not boost the animation threads. As the animation threads are changing priorities, // boosting it might mess up the priority because we reset it the the previous priority. - if (myTid() == mAnimationThreadId) { + final int myTid = myTid(); + if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) { return; } super.boost(); @@ -62,7 +65,8 @@ class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster { public void reset() { // See comment in boost(). - if (myTid() == mAnimationThreadId) { + final int myTid = myTid(); + if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) { return; } super.reset(); @@ -92,5 +96,6 @@ class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster { ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY; setBoostToPriority(priority); setThreadPriority(mAnimationThreadId, priority); + setThreadPriority(mSurfaceAnimationThreadId, priority); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ddc1eace1ea2..c0aff4c8a701 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -142,6 +142,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.MergedConfiguration; import android.util.Slog; @@ -1432,7 +1433,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this? boolean isWinVisibleLw() { - return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating) + return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.isSelfAnimating()) && isVisible(); } @@ -1441,7 +1442,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * not the pending requested hidden state. */ boolean isVisibleNow() { - return (!mToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING) + return (!mToken.isHidden() || mAttrs.type == TYPE_APPLICATION_STARTING) && isVisible(); } @@ -1479,7 +1480,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final AppWindowToken atoken = mAppToken; if (atoken != null) { return ((!isParentWindowHidden() && !atoken.hiddenRequested) - || mWinAnimator.isAnimationSet() || atoken.mAppAnimator.animation != null); + || mWinAnimator.isAnimationSet()); } return !isParentWindowHidden() || mWinAnimator.isAnimationSet(); } @@ -1501,7 +1502,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ boolean isInteresting() { return mAppToken != null && !mAppDied - && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing); + && (!mAppToken.isFreezingScreen() || !mAppFreezing); } /** @@ -1513,33 +1514,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } return mHasSurface && mPolicyVisibility && !mDestroying - && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden) - || mWinAnimator.isAnimationSet() - || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null))); + && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.isHidden()) + || mWinAnimator.isAnimationSet()); } // TODO: Another visibility method that was added late in the release to minimize risk. @Override public boolean canAffectSystemUiFlags() { - final boolean shown = mWinAnimator.getShown(); - - // We only consider the app to be exiting when the animation has started. After the app - // transition is executed the windows are marked exiting before the new windows have been - // shown. Thus, wait considering a window to be exiting after the animation has actually - // started. - final boolean appAnimationStarting = mAppToken != null - && mAppToken.mAppAnimator.isAnimationStarting(); - final boolean exitingSelf = mAnimatingExit && !appAnimationStarting; - final boolean appExiting = mAppToken != null && mAppToken.hidden && !appAnimationStarting; - - final boolean exiting = exitingSelf || mDestroying || appExiting; final boolean translucent = mAttrs.alpha == 0.0f; - - // If we are entering with a dummy animation, avoid affecting SystemUI flags until the - // transition is starting. - final boolean enteringWithDummyAnimation = - mWinAnimator.isDummyAnimation() && mWinAnimator.mShownAlpha == 0f; - return shown && !exiting && !translucent && !enteringWithDummyAnimation; + if (mAppToken == null) { + final boolean shown = mWinAnimator.getShown(); + final boolean exiting = mAnimatingExit || mDestroying; + return shown && !exiting && !translucent; + } else { + return !mAppToken.isHidden(); + } } /** @@ -1550,10 +1539,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP public boolean isDisplayedLw() { final AppWindowToken atoken = mAppToken; return isDrawnLw() && mPolicyVisibility - && ((!isParentWindowHidden() && - (atoken == null || !atoken.hiddenRequested)) - || mWinAnimator.isAnimationSet() - || (atoken != null && atoken.mAppAnimator.animation != null)); + && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested)) + || mWinAnimator.isAnimationSet()); } /** @@ -1561,8 +1548,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ @Override public boolean isAnimatingLw() { - return mWinAnimator.isAnimationSet() - || (mAppToken != null && mAppToken.mAppAnimator.animation != null); + return isAnimating(); } @Override @@ -1570,7 +1556,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final AppWindowToken atoken = mAppToken; return mViewVisibility == View.GONE || !mRelayoutCalled - || (atoken == null && mToken.hidden) + || (atoken == null && mToken.isHidden()) || (atoken != null && atoken.hiddenRequested) || isParentWindowHidden() || (mAnimatingExit && !isAnimatingLw()) @@ -1608,8 +1594,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // to determine if it's occluding apps. return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE) || (mIsWallpaper && mWallpaperVisible)) - && isDrawnLw() && !mWinAnimator.isAnimationSet() - && (mAppToken == null || mAppToken.mAppAnimator.animation == null); + && isDrawnLw() && !mWinAnimator.isAnimationSet(); } @Override @@ -1631,7 +1616,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Starting window that's exiting will be removed when the animation finishes. // Mark all relevant flags for that onExitAnimationDone will proceed all the way // to actually remove it. - if (!visible && isVisibleNow() && mAppToken.mAppAnimator.isAnimating()) { + if (!visible && isVisibleNow() && mAppToken.isSelfAnimating()) { mAnimatingExit = true; mRemoveOnExit = true; mWindowRemovalAllowed = true; @@ -1908,7 +1893,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " surfaceShowing=" + mWinAnimator.getShown() + " isAnimationSet=" + mWinAnimator.isAnimationSet() + " app-animation=" - + (mAppToken != null ? mAppToken.mAppAnimator.animation : null) + + (mAppToken != null ? mAppToken.isSelfAnimating() : "false") + " mWillReplaceWindow=" + mWillReplaceWindow + " inPendingTransaction=" + (mAppToken != null ? mAppToken.inPendingTransaction : false) @@ -1968,8 +1953,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mService.mAccessibilityController.onWindowTransitionLocked(this, transit); } } - final boolean isAnimating = - mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation(); + final boolean isAnimating = mWinAnimator.isAnimationSet() + && (mAppToken == null || !mAppToken.isWaitingForTransitionStart()); final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null && mAppToken.isLastWindow(this); // We delay the removal of a window if it has a showing surface that can be used to run @@ -2019,28 +2004,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mHasSurface = hasSurface; } - int getAnimLayerAdjustment() { - if (mIsImWindow && mService.mInputMethodTarget != null) { - final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken; - if (appToken != null) { - return appToken.getAnimLayerAdjustment(); - } - } - - return mToken.getAnimLayerAdjustment(); - } - - int getSpecialWindowAnimLayerAdjustment() { - int specialAdjustment = 0; - if (mIsImWindow) { - specialAdjustment = getDisplayContent().mInputMethodAnimLayerAdjustment; - } else if (mIsWallpaper) { - specialAdjustment = getDisplayContent().mWallpaperController.getAnimLayerAdjustment(); - } - - return mLayer + specialAdjustment; - } - boolean canBeImeTarget() { if (mIsImWindow) { // IME windows can't be IME targets. IME targets are required to be below the IME @@ -2260,12 +2223,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator + ": " + mPolicyVisibilityAfterAnim); } mPolicyVisibility = mPolicyVisibilityAfterAnim; - setDisplayLayoutNeeded(); if (!mPolicyVisibility) { + mWinAnimator.hide("checkPolicyVisibilityChange"); if (mService.mCurrentFocus == this) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "setAnimationLocked: setting mFocusMayChange true"); mService.mFocusMayChange = true; + setDisplayLayoutNeeded(); } // Window is no longer visible -- make sure if we were waiting // for it to be displayed before enabling the display, that @@ -3170,7 +3134,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); pw.print(" mSubLayer="); pw.print(mSubLayer); pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+"); - pw.print(getAnimLayerAdjustment()); pw.print("="); pw.print(mWinAnimator.mAnimLayer); pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer); } @@ -3697,10 +3660,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " parentHidden=" + isParentWindowHidden() + " tok.hiddenRequested=" + (mAppToken != null && mAppToken.hiddenRequested) - + " tok.hidden=" + (mAppToken != null && mAppToken.hidden) + + " tok.hidden=" + (mAppToken != null && mAppToken.isHidden()) + " animationSet=" + mWinAnimator.isAnimationSet() + " tok animating=" - + (mWinAnimator.mAppAnimator != null && mWinAnimator.mAppAnimator.animating) + + (mAppToken != null && mAppToken.isSelfAnimating()) + " Callers=" + Debug.getCallers(4)); } } @@ -3714,6 +3677,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP windowInfo.activityToken = mAppToken.appToken.asBinder(); } windowInfo.title = mAttrs.accessibilityTitle; + // Panel windows have no public way to set the a11y title directly. Use the + // regular title as a fallback. + if (TextUtils.isEmpty(windowInfo.title) + && (mAttrs.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW) + && (mAttrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW)) { + windowInfo.title = mAttrs.getTitle(); + } windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor; windowInfo.focused = isFocused(); Task task = getTask(); @@ -4292,7 +4262,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP anim.restrictDuration(MAX_ANIMATION_DURATION); anim.scaleCurrentDuration(mService.getWindowAnimationScaleLocked()); final AnimationAdapter adapter = new LocalAnimationAdapter( - new WindowAnimationSpec(anim, mSurfacePosition), mService.mSurfaceAnimationRunner); + new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */), + mService.mSurfaceAnimationRunner); startAnimation(mPendingTransaction, adapter); commitPendingTransaction(); } @@ -4329,8 +4300,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx; float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy; float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy; - float9[Matrix.MTRANS_X] = mSurfacePosition.x + mShownPosition.x; - float9[Matrix.MTRANS_Y] = mSurfacePosition.y + mShownPosition.y; + int x = mSurfacePosition.x + mShownPosition.x; + int y = mSurfacePosition.y + mShownPosition.y; + + // If changed, also adjust transformFrameToSurfacePosition + final WindowContainer parent = getParent(); + if (isChildWindow()) { + final WindowState parentWindow = getParentWindow(); + x += parentWindow.mFrame.left - parentWindow.mAttrs.surfaceInsets.left; + y += parentWindow.mFrame.top - parentWindow.mAttrs.surfaceInsets.top; + } else if (parent != null) { + final Rect parentBounds = parent.getBounds(); + x += parentBounds.left; + y += parentBounds.top; + } + float9[Matrix.MTRANS_X] = x; + float9[Matrix.MTRANS_Y] = y; float9[Matrix.MPERSP_0] = 0; float9[Matrix.MPERSP_1] = 0; float9[Matrix.MPERSP_2] = 1; @@ -4415,7 +4400,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean needsZBoost() { - return getAnimLayerAdjustment() > 0 || mWillReplaceWindow; + if (mIsImWindow && mService.mInputMethodTarget != null) { + final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken; + if (appToken != null) { + return appToken.needsZBoost(); + } + } + return mWillReplaceWindow; } private void applyDims(Dimmer dimmer) { @@ -4449,6 +4440,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Leash is now responsible for position, so set our position to 0. t.setPosition(mSurfaceControl, 0, 0); + mLastSurfacePosition.set(0, 0); } @Override @@ -4457,24 +4449,36 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateSurfacePosition(t); } + @Override void updateSurfacePosition(Transaction t) { if (mSurfaceControl == null) { return; } transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition); - if (!mSurfaceAnimator.hasLeash()) { + if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) { t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y); + mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y); } } private void transformFrameToSurfacePosition(int left, int top, Point outPoint) { outPoint.set(left, top); + + // If changed, also adjust getTransformationMatrix + final WindowContainer parentWindowContainer = getParent(); if (isChildWindow()) { // TODO: This probably falls apart at some point and we should // actually compute relative coordinates. + + // Since the parent was outset by its surface insets, we need to undo the outsetting + // with insetting by the same amount. final WindowState parent = getParentWindow(); - outPoint.offset(-parent.mFrame.left, -parent.mFrame.top); + outPoint.offset(-parent.mFrame.left + parent.mAttrs.surfaceInsets.left, + -parent.mFrame.top + parent.mAttrs.surfaceInsets.top); + } else if (parentWindowContainer != null) { + final Rect parentBounds = parentWindowContainer.getBounds(); + outPoint.offset(-parentBounds.left, -parentBounds.top); } // Expand for surface insets. See WindowState.expandForSurfaceInsets. diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 0eabc898451a..d2247ac2c973 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -25,7 +25,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.AppWindowAnimator.sDummyAnimation; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; @@ -46,7 +45,6 @@ import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN; import static com.android.server.wm.proto.WindowStateAnimatorProto.LAST_CLIP_RECT; import static com.android.server.wm.proto.WindowStateAnimatorProto.SURFACE; -import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Matrix; import android.graphics.PixelFormat; @@ -65,7 +63,6 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.view.animation.Transformation; import com.android.server.policy.WindowManagerPolicy; @@ -103,7 +100,6 @@ class WindowStateAnimator { final WindowState mWin; private final WindowStateAnimator mParentWinAnimator; final WindowAnimator mAnimator; - AppWindowAnimator mAppAnimator; final Session mSession; final WindowManagerPolicy mPolicy; final Context mContext; @@ -228,7 +224,6 @@ class WindowStateAnimator { mWin = win; mParentWinAnimator = !win.isChildWindow() ? null : win.getParentWindow().mWinAnimator; - mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator; mSession = win.mSession; mAttrType = win.mAttrs.type; mIsWallpaper = win.mIsWallpaper; @@ -242,17 +237,12 @@ class WindowStateAnimator { return mWin.isAnimating(); } - /** Is the window animating the DummyAnimation? */ - boolean isDummyAnimation() { - return mAppAnimator != null - && mAppAnimator.animation == sDummyAnimation; - } - /** * Is this window currently waiting to run an opening animation? */ boolean isWaitingForOpening() { - return mService.mAppTransition.isTransitionSet() && isDummyAnimation() + return mService.mAppTransition.isTransitionSet() + && (mWin.mAppToken != null && mWin.mAppToken.isHidden()) && mService.mOpeningApps.contains(mWin.mAppToken); } @@ -423,7 +413,7 @@ class WindowStateAnimator { return; } - if (mWin.mAppToken.mAppAnimator.animation == null) { + if (!mWin.mAppToken.isSelfAnimating()) { mWin.mAppToken.clearAllDrawn(); } else { // Currently animating, persist current state of allDrawn until animation @@ -667,8 +657,6 @@ class WindowStateAnimator { } void computeShownFrameLocked() { - Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation) - ? mAppAnimator.transformation : null; final int displayId = mWin.getDisplayId(); final ScreenRotationAnimation screenRotationAnimation = @@ -677,14 +665,14 @@ class WindowStateAnimator { screenRotationAnimation != null && screenRotationAnimation.isAnimating(); mHasClipRect = false; - if (appTransformation != null || screenAnimation) { + if (screenAnimation) { // cache often used attributes locally final Rect frame = mWin.mFrame; final float tmpFloats[] = mService.mTmpFloats; final Matrix tmpMatrix = mWin.mTmpMatrix; // Compute the desired transformation. - if (screenAnimation && screenRotationAnimation.isRotating()) { + if (screenRotationAnimation.isRotating()) { // If we are doing a screen animation, the global rotation // applied to windows can result in windows that are carefully // aligned with each other to slightly separate, allowing you @@ -702,6 +690,7 @@ class WindowStateAnimator { } else { tmpMatrix.reset(); } + tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale); // WindowState.prepareSurfaces expands for surface insets (in order they don't get @@ -709,9 +698,6 @@ class WindowStateAnimator { tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left, mWin.mYOffset + mWin.mAttrs.surfaceInsets.top); - if (appTransformation != null) { - tmpMatrix.postConcat(appTransformation.getMatrix()); - } // "convert" it into SurfaceFlinger's format // (a 2x2 matrix + an offset) @@ -740,24 +726,6 @@ class WindowStateAnimator { || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy) && x == frame.left && y == frame.top))) { //Slog.i(TAG_WM, "Applying alpha transform"); - if (appTransformation != null) { - mShownAlpha *= appTransformation.getAlpha(); - if (appTransformation.hasClipRect()) { - mClipRect.set(appTransformation.getClipRect()); - mHasClipRect = true; - // The app transformation clip will be in the coordinate space of the main - // activity window, which the animation correctly assumes will be placed at - // (0,0)+(insets) relative to the containing frame. This isn't necessarily - // true for child windows though which can have an arbitrary frame position - // relative to their containing frame. We need to offset the difference - // between the containing frame as used to calculate the crop and our - // bounds to compensate for this. - if (mWin.layoutInParentFrame()) { - mClipRect.offset( (mWin.mContainingFrame.left - mWin.mFrame.left), - mWin.mContainingFrame.top - mWin.mFrame.top ); - } - } - } if (screenAnimation) { mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha(); } @@ -768,7 +736,6 @@ class WindowStateAnimator { if ((DEBUG_ANIM || WindowManagerService.localLOGV) && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v( TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha - + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha()) + " screen=" + (screenAnimation ? screenRotationAnimation.getEnterTransformation().getAlpha() : "null")); return; @@ -800,47 +767,6 @@ class WindowStateAnimator { } /** - * In some scenarios we use a screen space clip rect (so called, final clip rect) - * to crop to stack bounds. Generally because it's easier to deal with while - * animating. - * - * @return True in scenarios where we use the final clip rect for stack clipping. - */ - private boolean useFinalClipRect() { - return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM) - || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode(); - } - - /** - * Calculate the screen-space crop rect and fill finalClipRect. - * @return true if finalClipRect has been filled, otherwise, - * no screen space crop should be applied. - */ - private boolean calculateFinalCrop(Rect finalClipRect) { - final WindowState w = mWin; - final DisplayContent displayContent = w.getDisplayContent(); - finalClipRect.setEmpty(); - - if (displayContent == null) { - return false; - } - - if (!shouldCropToStackBounds() || !useFinalClipRect()) { - return false; - } - - // Task is non-null per shouldCropToStackBounds - final TaskStack stack = w.getTask().mStack; - stack.getDimBounds(finalClipRect); - - if (stack.getWindowConfiguration().tasksAreFloating()) { - w.expandForSurfaceInsets(finalClipRect); - } - - return true; - } - - /** * Calculate the window-space crop rect and fill clipRect. * @return true if clipRect has been filled otherwise, no window space crop should be applied. */ @@ -900,9 +826,6 @@ class WindowStateAnimator { // so we need to translate to match the actual surface coordinates. clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top); - if (!useFinalClipRect()) { - adjustCropToStackBounds(clipRect, isFreeformResizing); - } if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Clip rect after stack adjustment=" + clipRect); @@ -911,9 +834,9 @@ class WindowStateAnimator { return true; } - private void applyCrop(Rect clipRect, Rect finalClipRect, boolean recoveringMemory) { + private void applyCrop(Rect clipRect, boolean recoveringMemory) { if (DEBUG_WINDOW_CROP) Slog.d(TAG, "applyCrop: win=" + mWin - + " clipRect=" + clipRect + " finalClipRect=" + finalClipRect); + + " clipRect=" + clipRect); if (clipRect != null) { if (!clipRect.equals(mLastClipRect)) { mLastClipRect.set(clipRect); @@ -922,93 +845,6 @@ class WindowStateAnimator { } else { mSurfaceController.clearCropInTransaction(recoveringMemory); } - - if (finalClipRect == null) { - finalClipRect = mService.mTmpRect; - finalClipRect.setEmpty(); - } - if (!finalClipRect.equals(mLastFinalClipRect)) { - mLastFinalClipRect.set(finalClipRect); - mSurfaceController.setFinalCropInTransaction(finalClipRect); - if (mDestroyPreservedSurfaceUponRedraw && mPendingDestroySurface != null) { - mPendingDestroySurface.setFinalCropInTransaction(finalClipRect); - } - } - } - - private int resolveStackClip() { - // App animation overrides window animation stack clip mode. - if (mAppAnimator != null && mAppAnimator.animation != null) { - return mAppAnimator.getStackClip(); - } else { - return STACK_CLIP_AFTER_ANIM; - } - } - - private boolean shouldCropToStackBounds() { - final WindowState w = mWin; - final DisplayContent displayContent = w.getDisplayContent(); - if (displayContent != null && !displayContent.isDefaultDisplay) { - // There are some windows that live on other displays while their app and main window - // live on the default display (e.g. casting...). We don't want to crop this windows - // to the stack bounds which is only currently supported on the default display. - // TODO(multi-display): Need to support cropping to stack bounds on other displays - // when we have stacks on other displays. - return false; - } - - final Task task = w.getTask(); - if (task == null || !task.cropWindowsToStackBounds()) { - return false; - } - - final int stackClip = resolveStackClip(); - - // It's animating and we don't want to clip it to stack bounds during animation - abort. - if (isAnimationSet() && stackClip == STACK_CLIP_NONE) { - return false; - } - return true; - } - - private void adjustCropToStackBounds(Rect clipRect, - boolean isFreeformResizing) { - final WindowState w = mWin; - - if (!shouldCropToStackBounds()) { - return; - } - - final TaskStack stack = w.getTask().mStack; - stack.getDimBounds(mTmpStackBounds); - final Rect surfaceInsets = w.getAttrs().surfaceInsets; - // When we resize we use the big surface approach, which means we can't trust the - // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid - // hardcoding it, we use surface coordinates. - final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() : - w.mFrame.left + mWin.mXOffset - surfaceInsets.left; - final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() : - w.mFrame.top + mWin.mYOffset - surfaceInsets.top; - - // We need to do some acrobatics with surface position, because their clip region is - // relative to the inside of the surface, but the stack bounds aren't. - final WindowConfiguration winConfig = w.getWindowConfiguration(); - if (winConfig.hasWindowShadow() && !winConfig.canResizeTask()) { - // The windows in this stack display drop shadows and the fill the entire stack - // area. Adjust the stack bounds we will use to cropping take into account the - // offsets we use to display the drop shadow so it doesn't get cropped. - mTmpStackBounds.inset(-surfaceInsets.left, -surfaceInsets.top, - -surfaceInsets.right, -surfaceInsets.bottom); - } - - clipRect.left = Math.max(0, - Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX); - clipRect.top = Math.max(0, - Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY); - clipRect.right = Math.max(0, - Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX); - clipRect.bottom = Math.max(0, - Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY); } void setSurfaceBoundariesLocked(final boolean recoveringMemory) { @@ -1053,13 +889,10 @@ class WindowStateAnimator { // updates until a resize occurs. mService.markForSeamlessRotation(w, w.mSeamlesslyRotated && !mSurfaceResized); - Rect clipRect = null, finalClipRect = null; + Rect clipRect = null; if (calculateCrop(mTmpClipRect)) { clipRect = mTmpClipRect; } - if (calculateFinalCrop(mTmpFinalClipRect)) { - finalClipRect = mTmpFinalClipRect; - } float surfaceWidth = mSurfaceController.getWidth(); float surfaceHeight = mSurfaceController.getHeight(); @@ -1124,7 +957,6 @@ class WindowStateAnimator { // Always clip to the stack bounds since the surface can be larger with the current // scale clipRect = null; - finalClipRect = mTmpStackBounds; } else { // We want to calculate the scaling based on the content area, not based on // the entire surface, so that we scale in sync with windows that don't have insets. @@ -1135,7 +967,6 @@ class WindowStateAnimator { // expose the whole window in buffer space, and not risk extending // past where the system would have cropped us clipRect = null; - finalClipRect = null; } // In the case of ForceScaleToStack we scale entire tasks together, @@ -1183,7 +1014,7 @@ class WindowStateAnimator { } if (!w.mSeamlesslyRotated) { - applyCrop(clipRect, finalClipRect, recoveringMemory); + applyCrop(clipRect, recoveringMemory); mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale, mDtDx * w.mVScale * mExtraVScale, mDtDy * w.mHScale * mExtraHScale, @@ -1381,7 +1212,7 @@ class WindowStateAnimator { mService.openSurfaceTransaction(); mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left, mWin.mFrame.top + top, false); - applyCrop(null, null, false); + applyCrop(null, false); } catch (RuntimeException e) { Slog.w(TAG, "Error positioning surface of " + mWin + " pos=(" + left + "," + top + ")", e); diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index d8e7457d95ef..bdab9c765d36 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -97,9 +97,6 @@ class WindowSurfacePlacer { static final int SET_TURN_ON_SCREEN = 1 << 4; static final int SET_WALLPAPER_ACTION_PENDING = 1 << 5; - private final Rect mTmpStartRect = new Rect(); - private final Rect mTmpContentRect = new Rect(); - private boolean mTraversalScheduled; private int mDeferDepth = 0; @@ -361,14 +358,9 @@ class WindowSurfacePlacer { mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp); - final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null : - topOpeningApp.mAppAnimator; - final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null : - topClosingApp.mAppAnimator; - final int flags = mService.mAppTransition.getTransitFlags(); - int layoutRedo = mService.mAppTransition.goodToGo(transit, openingAppAnimator, - closingAppAnimator, mService.mOpeningApps, mService.mClosingApps); + int layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp, + topClosingApp, mService.mOpeningApps, mService.mClosingApps); handleNonAppWindowsInTransition(transit, flags); mService.mAppTransition.postAnimationCallback(); mService.mAppTransition.clear(); @@ -405,14 +397,8 @@ class WindowSurfacePlacer { final int appsCount = mService.mOpeningApps.size(); for (int i = 0; i < appsCount; i++) { AppWindowToken wtoken = mService.mOpeningApps.valueAt(i); - final AppWindowAnimator appAnimator = wtoken.mAppAnimator; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken); - if (!appAnimator.usingTransferredAnimation) { - appAnimator.clearThumbnail(); - appAnimator.setNullAnimation(); - } - if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)){ // This token isn't going to be animating. Add it to the list of tokens to // be notified of app transition complete since the notification will not be @@ -421,19 +407,17 @@ class WindowSurfacePlacer { } wtoken.updateReportedVisibilityLocked(); wtoken.waitingToShow = false; - wtoken.setAllAppWinAnimators(); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()"); mService.openSurfaceTransaction(); try { - mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked()); + wtoken.showAllWindowsLocked(); } finally { mService.closeSurfaceTransaction("handleAppTransitionReadyLocked"); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()"); } - mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating(); if (animLp != null) { final int layer = wtoken.getHighestAnimLayer(); @@ -443,7 +427,7 @@ class WindowSurfacePlacer { } } if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) { - createThumbnailAppAnimator(transit, wtoken); + wtoken.attachThumbnailAnimation(); } } return topOpeningApp; @@ -456,18 +440,11 @@ class WindowSurfacePlacer { for (int i = 0; i < appsCount; i++) { AppWindowToken wtoken = mService.mClosingApps.valueAt(i); - final AppWindowAnimator appAnimator = wtoken.mAppAnimator; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken); - appAnimator.clearThumbnail(); - appAnimator.setNullAnimation(); // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not // animating? - wtoken.setAllAppWinAnimators(); wtoken.setVisibility(animLp, false, transit, false, voiceInteraction); wtoken.updateReportedVisibilityLocked(); - // setAllAppWinAnimators so the windows get onExitAnimationDone once the animation is - // done. - wtoken.setAllAppWinAnimators(); // Force the allDrawn flag, because we want to start // this guy's animations regardless of whether it's // gotten drawn. @@ -479,7 +456,6 @@ class WindowSurfacePlacer { && wtoken.getController() != null) { wtoken.getController().removeStartingWindow(); } - mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating(); if (animLp != null) { int layer = wtoken.getHighestAnimLayer(); @@ -489,7 +465,7 @@ class WindowSurfacePlacer { } } if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) { - createThumbnailAppAnimator(transit, wtoken); + wtoken.attachThumbnailAnimation(); } } } @@ -666,99 +642,13 @@ class WindowSurfacePlacer { final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow(); if (win != null) { final AppWindowToken wtoken = win.mAppToken; - final AppWindowAnimator appAnimator = wtoken.mAppAnimator; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now animating app in place " + wtoken); - appAnimator.clearThumbnail(); - appAnimator.setNullAnimation(); - mService.updateTokenInPlaceLocked(wtoken, transit); + wtoken.cancelAnimation(); + wtoken.applyAnimationLocked(null, transit, false, false); wtoken.updateReportedVisibilityLocked(); - wtoken.setAllAppWinAnimators(); - mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating(); - mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked()); - } - } - } - - private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) { - AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator; - if (openingAppAnimator == null || openingAppAnimator.animation == null) { - return; - } - final int taskId = appToken.getTask().mTaskId; - final GraphicBuffer thumbnailHeader = - mService.mAppTransition.getAppTransitionThumbnailHeader(taskId); - if (thumbnailHeader == null) { - if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId); - return; - } - // This thumbnail animation is very special, we need to have - // an extra surface with the thumbnail included with the animation. - Rect dirty = new Rect(0, 0, thumbnailHeader.getWidth(), thumbnailHeader.getHeight()); - try { - // TODO(multi-display): support other displays - final DisplayContent displayContent = mService.getDefaultDisplayContentLocked(); - final Display display = displayContent.getDisplay(); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - - // Create a new surface for the thumbnail - WindowState window = appToken.findMainWindow(); - final SurfaceControl surfaceControl = appToken.makeSurface() - .setName("thumbnail anim") - .setSize(dirty.width(), dirty.height()) - .setFormat(PixelFormat.TRANSLUCENT) - .setMetadata(appToken.windowType, - window != null ? window.mOwnerUid : Binder.getCallingUid()) - .build(); - - if (SHOW_TRANSACTIONS) { - Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE"); - } - - // Transfer the thumbnail to the surface - Surface drawSurface = new Surface(); - drawSurface.copyFrom(surfaceControl); - drawSurface.attachAndQueueBuffer(thumbnailHeader); - drawSurface.release(); - - // Get the thumbnail animation - Animation anim; - if (mService.mAppTransition.isNextThumbnailTransitionAspectScaled()) { - // If this is a multi-window scenario, we use the windows frame as - // destination of the thumbnail header animation. If this is a full screen - // window scenario, we use the whole display as the target. - WindowState win = appToken.findMainWindow(); - Rect appRect = win != null ? win.getContentFrameLw() : - new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight); - Rect insets = win != null ? win.mContentInsets : null; - final Configuration displayConfig = displayContent.getConfiguration(); - // For the new aspect-scaled transition, we want it to always show - // above the animating opening/closing window, and we want to - // synchronize its thumbnail surface with the surface for the - // open/close animation (only on the way down) - anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect, - insets, thumbnailHeader, taskId, displayConfig.uiMode, - displayConfig.orientation); - openingAppAnimator.deferThumbnailDestruction = - !mService.mAppTransition.isNextThumbnailTransitionScaleUp(); - } else { - anim = mService.mAppTransition.createThumbnailScaleAnimationLocked( - displayInfo.appWidth, displayInfo.appHeight, transit, thumbnailHeader); + wtoken.showAllWindowsLocked(); } - anim.restrictDuration(MAX_ANIMATION_DURATION); - anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked()); - - openingAppAnimator.thumbnail = surfaceControl; - openingAppAnimator.thumbnailAnimation = anim; - mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect); - - // We parent the thumbnail to the app token, and just place it - // on top of anything else in the app token. - surfaceControl.setLayer(Integer.MAX_VALUE); - } catch (Surface.OutOfResourcesException e) { - Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" - + dirty.width() + " h=" + dirty.height(), e); - openingAppAnimator.clearThumbnail(); } } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 5bcf59cf1711..bad9bf53fc3b 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -63,7 +63,7 @@ class WindowToken extends WindowContainer<WindowState> { boolean paused = false; // Should this token's windows be hidden? - boolean hidden; + private boolean mHidden; // Temporary for finding which tokens no longer have visible windows. boolean hasVisible; @@ -112,6 +112,16 @@ class WindowToken extends WindowContainer<WindowState> { onDisplayChanged(dc); } + void setHidden(boolean hidden) { + if (hidden != mHidden) { + mHidden = hidden; + } + } + + boolean isHidden() { + return mHidden; + } + void removeAllWindowsIfPossible() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState win = mChildren.get(i); @@ -130,7 +140,7 @@ class WindowToken extends WindowContainer<WindowState> { // This token is exiting, so allow it to be removed when it no longer contains any windows. mPersistOnEmpty = false; - if (hidden) { + if (mHidden) { return; } @@ -146,7 +156,7 @@ class WindowToken extends WindowContainer<WindowState> { changed |= win.onSetAppExiting(); } - hidden = true; + setHidden(true); if (changed) { mService.mWindowPlacerLocked.performSurfacePlacement(); @@ -189,11 +199,6 @@ class WindowToken extends WindowContainer<WindowState> { return mChildren.isEmpty(); } - // Used by AppWindowToken. - int getAnimLayerAdjustment() { - return 0; - } - WindowState getReplacingWindow() { for (int i = mChildren.size() - 1; i >= 0; i--) { final WindowState win = mChildren.get(i); @@ -274,10 +279,11 @@ class WindowToken extends WindowContainer<WindowState> { proto.end(token); } - void dump(PrintWriter pw, String prefix) { + void dump(PrintWriter pw, String prefix, boolean dumpAll) { + super.dump(pw, prefix, dumpAll); pw.print(prefix); pw.print("windows="); pw.println(mChildren); pw.print(prefix); pw.print("windowType="); pw.print(windowType); - pw.print(" hidden="); pw.print(hidden); + pw.print(" hidden="); pw.print(mHidden); pw.print(" hasVisible="); pw.println(hasVisible); if (waitingToShow || sendingToBottom) { pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 246bd426e68c..67bad0fab7c5 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -48,6 +48,7 @@ static jmethodID method_reportAGpsStatus; static jmethodID method_reportNmea; static jmethodID method_setEngineCapabilities; static jmethodID method_setGnssYearOfHardware; +static jmethodID method_setGnssHardwareModelName; static jmethodID method_xtraDownloadRequest; static jmethodID method_reportNiNotification; static jmethodID method_requestRefLocation; @@ -373,12 +374,11 @@ struct GnssCallback : public IGnssCallback { Return<void> GnssCallback::gnssNameCb(const android::hardware::hidl_string& name) { ALOGD("%s: name=%s\n", __func__, name.c_str()); - // TODO(b/38003769): build Java code to connect to below code - /* JNIEnv* env = getJniEnv(); - env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareName, name); + jstring jstringName = env->NewStringUTF(name.c_str()); + env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName); checkAndClearExceptionFromCallback(env, __FUNCTION__); - */ + return Void(); } @@ -1031,6 +1031,8 @@ static void android_location_GnssLocationProvider_class_init_native(JNIEnv* env, method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V"); method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V"); method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V"); + method_setGnssHardwareModelName = env->GetMethodID(clazz, "setGnssHardwareModelName", + "(Ljava/lang/String;)V"); method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V"); method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;II)V"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index e55d4ea35739..c1e95ebeddf2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -67,7 +67,8 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void transferOwner(ComponentName admin, ComponentName target, PersistableBundle bundle) {} public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm, - ParcelableKeyGenParameterSpec keySpec, KeymasterCertificateChain attestationChain) { + ParcelableKeyGenParameterSpec keySpec, int idAttestationFlags, + KeymasterCertificateChain attestationChain) { return false; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e5351b48e14d..11fce4d7101c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -45,6 +45,10 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_ import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES; import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; +import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; +import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; +import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; +import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL; import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; @@ -217,8 +221,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map.Entry; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -5052,17 +5058,82 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } + private void enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner( + ComponentName who, String callerPackage, int callerUid) throws SecurityException { + if (who == null) { + if (!mOwners.hasDeviceOwner()) { + throw new SecurityException("Not in Device Owner mode."); + } + if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) { + throw new SecurityException("Caller not from device owner user"); + } + if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) { + throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() + + "has no permission to generate keys."); + } + } else { + // Caller provided - check it is the device owner. + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + } + } + + @VisibleForTesting + public static int[] translateIdAttestationFlags( + int idAttestationFlags) { + Map<Integer, Integer> idTypeToAttestationFlag = new HashMap(); + idTypeToAttestationFlag.put(ID_TYPE_SERIAL, AttestationUtils.ID_TYPE_SERIAL); + idTypeToAttestationFlag.put(ID_TYPE_IMEI, AttestationUtils.ID_TYPE_IMEI); + idTypeToAttestationFlag.put(ID_TYPE_MEID, AttestationUtils.ID_TYPE_MEID); + + int numFlagsSet = Integer.bitCount(idAttestationFlags); + // No flags are set - return null to indicate no device ID attestation information should + // be included in the attestation record. + if (numFlagsSet == 0) { + return null; + } + + // If the ID_TYPE_BASE_INFO is set, make sure that a non-null array is returned, even if + // no other flag is set. That will lead to inclusion of general device make data in the + // attestation record, but no specific device identifiers. + if ((idAttestationFlags & ID_TYPE_BASE_INFO) != 0) { + numFlagsSet -= 1; + idAttestationFlags = idAttestationFlags & (~ID_TYPE_BASE_INFO); + } + + int[] attestationUtilsFlags = new int[numFlagsSet]; + int i = 0; + for (Integer idType: idTypeToAttestationFlag.keySet()) { + if ((idType & idAttestationFlags) != 0) { + attestationUtilsFlags[i++] = idTypeToAttestationFlag.get(idType); + } + } + + return attestationUtilsFlags; + } + @Override public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm, ParcelableKeyGenParameterSpec parcelableKeySpec, + int idAttestationFlags, KeymasterCertificateChain attestationChain) { - enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, - DELEGATION_CERT_INSTALL); + // Get attestation flags, if any. + final int[] attestationUtilsFlags = translateIdAttestationFlags(idAttestationFlags); + final boolean deviceIdAttestationRequired = attestationUtilsFlags != null; + final int callingUid = mInjector.binderGetCallingUid(); + + if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) { + enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(who, callerPackage, callingUid); + } else { + enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, + DELEGATION_CERT_INSTALL); + } final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec(); - if (TextUtils.isEmpty(keySpec.getKeystoreAlias())) { + final String alias = keySpec.getKeystoreAlias(); + if (TextUtils.isEmpty(alias)) { throw new IllegalArgumentException("Empty alias provided."); } - final String alias = keySpec.getKeystoreAlias(); // As the caller will be granted access to the key, ensure no UID was specified, as // it will not have the desired effect. if (keySpec.getUid() != KeyStore.UID_SELF) { @@ -5070,7 +5141,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } - final int callingUid = mInjector.binderGetCallingUid(); + if (deviceIdAttestationRequired && (keySpec.getAttestationChallenge() == null)) { + throw new IllegalArgumentException( + "Requested Device ID attestation but challenge is empty."); + } final UserHandle userHandle = mInjector.binderGetCallingUserHandle(); final long id = mInjector.binderClearCallingIdentity(); @@ -5103,7 +5177,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final byte[] attestationChallenge = keySpec.getAttestationChallenge(); if (attestationChallenge != null) { final boolean attestationResult = keyChain.attestKey( - alias, attestationChallenge, attestationChain); + alias, attestationChallenge, attestationUtilsFlags, attestationChain); if (!attestationResult) { Log.e(LOG_TAG, String.format( "Attestation for %s failed, deleting key.", alias)); @@ -11802,10 +11876,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long id = mInjector.binderClearCallingIdentity(); try { - //STOPSHIP add support for COMP, DO, edge cases when device is rebooted/work mode off, + //STOPSHIP add support for COMP, edge cases when device is rebooted/work mode off, //transfer callbacks and broadcast - if (isProfileOwner(admin, callingUserId)) { - transferProfileOwner(admin, target, callingUserId); + synchronized (this) { + if (isProfileOwner(admin, callingUserId)) { + transferProfileOwnerLocked(admin, target, callingUserId); + } else if (isDeviceOwner(admin, callingUserId)) { + transferDeviceOwnerLocked(admin, target, callingUserId); + } } } finally { mInjector.binderRestoreCallingIdentity(id); @@ -11815,15 +11893,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Transfers the profile owner for user with id profileOwnerUserId from admin to target. */ - private void transferProfileOwner(ComponentName admin, ComponentName target, + private void transferProfileOwnerLocked(ComponentName admin, ComponentName target, int profileOwnerUserId) { - synchronized (this) { - transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId); - mOwners.transferProfileOwner(target, profileOwnerUserId); - Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId); - mOwners.writeProfileOwner(profileOwnerUserId); - mDeviceAdminServiceController.startServiceForOwner( - target.getPackageName(), profileOwnerUserId, "transfer-profile-owner"); - } + transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId); + mOwners.transferProfileOwner(target, profileOwnerUserId); + Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId); + mOwners.writeProfileOwner(profileOwnerUserId); + mDeviceAdminServiceController.startServiceForOwner( + target.getPackageName(), profileOwnerUserId, "transfer-profile-owner"); + } + + /** + * Transfers the device owner for user with id userId from admin to target. + */ + private void transferDeviceOwnerLocked(ComponentName admin, ComponentName target, int userId) { + transferActiveAdminUncheckedLocked(target, admin, userId); + mOwners.transferDeviceOwner(target); + Slog.i(LOG_TAG, "Device owner set: " + target + " on user " + userId); + mOwners.writeDeviceOwner(); + mDeviceAdminServiceController.startServiceForOwner( + target.getPackageName(), userId, "transfer-device-owner"); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 9042a8d8b305..2a23888b875c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -288,6 +288,17 @@ class Owners { } } + void transferDeviceOwner(ComponentName target) { + synchronized (mLock) { + // We don't set a name because it's not used anyway. + // See DevicePolicyManagerService#getDeviceOwnerName + mDeviceOwner = new OwnerInfo(null, target, + mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri, + mDeviceOwner.remoteBugreportHash); + pushToPackageManagerLocked(); + } + } + ComponentName getProfileOwnerComponent(int userId) { synchronized (mLock) { OwnerInfo profileOwner = mProfileOwners.get(userId); diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java new file mode 100644 index 000000000000..a473bc61941c --- /dev/null +++ b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AlarmManager; +import android.content.Context; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; + +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderClasses; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderClasses({BackupManagerConstants.class}) +@Presubmit +public class BackupManagerConstantsTest { + private static final String PACKAGE_NAME = "some.package.name"; + private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testDefaultValues() throws Exception { + final Context context = RuntimeEnvironment.application.getApplicationContext(); + final Handler handler = new Handler(); + + Settings.Secure.putString( + context.getContentResolver(), Settings.Secure.BACKUP_MANAGER_CONSTANTS, null); + + final BackupManagerConstants constants = + new BackupManagerConstants(handler, context.getContentResolver()); + constants.start(); + + assertThat(constants.getKeyValueBackupIntervalMilliseconds()) + .isEqualTo(4 * AlarmManager.INTERVAL_HOUR); + assertThat(constants.getKeyValueBackupFuzzMilliseconds()).isEqualTo(10 * 60 * 1000); + assertThat(constants.getKeyValueBackupRequireCharging()).isEqualTo(true); + assertThat(constants.getKeyValueBackupRequiredNetworkType()).isEqualTo(1); + + assertThat(constants.getFullBackupIntervalMilliseconds()) + .isEqualTo(24 * AlarmManager.INTERVAL_HOUR); + assertThat(constants.getFullBackupRequireCharging()).isEqualTo(true); + assertThat(constants.getFullBackupRequiredNetworkType()).isEqualTo(2); + assertThat(constants.getBackupFinishedNotificationReceivers()).isEqualTo(new String[0]); + } + + @Test + public void testParseNotificationReceivers() throws Exception { + final Context context = RuntimeEnvironment.application.getApplicationContext(); + final Handler handler = new Handler(); + + final String recieversSetting = + "backup_finished_notification_receivers=" + + PACKAGE_NAME + + ':' + + ANOTHER_PACKAGE_NAME; + Settings.Secure.putString( + context.getContentResolver(), + Settings.Secure.BACKUP_MANAGER_CONSTANTS, + recieversSetting); + + final BackupManagerConstants constants = + new BackupManagerConstants(handler, context.getContentResolver()); + constants.start(); + + assertThat(constants.getBackupFinishedNotificationReceivers()) + .isEqualTo(new String[] {PACKAGE_NAME, ANOTHER_PACKAGE_NAME}); + } +} diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index ced9b1ec8042..82830fe5c479 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -18,8 +18,6 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; -import static junit.framework.Assert.fail; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; @@ -58,7 +56,6 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; -import org.testng.Assert.ThrowingRunnable; import java.util.ArrayList; import java.util.Arrays; diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java new file mode 100644 index 000000000000..73f1c2fbda0a --- /dev/null +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.internal; + +import static android.app.backup.BackupTransport.TRANSPORT_ERROR; +import static android.app.backup.BackupTransport.TRANSPORT_OK; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.Application; +import android.app.PendingIntent; +import android.app.backup.IBackupObserver; +import android.os.DeadObjectException; +import android.platform.test.annotations.Presubmit; + +import com.android.internal.backup.IBackupTransport; +import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.TransportManager; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportNotAvailableException; +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderClasses; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderClasses({PerformInitializeTaskTest.class, TransportManager.class}) +@Presubmit +public class PerformInitializeTaskTest { + private static final String[] TRANSPORT_NAMES = { + "android/com.android.internal.backup.LocalTransport", + "com.google.android.gms/.backup.migrate.service.D2dTransport", + "com.google.android.gms/.backup.BackupTransportService" + }; + + private static final String TRANSPORT_NAME = TRANSPORT_NAMES[0]; + + @Mock private RefactoredBackupManagerService mBackupManagerService; + @Mock private TransportManager mTransportManager; + @Mock private OnTaskFinishedListener mListener; + @Mock private IBackupTransport mTransport; + @Mock private IBackupObserver mObserver; + @Mock private AlarmManager mAlarmManager; + @Mock private PendingIntent mRunInitIntent; + private File mBaseStateDir; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Application context = RuntimeEnvironment.application; + mBaseStateDir = new File(context.getCacheDir(), "base_state_dir"); + assertThat(mBaseStateDir.mkdir()).isTrue(); + + when(mBackupManagerService.getAlarmManager()).thenReturn(mAlarmManager); + when(mBackupManagerService.getRunInitIntent()).thenReturn(mRunInitIntent); + } + + @Test + public void testRun_callsTransportCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransport).initializeDevice(); + verify(mTransport).finishBackup(); + } + + @Test + public void testRun_callsBackupManagerCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mBackupManagerService) + .recordInitPending(false, TRANSPORT_NAME, dirName(TRANSPORT_NAME)); + verify(mBackupManagerService) + .resetBackupState(eq(new File(mBaseStateDir, dirName(TRANSPORT_NAME)))); + } + + @Test + public void testRun_callsObserverAndListenerCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK)); + verify(mObserver).backupFinished(eq(TRANSPORT_OK)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenInitializeDeviceFails() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransport).initializeDevice(); + verify(mTransport, never()).finishBackup(); + verify(mBackupManagerService) + .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME)); + } + + @Test + public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly() + throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent)); + } + + @Test + public void testRun_whenFinishBackupFails() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransport).initializeDevice(); + verify(mTransport).finishBackup(); + verify(mBackupManagerService) + .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME)); + } + + @Test + public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception { + setUpTransport(TRANSPORT_NAME); + configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent)); + } + + @Test + public void testRun_whenOnlyOneTransportFails() throws Exception { + List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); + configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = + createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + + performInitializeTask.run(); + + verify(transports.get(1).transportMock).initializeDevice(); + verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK)); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + } + + @Test + public void testRun_withMultipleTransports() throws Exception { + List<TransportData> transports = setUpTransports(TRANSPORT_NAMES); + configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES); + + performInitializeTask.run(); + + for (TransportData transport : transports) { + verify(mTransportManager).getTransportClient(eq(transport.transportName), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transport.transportClientMock), any()); + } + } + + @Test + public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception { + List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); + configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = + createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + + performInitializeTask.run(); + + verify(mTransportManager) + .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any()); + } + + @Test + public void testRun_whenTransportNotRegistered() throws Exception { + setUpTransport(new TransportData(TRANSPORT_NAME, null, null)); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransportManager, never()).disposeOfTransportClient(any(), any()); + verify(mObserver, never()).onResult(any(), anyInt()); + verify(mObserver).backupFinished(eq(TRANSPORT_OK)); + } + + @Test + public void testRun_whenOnlyOneTransportNotRegistered() throws Exception { + List<TransportData> transports = + setUpTransports( + new TransportData(TRANSPORT_NAMES[0], null, null), + new TransportData(TRANSPORT_NAMES[1])); + String registeredTransportName = transports.get(1).transportName; + IBackupTransport registeredTransport = transports.get(1).transportMock; + TransportClient registeredTransportClient = transports.get(1).transportClientMock; + PerformInitializeTask performInitializeTask = + createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + + performInitializeTask.run(); + + verify(registeredTransport).initializeDevice(); + verify(mTransportManager).disposeOfTransportClient(eq(registeredTransportClient), any()); + verify(mObserver).onResult(eq(registeredTransportName), eq(TRANSPORT_OK)); + } + + @Test + public void testRun_whenTransportNotAvailable() throws Exception { + TransportClient transportClient = mock(TransportClient.class); + setUpTransport(new TransportData(TRANSPORT_NAME, null, transportClient)); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + @Test + public void testRun_whenTransportThrowsDeadObjectException() throws Exception { + TransportClient transportClient = mock(TransportClient.class); + setUpTransport(new TransportData(TRANSPORT_NAME, mTransport, transportClient)); + when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + + performInitializeTask.run(); + + verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); + verify(mListener).onFinished(any()); + } + + private PerformInitializeTask createPerformInitializeTask(String... transportNames) { + return new PerformInitializeTask( + mBackupManagerService, + mTransportManager, + transportNames, + mObserver, + mListener, + mBaseStateDir); + } + + private void configureTransport( + IBackupTransport transportMock, int initializeDeviceStatus, int finishBackupStatus) + throws Exception { + when(transportMock.initializeDevice()).thenReturn(initializeDeviceStatus); + when(transportMock.finishBackup()).thenReturn(finishBackupStatus); + } + + private List<TransportData> setUpTransports(String... transportNames) throws Exception { + return setUpTransports( + Arrays.stream(transportNames) + .map(TransportData::new) + .toArray(TransportData[]::new)); + } + + /** @see #setUpTransport(TransportData) */ + private List<TransportData> setUpTransports(TransportData... transports) throws Exception { + for (TransportData transport : transports) { + setUpTransport(transport); + } + return Arrays.asList(transports); + } + + private void setUpTransport(String transportName) throws Exception { + setUpTransport(new TransportData(transportName, mTransport, mock(TransportClient.class))); + } + + /** + * Configures transport according to {@link TransportData}: + * + * <ul> + * <li>{@link TransportData#transportMock} {@code null} means {@link + * TransportClient#connectOrThrow(String)} throws {@link TransportNotAvailableException}. + * <li>{@link TransportData#transportClientMock} {@code null} means {@link + * TransportManager#getTransportClient(String, String)} returns {@code null}. + * </ul> + */ + private void setUpTransport(TransportData transport) throws Exception { + String transportName = transport.transportName; + String transportDirName = dirName(transportName); + IBackupTransport transportMock = transport.transportMock; + TransportClient transportClientMock = transport.transportClientMock; + + if (transportMock != null) { + when(transportMock.name()).thenReturn(transportName); + when(transportMock.transportDirName()).thenReturn(transportDirName); + } + + if (transportClientMock != null) { + when(transportClientMock.getTransportDirName()).thenReturn(transportDirName); + if (transportMock != null) { + when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); + } else { + when(transportClientMock.connectOrThrow(any())) + .thenThrow(TransportNotAvailableException.class); + } + } + + when(mTransportManager.getTransportClient(eq(transportName), any())) + .thenReturn(transportClientMock); + } + + private String dirName(String transportName) { + return transportName + "_dir_name"; + } + + private static class TransportData { + private final String transportName; + @Nullable private final IBackupTransport transportMock; + @Nullable private final TransportClient transportClientMock; + + private TransportData( + String transportName, + @Nullable IBackupTransport transportMock, + @Nullable TransportClient transportClientMock) { + this.transportName = transportName; + this.transportMock = transportMock; + this.transportClientMock = transportClientMock; + } + + private TransportData(String transportName) { + this(transportName, mock(IBackupTransport.class), mock(TransportClient.class)); + } + } +} diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java index 78ac4ed92788..6c7313ba639e 100644 --- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java +++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java @@ -27,14 +27,9 @@ import org.robolectric.internal.bytecode.InstrumentationConfiguration; import org.robolectric.internal.bytecode.SandboxClassLoader; import org.robolectric.util.Util; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; import java.net.URL; -import java.util.Arrays; import java.util.Set; import javax.annotation.Nonnull; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java index 8a54c4e7f9c0..8d5556eac447 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.content.Context; +import android.os.Message; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.util.DebugUtils; @@ -48,6 +49,34 @@ import org.junit.runner.RunWith; import java.util.function.IntConsumer; +/** + * Tests the state transitions of {@link MagnificationGestureHandler} + * + * Here's a dot graph describing the transitions being tested: + * {@code + * digraph { + * IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"] + * SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"] + * IDLE -> DOUBLE_TAP [label="2tap"] + * DOUBLE_TAP -> IDLE [label="timeout"] + * DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"] + * SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"] + * TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"] + * TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"] + * DRAGGING_TMP -> IDLE [label="release"] + * ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"] + * ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"] + * ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"] + * ZOOMED_DOUBLE_TAP -> IDLE [label="tap"] + * DRAGGING -> ZOOMED [label="release"] + * ZOOMED -> IDLE [label="a11y\nbtn"] + * ZOOMED -> PANNING [label="2hold"] + * PANNING -> PANNING_SCALING [label="pinch"] + * PANNING_SCALING -> ZOOMED [label="release"] + * PANNING -> ZOOMED [label="release"] + * } + * } + */ @RunWith(AndroidJUnit4.class) public class MagnificationGestureHandlerTest { @@ -76,6 +105,8 @@ public class MagnificationGestureHandlerTest { private MagnificationGestureHandler mMgh; private TestHandler mHandler; + private long mLastDownTime = Integer.MIN_VALUE; + @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); @@ -104,7 +135,13 @@ public class MagnificationGestureHandlerTest { MagnificationGestureHandler h = new MagnificationGestureHandler( mContext, mMagnificationController, detectTripleTap, detectShortcutTrigger); - mHandler = new TestHandler(h.mDetectingState, mClock); + mHandler = new TestHandler(h.mDetectingState, mClock) { + @Override + protected String messageToString(Message m) { + return DebugUtils.valueToString( + MagnificationGestureHandler.DetectingState.class, "MESSAGE_", m.what); + } + }; h.mDetectingState.mHandler = mHandler; h.setNext(strictMock(EventStreamTransformation.class)); return h; @@ -184,11 +221,11 @@ public class MagnificationGestureHandlerTest { fastForward1sec(); }, STATE_ZOOMED); - // tap+tap+swipe gets delegated - assertTransition(STATE_2TAPS, () -> { - allowEventDelegation(); - swipe(); - }, STATE_IDLE); + // tap+tap+swipe doesn't get delegated + assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE); + + // tap+tap+swipe initiates viewport dragging immediately + assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP); } @Test @@ -439,23 +476,24 @@ public class MagnificationGestureHandlerTest { } private void tap() { - MotionEvent downEvent = downEvent(); - send(downEvent); - send(upEvent(downEvent.getDownTime())); + send(downEvent()); + send(upEvent()); } private void swipe() { - MotionEvent downEvent = downEvent(); - send(downEvent); + swipeAndHold(); + send(upEvent()); + } + + private void swipeAndHold() { + send(downEvent()); send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2)); - send(upEvent(downEvent.getDownTime())); } private void longTap() { - MotionEvent downEvent = downEvent(); - send(downEvent); + send(downEvent()); fastForward(2000); - send(upEvent(downEvent.getDownTime())); + send(upEvent()); } private void triggerShortcut() { @@ -473,16 +511,17 @@ public class MagnificationGestureHandlerTest { } private MotionEvent moveEvent(float x, float y) { - return MotionEvent.obtain(defaultDownTime(), mClock.now(), ACTION_MOVE, x, y, 0); + return MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0); } private MotionEvent downEvent() { - return MotionEvent.obtain(mClock.now(), mClock.now(), + mLastDownTime = mClock.now(); + return MotionEvent.obtain(mLastDownTime, mLastDownTime, ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0); } private MotionEvent upEvent() { - return upEvent(defaultDownTime()); + return upEvent(mLastDownTime); } private MotionEvent upEvent(long downTime) { @@ -490,11 +529,6 @@ public class MagnificationGestureHandlerTest { MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0); } - private long defaultDownTime() { - MotionEvent lastDown = mMgh.mDetectingState.mLastDown; - return lastDown == null ? mClock.now() - 1 : lastDown.getDownTime(); - } - private MotionEvent pointerEvent(int action, float x, float y) { MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties(); defPointerProperties.id = 0; diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index f38404436e69..5a2110258828 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -572,7 +572,6 @@ public class RecentTasksTest extends ActivityTestsBase { }); assertSecurityException(expectCallable, () -> mService.getTaskDescription(0)); assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0)); - assertSecurityException(expectCallable, () -> mService.cancelTaskThumbnailTransition(0)); assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, null, 0)); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 60783db8bb3c..58ac7d28eade 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -17,6 +17,10 @@ package com.android.server.devicepolicy; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; +import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; +import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; +import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; +import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY; import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY; @@ -71,6 +75,7 @@ import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.security.KeyChain; +import android.security.keystore.AttestationUtils; import android.telephony.TelephonyManager; import android.test.MoreAsserts; import android.test.suitebuilder.annotation.SmallTest; @@ -4459,6 +4464,47 @@ public class DevicePolicyManagerTest extends DpmTestBase { }); } + private void assertAttestationFlags(int attestationFlags, int[] expectedFlags) { + int[] gotFlags = DevicePolicyManagerService.translateIdAttestationFlags(attestationFlags); + Arrays.sort(gotFlags); + Arrays.sort(expectedFlags); + assertTrue(Arrays.equals(expectedFlags, gotFlags)); + } + + public void testTranslationOfIdAttestationFlag() { + int[] allIdTypes = new int[]{ID_TYPE_SERIAL, ID_TYPE_IMEI, ID_TYPE_MEID}; + int[] correspondingAttUtilsTypes = new int[]{ + AttestationUtils.ID_TYPE_SERIAL, AttestationUtils.ID_TYPE_IMEI, + AttestationUtils.ID_TYPE_MEID}; + + // Test translation of zero flags + assertNull(DevicePolicyManagerService.translateIdAttestationFlags(0)); + + // Test translation of the ID_TYPE_BASE_INFO flag, which should yield an empty, but + // non-null array + assertAttestationFlags(ID_TYPE_BASE_INFO, new int[] {}); + + // Test translation of a single flag + assertAttestationFlags(ID_TYPE_BASE_INFO | ID_TYPE_SERIAL, + new int[] {AttestationUtils.ID_TYPE_SERIAL}); + assertAttestationFlags(ID_TYPE_SERIAL, new int[] {AttestationUtils.ID_TYPE_SERIAL}); + + // Test translation of two flags + assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI, + new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL}); + assertAttestationFlags(ID_TYPE_BASE_INFO | ID_TYPE_MEID | ID_TYPE_SERIAL, + new int[] {AttestationUtils.ID_TYPE_MEID, AttestationUtils.ID_TYPE_SERIAL}); + + // Test translation of all three flags + assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI | ID_TYPE_MEID, + new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL, + AttestationUtils.ID_TYPE_MEID}); + // Test translation of all three flags + assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI | ID_TYPE_MEID | ID_TYPE_BASE_INFO, + new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL, + AttestationUtils.ID_TYPE_MEID}); + } + private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) { when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, userhandle)).thenReturn(isUserSetupComplete ? 1 : 0); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java index 4447fe91e34e..939a2725fb07 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java @@ -261,7 +261,7 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { } private void verifyAppsAreNonRequired(String action, String... appArray) { - assertEquals(listFromArray(appArray), + assertEquals(setFromArray(appArray), mHelper.getNonRequiredApps(TEST_MDM_COMPONENT_NAME, TEST_USER_ID, action)); } @@ -347,14 +347,7 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { if (array == null) { return null; } - return new HashSet<T>(Arrays.asList(array)); - } - - private <T> List<T> listFromArray(T... array) { - if (array == null) { - return null; - } - return Arrays.asList(array); + return new HashSet<>(Arrays.asList(array)); } class FakePackageManager extends MockPackageManager { diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 2629b12375a4..5105f4e2a9c9 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -20,7 +20,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.PowerManager; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -35,18 +41,18 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class BrightnessMappingStrategyTest { - private static final float[] LUX_LEVELS = { - 0f, - 5f, - 20f, - 40f, - 100f, - 325f, - 600f, - 1250f, - 2200f, - 4000f, - 5000f + private static final int[] LUX_LEVELS = { + 0, + 5, + 20, + 40, + 100, + 325, + 600, + 1250, + 2200, + 4000, + 5000 }; private static final float[] DISPLAY_LEVELS_NITS = { @@ -80,11 +86,13 @@ public class BrightnessMappingStrategyTest { private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; private static final int[] BACKLIGHT_RANGE = { 1, 255 }; + private static final float[] EMPTY_FLOAT_ARRAY = new float[0]; + private static final int[] EMPTY_INT_ARRAY = new int[0]; + @Test public void testSimpleStrategyMappingAtControlPoints() { - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create( - LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, - null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 0; i < LUX_LEVELS.length; i++) { final float expectedLevel = @@ -96,9 +104,8 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyMappingBetweenControlPoints() { - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create( - LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, - null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 1; i < LUX_LEVELS.length; i++) { final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; @@ -111,9 +118,9 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyMappingAtControlPoints() { - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create( - LUX_LEVELS, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, + DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); assertNotNull("BrightnessMappingStrategy should not be null", physical); for (int i = 0; i < LUX_LEVELS.length; i++) { final float expectedLevel = DISPLAY_LEVELS_NITS[i] / DISPLAY_RANGE_NITS[1]; @@ -124,9 +131,9 @@ public class BrightnessMappingStrategyTest { @Test public void testPhysicalStrategyMappingBetweenControlPoints() { - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create( - LUX_LEVELS, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, + DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); assertNotNull("BrightnessMappingStrategy should not be null", physical); Spline backlightToBrightness = Spline.createSpline(toFloatArray(BACKLIGHT_RANGE), DISPLAY_RANGE_NITS); @@ -141,82 +148,79 @@ public class BrightnessMappingStrategyTest { @Test public void testDefaultStrategyIsPhysical() { - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create( - LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy); } @Test public void testNonStrictlyIncreasingLuxLevelsFails() { - final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length); + final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length); final int idx = lux.length / 2; - float tmp = lux[idx]; + int tmp = lux[idx]; lux[idx] = lux[idx+1]; lux[idx+1] = tmp; - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create( - lux, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + Resources res = createResources(lux, DISPLAY_LEVELS_NITS, + DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); assertNull(strategy); // And make sure we get the same result even if it's monotone but not increasing. lux[idx] = lux[idx+1]; - strategy = BrightnessMappingStrategy.create( - lux, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + res = createResources(lux, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + strategy = BrightnessMappingStrategy.create(res); assertNull(strategy); } @Test public void testDifferentNumberOfControlPointValuesFails() { //Extra lux level - final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1); + final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1); // Make sure it's strictly increasing so that the only failure is the differing array // lengths lux[lux.length - 1] = lux[lux.length - 2] + 1; - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create( - lux, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + Resources res = createResources(lux, DISPLAY_LEVELS_NITS, + DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); assertNull(strategy); - strategy = BrightnessMappingStrategy.create( - lux, DISPLAY_LEVELS_BACKLIGHT, - null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/); + res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT); + strategy = BrightnessMappingStrategy.create(res); assertNull(strategy); // Extra backlight level final int[] backlight = Arrays.copyOf( DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1); backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1; - strategy = BrightnessMappingStrategy.create( - LUX_LEVELS, backlight, - null /*brightnessLevelsNits*/, null /*nitsRange*/, null /*backlightRange*/); + res = createResources(LUX_LEVELS, backlight); + strategy = BrightnessMappingStrategy.create(res); assertNull(strategy); // Extra nits level final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1); nits[nits.length - 1] = nits[nits.length - 2] + 1; - strategy = BrightnessMappingStrategy.create( - LUX_LEVELS, null /*brightnessLevelsBacklight*/, - nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + res = createResources(LUX_LEVELS, nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + strategy = BrightnessMappingStrategy.create(res); assertNull(strategy); } @Test public void testPhysicalStrategyRequiresNitsMapping() { - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create( - LUX_LEVELS, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, null, BACKLIGHT_RANGE); + Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, + DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, BACKLIGHT_RANGE); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); assertNull(physical); - physical = BrightnessMappingStrategy.create( - LUX_LEVELS, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, null); + res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, + DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, EMPTY_INT_ARRAY /*backlightRange*/); + physical = BrightnessMappingStrategy.create(res); assertNull(physical); - physical = BrightnessMappingStrategy.create( - LUX_LEVELS, null /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, null, null); + res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, + DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, + EMPTY_INT_ARRAY /*backlightRange*/); + physical = BrightnessMappingStrategy.create(res); assertNull(physical); } @@ -227,4 +231,73 @@ public class BrightnessMappingStrategyTest { } return newVals; } + + private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) { + return createResources(luxLevels, brightnessLevelsBacklight, + EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/, EMPTY_FLOAT_ARRAY /*nitsRange*/, + EMPTY_INT_ARRAY /*backlightRange*/); + } + + private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits, + float[] nitsRange, int[] backlightRange) { + return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, + brightnessLevelsNits, nitsRange, backlightRange); + } + + private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight, + float[] brightnessLevelsNits, float[] nitsRange, int[] backlightRange) { + Resources mockResources = mock(Resources.class); + // For historical reasons, the lux levels resource implicitly defines the first point as 0, + // so we need to chop it off of the array the mock resource object returns. + int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length); + when(mockResources.getIntArray(com.android.internal.R.array.config_autoBrightnessLevels)) + .thenReturn(luxLevelsResource); + + when(mockResources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues)) + .thenReturn(brightnessLevelsBacklight); + + TypedArray mockBrightnessLevelNits = createFloatTypedArray(brightnessLevelsNits); + when(mockResources.obtainTypedArray( + com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) + .thenReturn(mockBrightnessLevelNits); + + TypedArray mockNitsRange = createFloatTypedArray(nitsRange); + when(mockResources.obtainTypedArray( + com.android.internal.R.array.config_screenBrightnessNits)) + .thenReturn(mockNitsRange); + + when(mockResources.getIntArray( + com.android.internal.R.array.config_screenBrightnessBacklight)) + .thenReturn(backlightRange); + + when(mockResources.getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingMinimum)) + .thenReturn(1); + when(mockResources.getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingMaximum)) + .thenReturn(255); + return mockResources; + } + + private TypedArray createFloatTypedArray(float[] vals) { + TypedArray mockArray = mock(TypedArray.class); + when(mockArray.length()).thenAnswer(invocation -> { + return vals.length; + }); + when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> { + final float def = (float) invocation.getArguments()[1]; + if (vals == null) { + return def; + } + int idx = (int) invocation.getArguments()[0]; + if (idx >= 0 && idx < vals.length) { + return vals[idx]; + } else { + return def; + } + }); + return mockArray; + } + } diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index 926009ebfbae..08edd52c7112 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -66,6 +66,8 @@ import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) public class BrightnessTrackerTest { + private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f; + private static final float FLOAT_DELTA = 0.01f; private BrightnessTracker mTracker; private TestInjector mInjector; @@ -98,7 +100,6 @@ public class BrightnessTrackerTest { mInjector.mInteractive = false; startTracker(mTracker); assertNull(mInjector.mSensorListener); - assertNotNull(mInjector.mSettingsObserver); assertNotNull(mInjector.mBroadcastReceiver); assertTrue(mInjector.mIdleScheduled); Intent onIntent = new Intent(); @@ -119,7 +120,6 @@ public class BrightnessTrackerTest { mTracker.stop(); assertNull(mInjector.mSensorListener); - assertNull(mInjector.mSettingsObserver); assertNull(mInjector.mBroadcastReceiver); assertFalse(mInjector.mIdleScheduled); } @@ -128,12 +128,10 @@ public class BrightnessTrackerTest { public void testBrightnessEvent() { final int brightness = 20; - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness); startTracker(mTracker); mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f)); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, brightness); List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); mTracker.stop(); @@ -141,10 +139,11 @@ public class BrightnessTrackerTest { BrightnessChangeEvent event = events.get(0); assertEquals(mInjector.currentTimeMillis(), event.timeStamp); assertEquals(1, event.luxValues.length); - assertEquals(1.0f, event.luxValues[0], 0.1f); + assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA); assertEquals(mInjector.currentTimeMillis() - TimeUnit.SECONDS.toMillis(2), event.luxTimestamps[0]); - assertEquals(brightness, event.brightness); + assertEquals(brightness, event.brightness, FLOAT_DELTA); + assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA); // System had no data so these should all be at defaults. assertEquals(Float.NaN, event.batteryLevel, 0.0); @@ -154,21 +153,18 @@ public class BrightnessTrackerTest { @Test public void testBrightnessFullPopulatedEvent() { - final int lastBrightness = 230; + final int initialBrightness = 230; final int brightness = 130; - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, lastBrightness); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333); - startTracker(mTracker); - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness); + startTracker(mTracker, initialBrightness); mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), batteryChangeEvent(30, 60)); mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f)); final long sensorTime = mInjector.currentTimeMillis(); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, brightness); List<BrightnessChangeEvent> eventsNoPackage = mTracker.getEvents(0, false).getList(); List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); @@ -179,9 +175,9 @@ public class BrightnessTrackerTest { assertEquals(event.timeStamp, mInjector.currentTimeMillis()); assertArrayEquals(new float[] {1000.0f}, event.luxValues, 0.01f); assertArrayEquals(new long[] {sensorTime}, event.luxTimestamps); - assertEquals(brightness, event.brightness); - assertEquals(lastBrightness, event.lastBrightness); - assertEquals(0.5, event.batteryLevel, 0.01); + assertEquals(brightness, event.brightness, FLOAT_DELTA); + assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA); + assertEquals(0.5, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); assertEquals(3333, event.colorTemperature); assertEquals("a.package", event.packageName); @@ -192,45 +188,34 @@ public class BrightnessTrackerTest { } @Test - public void testIgnoreSelfChange() { + public void testIgnoreAutomaticBrightnessChange() { final int initialBrightness = 30; - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, initialBrightness); - startTracker(mTracker); + startTracker(mTracker, initialBrightness); mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f)); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1)); final int systemUpdatedBrightness = 20; - mTracker.setBrightness(systemUpdatedBrightness, 0); - assertEquals(systemUpdatedBrightness, - (int) mInjector.mSystemIntSettings.get(Settings.System.SCREEN_BRIGHTNESS)); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/); List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); // No events because we filtered out our change. assertEquals(0, events.size()); final int firstUserUpdateBrightness = 20; // Then change comes from somewhere else so we shouldn't filter. - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, - firstUserUpdateBrightness); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, firstUserUpdateBrightness); // and with a different brightness value. final int secondUserUpdateBrightness = 34; - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, - secondUserUpdateBrightness); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, secondUserUpdateBrightness); events = mTracker.getEvents(0, true).getList(); assertEquals(2, events.size()); // First event is change from system update (20) to first user update (20) - assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness); - assertEquals(firstUserUpdateBrightness, events.get(0).brightness); + assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA); + assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA); // Second event is from first to second user update. - assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness); - assertEquals(secondUserUpdateBrightness, events.get(1).brightness); + assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA); + assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA); mTracker.stop(); } @@ -243,9 +228,7 @@ public class BrightnessTrackerTest { for (int brightness = 0; brightness <= 255; ++brightness) { mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f)); mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1)); - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, brightness); } List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); mTracker.stop(); @@ -254,14 +237,13 @@ public class BrightnessTrackerTest { assertEquals(100, events.size()); for (int i = 0; i < events.size(); i++) { BrightnessChangeEvent event = events.get(i); - assertEquals(156 + i, event.brightness); + assertEquals(156 + i, event.brightness, FLOAT_DELTA); } } @Test public void testLimitedSensorEvents() { final int brightness = 20; - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness); startTracker(mTracker); // 20 Sensor events 1 second apart. @@ -269,8 +251,7 @@ public class BrightnessTrackerTest { mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1)); mInjector.mSensorListener.onSensorChanged(createSensorEvent(i + 1.0f)); } - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, 20); List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); mTracker.stop(); @@ -284,7 +265,7 @@ public class BrightnessTrackerTest { assertEquals(event.luxTimestamps[11 - i], mInjector.currentTimeMillis() - i * TimeUnit.SECONDS.toMillis(1)); } - assertEquals(brightness, event.brightness); + assertEquals(brightness, event.brightness, FLOAT_DELTA); } @Test @@ -298,25 +279,25 @@ public class BrightnessTrackerTest { String eventFile = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<events>\n" - + "<event brightness=\"194\" timestamp=\"" + + "<event nits=\"194.2\" timestamp=\"" + Long.toString(someTimeAgo) + "\" packageName=\"" + "com.example.app\" user=\"10\" " - + "lastBrightness=\"32\" " + + "lastNits=\"32.333\" " + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\"\n" + "lux=\"32.2,31.1\" luxTimestamps=\"" + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\"/>" - + "<event brightness=\"71\" timestamp=\"" + + "<event nits=\"71\" timestamp=\"" + Long.toString(someTimeAgo) + "\" packageName=\"" + "com.android.anapp\" user=\"11\" " - + "lastBrightness=\"32\" " + + "lastNits=\"32\" " + "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\"\n" + "lux=\"132.2,131.1\" luxTimestamps=\"" + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\"/>" // Event that is too old so shouldn't show up. - + "<event brightness=\"142\" timestamp=\"" + + "<event nits=\"142\" timestamp=\"" + Long.toString(twoMonthsAgo) + "\" packageName=\"" + "com.example.app\" user=\"10\" " - + "lastBrightness=\"32\" " + + "lastNits=\"32\" " + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\"\n" + "lux=\"32.2,31.1\" luxTimestamps=\"" + Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>" @@ -326,27 +307,27 @@ public class BrightnessTrackerTest { assertEquals(1, events.size()); BrightnessChangeEvent event = events.get(0); assertEquals(someTimeAgo, event.timeStamp); - assertEquals(194, event.brightness); - assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, 0.01f); + assertEquals(194.2, event.brightness, FLOAT_DELTA); + assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps); - assertEquals(32, event.lastBrightness); + assertEquals(32.333, event.lastBrightness, FLOAT_DELTA); assertEquals(0, event.userId); assertFalse(event.nightMode); - assertEquals(1.0f, event.batteryLevel, 0.01); + assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA); assertEquals("com.example.app", event.packageName); events = tracker.getEvents(1, true).getList(); assertEquals(1, events.size()); event = events.get(0); assertEquals(someTimeAgo, event.timeStamp); - assertEquals(71, event.brightness); - assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, 0.01f); + assertEquals(71, event.brightness, FLOAT_DELTA); + assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps); - assertEquals(32, event.lastBrightness); + assertEquals(32, event.lastBrightness, FLOAT_DELTA); assertEquals(1, event.userId); assertTrue(event.nightMode); assertEquals(3235, event.colorTemperature); - assertEquals(0.5f, event.batteryLevel, 0.01); + assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA); assertEquals("com.android.anapp", event.packageName); } @@ -370,7 +351,7 @@ public class BrightnessTrackerTest { eventFile = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<events>\n" - + "<event brightness=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\"" + + "<event nits=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\"" + "com.example.app\" user=\"10\" " + "batteryLevel=\"0.7\" nightMode=\"false\" colorTemperature=\"0\" />\n" + "</events>"; @@ -386,7 +367,6 @@ public class BrightnessTrackerTest { public void testWriteThenRead() throws Exception { final int brightness = 20; - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339); @@ -399,8 +379,7 @@ public class BrightnessTrackerTest { mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f)); final long secondSensorTime = mInjector.currentTimeMillis(); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3)); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, brightness); ByteArrayOutputStream baos = new ByteArrayOutputStream(); mTracker.writeEventsLocked(baos); mTracker.stop(); @@ -414,10 +393,10 @@ public class BrightnessTrackerTest { assertEquals(1, events.size()); BrightnessChangeEvent event = events.get(0); - assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f); + assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {firstSensorTime, secondSensorTime}, event.luxTimestamps); - assertEquals(brightness, event.brightness); - assertEquals(0.3, event.batteryLevel, 0.01f); + assertEquals(brightness, event.brightness, FLOAT_DELTA); + assertEquals(0.3, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); assertEquals(3339, event.colorTemperature); } @@ -426,7 +405,6 @@ public class BrightnessTrackerTest { public void testWritePrunesOldEvents() throws Exception { final int brightness = 20; - mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339); @@ -437,14 +415,12 @@ public class BrightnessTrackerTest { mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1)); mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f)); final long sensorTime = mInjector.currentTimeMillis(); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, brightness); // 31 days later mInjector.incrementTime(TimeUnit.DAYS.toMillis(31)); mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f)); - mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS)); + notifyBrightnessChanged(mTracker, brightness); final long eventTime = mInjector.currentTimeMillis(); List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); @@ -460,10 +436,10 @@ public class BrightnessTrackerTest { assertEquals(eventTime, event.timeStamp); // We will keep one of the old sensor events because we keep 1 event outside the window. - assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f); + assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps); - assertEquals(brightness, event.brightness); - assertEquals(0.3, event.batteryLevel, 0.01f); + assertEquals(brightness, event.brightness, FLOAT_DELTA); + assertEquals(0.3, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); assertEquals(3339, event.colorTemperature); } @@ -472,7 +448,7 @@ public class BrightnessTrackerTest { public void testParcelUnParcel() { Parcel parcel = Parcel.obtain(); BrightnessChangeEvent event = new BrightnessChangeEvent(); - event.brightness = 23; + event.brightness = 23f; event.timeStamp = 345L; event.packageName = "com.example"; event.userId = 12; @@ -485,7 +461,7 @@ public class BrightnessTrackerTest { event.batteryLevel = 0.7f; event.nightMode = false; event.colorTemperature = 345; - event.lastBrightness = 50; + event.lastBrightness = 50f; event.writeToParcel(parcel, 0); byte[] parceled = parcel.marshall(); @@ -497,16 +473,16 @@ public class BrightnessTrackerTest { BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel); parcel.recycle(); - assertEquals(event.brightness, event2.brightness); + assertEquals(event.brightness, event2.brightness, FLOAT_DELTA); assertEquals(event.timeStamp, event2.timeStamp); assertEquals(event.packageName, event2.packageName); assertEquals(event.userId, event2.userId); - assertArrayEquals(event.luxValues, event2.luxValues, 0.01f); + assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA); assertArrayEquals(event.luxTimestamps, event2.luxTimestamps); - assertEquals(event.batteryLevel, event2.batteryLevel, 0.01f); + assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA); assertEquals(event.nightMode, event2.nightMode); assertEquals(event.colorTemperature, event2.colorTemperature); - assertEquals(event.lastBrightness, event2.lastBrightness); + assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA); parcel = Parcel.obtain(); event.batteryLevel = Float.NaN; @@ -518,7 +494,7 @@ public class BrightnessTrackerTest { parcel.unmarshall(parceled, 0, parceled.length); parcel.setDataPosition(0); event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel); - assertEquals(event.batteryLevel, event2.batteryLevel, 0.01f); + assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA); } private InputStream getInputStream(String data) { @@ -550,7 +526,21 @@ public class BrightnessTrackerTest { } private void startTracker(BrightnessTracker tracker) { - tracker.start(); + startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS); + } + + private void startTracker(BrightnessTracker tracker, float initialBrightness) { + tracker.start(initialBrightness); + mInjector.waitForHandler(); + } + + private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) { + notifyBrightnessChanged(tracker, brightness, true /*userInitiated*/); + } + + private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness, + boolean userInitiated) { + tracker.notifyBrightnessChanged(brightness, userInitiated); mInjector.waitForHandler(); } @@ -578,7 +568,6 @@ public class BrightnessTrackerTest { private class TestInjector extends BrightnessTracker.Injector { SensorEventListener mSensorListener; - ContentObserver mSettingsObserver; BroadcastReceiver mBroadcastReceiver; Map<String, Integer> mSystemIntSettings = new HashMap<>(); Map<String, Integer> mSecureIntSettings = new HashMap<>(); @@ -610,18 +599,6 @@ public class BrightnessTrackerTest { } @Override - public void registerBrightnessObserver(ContentResolver resolver, - ContentObserver settingsObserver) { - mSettingsObserver = settingsObserver; - } - - @Override - public void unregisterBrightnessObserver(Context context, - ContentObserver settingsObserver) { - mSettingsObserver = null; - } - - @Override public void registerReceiver(Context context, BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) { mBroadcastReceiver = shutdownReceiver; @@ -647,23 +624,6 @@ public class BrightnessTrackerTest { } @Override - public int getSystemIntForUser(ContentResolver resolver, String setting, int defaultValue, - int userId) { - Integer value = mSystemIntSettings.get(setting); - if (value == null) { - return defaultValue; - } else { - return value; - } - } - - @Override - public void putSystemIntForUser(ContentResolver resolver, String setting, int value, - int userId) { - mSystemIntSettings.put(setting, value); - } - - @Override public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue, int userId) { Integer value = mSecureIntSettings.get(setting); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java new file mode 100644 index 000000000000..1aac8ce0a1f6 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings.recoverablekeystore; + +import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD; +import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN; +import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN; + +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.security.keystore.AndroidKeyStoreSecretKey; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.security.recoverablekeystore.KeyDerivationParameters; +import android.security.recoverablekeystore.KeyEntryRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryData; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class KeySyncTaskTest { + private static final String KEY_ALGORITHM = "AES"; + private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; + private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey"; + private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; + private static final int TEST_USER_ID = 1000; + private static final int TEST_APP_UID = 10009; + private static final int TEST_RECOVERY_AGENT_UID = 90873; + private static final long TEST_DEVICE_ID = 13295035643L; + private static final String TEST_APP_KEY_ALIAS = "rcleaver"; + private static final int TEST_GENERATION_ID = 2; + private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD; + private static final String TEST_CREDENTIAL = "password1234"; + private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER = + "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8); + + @Mock private PlatformKeyManager mPlatformKeyManager; + @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage; + + private RecoverySnapshotStorage mRecoverySnapshotStorage; + private RecoverableKeyStoreDb mRecoverableKeyStoreDb; + private File mDatabaseFile; + private KeyPair mKeyPair; + private AndroidKeyStoreSecretKey mWrappingKey; + private PlatformEncryptionKey mEncryptKey; + + private KeySyncTask mKeySyncTask; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Context context = InstrumentationRegistry.getTargetContext(); + mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); + mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); + mKeyPair = SecureBox.genKeyPair(); + + mRecoverySnapshotStorage = new RecoverySnapshotStorage(); + + mKeySyncTask = new KeySyncTask( + mRecoverableKeyStoreDb, + mRecoverySnapshotStorage, + mSnapshotListenersStorage, + TEST_USER_ID, + TEST_CREDENTIAL_TYPE, + TEST_CREDENTIAL, + () -> mPlatformKeyManager); + + mWrappingKey = generateAndroidKeyStoreKey(); + mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey); + when(mPlatformKeyManager.getDecryptKey()).thenReturn( + new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey)); + } + + @After + public void tearDown() { + mRecoverableKeyStoreDb.close(); + mDatabaseFile.delete(); + } + + @Test + public void isPin_isTrueForNumericString() { + assertTrue(KeySyncTask.isPin("3298432574398654376547")); + } + + @Test + public void isPin_isFalseForStringContainingLetters() { + assertFalse(KeySyncTask.isPin("398i54369548654")); + } + + @Test + public void isPin_isFalseForStringContainingSymbols() { + assertFalse(KeySyncTask.isPin("-3987543643")); + } + + @Test + public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() { + String credentials = "password1234"; + byte[] salt = randomBytes(16); + + assertArrayEquals( + KeySyncTask.hashCredentials(salt, credentials), + KeySyncTask.hashCredentials(salt, credentials)); + } + + @Test + public void hashCredentials_returnsDifferentHashForDifferentCredentials() { + byte[] salt = randomBytes(16); + + assertFalse( + Arrays.equals( + KeySyncTask.hashCredentials(salt, "password1234"), + KeySyncTask.hashCredentials(salt, "password12345"))); + } + + @Test + public void hashCredentials_returnsDifferentHashForDifferentSalt() { + String credentials = "wowmuch"; + + assertFalse( + Arrays.equals( + KeySyncTask.hashCredentials(randomBytes(64), credentials), + KeySyncTask.hashCredentials(randomBytes(64), credentials))); + } + + @Test + public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() { + assertFalse( + Arrays.equals( + KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"), + KeySyncTask.hashCredentials(utf8Bytes("1234"), "567"))); + } + + @Test + public void getUiFormat_returnsPinIfPin() { + assertEquals(TYPE_PIN, + KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234")); + } + + @Test + public void getUiFormat_returnsPasswordIfPassword() { + assertEquals(TYPE_PASSWORD, + KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a")); + } + + @Test + public void getUiFormat_returnsPatternIfPattern() { + assertEquals(TYPE_PATTERN, + KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234")); + + } + + @Test + public void run_doesNotSendAnythingIfNoKeysToSync() throws Exception { + // TODO: proper test here, once we have proper implementation for checking that keys need + // to be synced. + mKeySyncTask.run(); + + assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); + } + + @Test + public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception { + SecretKey applicationKey = generateKey(); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + mRecoverableKeyStoreDb.insertKey( + TEST_USER_ID, + TEST_APP_UID, + TEST_APP_KEY_ALIAS, + WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); + when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); + + mKeySyncTask.run(); + + assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); + } + + @Test + public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { + SecretKey applicationKey = generateKey(); + mRecoverableKeyStoreDb.setServerParameters( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + mRecoverableKeyStoreDb.insertKey( + TEST_USER_ID, + TEST_APP_UID, + TEST_APP_KEY_ALIAS, + WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); + + mKeySyncTask.run(); + + assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); + } + + @Test + public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception { + SecretKey applicationKey = generateKey(); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + mRecoverableKeyStoreDb.insertKey( + TEST_USER_ID, + TEST_APP_UID, + TEST_APP_KEY_ALIAS, + WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); + when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); + + mKeySyncTask.run(); + + assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); + } + + @Test + public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception { + SecretKey applicationKey = generateKey(); + mRecoverableKeyStoreDb.setServerParameters( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID); + mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); + mRecoverableKeyStoreDb.insertKey( + TEST_USER_ID, + TEST_APP_UID, + TEST_APP_KEY_ALIAS, + WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey( + TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); + when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); + + mKeySyncTask.run(); + + KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_USER_ID); + KeyDerivationParameters keyDerivationParameters = + recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters(); + assertEquals(KeyDerivationParameters.ALGORITHM_SHA256, + keyDerivationParameters.getAlgorithm()); + verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID); + byte[] lockScreenHash = KeySyncTask.hashCredentials( + keyDerivationParameters.getSalt(), + TEST_CREDENTIAL); + // TODO: what should counter_id be here? + byte[] recoveryKey = decryptThmEncryptedKey( + lockScreenHash, + recoveryData.getEncryptedRecoveryKeyBlob(), + /*vaultParams=*/ KeySyncUtils.packVaultParams( + mKeyPair.getPublic(), + /*counterId=*/ 1, + /*maxAttempts=*/ 10, + TEST_DEVICE_ID)); + List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs(); + assertEquals(1, applicationKeys.size()); + KeyEntryRecoveryData keyData = applicationKeys.get(0); + assertArrayEquals(TEST_APP_KEY_ALIAS.getBytes(StandardCharsets.UTF_8), keyData.getAlias()); + byte[] appKey = KeySyncUtils.decryptApplicationKey( + recoveryKey, keyData.getEncryptedKeyMaterial()); + assertArrayEquals(applicationKey.getEncoded(), appKey); + } + + private byte[] decryptThmEncryptedKey( + byte[] lockScreenHash, byte[] encryptedKey, byte[] vaultParams) throws Exception { + byte[] locallyEncryptedKey = SecureBox.decrypt( + mKeyPair.getPrivate(), + /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash), + /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams), + encryptedKey + ); + return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey); + } + + private SecretKey generateKey() throws Exception { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + keyGenerator.init(/*keySize=*/ 256); + return keyGenerator.generateKey(); + } + + private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + KeyGenerator keyGenerator = KeyGenerator.getInstance( + KEY_ALGORITHM, + ANDROID_KEY_STORE_PROVIDER); + keyGenerator.init(new KeyGenParameterSpec.Builder( + WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .build()); + return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + } + + private static byte[] utf8Bytes(String s) { + return s.getBytes(StandardCharsets.UTF_8); + } + + private static byte[] randomBytes(int n) { + byte[] bytes = new byte[n]; + new Random().nextBytes(bytes); + return bytes; + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java index c328dda68389..114da1aaebb5 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java @@ -30,9 +30,12 @@ import com.google.common.collect.ImmutableMap; import org.junit.Test; import org.junit.runner.RunWith; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.MessageDigest; +import java.security.PublicKey; import java.util.Arrays; import java.util.Map; import java.util.Random; @@ -55,6 +58,10 @@ public class KeySyncUtilsTest { utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe"); private static final byte[] RECOVERY_CLAIM_HEADER = "V1 KF_claim".getBytes(StandardCharsets.UTF_8); + private static final byte[] RECOVERY_RESPONSE_HEADER = + "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); + private static final int PUBLIC_KEY_LENGTH_BYTES = 65; + private static final int VAULT_PARAMS_LENGTH_BYTES = 85; @Test public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception { @@ -173,6 +180,42 @@ public class KeySyncUtilsTest { } @Test + public void decryptRecoveryClaimResponse_decryptsAValidResponse() throws Exception { + byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); + byte[] vaultParams = randomBytes(100); + byte[] recoveryKey = randomBytes(32); + byte[] encryptedPayload = SecureBox.encrypt( + /*theirPublicKey=*/ null, + /*sharedSecret=*/ keyClaimant, + /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*payload=*/ recoveryKey); + + byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse( + keyClaimant, vaultParams, encryptedPayload); + + assertArrayEquals(recoveryKey, decrypted); + } + + @Test + public void decryptRecoveryClaimResponse_throwsIfCannotDecrypt() throws Exception { + byte[] vaultParams = randomBytes(100); + byte[] recoveryKey = randomBytes(32); + byte[] encryptedPayload = SecureBox.encrypt( + /*theirPublicKey=*/ null, + /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(), + /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*payload=*/ recoveryKey); + + try { + KeySyncUtils.decryptRecoveryClaimResponse( + KeySyncUtils.generateKeyClaimant(), vaultParams, encryptedPayload); + fail("Did not throw decrypting with bad keyClaimant"); + } catch (AEADBadTagException error) { + // expected + } + } + + @Test public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception { KeyPair keyPair = SecureBox.genKeyPair(); byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); @@ -298,6 +341,83 @@ public class KeySyncUtilsTest { } } + @Test + public void packVaultParams_returns85Bytes() throws Exception { + PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); + + byte[] packedForm = KeySyncUtils.packVaultParams( + thmPublicKey, + /*counterId=*/ 1001L, + /*maxAttempts=*/ 10, + /*deviceId=*/ 1L); + + assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length); + } + + @Test + public void packVaultParams_encodesPublicKeyInFirst65Bytes() throws Exception { + PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic(); + + byte[] packedForm = KeySyncUtils.packVaultParams( + thmPublicKey, + /*counterId=*/ 1001L, + /*maxAttempts=*/ 10, + /*deviceId=*/ 1L); + + assertArrayEquals( + SecureBox.encodePublicKey(thmPublicKey), + Arrays.copyOf(packedForm, PUBLIC_KEY_LENGTH_BYTES)); + } + + @Test + public void packVaultParams_encodesCounterIdAsSecondParam() throws Exception { + long counterId = 103502L; + + byte[] packedForm = KeySyncUtils.packVaultParams( + SecureBox.genKeyPair().getPublic(), + counterId, + /*maxAttempts=*/ 10, + /*deviceId=*/ 1L); + + ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) + .order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES); + assertEquals(counterId, byteBuffer.getLong()); + } + + @Test + public void packVaultParams_encodesDeviceIdAsThirdParam() throws Exception { + long deviceId = 102942158152L; + + byte[] packedForm = KeySyncUtils.packVaultParams( + SecureBox.genKeyPair().getPublic(), + /*counterId=*/ 10021L, + /*maxAttempts=*/ 10, + deviceId); + + ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) + .order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES); + assertEquals(deviceId, byteBuffer.getLong()); + } + + @Test + public void packVaultParams_encodesMaxAttemptsAsLastParam() throws Exception { + int maxAttempts = 10; + + byte[] packedForm = KeySyncUtils.packVaultParams( + SecureBox.genKeyPair().getPublic(), + /*counterId=*/ 1001L, + maxAttempts, + /*deviceId=*/ 1L); + + ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) + .order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + 2 * Long.BYTES); + assertEquals(maxAttempts, byteBuffer.getInt()); + } + + private static byte[] randomBytes(int n) { byte[] bytes = new byte[n]; new Random().nextBytes(bytes); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index e20f664d14cf..97fbca2403bf 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.when; import android.app.KeyguardManager; import android.content.Context; -import android.content.SharedPreferences; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.support.test.InstrumentationRegistry; @@ -206,6 +205,14 @@ public class PlatformKeyManagerTest { } @Test + public void init_savesGenerationIdToDatabase() throws Exception { + mPlatformKeyManager.init(); + + assertEquals(1, + mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE)); + } + + @Test public void init_setsGenerationIdTo1() throws Exception { mPlatformKeyManager.init(); @@ -213,7 +220,38 @@ public class PlatformKeyManagerTest { } @Test + public void init_incrementsGenerationIdIfKeyIsUnavailable() throws Exception { + mPlatformKeyManager.init(); + + mPlatformKeyManager.init(); + + assertEquals(2, mPlatformKeyManager.getGenerationId()); + } + + @Test + public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception { + mPlatformKeyManager.init(); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/decrypt")).thenReturn(true); + when(mKeyStoreProxy + .containsAlias("com.android.server.locksettings.recoverablekeystore/" + + "platform/42/1/encrypt")).thenReturn(true); + + mPlatformKeyManager.init(); + + assertEquals(1, mPlatformKeyManager.getGenerationId()); + } + + @Test + public void getGenerationId_returnsMinusOneIfNotInitialized() throws Exception { + assertEquals(-1, mPlatformKeyManager.getGenerationId()); + } + + @Test public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + mPlatformKeyManager.getDecryptKey(); verify(mKeyStoreProxy).getKey( @@ -223,6 +261,8 @@ public class PlatformKeyManagerTest { @Test public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + mPlatformKeyManager.getEncryptKey(); verify(mKeyStoreProxy).getKey( diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java index b3dbf93b6e7f..8a461ac508fa 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java @@ -16,13 +16,12 @@ package com.android.server.locksettings.recoverablekeystore; -import static junit.framework.Assert.fail; +import static junit.framework.Assert.assertNotNull; import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import android.content.Context; -import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -32,8 +31,6 @@ import android.support.test.runner.AndroidJUnit4; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; -import com.google.common.collect.ImmutableMap; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -41,13 +38,10 @@ import org.junit.runner.RunWith; import java.io.File; import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; import java.security.KeyStore; -import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; @SmallTest @@ -57,14 +51,13 @@ public class RecoverableKeyGeneratorTest { private static final int TEST_GENERATION_ID = 3; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String KEY_ALGORITHM = "AES"; - private static final String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding"; + private static final int KEY_SIZE_BYTES = 32; + private static final String KEY_WRAP_ALGORITHM = "AES/GCM/NoPadding"; private static final String TEST_ALIAS = "karlin"; private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey"; private static final int TEST_USER_ID = 1000; private static final int KEYSTORE_UID_SELF = -1; private static final int GCM_TAG_LENGTH_BITS = 128; - private static final int GCM_NONCE_LENGTH_BYTES = 12; private PlatformEncryptionKey mPlatformKey; private PlatformDecryptionKey mDecryptKey; @@ -95,67 +88,33 @@ public class RecoverableKeyGeneratorTest { } @Test - public void generateAndStoreKey_setsKeyInKeyStore() throws Exception { - mRecoverableKeyGenerator.generateAndStoreKey( - mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS); - - KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF); - assertTrue(keyStore.containsAlias(TEST_ALIAS)); - } - - @Test - public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt() - throws Exception { + public void generateAndStoreKey_storesWrappedKey() throws Exception { mRecoverableKeyGenerator.generateAndStoreKey( mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS); - KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF); - SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null); - Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES]; - Arrays.fill(nonce, (byte) 0); - cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce)); + WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS); + assertNotNull(wrappedKey); } @Test - public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception { - mRecoverableKeyGenerator.generateAndStoreKey( + public void generateAndStoreKey_returnsRawMaterialOfCorrectLength() throws Exception { + byte[] rawKey = mRecoverableKeyGenerator.generateAndStoreKey( mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS); - KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF); - SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null); - Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM); - - try { - cipher.init(Cipher.ENCRYPT_MODE, key); - fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM); - } catch (InvalidKeyException e) { - // expected - } + assertEquals(KEY_SIZE_BYTES, rawKey.length); } @Test - public void generateAndStoreKey_storesWrappedKey() throws Exception { - mRecoverableKeyGenerator.generateAndStoreKey( + public void generateAndStoreKey_storesTheWrappedVersionOfTheRawMaterial() throws Exception { + byte[] rawMaterial = mRecoverableKeyGenerator.generateAndStoreKey( mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS); - KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF); - SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null); WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS); - SecretKey unwrappedKey = WrappedKey - .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey)) - .get(TEST_ALIAS); - - // key and unwrappedKey should be equivalent. let's check! - byte[] plaintext = getUtf8Bytes("dtianpos"); - Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] encrypted = cipher.doFinal(plaintext); - byte[] iv = cipher.getIV(); - cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv)); - byte[] decrypted = cipher.doFinal(encrypted); - assertArrayEquals(decrypted, plaintext); + Cipher cipher = Cipher.getInstance(KEY_WRAP_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, mDecryptKey.getKey(), + new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce())); + byte[] unwrappedMaterial = cipher.doFinal(wrappedKey.getKeyMaterial()); + assertArrayEquals(rawMaterial, unwrappedMaterial); } private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 35b18b14ad3c..88df62bae3df 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -19,16 +19,27 @@ package com.android.server.locksettings.recoverablekeystore; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.KeyguardManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; +import android.os.Binder; import android.os.RemoteException; +import android.os.UserHandle; import android.security.recoverablekeystore.KeyDerivationParameters; +import android.security.recoverablekeystore.KeyEntryRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import android.security.recoverablekeystore.RecoverableKeyStoreLoader; import android.support.test.InstrumentationRegistry; @@ -37,8 +48,10 @@ import android.support.test.runner.AndroidJUnit4; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.junit.After; import org.junit.Before; @@ -49,12 +62,21 @@ import org.mockito.MockitoAnnotations; import java.io.File; import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executors; +import java.util.Map; +import java.util.Random; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverableKeyStoreManagerTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; + private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding"; private static final String TEST_SESSION_ID = "karlin"; private static final byte[] TEST_PUBLIC_KEY = new byte[] { (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, @@ -76,13 +98,24 @@ public class RecoverableKeyStoreManagerTest { private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params"); private static final int TEST_USER_ID = 10009; private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; + private static final byte[] RECOVERY_RESPONSE_HEADER = + "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); + private static final String TEST_ALIAS = "nick"; + private static final int RECOVERABLE_KEY_SIZE_BYTES = 32; + private static final int GENERATION_ID = 1; + private static final byte[] NONCE = getUtf8Bytes("nonce"); + private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial"); + private static final int GCM_TAG_SIZE_BITS = 128; @Mock private Context mMockContext; + @Mock private RecoverySnapshotListenersStorage mMockListenersStorage; + @Mock private KeyguardManager mKeyguardManager; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; private RecoverableKeyStoreManager mRecoverableKeyStoreManager; private RecoverySessionStorage mRecoverySessionStorage; + private RecoverySnapshotStorage mRecoverySnapshotStorage; @Before public void setUp() { @@ -91,11 +124,21 @@ public class RecoverableKeyStoreManagerTest { Context context = InstrumentationRegistry.getTargetContext(); mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); + mRecoverySessionStorage = new RecoverySessionStorage(); + + when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager); + when(mMockContext.getSystemServiceName(any())).thenReturn("test"); + when(mMockContext.getApplicationContext()).thenReturn(mMockContext); + when(mKeyguardManager.isDeviceSecure(anyInt())).thenReturn(true); + mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( mMockContext, mRecoverableKeyStoreDb, - mRecoverySessionStorage); + mRecoverySessionStorage, + Executors.newSingleThreadExecutor(), + mRecoverySnapshotStorage, + mMockListenersStorage); } @After @@ -105,22 +148,38 @@ public class RecoverableKeyStoreManagerTest { } @Test + public void generateAndStoreKey_storesTheKey() throws Exception { + int uid = Binder.getCallingUid(); + + mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); + + assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull(); + } + + @Test + public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception { + assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS)) + .hasLength(RECOVERABLE_KEY_SIZE_BYTES); + } + + @Test public void startRecoverySession_checksPermissionFirst() throws Exception { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, - ImmutableList.of(new KeyStoreRecoveryMetadata( - TYPE_LOCKSCREEN, - TYPE_PASSWORD, - KeyDerivationParameters.createSHA256Parameters(TEST_SALT), - TEST_SECRET)), + ImmutableList.of( + new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), TEST_USER_ID); - verify(mMockContext, times(1)).enforceCallingOrSelfPermission( - eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), - any()); + verify(mMockContext, times(1)) + .enforceCallingOrSelfPermission( + eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any()); } @Test @@ -130,16 +189,17 @@ public class RecoverableKeyStoreManagerTest { TEST_PUBLIC_KEY, TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, - ImmutableList.of(new KeyStoreRecoveryMetadata( - TYPE_LOCKSCREEN, - TYPE_PASSWORD, - KeyDerivationParameters.createSHA256Parameters(TEST_SALT), - TEST_SECRET)), + ImmutableList.of( + new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), TEST_USER_ID); assertEquals(1, mRecoverySessionStorage.size()); - RecoverySessionStorage.Entry entry = mRecoverySessionStorage.get( - TEST_USER_ID, TEST_SESSION_ID); + RecoverySessionStorage.Entry entry = + mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID); assertArrayEquals(TEST_SECRET, entry.getLskfHash()); assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length); } @@ -154,9 +214,9 @@ public class RecoverableKeyStoreManagerTest { TEST_VAULT_CHALLENGE, ImmutableList.of(), TEST_USER_ID); + fail("should have thrown"); } catch (RemoteException e) { - assertEquals("Only a single KeyStoreRecoveryMetadata is supported", - e.getMessage()); + assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage()); } } @@ -168,19 +228,254 @@ public class RecoverableKeyStoreManagerTest { getUtf8Bytes("0"), TEST_VAULT_PARAMS, TEST_VAULT_CHALLENGE, - ImmutableList.of(new KeyStoreRecoveryMetadata( - TYPE_LOCKSCREEN, - TYPE_PASSWORD, - KeyDerivationParameters.createSHA256Parameters(TEST_SALT), - TEST_SECRET)), + ImmutableList.of( + new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + fail("should have thrown"); + } catch (RemoteException e) { + assertEquals("Not a valid X509 key", e.getMessage()); + } + } + + @Test + public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception { + try { + mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + /*recoveryKeyBlob=*/ randomBytes(32), + /*applicationKeys=*/ ImmutableList.of( + new KeyEntryRecoveryData(getUtf8Bytes("alias"), randomBytes(32)) + ), TEST_USER_ID); + fail("should have thrown"); } catch (RemoteException e) { - assertEquals("Not a valid X509 key", + assertEquals("User 10009 does not have pending session 'karlin'", e.getMessage()); } } + @Test + public void recoverKeys_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + + try { + mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + /*encryptedRecoveryKey=*/ randomBytes(60), + /*applicationKeys=*/ ImmutableList.of(), + /*uid=*/ TEST_USER_ID); + fail("should have thrown"); + } catch (RemoteException e) { + assertEquals("Failed to decrypt recovery key", e.getMessage()); + } + } + + @Test + public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID) + .getKeyClaimant(); + SecretKey recoveryKey = randomRecoveryKey(); + byte[] encryptedClaimResponse = encryptClaimResponse( + keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); + KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData( + TEST_ALIAS.getBytes(StandardCharsets.UTF_8), + randomBytes(32)); + + try { + mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + /*encryptedRecoveryKey=*/ encryptedClaimResponse, + /*applicationKeys=*/ ImmutableList.of(badApplicationKey), + /*uid=*/ TEST_USER_ID); + fail("should have thrown"); + } catch (RemoteException e) { + assertEquals("Failed to recover key with alias 'nick'", e.getMessage()); + } + } + + @Test + public void recoverKeys_returnsDecryptedKeys() throws Exception { + mRecoverableKeyStoreManager.startRecoverySession( + TEST_SESSION_ID, + TEST_PUBLIC_KEY, + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of(new KeyStoreRecoveryMetadata( + TYPE_LOCKSCREEN, + TYPE_PASSWORD, + KeyDerivationParameters.createSHA256Parameters(TEST_SALT), + TEST_SECRET)), + TEST_USER_ID); + byte[] keyClaimant = mRecoverySessionStorage.get(TEST_USER_ID, TEST_SESSION_ID) + .getKeyClaimant(); + SecretKey recoveryKey = randomRecoveryKey(); + byte[] encryptedClaimResponse = encryptClaimResponse( + keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); + byte[] applicationKeyBytes = randomBytes(32); + KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData( + TEST_ALIAS.getBytes(StandardCharsets.UTF_8), + encryptedApplicationKey(recoveryKey, applicationKeyBytes)); + + Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys( + TEST_SESSION_ID, + encryptedClaimResponse, + ImmutableList.of(applicationKey), + TEST_USER_ID); + + assertThat(recoveredKeys).hasSize(1); + assertThat(recoveredKeys.get(TEST_ALIAS)).isEqualTo(applicationKeyBytes); + } + + @Test + public void setSnapshotCreatedPendingIntent() throws Exception { + int uid = Binder.getCallingUid(); + PendingIntent intent = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), /*requestCode=*/1, + new Intent(), /*flags=*/ 0); + mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, /*userId=*/ 0); + verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class)); + } + + @Test + public void setRecoverySecretTypes() throws Exception { + int userId = UserHandle.getCallingUserId(); + int[] types1 = new int[]{11, 2000}; + int[] types2 = new int[]{1, 2, 3}; + int[] types3 = new int[]{}; + + mRecoverableKeyStoreManager.setRecoverySecretTypes(types1, userId); + assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo( + types1); + + mRecoverableKeyStoreManager.setRecoverySecretTypes(types2, userId); + assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo( + types2); + + mRecoverableKeyStoreManager.setRecoverySecretTypes(types3, userId); + assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo( + types3); + } + + @Test + public void setRecoveryStatus_forOneAlias() throws Exception { + int userId = UserHandle.getCallingUserId(); + int uid = Binder.getCallingUid(); + int status = 100; + int status2 = 200; + String alias = "key1"; + WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status); + mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey); + Map<String, Integer> statuses = + mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId); + assertThat(statuses).hasSize(1); + assertThat(statuses).containsEntry(alias, status); + + mRecoverableKeyStoreManager.setRecoveryStatus( + /*packageName=*/ null, new String[] {alias}, status2, userId); + statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId); + assertThat(statuses).hasSize(1); + assertThat(statuses).containsEntry(alias, status2); // updated + } + + @Test + public void setRecoveryStatus_for2Aliases() throws Exception { + int userId = UserHandle.getCallingUserId(); + int uid = Binder.getCallingUid(); + int status = 100; + int status2 = 200; + int status3 = 300; + String alias = "key1"; + String alias2 = "key2"; + WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status); + mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey); + mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey); + Map<String, Integer> statuses = + mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId); + assertThat(statuses).hasSize(2); + assertThat(statuses).containsEntry(alias, status); + assertThat(statuses).containsEntry(alias2, status); + + mRecoverableKeyStoreManager.setRecoveryStatus( + /*packageName=*/ null, /*aliases=*/ null, status2, userId); + statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId); + assertThat(statuses).hasSize(2); + assertThat(statuses).containsEntry(alias, status2); // updated + assertThat(statuses).containsEntry(alias2, status2); // updated + + mRecoverableKeyStoreManager.setRecoveryStatus( + /*packageName=*/ null, new String[] {alias2}, status3, userId); + + statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId); + assertThat(statuses).hasSize(2); + assertThat(statuses).containsEntry(alias, status2); + assertThat(statuses).containsEntry(alias2, status3); // updated + + mRecoverableKeyStoreManager.setRecoveryStatus( + /*packageName=*/ null, new String[] {alias, alias2}, status, userId); + + statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null, userId); + assertThat(statuses).hasSize(2); + assertThat(statuses).containsEntry(alias, status); // updated + assertThat(statuses).containsEntry(alias2, status); // updated + } + + private static byte[] encryptedApplicationKey( + SecretKey recoveryKey, byte[] applicationKey) throws Exception { + return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of( + "alias", new SecretKeySpec(applicationKey, "AES") + )).get("alias"); + } + + private static byte[] encryptClaimResponse( + byte[] keyClaimant, + byte[] lskfHash, + byte[] vaultParams, + SecretKey recoveryKey) throws Exception { + byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey( + lskfHash, recoveryKey); + return SecureBox.encrypt( + /*theirPublicKey=*/ null, + /*sharedSecret=*/ keyClaimant, + /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*payload=*/ locallyEncryptedRecoveryKey); + } + + private static SecretKey randomRecoveryKey() { + return new SecretKeySpec(randomBytes(32), "AES"); + } + private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } + + private static byte[] randomBytes(int n) { + byte[] bytes = new byte[n]; + new Random().nextBytes(bytes); + return bytes; + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java new file mode 100644 index 000000000000..b9c176452b9e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java @@ -0,0 +1,37 @@ +package com.android.server.locksettings.recoverablekeystore; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.app.PendingIntent; +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RecoverySnapshotListenersStorageTest { + + private final RecoverySnapshotListenersStorage mStorage = + new RecoverySnapshotListenersStorage(); + + @Test + public void hasListener_isFalseForUnregisteredUid() { + assertFalse(mStorage.hasListener(1000)); + } + + @Test + public void hasListener_isTrueForRegisteredUid() { + int recoveryAgentUid = 1000; + PendingIntent intent = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), /*requestCode=*/1, + new Intent(), /*flags=*/ 0); + mStorage.setSnapshotListener(recoveryAgentUid, intent); + + assertTrue(mStorage.hasListener(recoveryAgentUid)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java index 72b69f046b33..35ec23b2ee6b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java @@ -274,9 +274,9 @@ public class SecureBoxTest { @Test public void decrypt_nullEncryptedPayload() throws Exception { - IllegalArgumentException expected = + NullPointerException expected = expectThrows( - IllegalArgumentException.class, + NullPointerException.class, () -> SecureBox.decrypt( THM_PRIVATE_KEY, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java index 76cbea8e5d66..a8c7d5e5adba 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java @@ -16,6 +16,7 @@ package com.android.server.locksettings.recoverablekeystore.storage; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -27,6 +28,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import android.content.Context; +import android.content.SharedPreferences; +import android.security.recoverablekeystore.RecoverableKeyStoreLoader; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -34,6 +37,9 @@ import com.android.server.locksettings.recoverablekeystore.WrappedKey; import java.io.File; import java.nio.charset.StandardCharsets; +import java.security.KeyPairGenerator; +import java.security.PublicKey; +import java.security.spec.ECGenParameterSpec; import java.util.Map; @SmallTest @@ -119,10 +125,11 @@ public class RecoverableKeyStoreDbTest { int userId = 12; int uid = 1009; int generationId = 6; + int status = 120; String alias = "test"; byte[] nonce = getUtf8Bytes("nonce"); byte[] keyMaterial = getUtf8Bytes("keymaterial"); - WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId); + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, 120); mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey); WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias); @@ -130,6 +137,7 @@ public class RecoverableKeyStoreDbTest { assertArrayEquals(nonce, retrievedKey.getNonce()); assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId()); + assertEquals(status,retrievedKey.getRecoveryStatus()); } @Test @@ -208,7 +216,327 @@ public class RecoverableKeyStoreDbTest { assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId)); } + @Test + public void setRecoveryStatus_withSingleKey() { + int userId = 12; + int uid = 1009; + int generationId = 6; + int status = 120; + int status2 = 121; + String alias = "test"; + byte[] nonce = getUtf8Bytes("nonce"); + byte[] keyMaterial = getUtf8Bytes("keymaterial"); + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status); + mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey); + + WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias); + assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status); + + mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2); + + retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias); + assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status2); + } + + @Test + public void getStatusForAllKeys_with3Keys() { + int userId = 12; + int uid = 1009; + int generationId = 6; + int status = 120; + int status2 = 121; + String alias = "test"; + String alias2 = "test2"; + String alias3 = "test3"; + byte[] nonce = getUtf8Bytes("nonce"); + byte[] keyMaterial = getUtf8Bytes("keymaterial"); + + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status); + mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey); + WrappedKey wrappedKey2 = new WrappedKey(nonce, keyMaterial, generationId, status); + mRecoverableKeyStoreDb.insertKey(userId, uid, alias3, wrappedKey); + WrappedKey wrappedKeyWithDefaultStatus = new WrappedKey(nonce, keyMaterial, generationId); + mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKeyWithDefaultStatus); + + Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid); + assertThat(statuses).hasSize(3); + assertThat(statuses).containsEntry(alias, + RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS); + assertThat(statuses).containsEntry(alias2, status); + assertThat(statuses).containsEntry(alias3, status); + + int updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2); + assertThat(updates).isEqualTo(1); + updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias3, status2); + assertThat(updates).isEqualTo(1); + statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid); + + assertThat(statuses).hasSize(3); + assertThat(statuses).containsEntry(alias, status2); // updated from default + assertThat(statuses).containsEntry(alias2, status); + assertThat(statuses).containsEntry(alias3, status2); // updated + } + + @Test + public void setRecoveryStatus_withEmptyDatabase() throws Exception{ + int uid = 1009; + String alias = "test"; + int status = 120; + int updates = mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status); + assertThat(updates).isEqualTo(0); // database was empty + } + + + @Test + public void getStatusForAllKeys_withEmptyDatabase() { + int uid = 1009; + Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid); + assertThat(statuses).hasSize(0); + } + + @Test + public void setRecoveryServicePublicKey_replaceOldKey() throws Exception { + int userId = 12; + int uid = 10009; + PublicKey pubkey1 = genRandomPublicKey(); + PublicKey pubkey2 = genRandomPublicKey(); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey2); + } + + @Test + public void getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception { + int userId = 12; + int uid = 10009; + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); + + long serverParams = 123456L; + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); + } + + @Test + public void getRecoveryServicePublicKey_returnsInsertedKey() throws Exception { + int userId = 12; + int uid = 10009; + PublicKey pubkey = genRandomPublicKey(); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey); + } + + @Test + public void getRecoveryAgentUid_returnsUidIfSet() throws Exception { + int userId = 12; + int uid = 190992; + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, genRandomPublicKey()); + + assertThat(mRecoverableKeyStoreDb.getRecoveryAgentUid(userId)).isEqualTo(uid); + } + + @Test + public void getRecoveryAgentUid_returnsMinusOneForNonexistentAgent() throws Exception { + assertThat(mRecoverableKeyStoreDb.getRecoveryAgentUid(12)).isEqualTo(-1); + } + + public void setRecoverySecretTypes_emptyDefaultValue() throws Exception { + int userId = 12; + int uid = 10009; + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + new int[]{}); // default + } + + @Test + public void setRecoverySecretTypes_updateValue() throws Exception { + int userId = 12; + int uid = 10009; + int[] types1 = new int[]{1}; + int[] types2 = new int[]{2}; + + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types2); + } + + @Test + public void setRecoverySecretTypes_withMultiElementArrays() throws Exception { + int userId = 12; + int uid = 10009; + int[] types1 = new int[]{11, 2000}; + int[] types2 = new int[]{1, 2, 3}; + int[] types3 = new int[]{}; + + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types2); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types3); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types3); + } + + @Test + public void setRecoverySecretTypes_withDifferentUid() throws Exception { + int userId = 12; + int uid1 = 10011; + int uid2 = 10012; + int[] types1 = new int[]{1}; + int[] types2 = new int[]{2}; + + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid1, types1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid2, types2); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid1)).isEqualTo( + types1); + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid2)).isEqualTo( + types2); + } + + @Test + public void setRecoveryServiceMetadataMethods() throws Exception { + int userId = 12; + int uid = 10009; + + PublicKey pubkey1 = genRandomPublicKey(); + int[] types1 = new int[]{1}; + long serverParams1 = 111L; + + PublicKey pubkey2 = genRandomPublicKey(); + int[] types2 = new int[]{2}; + long serverParams2 = 222L; + + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1); + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1); + + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types1); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( + serverParams1); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey1); + + // Check that the methods don't interfere with each other. + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2); + mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2); + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2); + + assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( + types2); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( + serverParams2); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey2); + } + + @Test + public void getRecoveryServicePublicKey_returnsFirstKey() throws Exception { + int userId = 68; + int uid = 12904; + PublicKey publicKey = genRandomPublicKey(); + + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, publicKey); + + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId)).isEqualTo(publicKey); + } + + @Test + public void setServerParameters_replaceOldValue() throws Exception { + int userId = 12; + int uid = 10009; + long serverParams1 = 111L; + long serverParams2 = 222L; + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1); + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( + serverParams2); + } + + @Test + public void getServerParameters_returnsNullIfNoValue() throws Exception { + int userId = 12; + int uid = 10009; + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull(); + + PublicKey pubkey = genRandomPublicKey(); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull(); + } + + @Test + public void getServerParameters_returnsInsertedValue() throws Exception { + int userId = 12; + int uid = 10009; + long serverParams = 123456L; + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams); + } + + @Test + public void setRecoveryServiceMetadataEntry_allowsAUserToHaveTwoUids() throws Exception { + int userId = 12; + int uid1 = 10009; + int uid2 = 20009; + PublicKey pubkey = genRandomPublicKey(); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid1, pubkey); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid2, pubkey); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid1)).isEqualTo( + pubkey); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid2)).isEqualTo( + pubkey); + } + + @Test + public void setRecoveryServiceMetadataEntry_allowsTwoUsersToHaveTheSameUid() throws Exception { + int userId1 = 12; + int userId2 = 23; + int uid = 10009; + PublicKey pubkey = genRandomPublicKey(); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId1, uid, pubkey); + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId2, uid, pubkey); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId1, uid)).isEqualTo( + pubkey); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId2, uid)).isEqualTo( + pubkey); + } + + @Test + public void setRecoveryServiceMetadataEntry_updatesColumnsSeparately() throws Exception { + int userId = 12; + int uid = 10009; + PublicKey pubkey1 = genRandomPublicKey(); + PublicKey pubkey2 = genRandomPublicKey(); + long serverParams = 123456L; + + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey1); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull(); + + mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey1); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams); + + mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2); + assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( + pubkey2); + assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams); + } + private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } + + private static PublicKey genRandomPublicKey() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); + keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1")); + return keyPairGenerator.generateKeyPair().getPublic(); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java index 6aeff2892b9c..6f93fe409e48 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java @@ -37,6 +37,7 @@ public class RecoverySessionStorageTest { private static final int TEST_USER_ID = 696; private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf"); private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333"); + private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault params vault params"); @Test public void size_isZeroForEmpty() { @@ -47,7 +48,7 @@ public class RecoverySessionStorageTest { public void size_incrementsAfterAdd() { RecoverySessionStorage storage = new RecoverySessionStorage(); storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture())); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture())); assertEquals(1, storage.size()); } @@ -56,7 +57,7 @@ public class RecoverySessionStorageTest { public void size_decrementsAfterRemove() { RecoverySessionStorage storage = new RecoverySessionStorage(); storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture())); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture())); storage.remove(TEST_USER_ID); assertEquals(0, storage.size()); @@ -66,7 +67,7 @@ public class RecoverySessionStorageTest { public void remove_overwritesLskfHashMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.remove(TEST_USER_ID); @@ -78,7 +79,7 @@ public class RecoverySessionStorageTest { public void remove_overwritesKeyClaimantMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.remove(TEST_USER_ID); @@ -90,7 +91,7 @@ public class RecoverySessionStorageTest { public void destroy_overwritesLskfHashMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.destroy(); @@ -102,7 +103,7 @@ public class RecoverySessionStorageTest { public void destroy_overwritesKeyClaimantMemory() { RecoverySessionStorage storage = new RecoverySessionStorage(); RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry( - TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()); + TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture(), vaultParamsFixture()); storage.add(TEST_USER_ID, entry); storage.destroy(); @@ -126,6 +127,10 @@ public class RecoverySessionStorageTest { return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length); } + private static byte[] vaultParamsFixture() { + return Arrays.copyOf(TEST_VAULT_PARAMS, TEST_VAULT_PARAMS.length); + } + private static byte[] getUtf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java new file mode 100644 index 000000000000..2759e39ed318 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java @@ -0,0 +1,53 @@ +package com.android.server.locksettings.recoverablekeystore.storage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.security.recoverablekeystore.KeyStoreRecoveryData; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RecoverySnapshotStorageTest { + + private final RecoverySnapshotStorage mRecoverySnapshotStorage = new RecoverySnapshotStorage(); + + @Test + public void get_isNullForNonExistentSnapshot() { + assertNull(mRecoverySnapshotStorage.get(1000)); + } + + @Test + public void get_returnsSetSnapshot() { + int userId = 1000; + KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData( + /*snapshotVersion=*/ 1, + new ArrayList<>(), + new ArrayList<>(), + new byte[0]); + mRecoverySnapshotStorage.put(userId, recoveryData); + + assertEquals(recoveryData, mRecoverySnapshotStorage.get(userId)); + } + + @Test + public void remove_removesSnapshots() { + int userId = 1000; + KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData( + /*snapshotVersion=*/ 1, + new ArrayList<>(), + new ArrayList<>(), + new byte[0]); + mRecoverySnapshotStorage.put(userId, recoveryData); + + mRecoverySnapshotStorage.remove(userId); + + assertNull(mRecoverySnapshotStorage.get(1000)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java index 0c35fcb05da6..c7fa62e56380 100644 --- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java +++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java @@ -38,6 +38,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; import android.support.test.uiautomator.UiDevice; +import android.text.TextUtils; import android.util.Log; import org.junit.AfterClass; @@ -81,7 +82,7 @@ public class ConnOnActivityStartTest { private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec - private static final long SCREEN_ON_DELAY_MS = 1000; // 1 sec + private static final long SCREEN_ON_DELAY_MS = 2000; // 2 sec private static final long BIND_SERVICE_TIMEOUT_SEC = 4; @@ -214,7 +215,6 @@ public class ConnOnActivityStartTest { try{ turnBatteryOff(); setAppIdle(true); - SystemClock.sleep(30000); turnScreenOn(); startActivityAndCheckNetworkAccess(); } finally { @@ -239,7 +239,6 @@ public class ConnOnActivityStartTest { try { Log.d(TAG, testName + " Start #" + i); turnScreenOn(); - SystemClock.sleep(SCREEN_ON_DELAY_MS); startActivityAndCheckNetworkAccess(); } finally { finishActivity(); @@ -287,7 +286,7 @@ public class ConnOnActivityStartTest { private void setAppIdle(boolean enabled) throws Exception { executeCommand("am set-inactive " + TEST_PKG + " " + enabled); assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled, - 10 /* maxTries */, 2000 /* napTimeMs */); + 15 /* maxTries */, 2000 /* napTimeMs */); } private void updateRestrictBackgroundBlacklist(boolean add) throws Exception { @@ -345,6 +344,8 @@ public class ConnOnActivityStartTest { private void turnScreenOn() throws Exception { executeCommand("input keyevent KEYCODE_WAKEUP"); executeCommand("wm dismiss-keyguard"); + // Wait for screen-on state to propagate through the system. + SystemClock.sleep(SCREEN_ON_DELAY_MS); } private String executeCommand(String cmd) throws IOException { @@ -404,15 +405,38 @@ public class ConnOnActivityStartTest { } private static void dumpOnFailure() throws Exception { - Log.d(TAG, ">>> Begin network_management dump"); - Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, - TAG, executeSilentCommand("dumpsys network_management"), null); - Log.d(TAG, "<<< End network_management dump"); - - Log.d(TAG, ">>> Begin netpolicy dump"); - Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, - TAG, executeSilentCommand("dumpsys netpolicy"), null); - Log.d(TAG, "<<< End netpolicy dump"); + dump("network_management"); + dump("netpolicy"); + dumpUsageStats(); + } + + private static void dumpUsageStats() throws Exception { + final String output = executeSilentCommand("dumpsys usagestats"); + final StringBuilder sb = new StringBuilder(); + final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n'); + splitter.setString(output); + String str; + while (splitter.hasNext()) { + str = splitter.next(); + if (str.contains("package=") && !str.contains(TEST_PKG)) { + continue; + } + if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) { + continue; + } + sb.append(str).append('\n'); + } + dump("usagestats", sb.toString()); + } + + private static void dump(String service) throws Exception { + dump(service, executeSilentCommand("dumpsys " + service)); + } + + private static void dump(String service, String dump) throws Exception { + Log.d(TAG, ">>> Begin dump " + service); + Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, TAG, dump, null); + Log.d(TAG, "<<< End dump " + service); } private void finishActivity() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java index f3cb98078602..212d25d42420 100644 --- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java @@ -95,41 +95,6 @@ public class WatchlistSettingsTests { } @Test - public void testWatchlistSettings_writeSettingsToDisk() throws Exception { - copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile); - WatchlistSettings settings = new WatchlistSettings(mTestXmlFile); - settings.writeSettingsToDisk(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32), - Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32), - Arrays.asList(TEST_NEW_CC_IP_SHA256)); - // Ensure old watchlist is not in memory - assertFalse(settings.containsDomain(TEST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_CC_IP)); - assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP)); - assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP)); - assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP)); - // Ensure new watchlist is in memory - assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN)); - assertTrue(settings.containsIp(TEST_NEW_CC_IP)); - // Reload settings from disk and test again - settings = new WatchlistSettings(mTestXmlFile); - // Ensure old watchlist is not in memory - assertFalse(settings.containsDomain(TEST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_CC_IP)); - assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN)); - assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP)); - assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP)); - assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN)); - assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP)); - // Ensure new watchlist is in memory - assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN)); - assertTrue(settings.containsIp(TEST_NEW_CC_IP)); - } - - @Test public void testWatchlistSettings_writeSettingsToMemory() throws Exception { copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile); WatchlistSettings settings = new WatchlistSettings(mTestXmlFile); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index e12a8da805f6..cdac516c9577 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -17,75 +17,93 @@ package com.android.server.pm; import android.content.IIntentReceiver; - import android.os.Bundle; +import android.support.test.runner.AndroidJUnit4; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.File; // runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services - -@SmallTest -public class PackageManagerServiceTest extends AndroidTestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); +// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest +@RunWith(AndroidJUnit4.class) +public class PackageManagerServiceTest { + @Before + public void setUp() throws Exception { } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { } + @Test public void testPackageRemoval() throws Exception { - class PackageSenderImpl implements PackageSender { - public void sendPackageBroadcast(final String action, final String pkg, - final Bundle extras, final int flags, final String targetPkg, - final IIntentReceiver finishedReceiver, final int[] userIds, - int[] instantUserIds) { + class PackageSenderImpl implements PackageSender { + public void sendPackageBroadcast(final String action, final String pkg, + final Bundle extras, final int flags, final String targetPkg, + final IIntentReceiver finishedReceiver, final int[] userIds, + int[] instantUserIds) { + } + + public void sendPackageAddedForNewUsers(String packageName, + boolean sendBootComplete, boolean includeStopped, int appId, + int[] userIds, int[] instantUserIds) { + } + + @Override + public void notifyPackageAdded(String packageName) { + } + + @Override + public void notifyPackageRemoved(String packageName) { + } } - public void sendPackageAddedForNewUsers(String packageName, - boolean sendBootComplete, boolean includeStopped, int appId, - int[] userIds, int[] instantUserIds) { - } - } - - PackageSenderImpl sender = new PackageSenderImpl(); - PackageSetting setting = null; - PackageManagerService.PackageRemovedInfo pri = - new PackageManagerService.PackageRemovedInfo(sender); - - // Initial conditions: nothing there - assertNull(pri.removedUsers); - assertNull(pri.broadcastUsers); - - // populateUsers with nothing leaves nothing - pri.populateUsers(null, setting); - assertNull(pri.broadcastUsers); - - // Create a real (non-null) PackageSetting and confirm that the removed - // users are copied properly - setting = new PackageSetting("name", "realName", new File("codePath"), - new File("resourcePath"), "legacyNativeLibraryPathString", - "primaryCpuAbiString", "secondaryCpuAbiString", - "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0, - null, null); - pri.populateUsers(new int[] {1, 2, 3, 4, 5}, setting); - assertNotNull(pri.broadcastUsers); - assertEquals(5, pri.broadcastUsers.length); - - // Exclude a user - pri.broadcastUsers = null; - final int EXCLUDED_USER_ID = 4; - setting.setInstantApp(true, EXCLUDED_USER_ID); - pri.populateUsers(new int[] {1, 2, 3, EXCLUDED_USER_ID, 5}, setting); - assertNotNull(pri.broadcastUsers); - assertEquals(5 - 1, pri.broadcastUsers.length); - - // TODO: test that sendApplicationHiddenForUser() actually fills in - // broadcastUsers + PackageSenderImpl sender = new PackageSenderImpl(); + PackageSetting setting = null; + PackageManagerService.PackageRemovedInfo pri = + new PackageManagerService.PackageRemovedInfo(sender); + + // Initial conditions: nothing there + Assert.assertNull(pri.removedUsers); + Assert.assertNull(pri.broadcastUsers); + + // populateUsers with nothing leaves nothing + pri.populateUsers(null, setting); + Assert.assertNull(pri.broadcastUsers); + + // Create a real (non-null) PackageSetting and confirm that the removed + // users are copied properly + setting = new PackageSetting("name", "realName", new File("codePath"), + new File("resourcePath"), "legacyNativeLibraryPathString", + "primaryCpuAbiString", "secondaryCpuAbiString", + "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0, + null, null); + pri.populateUsers(new int[] { + 1, 2, 3, 4, 5 + }, setting); + Assert.assertNotNull(pri.broadcastUsers); + Assert.assertEquals(5, pri.broadcastUsers.length); + Assert.assertNotNull(pri.instantUserIds); + Assert.assertEquals(0, pri.instantUserIds.length); + + // Exclude a user + pri.broadcastUsers = null; + final int EXCLUDED_USER_ID = 4; + setting.setInstantApp(true, EXCLUDED_USER_ID); + pri.populateUsers(new int[] { + 1, 2, 3, EXCLUDED_USER_ID, 5 + }, setting); + Assert.assertNotNull(pri.broadcastUsers); + Assert.assertEquals(4, pri.broadcastUsers.length); + Assert.assertNotNull(pri.instantUserIds); + Assert.assertEquals(1, pri.instantUserIds.length); + + // TODO: test that sendApplicationHiddenForUser() actually fills in + // broadcastUsers } } diff --git a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java index 880b77ed81da..ff55a2ba120b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java @@ -205,8 +205,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( PACKAGE_ONE, ACTIVITY_COMPONENT, - null, - null, UserHandle.of(PRIMARY_USER))); verify(mContext, never()) @@ -217,33 +215,6 @@ public class CrossProfileAppsServiceImplTest { } @Test - public void startActivityAsUser_profile_successWithOption() throws Exception { - Bundle options = Bundle.forPair("test_key", "test_value"); - - mCrossProfileAppsServiceImpl.startActivityAsUser( - PACKAGE_ONE, - ACTIVITY_COMPONENT, - null, - options, - UserHandle.of(PROFILE_OF_PRIMARY_USER)); - - ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); - ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); - - verify(mContext) - .startActivityAsUser( - intentCaptor.capture(), - bundleCaptor.capture(), - eq(UserHandle.of(PROFILE_OF_PRIMARY_USER))); - - Intent intent = intentCaptor.getValue(); - assertEquals(ACTIVITY_COMPONENT, intent.getComponent()); - - Bundle bundle = bundleCaptor.getValue(); - assertEquals("test_value", bundle.getString("test_key")); - } - - @Test public void startActivityAsUser_profile_notInstalled() throws Exception { mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false); @@ -253,8 +224,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( PACKAGE_ONE, ACTIVITY_COMPONENT, - null, - null, UserHandle.of(PROFILE_OF_PRIMARY_USER))); verify(mContext, never()) @@ -272,8 +241,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( PACKAGE_TWO, ACTIVITY_COMPONENT, - null, - null, UserHandle.of(PROFILE_OF_PRIMARY_USER))); verify(mContext, never()) @@ -293,8 +260,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( PACKAGE_ONE, ACTIVITY_COMPONENT, - null, - null, UserHandle.of(PROFILE_OF_PRIMARY_USER))); verify(mContext, never()) @@ -312,8 +277,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( PACKAGE_ONE, new ComponentName(PACKAGE_TWO, "test"), - null, - null, UserHandle.of(PROFILE_OF_PRIMARY_USER))); verify(mContext, never()) @@ -331,8 +294,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( PACKAGE_ONE, ACTIVITY_COMPONENT, - null, - null, UserHandle.of(SECONDARY_USER))); verify(mContext, never()) @@ -349,8 +310,6 @@ public class CrossProfileAppsServiceImplTest { mCrossProfileAppsServiceImpl.startActivityAsUser( PACKAGE_ONE, ACTIVITY_COMPONENT, - null, - null, UserHandle.of(PRIMARY_USER)); verify(mContext) diff --git a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java index 2d4bc0f8b7d0..029d9f1a8262 100644 --- a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java +++ b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java @@ -104,6 +104,15 @@ public class TestHandler extends Handler { return new PriorityQueue<>(mMessages); } + /** + * Optionally-overridable to allow deciphering message types + * + * @see android.util.DebugUtils#valueToString - a handy utility to use when overriding this + */ + protected String messageToString(Message message) { + return message.toString(); + } + private void dispatch(MsgInfo msg) { int msgId = msg.message.what; @@ -148,7 +157,7 @@ public class TestHandler extends Handler { @Override public String toString() { return "MsgInfo{" + - "message=" + message + + "message=" + messageToString(message) + ", sendTime=" + sendTime + '}'; } diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java index d9ab5c86bc72..759894b907d0 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java @@ -193,7 +193,7 @@ public class AppWindowTokenTests extends WindowTestsBase { assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation()); mToken.setFillsParent(true); - mToken.hidden = true; + mToken.setHidden(true); mToken.sendingToBottom = true; // Can not specify orientation if app isn't visible even though it fills parent. assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation()); diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java index 53a5899b2850..2bfe2742d419 100644 --- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -110,7 +110,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { } public void notifyTransitionStarting(int transit) { - mListener.onAppTransitionStartingLocked(transit, null, null, null, null); + mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0); } public void notifyTransitionFinished() { diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java index c309611bb139..17fe6427f756 100644 --- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java @@ -181,7 +181,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase { final Animation a = new TranslateAnimation(-10, 10, 0, 0); a.initialize(0, 0, 0, 0); a.setDuration(50); - return new WindowAnimationSpec(a, new Point(0, 0)); + return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */); } /** diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java index 6f739ca54eb6..6e57f47997fc 100644 --- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -44,6 +45,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; /** * Test class for {@link SurfaceAnimatorTest}. @@ -61,12 +63,14 @@ public class SurfaceAnimatorTest extends WindowTestsBase { private SurfaceSession mSession = new SurfaceSession(); private MyAnimatable mAnimatable; + private MyAnimatable mAnimatable2; @Before public void setUp() throws Exception { super.setUp(); MockitoAnnotations.initMocks(this); mAnimatable = new MyAnimatable(); + mAnimatable2 = new MyAnimatable(); } @Test @@ -74,15 +78,13 @@ public class SurfaceAnimatorTest extends WindowTestsBase { mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); - - assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); - assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation()); + assertAnimating(mAnimatable); verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle())); verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); callbackCaptor.getValue().onAnimationFinished(mSpec); - assertFalse(mAnimatable.mSurfaceAnimator.isAnimating()); - assertNull(mAnimatable.mSurfaceAnimator.getAnimation()); + waitUntilPrepareSurfaces(); + assertNotAnimating(mAnimatable); assertTrue(mAnimatable.mFinishedCallbackCalled); assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash)); // TODO: Verify reparenting once we use mPendingTransaction to reparent it back @@ -99,27 +101,28 @@ public class SurfaceAnimatorTest extends WindowTestsBase { final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); - assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); - assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation()); + assertAnimating(mAnimatable); verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); // First animation was finished, but this shouldn't cancel the second animation callbackCaptor.getValue().onAnimationFinished(mSpec); + waitUntilPrepareSurfaces(); assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); // Second animation was finished verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture()); callbackCaptor.getValue().onAnimationFinished(mSpec2); - assertFalse(mAnimatable.mSurfaceAnimator.isAnimating()); + waitUntilPrepareSurfaces(); + assertNotAnimating(mAnimatable); assertTrue(mAnimatable.mFinishedCallbackCalled); } @Test public void testCancelAnimation() throws Exception { mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); - assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); + assertAnimating(mAnimatable); mAnimatable.mSurfaceAnimator.cancelAnimation(); - assertFalse(mAnimatable.mSurfaceAnimator.isAnimating()); + assertNotAnimating(mAnimatable); verify(mSpec).onAnimationCancelled(any()); assertTrue(mAnimatable.mFinishedCallbackCalled); assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash)); @@ -130,7 +133,7 @@ public class SurfaceAnimatorTest extends WindowTestsBase { mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); verifyZeroInteractions(mSpec); - assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); + assertAnimating(mAnimatable); mAnimatable.mSurfaceAnimator.endDelayingAnimationStart(); verify(mSpec).startAnimation(any(), any(), any()); } @@ -141,11 +144,50 @@ public class SurfaceAnimatorTest extends WindowTestsBase { mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); mAnimatable.mSurfaceAnimator.cancelAnimation(); verifyZeroInteractions(mSpec); - assertFalse(mAnimatable.mSurfaceAnimator.isAnimating()); + assertNotAnimating(mAnimatable); assertTrue(mAnimatable.mFinishedCallbackCalled); assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash)); } + @Test + public void testTransferAnimation() throws Exception { + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + + final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( + OnAnimationFinishedCallback.class); + verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + final SurfaceControl leash = mAnimatable.mLeash; + + mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator); + assertNotAnimating(mAnimatable); + assertAnimating(mAnimatable2); + assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash); + assertFalse(mAnimatable.mPendingDestroySurfaces.contains(leash)); + callbackCaptor.getValue().onAnimationFinished(mSpec); + waitUntilPrepareSurfaces(); + assertNotAnimating(mAnimatable2); + assertTrue(mAnimatable2.mFinishedCallbackCalled); + assertTrue(mAnimatable2.mPendingDestroySurfaces.contains(leash)); + } + + private void assertAnimating(MyAnimatable animatable) { + assertTrue(animatable.mSurfaceAnimator.isAnimating()); + assertNotNull(animatable.mSurfaceAnimator.getAnimation()); + } + + private void assertNotAnimating(MyAnimatable animatable) { + assertFalse(animatable.mSurfaceAnimator.isAnimating()); + assertNull(animatable.mSurfaceAnimator.getAnimation()); + } + + private void waitUntilPrepareSurfaces() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + synchronized (sWm.mWindowMap) { + sWm.mAnimator.addAfterPrepareSurfacesRunnable(latch::countDown); + } + latch.await(); + } + private class MyAnimatable implements Animatable { final SurfaceControl mParent; @@ -204,6 +246,11 @@ public class SurfaceAnimatorTest extends WindowTestsBase { } @Override + public SurfaceControl getAnimationLeashParent() { + return mParent; + } + + @Override public SurfaceControl getSurfaceControl() { return mSurface; } @@ -223,8 +270,6 @@ public class SurfaceAnimatorTest extends WindowTestsBase { return 1; } - private final Runnable mFinishedCallback = () -> { - mFinishedCallbackCalled = true; - }; + private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true; } } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java new file mode 100644 index 000000000000..9cdef16194ff --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.view.animation.ClipRectAnimation; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link WindowAnimationSpec} class. + * + * atest FrameworksServicesTests:com.android.server.wm.WindowAnimationSpecTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowAnimationSpecTest { + private final SurfaceControl mSurfaceControl = mock(SurfaceControl.class); + private final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class); + private final Animation mAnimation = mock(Animation.class); + private final Rect mStackBounds = new Rect(0, 0, 10, 10); + + @Test + public void testApply_clipNone() { + Rect windowCrop = new Rect(0, 0, 20, 20); + Animation a = new ClipRectAnimation(windowCrop, windowCrop); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_NONE); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(windowCrop))); + } + + @Test + public void testApply_clipAfter() { + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_AFTER_ANIM); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); + verify(mTransaction).setFinalCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(mStackBounds))); + } + + @Test + public void testApply_clipBeforeNoAnimationBounds() { + // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0) + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(mStackBounds))); + } + + @Test + public void testApply_clipBeforeNoStackBounds() { + // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20) + Rect windowCrop = new Rect(0, 0, 20, 20); + Animation a = new ClipRectAnimation(windowCrop, windowCrop); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + null, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); + } + + @Test + public void testApply_clipBeforeSmallerAnimationClip() { + // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5) + Rect windowCrop = new Rect(0, 0, 5, 5); + Animation a = new ClipRectAnimation(windowCrop, windowCrop); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(windowCrop))); + } + + @Test + public void testApply_clipBeforeSmallerStackClip() { + // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20) + Rect windowCrop = new Rect(0, 0, 20, 20); + Animation a = new ClipRectAnimation(windowCrop, windowCrop); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(mStackBounds))); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java index 307deb487bbf..196b4a99d02d 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java @@ -564,6 +564,86 @@ public class WindowContainerTests extends WindowTestsBase { assertEquals(1, child2223.compareTo(child21)); } + @Test + public void testPrefixOrderIndex() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + + final TestWindowContainer child1 = root.addChildWindow(); + + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + + final TestWindowContainer child2 = root.addChildWindow(); + + final TestWindowContainer child21 = child2.addChildWindow(); + final TestWindowContainer child22 = child2.addChildWindow(); + + final TestWindowContainer child221 = child22.addChildWindow(); + final TestWindowContainer child222 = child22.addChildWindow(); + final TestWindowContainer child223 = child22.addChildWindow(); + + final TestWindowContainer child23 = child2.addChildWindow(); + + assertEquals(0, root.getPrefixOrderIndex()); + assertEquals(1, child1.getPrefixOrderIndex()); + assertEquals(2, child11.getPrefixOrderIndex()); + assertEquals(3, child12.getPrefixOrderIndex()); + assertEquals(4, child2.getPrefixOrderIndex()); + assertEquals(5, child21.getPrefixOrderIndex()); + assertEquals(6, child22.getPrefixOrderIndex()); + assertEquals(7, child221.getPrefixOrderIndex()); + assertEquals(8, child222.getPrefixOrderIndex()); + assertEquals(9, child223.getPrefixOrderIndex()); + assertEquals(10, child23.getPrefixOrderIndex()); + } + + @Test + public void testPrefixOrder_addEntireSubtree() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + final TestWindowContainer subtree = builder.build(); + final TestWindowContainer subtree2 = builder.build(); + + final TestWindowContainer child1 = subtree.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child2 = subtree2.addChildWindow(); + final TestWindowContainer child3 = subtree2.addChildWindow(); + subtree.addChild(subtree2, 1); + root.addChild(subtree, 0); + + assertEquals(0, root.getPrefixOrderIndex()); + assertEquals(1, subtree.getPrefixOrderIndex()); + assertEquals(2, child1.getPrefixOrderIndex()); + assertEquals(3, child11.getPrefixOrderIndex()); + assertEquals(4, subtree2.getPrefixOrderIndex()); + assertEquals(5, child2.getPrefixOrderIndex()); + assertEquals(6, child3.getPrefixOrderIndex()); + } + + @Test + public void testPrefixOrder_remove() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + + final TestWindowContainer child1 = root.addChildWindow(); + + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + + final TestWindowContainer child2 = root.addChildWindow(); + + assertEquals(0, root.getPrefixOrderIndex()); + assertEquals(1, child1.getPrefixOrderIndex()); + assertEquals(2, child11.getPrefixOrderIndex()); + assertEquals(3, child12.getPrefixOrderIndex()); + assertEquals(4, child2.getPrefixOrderIndex()); + + root.removeChild(child1); + + assertEquals(1, child2.getPrefixOrderIndex()); + } + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ private class TestWindowContainer extends WindowContainer<TestWindowContainer> { private final int mLayer; diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java index 5f587444fcf4..012fc2329ea4 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java @@ -160,7 +160,6 @@ public class WindowTestUtils { /* Used so we can gain access to some protected members of the {@link WindowToken} class */ public static class TestWindowToken extends WindowToken { - int adj = 0; TestWindowToken(int type, DisplayContent dc) { this(type, dc, false /* persistOnEmpty */); @@ -178,11 +177,6 @@ public class WindowTestUtils { boolean hasWindow(WindowState w) { return mChildren.contains(w); } - - @Override - int getAnimLayerAdjustment() { - return adj; - } } /* Used so we can gain access to some protected members of the {@link Task} class */ diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index c699a94db279..ff840f3aeea9 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -103,6 +103,7 @@ class WindowTestsBase { context.getDisplay().getDisplayInfo(mDisplayInfo); mDisplayContent = createNewDisplay(); + sWm.mAnimator.mInitialized = true; sWm.mDisplayEnabled = true; sWm.mDisplayReady = true; diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/provider/Telephony.java index 942ea009f684..942ea009f684 100644 --- a/telephony/java/android/telephony/Telephony.java +++ b/telephony/java/android/provider/Telephony.java diff --git a/telephony/java/android/telephony/CellIdentityCdma.aidl b/telephony/java/android/telephony/CellIdentityCdma.aidl new file mode 100644 index 000000000000..b31ad0bded0c --- /dev/null +++ b/telephony/java/android/telephony/CellIdentityCdma.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @hide */ +package android.telephony; + +parcelable CellIdentityCdma; diff --git a/telephony/java/android/telephony/CellIdentityGsm.aidl b/telephony/java/android/telephony/CellIdentityGsm.aidl new file mode 100644 index 000000000000..bcc07510277d --- /dev/null +++ b/telephony/java/android/telephony/CellIdentityGsm.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @hide */ +package android.telephony; + +parcelable CellIdentityGsm; diff --git a/telephony/java/android/telephony/CellIdentityLte.aidl b/telephony/java/android/telephony/CellIdentityLte.aidl new file mode 100644 index 000000000000..940d170b6114 --- /dev/null +++ b/telephony/java/android/telephony/CellIdentityLte.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @hide */ +package android.telephony; + +parcelable CellIdentityLte; diff --git a/telephony/java/android/telephony/CellIdentityWcdma.aidl b/telephony/java/android/telephony/CellIdentityWcdma.aidl new file mode 100644 index 000000000000..462ce2cccf45 --- /dev/null +++ b/telephony/java/android/telephony/CellIdentityWcdma.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @hide */ +package android.telephony; + +parcelable CellIdentityWcdma; diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java index a554c693ccdd..059a2d070ec3 100644 --- a/telephony/java/android/telephony/MbmsDownloadSession.java +++ b/telephony/java/android/telephony/MbmsDownloadSession.java @@ -502,8 +502,10 @@ public class MbmsDownloadSession implements AutoCloseable { * Asynchronous errors through the callback may include any error not specific to the * streaming use-case. * @param request The request that specifies what should be downloaded. + * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, + * and some other error code otherwise. */ - public void download(@NonNull DownloadRequest request) { + public int download(@NonNull DownloadRequest request) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { throw new IllegalStateException("Middleware not yet bound"); @@ -519,13 +521,16 @@ public class MbmsDownloadSession implements AutoCloseable { setTempFileRootDirectory(tempRootDirectory); } - writeDownloadRequestToken(request); try { - downloadService.download(request); + int result = downloadService.download(request); + if (result == MbmsErrors.SUCCESS) { + writeDownloadRequestToken(request); + } + return result; } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return MbmsErrors.ERROR_MIDDLEWARE_LOST; } } @@ -565,8 +570,10 @@ public class MbmsDownloadSession implements AutoCloseable { * @param callback The callback that should be called when the middleware has information to * share on the download. * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on. + * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, + * and some other error code otherwise. */ - public void registerStateCallback(@NonNull DownloadRequest request, + public int registerStateCallback(@NonNull DownloadRequest request, @NonNull DownloadStateCallback callback, @NonNull Handler handler) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { @@ -583,16 +590,15 @@ public class MbmsDownloadSession implements AutoCloseable { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); } - sendErrorToApp(result, null); - return; + return result; } } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); - return; + return MbmsErrors.ERROR_MIDDLEWARE_LOST; } mInternalDownloadCallbacks.put(callback, internalCallback); + return MbmsErrors.SUCCESS; } /** @@ -606,8 +612,10 @@ public class MbmsDownloadSession implements AutoCloseable { * * @param request The {@link DownloadRequest} provided during registration * @param callback The callback provided during registration. + * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, + * and some other error code otherwise. */ - public void unregisterStateCallback(@NonNull DownloadRequest request, + public int unregisterStateCallback(@NonNull DownloadRequest request, @NonNull DownloadStateCallback callback) { try { IMbmsDownloadService downloadService = mService.get(); @@ -617,6 +625,9 @@ public class MbmsDownloadSession implements AutoCloseable { InternalDownloadStateCallback internalCallback = mInternalDownloadCallbacks.get(callback); + if (internalCallback == null) { + throw new IllegalArgumentException("Provided callback was never registered"); + } try { int result = downloadService.unregisterStateCallback(request, internalCallback); @@ -624,12 +635,12 @@ public class MbmsDownloadSession implements AutoCloseable { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); } - sendErrorToApp(result, null); + return result; } } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return MbmsErrors.ERROR_MIDDLEWARE_LOST; } } finally { InternalDownloadStateCallback internalCallback = @@ -638,6 +649,7 @@ public class MbmsDownloadSession implements AutoCloseable { internalCallback.stop(); } } + return MbmsErrors.SUCCESS; } /** @@ -647,8 +659,10 @@ public class MbmsDownloadSession implements AutoCloseable { * this method will throw an {@link IllegalArgumentException}. * * @param downloadRequest The download request that you wish to cancel. + * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, + * and some other error code otherwise. */ - public void cancelDownload(@NonNull DownloadRequest downloadRequest) { + public int cancelDownload(@NonNull DownloadRequest downloadRequest) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { throw new IllegalStateException("Middleware not yet bound"); @@ -660,16 +674,15 @@ public class MbmsDownloadSession implements AutoCloseable { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); } - sendErrorToApp(result, null); - return; + } else { + deleteDownloadRequestToken(downloadRequest); } + return result; } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); - return; + return MbmsErrors.ERROR_MIDDLEWARE_LOST; } - deleteDownloadRequestToken(downloadRequest); } /** diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java index f15fde8f0700..a27721261dad 100644 --- a/telephony/java/android/telephony/NetworkScan.java +++ b/telephony/java/android/telephony/NetworkScan.java @@ -19,50 +19,92 @@ package android.telephony; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; +import android.annotation.IntDef; import android.util.Log; import com.android.internal.telephony.ITelephony; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** - * Allows applications to request the system to perform a network scan. - * - * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} will - * receive a NetworkScan which contains the callback method to stop the scan requested. - * @hide + * The caller of + * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} + * will receive an instance of {@link NetworkScan}, which contains a callback method + * {@link #stop()} for stopping the in-progress scan. */ public class NetworkScan { - public static final String TAG = "NetworkScan"; + private static final String TAG = "NetworkScan"; // Below errors are mapped from RadioError which is returned from RIL. We will consolidate // RadioErrors during the mapping if those RadioErrors mean no difference to the users. + + /** + * Defines acceptable values of scan error code. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ERROR_MODEM_ERROR, ERROR_INVALID_SCAN, ERROR_MODEM_UNAVAILABLE, ERROR_UNSUPPORTED, + ERROR_RADIO_INTERFACE_ERROR, ERROR_INVALID_SCANID, ERROR_INTERRUPTED}) + public @interface ScanErrorCode {} + + /** + * The RIL has successfully performed the network scan. + */ public static final int SUCCESS = 0; // RadioError:NONE + + /** + * The scan has failed due to some modem errors. + */ public static final int ERROR_MODEM_ERROR = 1; // RadioError:RADIO_NOT_AVAILABLE // RadioError:NO_MEMORY // RadioError:INTERNAL_ERR // RadioError:MODEM_ERR // RadioError:OPERATION_NOT_ALLOWED + + /** + * The parameters of the scan is invalid. + */ public static final int ERROR_INVALID_SCAN = 2; // RadioError:INVALID_ARGUMENTS - public static final int ERROR_MODEM_BUSY = 3; // RadioError:DEVICE_IN_USE + + /** + * The modem can not perform the scan because it is doing something else. + */ + public static final int ERROR_MODEM_UNAVAILABLE = 3; // RadioError:DEVICE_IN_USE + + /** + * The modem does not support the request scan. + */ public static final int ERROR_UNSUPPORTED = 4; // RadioError:REQUEST_NOT_SUPPORTED + // Below errors are generated at the Telephony. - public static final int ERROR_RIL_ERROR = 10000; // Nothing or only exception is - // returned from RIL. - public static final int ERROR_INVALID_SCANID = 10001; // The scanId is invalid. The user is - // either trying to stop a scan which - // does not exist or started by others. - public static final int ERROR_INTERRUPTED = 10002; // Scan was interrupted by another scan - // with higher priority. + + /** + * The RIL returns nothing or exceptions. + */ + public static final int ERROR_RADIO_INTERFACE_ERROR = 10000; + + /** + * The scan ID is invalid. The user is either trying to stop a scan which does not exist + * or started by others. + */ + public static final int ERROR_INVALID_SCANID = 10001; + + /** + * The scan has been interrupted by another scan with higher priority. + */ + public static final int ERROR_INTERRUPTED = 10002; + private final int mScanId; private final int mSubId; /** * Stops the network scan * - * This is the callback method to stop an ongoing scan. When user requests a new scan, - * a NetworkScan object will be returned, and the user can stop the scan by calling this - * method. + * Use this method to stop an ongoing scan. When user requests a new scan, a {@link NetworkScan} + * object will be returned, and the user can stop the scan by calling this method. */ public void stop() throws RemoteException { try { diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java index 9674c9300602..ea503c3e4bd8 100644 --- a/telephony/java/android/telephony/NetworkScanRequest.java +++ b/telephony/java/android/telephony/NetworkScanRequest.java @@ -16,11 +16,14 @@ package android.telephony; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; import java.util.Arrays; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Defines a request to peform a network scan. @@ -28,7 +31,6 @@ import java.util.Arrays; * This class defines whether the network scan will be performed only once or periodically until * cancelled, when the scan is performed periodically, the time interval is not controlled by the * user but defined by the modem vendor. - * @hide */ public final class NetworkScanRequest implements Parcelable { @@ -54,6 +56,14 @@ public final class NetworkScanRequest implements Parcelable { /** @hide */ public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + SCAN_TYPE_ONE_SHOT, + SCAN_TYPE_PERIODIC, + }) + public @interface ScanType {} + /** Performs the scan only once */ public static final int SCAN_TYPE_ONE_SHOT = 0; /** @@ -65,21 +75,21 @@ public final class NetworkScanRequest implements Parcelable { public static final int SCAN_TYPE_PERIODIC = 1; /** Defines the type of the scan. */ - public int scanType; + private int mScanType; /** * Search periodicity (in seconds). * Expected range for the input is [5s - 300s] - * This value must be less than or equal to maxSearchTime + * This value must be less than or equal to mMaxSearchTime */ - public int searchPeriodicity; + private int mSearchPeriodicity; /** * Maximum duration of the periodic search (in seconds). * Expected range for the input is [60s - 3600s] * If the search lasts this long, it will be terminated. */ - public int maxSearchTime; + private int mMaxSearchTime; /** * Indicates whether the modem should report incremental @@ -87,18 +97,18 @@ public final class NetworkScanRequest implements Parcelable { * FALSE – Incremental results are not reported. * TRUE (default) – Incremental results are reported */ - public boolean incrementalResults; + private boolean mIncrementalResults; /** * Indicates the periodicity with which the modem should * report incremental results to the client (in seconds). * Expected range for the input is [1s - 10s] - * This value must be less than or equal to maxSearchTime + * This value must be less than or equal to mMaxSearchTime */ - public int incrementalResultsPeriodicity; + private int mIncrementalResultsPeriodicity; /** Describes the radio access technologies with bands or channels that need to be scanned. */ - public RadioAccessSpecifier[] specifiers; + private RadioAccessSpecifier[] mSpecifiers; /** * Describes the List of PLMN ids (MCC-MNC) @@ -107,20 +117,24 @@ public final class NetworkScanRequest implements Parcelable { * If list not sent, search to be completed till end and all PLMNs found to be reported. * Max size of array is MAX_MCC_MNC_LIST_SIZE */ - public ArrayList<String> mccMncs; + private ArrayList<String> mMccMncs; /** - * Creates a new NetworkScanRequest with scanType and network specifiers + * Creates a new NetworkScanRequest with mScanType and network mSpecifiers * - * @param scanType The type of the scan + * @param scanType The type of the scan, can be either one shot or periodic * @param specifiers the radio network with bands / channels to be scanned - * @param searchPeriodicity Search periodicity (in seconds) - * @param maxSearchTime Maximum duration of the periodic search (in seconds) + * @param searchPeriodicity The modem will restart the scan every searchPeriodicity seconds if + * no network has been found, until it reaches the maxSearchTime. Only + * valid when scan type is periodic scan. + * @param maxSearchTime Maximum duration of the search (in seconds) * @param incrementalResults Indicates whether the modem should report incremental * results of the network scan to the client * @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should - * report incremental results to the client (in seconds) - * @param mccMncs Describes the List of PLMN ids (MCC-MNC) + * report incremental results to the client (in seconds), + * only valid when incrementalResults is true + * @param mccMncs Describes the list of PLMN ids (MCC-MNC), once any network in the list has + * been found, the scan will be terminated by the modem. */ public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers, int searchPeriodicity, @@ -128,19 +142,63 @@ public final class NetworkScanRequest implements Parcelable { boolean incrementalResults, int incrementalResultsPeriodicity, ArrayList<String> mccMncs) { - this.scanType = scanType; - this.specifiers = specifiers; - this.searchPeriodicity = searchPeriodicity; - this.maxSearchTime = maxSearchTime; - this.incrementalResults = incrementalResults; - this.incrementalResultsPeriodicity = incrementalResultsPeriodicity; - if (mccMncs != null) { - this.mccMncs = mccMncs; + this.mScanType = scanType; + this.mSpecifiers = specifiers.clone(); + this.mSearchPeriodicity = searchPeriodicity; + this.mMaxSearchTime = maxSearchTime; + this.mIncrementalResults = incrementalResults; + this.mIncrementalResultsPeriodicity = incrementalResultsPeriodicity; + if (mMccMncs != null) { + this.mMccMncs = (ArrayList<String>) mccMncs.clone(); } else { - this.mccMncs = new ArrayList<>(); + this.mMccMncs = new ArrayList<>(); } } + /** Returns the type of the scan. */ + @ScanType + public int getScanType() { + return mScanType; + } + + /** Returns the search periodicity in seconds. */ + public int getSearchPeriodicity() { + return mSearchPeriodicity; + } + + /** Returns maximum duration of the periodic search in seconds. */ + public int getMaxSearchTime() { + return mMaxSearchTime; + } + + /** + * Returns whether incremental result is enabled. + * FALSE – Incremental results is not enabled. + * TRUE – Incremental results is reported. + */ + public boolean getIncrementalResults() { + return mIncrementalResults; + } + + /** Returns the periodicity in seconds of incremental results. */ + public int getIncrementalResultsPeriodicity() { + return mIncrementalResultsPeriodicity; + } + + /** Returns the radio access technologies with bands or channels that need to be scanned. */ + public RadioAccessSpecifier[] getSpecifiers() { + return mSpecifiers.clone(); + } + + /** + * Returns the List of PLMN ids (MCC-MNC) for early termination of scan. + * If any PLMN of this list is found, search should end at that point and + * results with all PLMN found till that point should be sent as response. + */ + public ArrayList<String> getPlmns() { + return (ArrayList<String>) mMccMncs.clone(); + } + @Override public int describeContents() { return 0; @@ -148,26 +206,26 @@ public final class NetworkScanRequest implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(scanType); - dest.writeParcelableArray(specifiers, flags); - dest.writeInt(searchPeriodicity); - dest.writeInt(maxSearchTime); - dest.writeBoolean(incrementalResults); - dest.writeInt(incrementalResultsPeriodicity); - dest.writeStringList(mccMncs); + dest.writeInt(mScanType); + dest.writeParcelableArray(mSpecifiers, flags); + dest.writeInt(mSearchPeriodicity); + dest.writeInt(mMaxSearchTime); + dest.writeBoolean(mIncrementalResults); + dest.writeInt(mIncrementalResultsPeriodicity); + dest.writeStringList(mMccMncs); } private NetworkScanRequest(Parcel in) { - scanType = in.readInt(); - specifiers = (RadioAccessSpecifier[]) in.readParcelableArray( + mScanType = in.readInt(); + mSpecifiers = (RadioAccessSpecifier[]) in.readParcelableArray( Object.class.getClassLoader(), RadioAccessSpecifier.class); - searchPeriodicity = in.readInt(); - maxSearchTime = in.readInt(); - incrementalResults = in.readBoolean(); - incrementalResultsPeriodicity = in.readInt(); - mccMncs = new ArrayList<>(); - in.readStringList(mccMncs); + mSearchPeriodicity = in.readInt(); + mMaxSearchTime = in.readInt(); + mIncrementalResults = in.readBoolean(); + mIncrementalResultsPeriodicity = in.readInt(); + mMccMncs = new ArrayList<>(); + in.readStringList(mMccMncs); } @Override @@ -184,25 +242,25 @@ public final class NetworkScanRequest implements Parcelable { return false; } - return (scanType == nsr.scanType - && Arrays.equals(specifiers, nsr.specifiers) - && searchPeriodicity == nsr.searchPeriodicity - && maxSearchTime == nsr.maxSearchTime - && incrementalResults == nsr.incrementalResults - && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity - && (((mccMncs != null) - && mccMncs.equals(nsr.mccMncs)))); + return (mScanType == nsr.mScanType + && Arrays.equals(mSpecifiers, nsr.mSpecifiers) + && mSearchPeriodicity == nsr.mSearchPeriodicity + && mMaxSearchTime == nsr.mMaxSearchTime + && mIncrementalResults == nsr.mIncrementalResults + && mIncrementalResultsPeriodicity == nsr.mIncrementalResultsPeriodicity + && (((mMccMncs != null) + && mMccMncs.equals(nsr.mMccMncs)))); } @Override public int hashCode () { - return ((scanType * 31) - + (Arrays.hashCode(specifiers)) * 37 - + (searchPeriodicity * 41) - + (maxSearchTime * 43) - + ((incrementalResults == true? 1 : 0) * 47) - + (incrementalResultsPeriodicity * 53) - + (mccMncs.hashCode() * 59)); + return ((mScanType * 31) + + (Arrays.hashCode(mSpecifiers)) * 37 + + (mSearchPeriodicity * 41) + + (mMaxSearchTime * 43) + + ((mIncrementalResults == true? 1 : 0) * 47) + + (mIncrementalResultsPeriodicity * 53) + + (mMccMncs.hashCode() * 59)); } public static final Creator<NetworkScanRequest> CREATOR = diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java index 33ce8b4212bc..5412c6172ba3 100644 --- a/telephony/java/android/telephony/RadioAccessSpecifier.java +++ b/telephony/java/android/telephony/RadioAccessSpecifier.java @@ -25,34 +25,40 @@ import java.util.Arrays; * Describes a particular radio access network to be scanned. * * The scan can be performed on either bands or channels for a specific radio access network type. - * @hide */ public final class RadioAccessSpecifier implements Parcelable { /** * The radio access network that needs to be scanned * + * This parameter must be provided or else the scan will be rejected. + * * See {@link RadioNetworkConstants.RadioAccessNetworks} for details. */ - public int radioAccessNetwork; + private int mRadioAccessNetwork; /** * The frequency bands that need to be scanned * - * bands must be used together with radioAccessNetwork + * When no specific bands are specified (empty array or null), all the frequency bands + * supported by the modem will be scanned. * * See {@link RadioNetworkConstants} for details. */ - public int[] bands; + private int[] mBands; /** * The frequency channels that need to be scanned * - * channels must be used together with radioAccessNetwork + * When any specific channels are provided for scan, the corresponding frequency bands that + * contains those channels must also be provided, or else the channels will be ignored. * - * See {@link RadioNetworkConstants.RadioAccessNetworks} for details. + * When no specific channels are specified (empty array or null), all the frequency channels + * supported by the modem will be scanned. + * + * See {@link RadioNetworkConstants} for details. */ - public int[] channels; + private int[] mChannels; /** * Creates a new RadioAccessSpecifier with radio network, bands and channels @@ -65,9 +71,34 @@ public final class RadioAccessSpecifier implements Parcelable { * @param channels the frequency bands to be scanned */ public RadioAccessSpecifier(int ran, int[] bands, int[] channels) { - this.radioAccessNetwork = ran; - this.bands = bands; - this.channels = channels; + this.mRadioAccessNetwork = ran; + this.mBands = bands.clone(); + this.mChannels = channels.clone(); + } + + /** + * Returns the radio access network that needs to be scanned. + * + * The returned value is define in {@link RadioNetworkConstants.RadioAccessNetworks}; + */ + public int getRadioAccessNetwork() { + return mRadioAccessNetwork; + } + + /** + * Returns the frequency bands that need to be scanned. + * + * The returned value is defined in either of {@link RadioNetworkConstants.GeranBands}, + * {@link RadioNetworkConstants.UtranBands} and {@link RadioNetworkConstants.EutranBands}, and + * it depends on the returned value of {@link #getRadioAccessNetwork()}. + */ + public int[] getBands() { + return mBands.clone(); + } + + /** Returns the frequency channels that need to be scanned. */ + public int[] getChannels() { + return mChannels.clone(); } public static final Parcelable.Creator<RadioAccessSpecifier> CREATOR = @@ -90,15 +121,15 @@ public final class RadioAccessSpecifier implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(radioAccessNetwork); - dest.writeIntArray(bands); - dest.writeIntArray(channels); + dest.writeInt(mRadioAccessNetwork); + dest.writeIntArray(mBands); + dest.writeIntArray(mChannels); } private RadioAccessSpecifier(Parcel in) { - radioAccessNetwork = in.readInt(); - bands = in.createIntArray(); - channels = in.createIntArray(); + mRadioAccessNetwork = in.readInt(); + mBands = in.createIntArray(); + mChannels = in.createIntArray(); } @Override @@ -115,15 +146,15 @@ public final class RadioAccessSpecifier implements Parcelable { return false; } - return (radioAccessNetwork == ras.radioAccessNetwork - && Arrays.equals(bands, ras.bands) - && Arrays.equals(channels, ras.channels)); + return (mRadioAccessNetwork == ras.mRadioAccessNetwork + && Arrays.equals(mBands, ras.mBands) + && Arrays.equals(mChannels, ras.mChannels)); } @Override public int hashCode () { - return ((radioAccessNetwork * 31) - + (Arrays.hashCode(bands) * 37) - + (Arrays.hashCode(channels)) * 39); + return ((mRadioAccessNetwork * 31) + + (Arrays.hashCode(mBands) * 37) + + (Arrays.hashCode(mChannels)) * 39); } } diff --git a/telephony/java/android/telephony/RadioNetworkConstants.java b/telephony/java/android/telephony/RadioNetworkConstants.java index 1a9072d3eb0a..5f5dd82eed61 100644 --- a/telephony/java/android/telephony/RadioNetworkConstants.java +++ b/telephony/java/android/telephony/RadioNetworkConstants.java @@ -18,7 +18,6 @@ package android.telephony; /** * Contains radio access network related constants. - * @hide */ public final class RadioNetworkConstants { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 9e77992d5d5e..af5b19082121 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3155,6 +3155,7 @@ public class TelephonyManager { * Initial SIM activation state, unknown. Not set by any carrier apps. * @hide */ + @SystemApi public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; /** @@ -3165,12 +3166,14 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_RESTRICTED * @hide */ + @SystemApi public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1; /** * Indicate SIM has been successfully activated with full service * @hide */ + @SystemApi public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; /** @@ -3180,6 +3183,7 @@ public class TelephonyManager { * deactivated sim state and set it back to activated after successfully run activation service. * @hide */ + @SystemApi public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; /** @@ -3187,14 +3191,47 @@ public class TelephonyManager { * note this is currently available for data activation state. For example out of byte sim. * @hide */ + @SystemApi public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; + /** @hide */ + @IntDef({ + SIM_ACTIVATION_STATE_UNKNOWN, + SIM_ACTIVATION_STATE_ACTIVATING, + SIM_ACTIVATION_STATE_ACTIVATED, + SIM_ACTIVATION_STATE_DEACTIVATED, + SIM_ACTIVATION_STATE_RESTRICTED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SimActivationState{} + + /** + * Sets the voice activation state + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. + * + * @param activationState The voice activation state + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #hasCarrierPrivileges + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setVoiceActivationState(@SimActivationState int activationState) { + setVoiceActivationState(getSubId(), activationState); + } + /** * Sets the voice activation state for the given subscriber. * * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * Or the calling app has carrier privileges. * * @param subId The subscription id. * @param activationState The voice activation state of the given subscriber. @@ -3202,24 +3239,48 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATING * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #hasCarrierPrivileges * @hide */ - public void setVoiceActivationState(int subId, int activationState) { + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setVoiceActivationState(int subId, @SimActivationState int activationState) { try { - ITelephony telephony = getITelephony(); - if (telephony != null) - telephony.setVoiceActivationState(subId, activationState); - } catch (RemoteException ex) { - } catch (NullPointerException ex) { - } + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setVoiceActivationState(subId, activationState); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * Sets the data activation state + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. + * + * @param activationState The data activation state + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #SIM_ACTIVATION_STATE_RESTRICTED + * @see #hasCarrierPrivileges + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setDataActivationState(@SimActivationState int activationState) { + setDataActivationState(getSubId(), activationState); } /** * Sets the data activation state for the given subscriber. * * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE} - * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. * * @param subId The subscription id. * @param activationState The data activation state of the given subscriber. @@ -3228,9 +3289,11 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED * @see #SIM_ACTIVATION_STATE_RESTRICTED + * @see #hasCarrierPrivileges * @hide */ - public void setDataActivationState(int subId, int activationState) { + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setDataActivationState(int subId, @SimActivationState int activationState) { try { ITelephony telephony = getITelephony(); if (telephony != null) @@ -3241,8 +3304,33 @@ public class TelephonyManager { } /** + * Returns the voice activation state + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * Or the calling app has carrier privileges. + * + * @return voiceActivationState + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #hasCarrierPrivileges + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @SimActivationState int getVoiceActivationState() { + return getVoiceActivationState(getSubId()); + } + + /** * Returns the voice activation state for the given subscriber. * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * Or the calling app has carrier privileges. + * * @param subId The subscription id. * * @return voiceActivationState for the given subscriber @@ -3250,10 +3338,11 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATING * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #hasCarrierPrivileges * @hide */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public int getVoiceActivationState(int subId) { + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @SimActivationState int getVoiceActivationState(int subId) { try { ITelephony telephony = getITelephony(); if (telephony != null) @@ -3265,8 +3354,34 @@ public class TelephonyManager { } /** + * Returns the data activation state + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * Or the calling app has carrier privileges. + * + * @return dataActivationState for the given subscriber + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #SIM_ACTIVATION_STATE_RESTRICTED + * @see #hasCarrierPrivileges + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @SimActivationState int getDataActivationState() { + return getDataActivationState(getSubId()); + } + + /** * Returns the data activation state for the given subscriber. * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * Or the calling app has carrier privileges. + * * @param subId The subscription id. * * @return dataActivationState for the given subscriber @@ -3275,10 +3390,11 @@ public class TelephonyManager { * @see #SIM_ACTIVATION_STATE_ACTIVATED * @see #SIM_ACTIVATION_STATE_DEACTIVATED * @see #SIM_ACTIVATION_STATE_RESTRICTED + * @see #hasCarrierPrivileges * @hide */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public int getDataActivationState(int subId) { + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @SimActivationState int getDataActivationState(int subId) { try { ITelephony telephony = getITelephony(); if (telephony != null) @@ -4849,15 +4965,14 @@ public class TelephonyManager { * Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} * Or the calling app has carrier privileges. @see #hasCarrierPrivileges - * - * @hide - * TODO: Add an overload that takes no args. */ - public void setNetworkSelectionModeAutomatic(int subId) { + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setNetworkSelectionModeAutomatic() { try { ITelephony telephony = getITelephony(); - if (telephony != null) - telephony.setNetworkSelectionModeAutomatic(subId); + if (telephony != null) { + telephony.setNetworkSelectionModeAutomatic(getSubId()); + } } catch (RemoteException ex) { Rlog.e(TAG, "setNetworkSelectionModeAutomatic RemoteException", ex); } catch (NullPointerException ex) { @@ -4905,9 +5020,9 @@ public class TelephonyManager { * * @param request Contains all the RAT with bands/channels that need to be scanned. * @param callback Returns network scan results or errors. - * @return A NetworkScan obj which contains a callback which can stop the scan. - * @hide + * @return A NetworkScan obj which contains a callback which can be used to stop the scan. */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public NetworkScan requestNetworkScan( NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) { synchronized (this) { @@ -4926,15 +5041,20 @@ public class TelephonyManager { * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} * Or the calling app has carrier privileges. @see #hasCarrierPrivileges * - * @hide - * TODO: Add an overload that takes no args. + * @param operatorNumeric the PLMN ID of the network to select. + * @param persistSelection whether the selection will persist until reboot. If true, only allows + * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume + * normal network selection next time. + * @return true on success; false on any failure. */ - public boolean setNetworkSelectionModeManual(int subId, OperatorInfo operator, - boolean persistSelection) { + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean setNetworkSelectionModeManual(String operatorNumeric, boolean persistSelection) { try { ITelephony telephony = getITelephony(); - if (telephony != null) - return telephony.setNetworkSelectionModeManual(subId, operator, persistSelection); + if (telephony != null) { + return telephony.setNetworkSelectionModeManual( + getSubId(), operatorNumeric, persistSelection); + } } catch (RemoteException ex) { Rlog.e(TAG, "setNetworkSelectionModeManual RemoteException", ex); } catch (NullPointerException ex) { @@ -4959,8 +5079,9 @@ public class TelephonyManager { public boolean setPreferredNetworkType(int subId, int networkType) { try { ITelephony telephony = getITelephony(); - if (telephony != null) + if (telephony != null) { return telephony.setPreferredNetworkType(subId, networkType); + } } catch (RemoteException ex) { Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex); } catch (NullPointerException ex) { diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index 7bcdcdcc30ba..c182e34943fb 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -38,7 +38,6 @@ import com.android.internal.telephony.ITelephony; /** * Manages the radio access network scan requests and callbacks. - * @hide */ public final class TelephonyScanManager { @@ -55,7 +54,8 @@ public final class TelephonyScanManager { public static final int CALLBACK_SCAN_COMPLETE = 3; /** - * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should + * The caller of + * {@link TelephonyManager#requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should * implement and provide this callback so that the scan results or errors can be returned. */ public static abstract class NetworkScanCallback { @@ -75,8 +75,10 @@ public final class TelephonyScanManager { * * This callback will be called whenever there is any error about the scan, and the scan * will be terminated. onComplete() will NOT be called. + * + * @param error Error code when the scan is failed, as defined in {@link NetworkScan}. */ - public void onError(int error) {} + public void onError(@NetworkScan.ScanErrorCode int error) {} } private static class NetworkScanInfo { diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 973df316d280..176057ddc23e 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -15,8 +15,10 @@ */ package android.telephony.euicc; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; @@ -29,6 +31,9 @@ import android.os.ServiceManager; import com.android.internal.telephony.euicc.IEuiccController; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs. * @@ -167,6 +172,35 @@ public class EuiccManager { */ public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon"; + /** + * Euicc OTA update status which can be got by {@link #getOtaStatus} + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"EUICC_OTA_"}, value = { + EUICC_OTA_IN_PROGRESS, + EUICC_OTA_FAILED, + EUICC_OTA_SUCCEEDED, + EUICC_OTA_NOT_NEEDED, + EUICC_OTA_STATUS_UNAVAILABLE + + }) + public @interface OtaStatus{} + + /** + * An OTA is in progress. During this time, the eUICC is not available and the user may lose + * network access. + */ + public static final int EUICC_OTA_IN_PROGRESS = 1; + /** The OTA update failed. */ + public static final int EUICC_OTA_FAILED = 2; + /** The OTA update finished successfully. */ + public static final int EUICC_OTA_SUCCEEDED = 3; + /** The OTA update not needed since current eUICC OS is latest. */ + public static final int EUICC_OTA_NOT_NEEDED = 4; + /** The OTA status is unavailable since eUICC service is unavailable. */ + public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5; + private final Context mContext; /** @hide */ @@ -211,6 +245,26 @@ public class EuiccManager { } /** + * Returns the current status of eUICC OTA. + * + * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. + * + * @return the status of eUICC OTA. If {@link #isEnabled()} is false or the eUICC is not ready, + * {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned. + */ + @SystemApi + public int getOtaStatus() { + if (!isEnabled()) { + return EUICC_OTA_STATUS_UNAVAILABLE; + } + try { + return getIEuiccController().getOtaStatus(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Attempt to download the given {@link DownloadableSubscription}. * * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java index 8529f525e06f..f78e7a6e54c4 100644 --- a/telephony/java/android/telephony/mbms/ServiceInfo.java +++ b/telephony/java/android/telephony/mbms/ServiceInfo.java @@ -51,8 +51,8 @@ public class ServiceInfo { /** @hide */ public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales, String newServiceId, Date start, Date end) { - if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName) - || newLocales == null || newLocales.isEmpty() || TextUtils.isEmpty(newServiceId) + if (newNames == null || newClassName == null + || newLocales == null || newServiceId == null || start == null || end == null) { throw new IllegalArgumentException("Bad ServiceInfo construction"); } diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java index 6ad54c1f7c0f..4f6f68c34e3a 100644 --- a/telephony/java/com/android/ims/ImsReasonInfo.java +++ b/telephony/java/com/android/ims/ImsReasonInfo.java @@ -104,6 +104,9 @@ public class ImsReasonInfo implements Parcelable { // MT : No action from user after alerting the call public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203; + //Call was blocked by call barring + public static final int CODE_CALL_BARRED = 240; + //Call failures for FDN public static final int CODE_FDN_BLOCKED = 241; diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 8ea53a594cc2..416146fcb98e 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -846,13 +846,13 @@ interface ITelephony { * Ask the radio to connect to the input network and change selection mode to manual. * * @param subId the id of the subscription. - * @param operatorInfo the operator to attach to. - * @param persistSelection should the selection persist till reboot or its - * turned off? Will also result in notification being not shown to - * the user if the signal is lost. + * @param operatorNumeric the PLMN of the operator to attach to. + * @param persistSelection Whether the selection will persist until reboot. If true, only allows + * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume + * normal network selection next time. * @return true if the request suceeded. */ - boolean setNetworkSelectionModeManual(int subId, in OperatorInfo operator, + boolean setNetworkSelectionModeManual(int subId, in String operatorNumeric, boolean persistSelection); /** diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index e2d25b8e352c..f804cb068b31 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -417,6 +417,8 @@ cat include/telephony/ril.h | \ int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141; int RIL_REQUEST_START_NETWORK_SCAN = 142; int RIL_REQUEST_STOP_NETWORK_SCAN = 143; + int RIL_REQUEST_GET_SLOT_STATUS = 144; + int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145; int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -471,4 +473,5 @@ cat include/telephony/ril.h | \ int RIL_UNSOL_MODEM_RESTART = 1047; int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048; int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049; + int RIL_UNSOL_ICC_SLOT_STATUS = 1050; } diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index b3fc90db75d3..0a0ad90b5954 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -30,6 +30,7 @@ interface IEuiccController { oneway void getDefaultDownloadableSubscriptionList( String callingPackage, in PendingIntent callbackIntent); String getEid(); + int getOtaStatus(); oneway void downloadSubscription(in DownloadableSubscription subscription, boolean switchAfterDownload, String callingPackage, in PendingIntent callbackIntent); EuiccInfo getEuiccInfo(); diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk new file mode 100644 index 000000000000..7187a3795433 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/Android.mk @@ -0,0 +1,49 @@ +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +# Build a tiny library that the test app can dynamically load + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerTestLibrary +LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl) + +include $(BUILD_JAVA_LIBRARY) + +dexloggertest_jar := $(LOCAL_BUILT_MODULE) + + +# Build the test app itself + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := platform +LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + truth-prebuilt \ + +# This gets us the javalib.jar built by DexLoggerTestLibrary above. +LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar) + +include $(BUILD_PACKAGE) diff --git a/tests/DexLoggerIntegrationTests/AndroidManifest.xml b/tests/DexLoggerIntegrationTests/AndroidManifest.xml new file mode 100644 index 000000000000..a847e8f3b921 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.dexloggertest"> + + <!-- Tests feature introduced in P (27) --> + <uses-sdk + android:minSdkVersion="27" + android:targetSdkVersion="27" /> + + <uses-permission android:name="android.permission.READ_LOGS" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.dexloggertest" + android:label="Integration test for DexLogger" /> +</manifest> diff --git a/tests/DexLoggerIntegrationTests/AndroidTest.xml b/tests/DexLoggerIntegrationTests/AndroidTest.xml new file mode 100644 index 000000000000..8ed19f893476 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs DexLogger Integration Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="DexLoggerIntegrationTests.apk"/> + <option name="cleanup-apks" value="true"/> + </target_preparer> + + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="DexLoggerIntegrationTests"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.frameworks.dexloggertest"/> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java new file mode 100644 index 000000000000..e995a26ea5c9 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dcl; + +/** Dummy class which is built into a jar purely so we can pass it to DexClassLoader. */ +public final class Simple { + public Simple() {} +} diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java new file mode 100644 index 000000000000..d9f34d589c41 --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.util.EventLog; + +import dalvik.system.DexClassLoader; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.List; + +/** + * Integration tests for {@link com.android.server.pm.dex.DexLogger}. + * + * The setup for the test dynamically loads code in a jar extracted + * from our assets (a secondary dex file). + * + * We then use adb to trigger secondary dex file reconcilation (and + * wait for it to complete). As a side-effect of this DexLogger should + * be notified of the file and should log the hash of the file's name + * and content. We verify that this message appears in the event log. + * + * Run with "atest DexLoggerIntegrationTests". + */ +@RunWith(JUnit4.class) +public final class DexLoggerIntegrationTests { + + private static final String TAG = DexLoggerIntegrationTests.class.getSimpleName(); + + private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest"; + + private static final int SNET_TAG = 0x534e4554; + private static final String DCL_SUBTAG = "dcl"; + + // Obtained via "echo -n copied.jar | sha256sum" + private static final String EXPECTED_NAME_HASH = + "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; + + private static String expectedContentHash; + + @BeforeClass + public static void setUpAll() throws Exception { + Context context = InstrumentationRegistry.getTargetContext(); + MessageDigest hasher = MessageDigest.getInstance("SHA-256"); + + // Copy the jar from our Java resources to a private data directory + File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar"); + try (InputStream input = DexLoggerIntegrationTests.class.getResourceAsStream("/javalib.jar"); + OutputStream output = new FileOutputStream(privateCopy)) { + byte[] buffer = new byte[1024]; + while (true) { + int numRead = input.read(buffer); + if (numRead < 0) { + break; + } + output.write(buffer, 0, numRead); + hasher.update(buffer, 0, numRead); + } + } + + // Remember the SHA-256 of the file content to check that it is the same as + // the value we see logged. + Formatter formatter = new Formatter(); + for (byte b : hasher.digest()) { + formatter.format("%02X", b); + } + expectedContentHash = formatter.toString(); + + // Feed the jar to a class loader and make sure it contains what we expect. + ClassLoader loader = + new DexClassLoader( + privateCopy.toString(), null, null, context.getClass().getClassLoader()); + loader.loadClass("com.android.dcl.Simple"); + } + + @Test + public void testDexLoggerReconcileGeneratesEvents() throws Exception { + int[] tagList = new int[] { SNET_TAG }; + List<EventLog.Event> events = new ArrayList<>(); + + // There may already be events in the event log - figure out the most recent one + EventLog.readEvents(tagList, events); + long previousEventNanos = events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); + events.clear(); + + Process process = Runtime.getRuntime().exec( + "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME); + int exitCode = process.waitFor(); + assertThat(exitCode).isEqualTo(0); + + int myUid = android.os.Process.myUid(); + String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash; + + EventLog.readEvents(tagList, events); + boolean found = false; + for (EventLog.Event event : events) { + if (event.getTimeNanos() <= previousEventNanos) { + continue; + } + Object[] data = (Object[]) event.getData(); + + // We only care about DCL events that we generated. + String subTag = (String) data[0]; + if (!DCL_SUBTAG.equals(subTag)) { + continue; + } + int uid = (int) data[1]; + if (uid != myUid) { + continue; + } + + String message = (String) data[2]; + assertThat(message).isEqualTo(expectedMessage); + found = true; + } + + assertThat(found).isTrue(); + } +} diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk new file mode 100644 index 000000000000..12568a09e71e --- /dev/null +++ b/tests/JankBench/Android.mk @@ -0,0 +1,38 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_MANIFEST_FILE := app/src/main/AndroidManifest.xml + +LOCAL_SDK_VERSION := current + +LOCAL_USE_AAPT2 := true + +# omit gradle 'build' dir +LOCAL_SRC_FILES := $(call all-java-files-under,app/src/main/java) + +# use appcompat/support lib from the tree, so improvements/ +# regressions are reflected in test data +LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/app/src/main/res \ + + +LOCAL_STATIC_ANDROID_LIBRARIES := \ + android-support-design \ + android-support-v4 \ + android-support-v7-appcompat \ + android-support-v7-cardview \ + android-support-v7-recyclerview \ + android-support-v17-leanback \ + +LOCAL_STATIC_JAVA_LIBRARIES := \ + apache-commons-math \ + junit + + +LOCAL_PACKAGE_NAME := JankBench + +LOCAL_COMPATIBILITY_SUITE := device-tests + +include $(BUILD_PACKAGE) diff --git a/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java new file mode 100644 index 000000000000..79aff900fd0e --- /dev/null +++ b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> + */ +public class ApplicationTest extends ApplicationTestCase<Application> { + public ApplicationTest() { + super(Application.class); + } +} diff --git a/tests/JankBench/app/src/main/AndroidManifest.xml b/tests/JankBench/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..58aa66fcd05d --- /dev/null +++ b/tests/JankBench/app/src/main/AndroidManifest.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.benchmark"> + + <uses-sdk android:minSdkVersion="24" /> + + <android:uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <android:uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <activity + android:name=".app.HomeActivity" + android:label="@string/app_name" + android:theme="@style/AppTheme.NoActionBar"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity + android:name=".app.RunLocalBenchmarksActivity" + android:exported="true"> + <intent-filter> + <action android:name="com.android.benchmark.ACTION_BENCHMARK" /> + </intent-filter> + + <meta-data + android:name="com.android.benchmark.benchmark_group" + android:resource="@xml/benchmark" /> + </activity> + <activity android:name=".ui.ListViewScrollActivity" /> + <activity android:name=".ui.ImageListViewScrollActivity" /> + <activity android:name=".ui.ShadowGridActivity" /> + <activity android:name=".ui.TextScrollActivity" /> + <activity android:name=".ui.EditTextInputActivity" /> + <activity android:name=".synthetic.MemoryActivity" /> + <activity android:name=".ui.FullScreenOverdrawActivity"></activity> + <activity android:name=".ui.BitmapUploadActivity"></activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java new file mode 100644 index 000000000000..b0a97ae0995b --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.app; + +import android.support.v4.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.benchmark.R; + +/** + * Fragment for the Benchmark dashboard + */ +public class BenchmarkDashboardFragment extends Fragment { + + public BenchmarkDashboardFragment() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_dashboard, container, false); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java new file mode 100644 index 000000000000..7419b30814f6 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.app; + +import android.graphics.Typeface; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.CheckBox; +import android.widget.TextView; + +import com.android.benchmark.registry.BenchmarkGroup; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.R; + +/** + * + */ +public class BenchmarkListAdapter extends BaseExpandableListAdapter { + + private final LayoutInflater mInflater; + private final BenchmarkRegistry mRegistry; + + BenchmarkListAdapter(LayoutInflater inflater, + BenchmarkRegistry registry) { + mInflater = inflater; + mRegistry = registry; + } + + @Override + public int getGroupCount() { + return mRegistry.getGroupCount(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return mRegistry.getBenchmarkCount(groupPosition); + } + + @Override + public Object getGroup(int groupPosition) { + return mRegistry.getBenchmarkGroup(groupPosition); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + BenchmarkGroup benchmarkGroup = mRegistry.getBenchmarkGroup(groupPosition); + + if (benchmarkGroup != null) { + return benchmarkGroup.getBenchmarks()[childPosition]; + } + + return null; + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + BenchmarkGroup group = (BenchmarkGroup) getGroup(groupPosition); + if (convertView == null) { + convertView = mInflater.inflate(R.layout.benchmark_list_group_row, null); + } + + TextView title = (TextView) convertView.findViewById(R.id.group_name); + title.setTypeface(null, Typeface.BOLD); + title.setText(group.getTitle()); + return convertView; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + BenchmarkGroup.Benchmark benchmark = + (BenchmarkGroup.Benchmark) getChild(groupPosition, childPosition); + if (convertView == null) { + convertView = mInflater.inflate(R.layout.benchmark_list_item, null); + } + + TextView name = (TextView) convertView.findViewById(R.id.benchmark_name); + name.setText(benchmark.getName()); + CheckBox enabledBox = (CheckBox) convertView.findViewById(R.id.benchmark_enable_checkbox); + enabledBox.setOnClickListener(benchmark); + enabledBox.setChecked(benchmark.isEnabled()); + + return convertView; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public int getChildrenHeight() { + // TODO + return 1024; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java new file mode 100644 index 000000000000..79bafd62e2a2 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.app; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Menu; +import android.view.MenuItem; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ExpandableListView; +import android.widget.Toast; + +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.R; +import com.android.benchmark.results.GlobalResultsStore; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.Queue; + +public class HomeActivity extends AppCompatActivity implements Button.OnClickListener { + + private FloatingActionButton mStartButton; + private BenchmarkRegistry mRegistry; + private Queue<Intent> mRunnableBenchmarks; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_home); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + mStartButton = (FloatingActionButton) findViewById(R.id.start_button); + mStartButton.setActivated(true); + mStartButton.setOnClickListener(this); + + mRegistry = new BenchmarkRegistry(this); + + mRunnableBenchmarks = new LinkedList<>(); + + ExpandableListView listView = (ExpandableListView) findViewById(R.id.test_list); + BenchmarkListAdapter adapter = + new BenchmarkListAdapter(LayoutInflater.from(this), mRegistry); + listView.setAdapter(adapter); + + adapter.notifyDataSetChanged(); + ViewGroup.LayoutParams layoutParams = listView.getLayoutParams(); + layoutParams.height = 2048; + listView.setLayoutParams(layoutParams); + listView.requestLayout(); + System.out.println(System.getProperties().stringPropertyNames()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... voids) { + try { + HomeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(HomeActivity.this, "Exporting...", Toast.LENGTH_LONG).show(); + } + }); + GlobalResultsStore.getInstance(HomeActivity.this).exportToCsv(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + HomeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(HomeActivity.this, "Done", Toast.LENGTH_LONG).show(); + } + }); + } + }.execute(); + + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onClick(View v) { + final int groupCount = mRegistry.getGroupCount(); + for (int i = 0; i < groupCount; i++) { + + Intent intent = mRegistry.getBenchmarkGroup(i).getIntent(); + if (intent != null) { + mRunnableBenchmarks.add(intent); + } + } + + handleNextBenchmark(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + + } + + private void handleNextBenchmark() { + Intent nextIntent = mRunnableBenchmarks.peek(); + startActivityForResult(nextIntent, 0); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java new file mode 100644 index 000000000000..1c82d6db3483 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.app; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.*; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.View; + +import com.android.benchmark.R; + + +/** + * TODO: document your custom view class. + */ +public class PerfTimeline extends View { + private String mExampleString; // TODO: use a default from R.string... + private int mExampleColor = Color.RED; // TODO: use a default from R.color... + private float mExampleDimension = 300; // TODO: use a default from R.dimen... + + private TextPaint mTextPaint; + private float mTextWidth; + private float mTextHeight; + + private Paint mPaintBaseLow; + private Paint mPaintBaseHigh; + private Paint mPaintValue; + + + public float[] mLinesLow; + public float[] mLinesHigh; + public float[] mLinesValue; + + public PerfTimeline(Context context) { + super(context); + init(null, 0); + } + + public PerfTimeline(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + public PerfTimeline(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(attrs, defStyle); + } + + private void init(AttributeSet attrs, int defStyle) { + // Load attributes + final TypedArray a = getContext().obtainStyledAttributes( + attrs, R.styleable.PerfTimeline, defStyle, 0); + + mExampleString = "xx";//a.getString(R.styleable.PerfTimeline_exampleString, "xx"); + mExampleColor = a.getColor(R.styleable.PerfTimeline_exampleColor, mExampleColor); + // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with + // values that should fall on pixel boundaries. + mExampleDimension = a.getDimension( + R.styleable.PerfTimeline_exampleDimension, + mExampleDimension); + + a.recycle(); + + // Set up a default TextPaint object + mTextPaint = new TextPaint(); + mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setTextAlign(Paint.Align.LEFT); + + // Update TextPaint and text measurements from attributes + invalidateTextPaintAndMeasurements(); + + mPaintBaseLow = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintBaseLow.setStyle(Paint.Style.FILL); + mPaintBaseLow.setColor(0xff000000); + + mPaintBaseHigh = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintBaseHigh.setStyle(Paint.Style.FILL); + mPaintBaseHigh.setColor(0x7f7f7f7f); + + mPaintValue = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintValue.setStyle(Paint.Style.FILL); + mPaintValue.setColor(0x7fff0000); + + } + + private void invalidateTextPaintAndMeasurements() { + mTextPaint.setTextSize(mExampleDimension); + mTextPaint.setColor(mExampleColor); + mTextWidth = mTextPaint.measureText(mExampleString); + + Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); + mTextHeight = fontMetrics.bottom; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // TODO: consider storing these as member variables to reduce + // allocations per draw cycle. + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + int paddingRight = getPaddingRight(); + int paddingBottom = getPaddingBottom(); + + int contentWidth = getWidth() - paddingLeft - paddingRight; + int contentHeight = getHeight() - paddingTop - paddingBottom; + + // Draw the text. + //canvas.drawText(mExampleString, + // paddingLeft + (contentWidth - mTextWidth) / 2, + // paddingTop + (contentHeight + mTextHeight) / 2, + // mTextPaint); + + + + + // Draw the shadow + //RectF rf = new RectF(10.f, 10.f, 100.f, 100.f); + //canvas.drawOval(rf, mShadowPaint); + + if (mLinesLow != null) { + canvas.drawLines(mLinesLow, mPaintBaseLow); + } + if (mLinesHigh != null) { + canvas.drawLines(mLinesHigh, mPaintBaseHigh); + } + if (mLinesValue != null) { + canvas.drawLines(mLinesValue, mPaintValue); + } + + +/* + // Draw the pie slices + for (int i = 0; i < mData.size(); ++i) { + Item it = mData.get(i); + mPiePaint.setShader(it.mShader); + canvas.drawArc(mBounds, + 360 - it.mEndAngle, + it.mEndAngle - it.mStartAngle, + true, mPiePaint); + } +*/ + // Draw the pointer + //canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint); + //canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint); + } + + /** + * Gets the example string attribute value. + * + * @return The example string attribute value. + */ + public String getExampleString() { + return mExampleString; + } + + /** + * Sets the view's example string attribute value. In the example view, this string + * is the text to draw. + * + * @param exampleString The example string attribute value to use. + */ + public void setExampleString(String exampleString) { + mExampleString = exampleString; + invalidateTextPaintAndMeasurements(); + } + + /** + * Gets the example color attribute value. + * + * @return The example color attribute value. + */ + public int getExampleColor() { + return mExampleColor; + } + + /** + * Sets the view's example color attribute value. In the example view, this color + * is the font color. + * + * @param exampleColor The example color attribute value to use. + */ + public void setExampleColor(int exampleColor) { + mExampleColor = exampleColor; + invalidateTextPaintAndMeasurements(); + } + + /** + * Gets the example dimension attribute value. + * + * @return The example dimension attribute value. + */ + public float getExampleDimension() { + return mExampleDimension; + } + + /** + * Sets the view's example dimension attribute value. In the example view, this dimension + * is the font size. + * + * @param exampleDimension The example dimension attribute value to use. + */ + public void setExampleDimension(float exampleDimension) { + mExampleDimension = exampleDimension; + invalidateTextPaintAndMeasurements(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java new file mode 100644 index 000000000000..7641d0095a70 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.app; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.ListFragment; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkGroup; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.results.GlobalResultsStore; +import com.android.benchmark.results.UiBenchmarkResult; +import com.android.benchmark.synthetic.MemoryActivity; +import com.android.benchmark.ui.BitmapUploadActivity; +import com.android.benchmark.ui.EditTextInputActivity; +import com.android.benchmark.ui.FullScreenOverdrawActivity; +import com.android.benchmark.ui.ImageListViewScrollActivity; +import com.android.benchmark.ui.ListViewScrollActivity; +import com.android.benchmark.ui.ShadowGridActivity; +import com.android.benchmark.ui.TextScrollActivity; + +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.util.ArrayList; +import java.util.HashMap; + +public class RunLocalBenchmarksActivity extends AppCompatActivity { + + public static final int RUN_COUNT = 5; + + private ArrayList<LocalBenchmark> mBenchmarksToRun; + private int mBenchmarkCursor; + private int mCurrentRunId; + private boolean mFinish; + + private Handler mHandler = new Handler(); + + private static final int[] ALL_TESTS = new int[] { + R.id.benchmark_list_view_scroll, + R.id.benchmark_image_list_view_scroll, + R.id.benchmark_shadow_grid, + R.id.benchmark_text_high_hitrate, + R.id.benchmark_text_low_hitrate, + R.id.benchmark_edit_text_input, + R.id.benchmark_overdraw, + }; + + public static class LocalBenchmarksList extends ListFragment { + private ArrayList<LocalBenchmark> mBenchmarks; + private int mRunId; + + public void setBenchmarks(ArrayList<LocalBenchmark> benchmarks) { + mBenchmarks = benchmarks; + } + + public void setRunId(int id) { + mRunId = id; + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + if (getActivity().findViewById(R.id.list_fragment_container) != null) { + FragmentManager fm = getActivity().getSupportFragmentManager(); + UiResultsFragment resultsView = new UiResultsFragment(); + String testName = BenchmarkRegistry.getBenchmarkName(v.getContext(), + mBenchmarks.get(position).id); + resultsView.setRunInfo(testName, mRunId); + FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.replace(R.id.list_fragment_container, resultsView); + fragmentTransaction.addToBackStack(null); + fragmentTransaction.commit(); + } + } + } + + + private class LocalBenchmark { + int id; + int runCount = 0; + int totalCount = 0; + ArrayList<String> mResultsUri = new ArrayList<>(); + + LocalBenchmark(int id, int runCount) { + this.id = id; + this.runCount = 0; + this.totalCount = runCount; + } + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_running_list); + + initLocalBenchmarks(getIntent()); + + if (findViewById(R.id.list_fragment_container) != null) { + FragmentManager fm = getSupportFragmentManager(); + LocalBenchmarksList listView = new LocalBenchmarksList(); + listView.setListAdapter(new LocalBenchmarksListAdapter(LayoutInflater.from(this))); + listView.setBenchmarks(mBenchmarksToRun); + listView.setRunId(mCurrentRunId); + fm.beginTransaction().add(R.id.list_fragment_container, listView).commit(); + } + + TextView scoreView = (TextView) findViewById(R.id.score_text_view); + scoreView.setText("Running tests!"); + } + + private int translateBenchmarkIndex(int index) { + if (index >= 0 && index < ALL_TESTS.length) { + return ALL_TESTS[index]; + } + + return -1; + } + + private void initLocalBenchmarks(Intent intent) { + mBenchmarksToRun = new ArrayList<>(); + int[] enabledIds = intent.getIntArrayExtra(BenchmarkGroup.BENCHMARK_EXTRA_ENABLED_TESTS); + int runCount = intent.getIntExtra(BenchmarkGroup.BENCHMARK_EXTRA_RUN_COUNT, RUN_COUNT); + mFinish = intent.getBooleanExtra(BenchmarkGroup.BENCHMARK_EXTRA_FINISH, false); + + if (enabledIds == null) { + // run all tests + enabledIds = ALL_TESTS; + } + + StringBuilder idString = new StringBuilder(); + idString.append(runCount); + idString.append(System.currentTimeMillis()); + + for (int i = 0; i < enabledIds.length; i++) { + int id = enabledIds[i]; + System.out.println("considering " + id); + if (!isValidBenchmark(id)) { + System.out.println("not valid " + id); + id = translateBenchmarkIndex(id); + System.out.println("got out " + id); + System.out.println("expected: " + R.id.benchmark_overdraw); + } + + if (isValidBenchmark(id)) { + int localRunCount = runCount; + if (isCompute(id)) { + localRunCount = 1; + } + mBenchmarksToRun.add(new LocalBenchmark(id, localRunCount)); + idString.append(id); + } + } + + mBenchmarkCursor = 0; + mCurrentRunId = idString.toString().hashCode(); + } + + private boolean isCompute(int id) { + switch (id) { + case R.id.benchmark_cpu_gflops: + case R.id.benchmark_cpu_heat_soak: + case R.id.benchmark_memory_bandwidth: + case R.id.benchmark_memory_latency: + case R.id.benchmark_power_management: + return true; + default: + return false; + } + } + + private static boolean isValidBenchmark(int benchmarkId) { + switch (benchmarkId) { + case R.id.benchmark_list_view_scroll: + case R.id.benchmark_image_list_view_scroll: + case R.id.benchmark_shadow_grid: + case R.id.benchmark_text_high_hitrate: + case R.id.benchmark_text_low_hitrate: + case R.id.benchmark_edit_text_input: + case R.id.benchmark_overdraw: + case R.id.benchmark_memory_bandwidth: + case R.id.benchmark_memory_latency: + case R.id.benchmark_power_management: + case R.id.benchmark_cpu_heat_soak: + case R.id.benchmark_cpu_gflops: + return true; + default: + return false; + } + } + + @Override + protected void onResume() { + super.onResume(); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + runNextBenchmark(); + } + }, 1000); + } + + private void computeOverallScore() { + final TextView scoreView = (TextView) findViewById(R.id.score_text_view); + scoreView.setText("Computing score..."); + new AsyncTask<Void, Void, Integer>() { + @Override + protected Integer doInBackground(Void... voids) { + GlobalResultsStore gsr = + GlobalResultsStore.getInstance(RunLocalBenchmarksActivity.this); + ArrayList<Double> testLevelScores = new ArrayList<>(); + final SummaryStatistics stats = new SummaryStatistics(); + for (LocalBenchmark b : mBenchmarksToRun) { + HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults = + gsr.loadDetailedResults(mCurrentRunId); + for (ArrayList<UiBenchmarkResult> testResult : detailedResults.values()) { + for (UiBenchmarkResult res : testResult) { + int score = res.getScore(); + if (score == 0) { + score = 1; + } + stats.addValue(score); + } + + testLevelScores.add(stats.getGeometricMean()); + stats.clear(); + } + + } + + for (double score : testLevelScores) { + stats.addValue(score); + } + + return (int)Math.round(stats.getGeometricMean()); + } + + @Override + protected void onPostExecute(Integer score) { + TextView view = (TextView) + RunLocalBenchmarksActivity.this.findViewById(R.id.score_text_view); + view.setText("Score: " + score); + } + }.execute(); + } + + private void runNextBenchmark() { + LocalBenchmark benchmark = mBenchmarksToRun.get(mBenchmarkCursor); + boolean runAgain = false; + + if (benchmark.runCount < benchmark.totalCount) { + runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount++); + } else if (mBenchmarkCursor + 1 < mBenchmarksToRun.size()) { + mBenchmarkCursor++; + benchmark = mBenchmarksToRun.get(mBenchmarkCursor); + runBenchmarkForId(benchmark.id, benchmark.runCount++); + } else if (runAgain) { + mBenchmarkCursor = 0; + initLocalBenchmarks(getIntent()); + + runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount); + } else if (mFinish) { + finish(); + } else { + Log.i("BENCH", "BenchmarkDone!"); + computeOverallScore(); + } + } + + private void runBenchmarkForId(int id, int iteration) { + Intent intent; + int syntheticTestId = -1; + + System.out.println("iteration: " + iteration); + + switch (id) { + case R.id.benchmark_list_view_scroll: + intent = new Intent(getApplicationContext(), ListViewScrollActivity.class); + break; + case R.id.benchmark_image_list_view_scroll: + intent = new Intent(getApplicationContext(), ImageListViewScrollActivity.class); + break; + case R.id.benchmark_shadow_grid: + intent = new Intent(getApplicationContext(), ShadowGridActivity.class); + break; + case R.id.benchmark_text_high_hitrate: + intent = new Intent(getApplicationContext(), TextScrollActivity.class); + intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 80); + intent.putExtra(BenchmarkRegistry.EXTRA_ID, id); + break; + case R.id.benchmark_text_low_hitrate: + intent = new Intent(getApplicationContext(), TextScrollActivity.class); + intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 20); + intent.putExtra(BenchmarkRegistry.EXTRA_ID, id); + break; + case R.id.benchmark_edit_text_input: + intent = new Intent(getApplicationContext(), EditTextInputActivity.class); + break; + case R.id.benchmark_overdraw: + intent = new Intent(getApplicationContext(), BitmapUploadActivity.class); + break; + case R.id.benchmark_memory_bandwidth: + syntheticTestId = 0; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_memory_latency: + syntheticTestId = 1; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_power_management: + syntheticTestId = 2; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_cpu_heat_soak: + syntheticTestId = 3; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + case R.id.benchmark_cpu_gflops: + syntheticTestId = 4; + intent = new Intent(getApplicationContext(), MemoryActivity.class); + intent.putExtra("test", syntheticTestId); + break; + + default: + intent = null; + } + + if (intent != null) { + intent.putExtra("com.android.benchmark.RUN_ID", mCurrentRunId); + intent.putExtra("com.android.benchmark.ITERATION", iteration); + startActivityForResult(intent, id & 0xffff, null); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case R.id.benchmark_shadow_grid: + case R.id.benchmark_list_view_scroll: + case R.id.benchmark_image_list_view_scroll: + case R.id.benchmark_text_high_hitrate: + case R.id.benchmark_text_low_hitrate: + case R.id.benchmark_edit_text_input: + break; + default: + } + } + + class LocalBenchmarksListAdapter extends BaseAdapter { + + private final LayoutInflater mInflater; + + LocalBenchmarksListAdapter(LayoutInflater inflater) { + mInflater = inflater; + } + + @Override + public int getCount() { + return mBenchmarksToRun.size(); + } + + @Override + public Object getItem(int i) { + return mBenchmarksToRun.get(i); + } + + @Override + public long getItemId(int i) { + return mBenchmarksToRun.get(i).id; + } + + @Override + public View getView(int i, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.running_benchmark_list_item, null); + } + + TextView name = (TextView) convertView.findViewById(R.id.benchmark_name); + name.setText(BenchmarkRegistry.getBenchmarkName( + RunLocalBenchmarksActivity.this, mBenchmarksToRun.get(i).id)); + return convertView; + } + + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java new file mode 100644 index 000000000000..56e94d538ab5 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.app; + +import android.annotation.TargetApi; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.ListFragment; +import android.util.Log; +import android.view.FrameMetrics; +import android.widget.SimpleAdapter; + +import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkGroup; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.results.GlobalResultsStore; +import com.android.benchmark.results.UiBenchmarkResult; + +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.io.FileWriter; +import java.io.IOException; +import java.net.URI; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@TargetApi(24) +public class UiResultsFragment extends ListFragment { + private static final String TAG = "UiResultsFragment"; + private static final int NUM_FIELDS = 20; + + private ArrayList<UiBenchmarkResult> mResults = new ArrayList<>(); + + private AsyncTask<Void, Void, ArrayList<Map<String, String>>> mLoadScoresTask = + new AsyncTask<Void, Void, ArrayList<Map<String, String>>>() { + @Override + protected ArrayList<Map<String, String>> doInBackground(Void... voids) { + String[] data; + if (mResults.size() == 0 || mResults.get(0) == null) { + data = new String[] { + "No metrics reported", "" + }; + } else { + data = new String[NUM_FIELDS * (1 + mResults.size()) + 2]; + SummaryStatistics stats = new SummaryStatistics(); + int totalFrameCount = 0; + double totalAvgFrameDuration = 0; + double total99FrameDuration = 0; + double total95FrameDuration = 0; + double total90FrameDuration = 0; + double totalLongestFrame = 0; + double totalShortestFrame = 0; + + for (int i = 0; i < mResults.size(); i++) { + int start = (i * NUM_FIELDS) + + NUM_FIELDS; + data[(start++)] = "Iteration"; + data[(start++)] = "" + i; + data[(start++)] = "Total Frames"; + int currentFrameCount = mResults.get(i).getTotalFrameCount(); + totalFrameCount += currentFrameCount; + data[(start++)] = Integer.toString(currentFrameCount); + data[(start++)] = "Average frame duration:"; + double currentAvgFrameDuration = mResults.get(i).getAverage(FrameMetrics.TOTAL_DURATION); + totalAvgFrameDuration += currentAvgFrameDuration; + data[(start++)] = String.format("%.2f", currentAvgFrameDuration); + data[(start++)] = "Frame duration 99th:"; + double current99FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 99); + total99FrameDuration += current99FrameDuration; + data[(start++)] = String.format("%.2f", current99FrameDuration); + data[(start++)] = "Frame duration 95th:"; + double current95FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 95); + total95FrameDuration += current95FrameDuration; + data[(start++)] = String.format("%.2f", current95FrameDuration); + data[(start++)] = "Frame duration 90th:"; + double current90FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 90); + total90FrameDuration += current90FrameDuration; + data[(start++)] = String.format("%.2f", current90FrameDuration); + data[(start++)] = "Longest frame:"; + double longestFrame = mResults.get(i).getMaximum(FrameMetrics.TOTAL_DURATION); + if (totalLongestFrame == 0 || longestFrame > totalLongestFrame) { + totalLongestFrame = longestFrame; + } + data[(start++)] = String.format("%.2f", longestFrame); + data[(start++)] = "Shortest frame:"; + double shortestFrame = mResults.get(i).getMinimum(FrameMetrics.TOTAL_DURATION); + if (totalShortestFrame == 0 || totalShortestFrame > shortestFrame) { + totalShortestFrame = shortestFrame; + } + data[(start++)] = String.format("%.2f", shortestFrame); + data[(start++)] = "Score:"; + double score = mResults.get(i).getScore(); + stats.addValue(score); + data[(start++)] = String.format("%.2f", score); + data[(start++)] = "=============="; + data[(start++)] = "============================"; + }; + + int start = 0; + data[0] = "Overall: "; + data[1] = ""; + data[(start++)] = "Total Frames"; + data[(start++)] = Integer.toString(totalFrameCount); + data[(start++)] = "Average frame duration:"; + data[(start++)] = String.format("%.2f", totalAvgFrameDuration / mResults.size()); + data[(start++)] = "Frame duration 99th:"; + data[(start++)] = String.format("%.2f", total99FrameDuration / mResults.size()); + data[(start++)] = "Frame duration 95th:"; + data[(start++)] = String.format("%.2f", total95FrameDuration / mResults.size()); + data[(start++)] = "Frame duration 90th:"; + data[(start++)] = String.format("%.2f", total90FrameDuration / mResults.size()); + data[(start++)] = "Longest frame:"; + data[(start++)] = String.format("%.2f", totalLongestFrame); + data[(start++)] = "Shortest frame:"; + data[(start++)] = String.format("%.2f", totalShortestFrame); + data[(start++)] = "Score:"; + data[(start++)] = String.format("%.2f", stats.getGeometricMean()); + data[(start++)] = "=============="; + data[(start++)] = "============================"; + } + + ArrayList<Map<String, String>> dataMap = new ArrayList<>(); + for (int i = 0; i < data.length - 1; i += 2) { + HashMap<String, String> map = new HashMap<>(); + map.put("name", data[i]); + map.put("value", data[i + 1]); + dataMap.add(map); + } + + return dataMap; + } + + @Override + protected void onPostExecute(ArrayList<Map<String, String>> dataMap) { + setListAdapter(new SimpleAdapter(getActivity(), dataMap, R.layout.results_list_item, + new String[] {"name", "value"}, new int[] { R.id.result_name, R.id.result_value })); + setListShown(true); + } + }; + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListShown(false); + mLoadScoresTask.execute(); + } + + public void setRunInfo(String name, int runId) { + mResults = GlobalResultsStore.getInstance(getActivity()).loadTestResults(name, runId); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java new file mode 100644 index 000000000000..d91e5798f05b --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.registry; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents the category of a particular benchmark. + */ +@Retention(RetentionPolicy.SOURCE) +@IntDef({BenchmarkCategory.GENERIC, BenchmarkCategory.UI, BenchmarkCategory.COMPUTE}) +@interface BenchmarkCategory { + int GENERIC = 0; + int UI = 1; + int COMPUTE = 2; +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java new file mode 100644 index 000000000000..4cb7716ee5e9 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.registry; + +import android.content.ComponentName; +import android.content.Intent; +import android.view.View; +import android.widget.CheckBox; + +/** + * Logical grouping of benchmarks + */ +public class BenchmarkGroup { + public static final String BENCHMARK_EXTRA_ENABLED_TESTS = + "com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS"; + + public static final String BENCHMARK_EXTRA_RUN_COUNT = + "com.android.benchmark.EXTRA_RUN_COUNT"; + public static final String BENCHMARK_EXTRA_FINISH = "com.android.benchmark.FINISH_WHEN_DONE"; + + public static class Benchmark implements CheckBox.OnClickListener { + /** The name of this individual benchmark test */ + private final String mName; + + /** The category of this individual benchmark test */ + private final @BenchmarkCategory int mCategory; + + /** Human-readable description of the benchmark */ + private final String mDescription; + + private final int mId; + + private boolean mEnabled; + + Benchmark(int id, String name, @BenchmarkCategory int category, String description) { + mId = id; + mName = name; + mCategory = category; + mDescription = description; + mEnabled = true; + } + + public boolean isEnabled() { return mEnabled; } + + public void setEnabled(boolean enabled) { mEnabled = enabled; } + + public int getId() { return mId; } + + public String getDescription() { return mDescription; } + + public @BenchmarkCategory int getCategory() { return mCategory; } + + public String getName() { return mName; } + + @Override + public void onClick(View view) { + setEnabled(((CheckBox)view).isChecked()); + } + } + + /** + * Component for this benchmark group. + */ + private final ComponentName mComponentName; + + /** + * Benchmark title, showed in the {@link android.widget.ListView} + */ + private final String mTitle; + + /** + * List of all benchmarks exported by this group + */ + private final Benchmark[] mBenchmarks; + + /** + * The intent to launch the benchmark + */ + private final Intent mIntent; + + /** Human-readable description of the benchmark group */ + private final String mDescription; + + BenchmarkGroup(ComponentName componentName, String title, + String description, Benchmark[] benchmarks, Intent intent) { + mComponentName = componentName; + mTitle = title; + mBenchmarks = benchmarks; + mDescription = description; + mIntent = intent; + } + + public Intent getIntent() { + int[] enabledBenchmarksIds = getEnabledBenchmarksIds(); + if (enabledBenchmarksIds.length != 0) { + mIntent.putExtra(BENCHMARK_EXTRA_ENABLED_TESTS, enabledBenchmarksIds); + return mIntent; + } + + return null; + } + + public ComponentName getComponentName() { + return mComponentName; + } + + public String getTitle() { + return mTitle; + } + + public Benchmark[] getBenchmarks() { + return mBenchmarks; + } + + public String getDescription() { + return mDescription; + } + + private int[] getEnabledBenchmarksIds() { + int enabledBenchmarkCount = 0; + for (int i = 0; i < mBenchmarks.length; i++) { + if (mBenchmarks[i].isEnabled()) { + enabledBenchmarkCount++; + } + } + + int writeIndex = 0; + int[] enabledBenchmarks = new int[enabledBenchmarkCount]; + for (int i = 0; i < mBenchmarks.length; i++) { + if (mBenchmarks[i].isEnabled()) { + enabledBenchmarks[writeIndex++] = mBenchmarks[i].getId(); + } + } + + return enabledBenchmarks; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java new file mode 100644 index 000000000000..89c6aedd8b5c --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.registry; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.benchmark.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + */ +public class BenchmarkRegistry { + + /** Metadata key for benchmark XML data */ + private static final String BENCHMARK_GROUP_META_KEY = + "com.android.benchmark.benchmark_group"; + + /** Intent action specifying an activity that runs a single benchmark test. */ + private static final String ACTION_BENCHMARK = "com.android.benchmark.ACTION_BENCHMARK"; + public static final String EXTRA_ID = "com.android.benchmark.EXTRA_ID"; + + private static final String TAG_BENCHMARK_GROUP = "com.android.benchmark.BenchmarkGroup"; + private static final String TAG_BENCHMARK = "com.android.benchmark.Benchmark"; + + private List<BenchmarkGroup> mGroups; + + private final Context mContext; + + public BenchmarkRegistry(Context context) { + mContext = context; + mGroups = new ArrayList<>(); + loadBenchmarks(); + } + + private Intent getIntentFromInfo(ActivityInfo inf) { + Intent intent = new Intent(); + intent.setClassName(inf.packageName, inf.name); + return intent; + } + + public void loadBenchmarks() { + Intent intent = new Intent(ACTION_BENCHMARK); + intent.setPackage(mContext.getPackageName()); + + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, + PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); + + for (ResolveInfo inf : resolveInfos) { + List<BenchmarkGroup> groups = parseBenchmarkGroup(inf.activityInfo); + if (groups != null) { + mGroups.addAll(groups); + } + } + } + + private boolean seekToTag(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException { + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + return eventType != XmlPullParser.END_DOCUMENT && tag.equals(parser.getName()); + } + + @BenchmarkCategory int getCategory(int category) { + switch (category) { + case BenchmarkCategory.COMPUTE: + return BenchmarkCategory.COMPUTE; + case BenchmarkCategory.UI: + return BenchmarkCategory.UI; + default: + return BenchmarkCategory.GENERIC; + } + } + + private List<BenchmarkGroup> parseBenchmarkGroup(ActivityInfo activityInfo) { + PackageManager pm = mContext.getPackageManager(); + + ComponentName componentName = new ComponentName( + activityInfo.packageName, activityInfo.name); + + SparseArray<List<BenchmarkGroup.Benchmark>> benchmarks = new SparseArray<>(); + String groupName, groupDescription; + try (XmlResourceParser parser = activityInfo.loadXmlMetaData(pm, BENCHMARK_GROUP_META_KEY)) { + + if (!seekToTag(parser, TAG_BENCHMARK_GROUP)) { + return null; + } + + Resources res = pm.getResourcesForActivity(componentName); + AttributeSet attributeSet = Xml.asAttributeSet(parser); + TypedArray groupAttribs = res.obtainAttributes(attributeSet, R.styleable.BenchmarkGroup); + + groupName = groupAttribs.getString(R.styleable.BenchmarkGroup_name); + groupDescription = groupAttribs.getString(R.styleable.BenchmarkGroup_description); + groupAttribs.recycle(); + parser.next(); + + while (seekToTag(parser, TAG_BENCHMARK)) { + TypedArray benchAttribs = + res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Benchmark); + int id = benchAttribs.getResourceId(R.styleable.Benchmark_id, -1); + String testName = benchAttribs.getString(R.styleable.Benchmark_name); + String testDescription = benchAttribs.getString(R.styleable.Benchmark_description); + int testCategory = benchAttribs.getInt(R.styleable.Benchmark_category, + BenchmarkCategory.GENERIC); + int category = getCategory(testCategory); + BenchmarkGroup.Benchmark benchmark = new BenchmarkGroup.Benchmark( + id, testName, category, testDescription); + List<BenchmarkGroup.Benchmark> benches = benchmarks.get(category); + if (benches == null) { + benches = new ArrayList<>(); + benchmarks.append(category, benches); + } + + benches.add(benchmark); + + benchAttribs.recycle(); + parser.next(); + } + } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException e) { + return null; + } + + List<BenchmarkGroup> result = new ArrayList<>(); + Intent testIntent = getIntentFromInfo(activityInfo); + for (int i = 0; i < benchmarks.size(); i++) { + int cat = benchmarks.keyAt(i); + List<BenchmarkGroup.Benchmark> thisGroup = benchmarks.get(cat); + BenchmarkGroup.Benchmark[] benchmarkArray = + new BenchmarkGroup.Benchmark[thisGroup.size()]; + thisGroup.toArray(benchmarkArray); + result.add(new BenchmarkGroup(componentName, + groupName + " - " + getCategoryString(cat), groupDescription, benchmarkArray, + testIntent)); + } + + return result; + } + + public int getGroupCount() { + return mGroups.size(); + } + + public int getBenchmarkCount(int benchmarkIndex) { + BenchmarkGroup group = getBenchmarkGroup(benchmarkIndex); + if (group != null) { + return group.getBenchmarks().length; + } + return 0; + } + + public BenchmarkGroup getBenchmarkGroup(int benchmarkIndex) { + if (benchmarkIndex >= mGroups.size()) { + return null; + } + + return mGroups.get(benchmarkIndex); + } + + public static String getCategoryString(int category) { + switch (category) { + case BenchmarkCategory.UI: + return "UI"; + case BenchmarkCategory.COMPUTE: + return "Compute"; + case BenchmarkCategory.GENERIC: + return "Generic"; + default: + return ""; + } + } + + public static String getBenchmarkName(Context context, int benchmarkId) { + switch (benchmarkId) { + case R.id.benchmark_list_view_scroll: + return context.getString(R.string.list_view_scroll_name); + case R.id.benchmark_image_list_view_scroll: + return context.getString(R.string.image_list_view_scroll_name); + case R.id.benchmark_shadow_grid: + return context.getString(R.string.shadow_grid_name); + case R.id.benchmark_text_high_hitrate: + return context.getString(R.string.text_high_hitrate_name); + case R.id.benchmark_text_low_hitrate: + return context.getString(R.string.text_low_hitrate_name); + case R.id.benchmark_edit_text_input: + return context.getString(R.string.edit_text_input_name); + case R.id.benchmark_memory_bandwidth: + return context.getString(R.string.memory_bandwidth_name); + case R.id.benchmark_memory_latency: + return context.getString(R.string.memory_latency_name); + case R.id.benchmark_power_management: + return context.getString(R.string.power_management_name); + case R.id.benchmark_cpu_heat_soak: + return context.getString(R.string.cpu_heat_soak_name); + case R.id.benchmark_cpu_gflops: + return context.getString(R.string.cpu_gflops_name); + case R.id.benchmark_overdraw: + return context.getString(R.string.overdraw_name); + default: + return "Some Benchmark"; + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java new file mode 100644 index 000000000000..5d0cba278053 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.results; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.view.FrameMetrics; +import android.widget.Toast; + +import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; + +import java.io.FileWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; + +public class GlobalResultsStore extends SQLiteOpenHelper { + private static final int VERSION = 2; + + private static GlobalResultsStore sInstance; + private static final String UI_RESULTS_TABLE = "ui_results"; + + private final Context mContext; + + private GlobalResultsStore(Context context) { + super(context, "BenchmarkResults", null, VERSION); + mContext = context; + } + + public static GlobalResultsStore getInstance(Context context) { + if (sInstance == null) { + sInstance = new GlobalResultsStore(context.getApplicationContext()); + } + + return sInstance; + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + sqLiteDatabase.execSQL("CREATE TABLE " + UI_RESULTS_TABLE + " (" + + " _id INTEGER PRIMARY KEY AUTOINCREMENT," + + " name TEXT," + + " run_id INTEGER," + + " iteration INTEGER," + + " timestamp TEXT," + + " unknown_delay REAL," + + " input REAL," + + " animation REAL," + + " layout REAL," + + " draw REAL," + + " sync REAL," + + " command_issue REAL," + + " swap_buffers REAL," + + " total_duration REAL," + + " jank_frame BOOLEAN, " + + " device_charging INTEGER);"); + } + + public void storeRunResults(String testName, int runId, int iteration, + UiBenchmarkResult result) { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + try { + String date = DateFormat.getDateTimeInstance().format(new Date()); + int jankIndexIndex = 0; + int[] sortedJankIndices = result.getSortedJankFrameIndices(); + int totalFrameCount = result.getTotalFrameCount(); + for (int frameIdx = 0; frameIdx < totalFrameCount; frameIdx++) { + ContentValues cv = new ContentValues(); + cv.put("name", testName); + cv.put("run_id", runId); + cv.put("iteration", iteration); + cv.put("timestamp", date); + cv.put("unknown_delay", + result.getMetricAtIndex(frameIdx, FrameMetrics.UNKNOWN_DELAY_DURATION)); + cv.put("input", + result.getMetricAtIndex(frameIdx, FrameMetrics.INPUT_HANDLING_DURATION)); + cv.put("animation", + result.getMetricAtIndex(frameIdx, FrameMetrics.ANIMATION_DURATION)); + cv.put("layout", + result.getMetricAtIndex(frameIdx, FrameMetrics.LAYOUT_MEASURE_DURATION)); + cv.put("draw", + result.getMetricAtIndex(frameIdx, FrameMetrics.DRAW_DURATION)); + cv.put("sync", + result.getMetricAtIndex(frameIdx, FrameMetrics.SYNC_DURATION)); + cv.put("command_issue", + result.getMetricAtIndex(frameIdx, FrameMetrics.COMMAND_ISSUE_DURATION)); + cv.put("swap_buffers", + result.getMetricAtIndex(frameIdx, FrameMetrics.SWAP_BUFFERS_DURATION)); + cv.put("total_duration", + result.getMetricAtIndex(frameIdx, FrameMetrics.TOTAL_DURATION)); + if (jankIndexIndex < sortedJankIndices.length && + sortedJankIndices[jankIndexIndex] == frameIdx) { + jankIndexIndex++; + cv.put("jank_frame", true); + } else { + cv.put("jank_frame", false); + } + db.insert(UI_RESULTS_TABLE, null, cv); + } + db.setTransactionSuccessful(); + Toast.makeText(mContext, "Score: " + result.getScore() + + " Jank: " + (100 * sortedJankIndices.length) / (float) totalFrameCount + "%", + Toast.LENGTH_LONG).show(); + } finally { + db.endTransaction(); + } + + } + + public ArrayList<UiBenchmarkResult> loadTestResults(String testName, int runId) { + SQLiteDatabase db = getReadableDatabase(); + ArrayList<UiBenchmarkResult> resultList = new ArrayList<>(); + try { + String[] columnsToQuery = new String[] { + "name", + "run_id", + "iteration", + "unknown_delay", + "input", + "animation", + "layout", + "draw", + "sync", + "command_issue", + "swap_buffers", + "total_duration", + }; + + Cursor cursor = db.query( + UI_RESULTS_TABLE, columnsToQuery, "run_id=? AND name=?", + new String[] { Integer.toString(runId), testName }, null, null, "iteration"); + + double[] values = new double[columnsToQuery.length - 3]; + + while (cursor.moveToNext()) { + int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration")); + + values[0] = cursor.getDouble( + cursor.getColumnIndexOrThrow("unknown_delay")); + values[1] = cursor.getDouble( + cursor.getColumnIndexOrThrow("input")); + values[2] = cursor.getDouble( + cursor.getColumnIndexOrThrow("animation")); + values[3] = cursor.getDouble( + cursor.getColumnIndexOrThrow("layout")); + values[4] = cursor.getDouble( + cursor.getColumnIndexOrThrow("draw")); + values[5] = cursor.getDouble( + cursor.getColumnIndexOrThrow("sync")); + values[6] = cursor.getDouble( + cursor.getColumnIndexOrThrow("command_issue")); + values[7] = cursor.getDouble( + cursor.getColumnIndexOrThrow("swap_buffers")); + values[8] = cursor.getDouble( + cursor.getColumnIndexOrThrow("total_duration")); + + UiBenchmarkResult iterationResult; + if (resultList.size() == iteration) { + iterationResult = new UiBenchmarkResult(values); + resultList.add(iteration, iterationResult); + } else { + iterationResult = resultList.get(iteration); + iterationResult.update(values); + } + } + + cursor.close(); + } finally { + db.close(); + } + + int total = resultList.get(0).getTotalFrameCount(); + for (int i = 0; i < total; i++) { + System.out.println(""+ resultList.get(0).getMetricAtIndex(0, FrameMetrics.TOTAL_DURATION)); + } + + return resultList; + } + + public HashMap<String, ArrayList<UiBenchmarkResult>> loadDetailedResults(int runId) { + SQLiteDatabase db = getReadableDatabase(); + HashMap<String, ArrayList<UiBenchmarkResult>> results = new HashMap<>(); + try { + String[] columnsToQuery = new String[] { + "name", + "run_id", + "iteration", + "unknown_delay", + "input", + "animation", + "layout", + "draw", + "sync", + "command_issue", + "swap_buffers", + "total_duration", + }; + + Cursor cursor = db.query( + UI_RESULTS_TABLE, columnsToQuery, "run_id=?", + new String[] { Integer.toString(runId) }, null, null, "name, iteration"); + + double[] values = new double[columnsToQuery.length - 3]; + while (cursor.moveToNext()) { + int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration")); + String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); + ArrayList<UiBenchmarkResult> resultList = results.get(name); + if (resultList == null) { + resultList = new ArrayList<>(); + results.put(name, resultList); + } + + values[0] = cursor.getDouble( + cursor.getColumnIndexOrThrow("unknown_delay")); + values[1] = cursor.getDouble( + cursor.getColumnIndexOrThrow("input")); + values[2] = cursor.getDouble( + cursor.getColumnIndexOrThrow("animation")); + values[3] = cursor.getDouble( + cursor.getColumnIndexOrThrow("layout")); + values[4] = cursor.getDouble( + cursor.getColumnIndexOrThrow("draw")); + values[5] = cursor.getDouble( + cursor.getColumnIndexOrThrow("sync")); + values[6] = cursor.getDouble( + cursor.getColumnIndexOrThrow("command_issue")); + values[7] = cursor.getDouble( + cursor.getColumnIndexOrThrow("swap_buffers")); + values[8] = cursor.getDouble( + cursor.getColumnIndexOrThrow("total_duration")); + values[8] = cursor.getDouble( + cursor.getColumnIndexOrThrow("total_duration")); + + UiBenchmarkResult iterationResult; + if (resultList.size() == iteration) { + iterationResult = new UiBenchmarkResult(values); + resultList.add(iterationResult); + } else { + iterationResult = resultList.get(iteration); + iterationResult.update(values); + } + } + + cursor.close(); + } finally { + db.close(); + } + + return results; + } + + public void exportToCsv() throws IOException { + String path = mContext.getFilesDir() + "/results-" + System.currentTimeMillis() + ".csv"; + SQLiteDatabase db = getReadableDatabase(); + + // stats across metrics for each run and each test + HashMap<String, DescriptiveStatistics> stats = new HashMap<>(); + + Cursor runIdCursor = db.query( + UI_RESULTS_TABLE, new String[] { "run_id" }, null, null, "run_id", null, null); + + while (runIdCursor.moveToNext()) { + + int runId = runIdCursor.getInt(runIdCursor.getColumnIndexOrThrow("run_id")); + HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults = + loadDetailedResults(runId); + + writeRawResults(runId, detailedResults); + + DescriptiveStatistics overall = new DescriptiveStatistics(); + try (FileWriter writer = new FileWriter(path, true)) { + writer.write("Run ID, " + runId + "\n"); + writer.write("Test, Iteration, Score, Jank Penalty, Consistency Bonus, 95th, " + + "90th\n"); + for (String testName : detailedResults.keySet()) { + ArrayList<UiBenchmarkResult> results = detailedResults.get(testName); + DescriptiveStatistics scoreStats = new DescriptiveStatistics(); + DescriptiveStatistics jankPenalty = new DescriptiveStatistics(); + DescriptiveStatistics consistencyBonus = new DescriptiveStatistics(); + for (int i = 0; i < results.size(); i++) { + UiBenchmarkResult result = results.get(i); + int score = result.getScore(); + scoreStats.addValue(score); + overall.addValue(score); + jankPenalty.addValue(result.getJankPenalty()); + consistencyBonus.addValue(result.getConsistencyBonus()); + + writer.write(testName); + writer.write(","); + writer.write("" + i); + writer.write(","); + writer.write("" + score); + writer.write(","); + writer.write("" + result.getJankPenalty()); + writer.write(","); + writer.write("" + result.getConsistencyBonus()); + writer.write(","); + writer.write(Double.toString( + result.getPercentile(FrameMetrics.TOTAL_DURATION, 95))); + writer.write(","); + writer.write(Double.toString( + result.getPercentile(FrameMetrics.TOTAL_DURATION, 90))); + writer.write("\n"); + } + + writer.write("Score CV," + + (100 * scoreStats.getStandardDeviation() + / scoreStats.getMean()) + "%\n"); + writer.write("Jank Penalty CV, " + + (100 * jankPenalty.getStandardDeviation() + / jankPenalty.getMean()) + "%\n"); + writer.write("Consistency Bonus CV, " + + (100 * consistencyBonus.getStandardDeviation() + / consistencyBonus.getMean()) + "%\n"); + writer.write("\n"); + } + + writer.write("Overall Score CV," + + (100 * overall.getStandardDeviation() / overall.getMean()) + "%\n"); + writer.flush(); + } + } + + runIdCursor.close(); + } + + private void writeRawResults(int runId, + HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults) { + StringBuilder path = new StringBuilder(); + path.append(mContext.getFilesDir()); + path.append("/"); + path.append(Integer.toString(runId)); + path.append(".csv"); + try (FileWriter writer = new FileWriter(path.toString())) { + for (String test : detailedResults.keySet()) { + writer.write("Test, " + test + "\n"); + writer.write("iteration, unknown delay, input, animation, layout, draw, sync, " + + "command issue, swap buffers\n"); + ArrayList<UiBenchmarkResult> runs = detailedResults.get(test); + for (int i = 0; i < runs.size(); i++) { + UiBenchmarkResult run = runs.get(i); + for (int j = 0; j < run.getTotalFrameCount(); j++) { + writer.write(Integer.toString(i) + "," + + run.getMetricAtIndex(j, FrameMetrics.UNKNOWN_DELAY_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.INPUT_HANDLING_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.ANIMATION_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.LAYOUT_MEASURE_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.DRAW_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.SYNC_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.COMMAND_ISSUE_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.SWAP_BUFFERS_DURATION) + "," + + run.getMetricAtIndex(j, FrameMetrics.TOTAL_DURATION) + "\n"); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int currentVersion) { + if (oldVersion < VERSION) { + sqLiteDatabase.execSQL("ALTER TABLE " + + UI_RESULTS_TABLE + " ADD COLUMN timestamp TEXT;"); + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java new file mode 100644 index 000000000000..da6e05ac051b --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.results; + +import android.annotation.TargetApi; +import android.view.FrameMetrics; + +import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility for storing and analyzing UI benchmark results. + */ +@TargetApi(24) +public class UiBenchmarkResult { + private static final int BASE_SCORE = 100; + private static final int ZERO_SCORE_TOTAL_DURATION_MS = 32; + private static final int JANK_PENALTY_THRESHOLD_MS = 12; + private static final int ZERO_SCORE_ABOVE_THRESHOLD_MS = + ZERO_SCORE_TOTAL_DURATION_MS - JANK_PENALTY_THRESHOLD_MS; + private static final double JANK_PENALTY_PER_MS_ABOVE_THRESHOLD = + BASE_SCORE / (double)ZERO_SCORE_ABOVE_THRESHOLD_MS; + private static final int CONSISTENCY_BONUS_MAX = 100; + + private static final int METRIC_WAS_JANKY = -1; + + private static final int[] METRICS = new int[] { + FrameMetrics.UNKNOWN_DELAY_DURATION, + FrameMetrics.INPUT_HANDLING_DURATION, + FrameMetrics.ANIMATION_DURATION, + FrameMetrics.LAYOUT_MEASURE_DURATION, + FrameMetrics.DRAW_DURATION, + FrameMetrics.SYNC_DURATION, + FrameMetrics.COMMAND_ISSUE_DURATION, + FrameMetrics.SWAP_BUFFERS_DURATION, + FrameMetrics.TOTAL_DURATION, + }; + public static final int FRAME_PERIOD_MS = 16; + + private final DescriptiveStatistics[] mStoredStatistics; + + public UiBenchmarkResult(List<FrameMetrics> instances) { + mStoredStatistics = new DescriptiveStatistics[METRICS.length]; + insertMetrics(instances); + } + + public UiBenchmarkResult(double[] values) { + mStoredStatistics = new DescriptiveStatistics[METRICS.length]; + insertValues(values); + } + + public void update(List<FrameMetrics> instances) { + insertMetrics(instances); + } + + public void update(double[] values) { + insertValues(values); + } + + public double getAverage(int id) { + int pos = getMetricPosition(id); + return mStoredStatistics[pos].getMean(); + } + + public double getMinimum(int id) { + int pos = getMetricPosition(id); + return mStoredStatistics[pos].getMin(); + } + + public double getMaximum(int id) { + int pos = getMetricPosition(id); + return mStoredStatistics[pos].getMax(); + } + + public int getMaximumIndex(int id) { + int pos = getMetricPosition(id); + double[] storedMetrics = mStoredStatistics[pos].getValues(); + int maxIdx = 0; + for (int i = 0; i < storedMetrics.length; i++) { + if (storedMetrics[i] >= storedMetrics[maxIdx]) { + maxIdx = i; + } + } + + return maxIdx; + } + + public double getMetricAtIndex(int index, int metricId) { + return mStoredStatistics[getMetricPosition(metricId)].getElement(index); + } + + public double getPercentile(int id, int percentile) { + if (percentile > 100) percentile = 100; + if (percentile < 0) percentile = 0; + + int metricPos = getMetricPosition(id); + return mStoredStatistics[metricPos].getPercentile(percentile); + } + + public int getTotalFrameCount() { + if (mStoredStatistics.length == 0) { + return 0; + } + + return (int) mStoredStatistics[0].getN(); + } + + public int getScore() { + SummaryStatistics badFramesStats = new SummaryStatistics(); + + int totalFrameCount = getTotalFrameCount(); + for (int i = 0; i < totalFrameCount; i++) { + double totalDuration = getMetricAtIndex(i, FrameMetrics.TOTAL_DURATION); + if (totalDuration >= 12) { + badFramesStats.addValue(totalDuration); + } + } + + int length = getSortedJankFrameIndices().length; + double jankFrameCount = 100 * length / (double) totalFrameCount; + + System.out.println("Mean: " + badFramesStats.getMean() + " JankP: " + jankFrameCount + + " StdDev: " + badFramesStats.getStandardDeviation() + + " Count Bad: " + badFramesStats.getN() + " Count Jank: " + length); + + return (int) Math.round( + (badFramesStats.getMean()) * jankFrameCount * badFramesStats.getStandardDeviation()); + } + + public int getJankPenalty() { + double total95th = mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)] + .getPercentile(95); + System.out.println("95: " + total95th); + double aboveThreshold = total95th - JANK_PENALTY_THRESHOLD_MS; + if (aboveThreshold <= 0) { + return 0; + } + + if (aboveThreshold > ZERO_SCORE_ABOVE_THRESHOLD_MS) { + return BASE_SCORE; + } + + return (int) Math.ceil(JANK_PENALTY_PER_MS_ABOVE_THRESHOLD * aboveThreshold); + } + + public int getConsistencyBonus() { + DescriptiveStatistics totalDurationStats = + mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)]; + + double standardDeviation = totalDurationStats.getStandardDeviation(); + if (standardDeviation == 0) { + return CONSISTENCY_BONUS_MAX; + } + + // 1 / CV of the total duration. + double bonus = totalDurationStats.getMean() / standardDeviation; + return (int) Math.min(Math.round(bonus), CONSISTENCY_BONUS_MAX); + } + + public int[] getSortedJankFrameIndices() { + ArrayList<Integer> jankFrameIndices = new ArrayList<>(); + boolean tripleBuffered = false; + int totalFrameCount = getTotalFrameCount(); + int totalDurationPos = getMetricPosition(FrameMetrics.TOTAL_DURATION); + + for (int i = 0; i < totalFrameCount; i++) { + double thisDuration = mStoredStatistics[totalDurationPos].getElement(i); + if (!tripleBuffered) { + if (thisDuration > FRAME_PERIOD_MS) { + tripleBuffered = true; + jankFrameIndices.add(i); + } + } else { + if (thisDuration > 2 * FRAME_PERIOD_MS) { + tripleBuffered = false; + jankFrameIndices.add(i); + } + } + } + + int[] res = new int[jankFrameIndices.size()]; + int i = 0; + for (Integer index : jankFrameIndices) { + res[i++] = index; + } + return res; + } + + private int getMetricPosition(int id) { + for (int i = 0; i < METRICS.length; i++) { + if (id == METRICS[i]) { + return i; + } + } + + return -1; + } + + private void insertMetrics(List<FrameMetrics> instances) { + for (FrameMetrics frame : instances) { + for (int i = 0; i < METRICS.length; i++) { + DescriptiveStatistics stats = mStoredStatistics[i]; + if (stats == null) { + stats = new DescriptiveStatistics(); + mStoredStatistics[i] = stats; + } + + mStoredStatistics[i].addValue(frame.getMetric(METRICS[i]) / (double) 1000000); + } + } + } + + private void insertValues(double[] values) { + if (values.length != METRICS.length) { + throw new IllegalArgumentException("invalid values array"); + } + + for (int i = 0; i < values.length; i++) { + DescriptiveStatistics stats = mStoredStatistics[i]; + if (stats == null) { + stats = new DescriptiveStatistics(); + mStoredStatistics[i] = stats; + } + + mStoredStatistics[i].addValue(values[i]); + } + } + } diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java new file mode 100644 index 000000000000..aba16d596e6f --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.synthetic; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import com.android.benchmark.R; +import com.android.benchmark.app.PerfTimeline; + +import junit.framework.Test; + + +public class MemoryActivity extends Activity { + private TextView mTextStatus; + private TextView mTextMin; + private TextView mTextMax; + private TextView mTextTypical; + private PerfTimeline mTimeline; + + TestInterface mTI; + int mActiveTest; + + private class SyntheticTestCallback extends TestInterface.TestResultCallback { + @Override + void onTestResult(int command, float result) { + Intent resultIntent = new Intent(); + resultIntent.putExtra("com.android.benchmark.synthetic.TEST_RESULT", result); + setResult(RESULT_OK, resultIntent); + finish(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_memory); + + mTextStatus = (TextView) findViewById(R.id.textView_status); + mTextMin = (TextView) findViewById(R.id.textView_min); + mTextMax = (TextView) findViewById(R.id.textView_max); + mTextTypical = (TextView) findViewById(R.id.textView_typical); + + mTimeline = (PerfTimeline) findViewById(R.id.mem_timeline); + + mTI = new TestInterface(mTimeline, 2, new SyntheticTestCallback()); + mTI.mTextMax = mTextMax; + mTI.mTextMin = mTextMin; + mTI.mTextStatus = mTextStatus; + mTI.mTextTypical = mTextTypical; + + mTimeline.mLinesLow = mTI.mLinesLow; + mTimeline.mLinesHigh = mTI.mLinesHigh; + mTimeline.mLinesValue = mTI.mLinesValue; + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + @Override + protected void onResume() { + super.onResume(); + Intent i = getIntent(); + mActiveTest = i.getIntExtra("test", 0); + + switch (mActiveTest) { + case 0: + mTI.runMemoryBandwidth(); + break; + case 1: + mTI.runMemoryLatency(); + break; + case 2: + mTI.runPowerManagement(); + break; + case 3: + mTI.runCPUHeatSoak(); + break; + case 4: + mTI.runCPUGFlops(); + break; + default: + break; + + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_memory, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + public void onCpuBandwidth(View v) { + + + } + + + + +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java new file mode 100644 index 000000000000..8f083a27be8d --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.synthetic; + +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.math.stat.StatUtils; +import org.apache.commons.math.stat.descriptive.SummaryStatistics; + +import java.util.LinkedList; +import java.util.Queue; + + +public class TestInterface { + native long nInit(long options); + native long nDestroy(long b); + native float nGetData(long b, float[] data); + native boolean nRunPowerManagementTest(long b, long options); + native boolean nRunCPUHeatSoakTest(long b, long options); + + native boolean nMemTestStart(long b); + native float nMemTestBandwidth(long b, long size); + native float nMemTestLatency(long b, long size); + native void nMemTestEnd(long b); + + native float nGFlopsTest(long b, long opt); + + public static class TestResultCallback { + void onTestResult(int command, float result) { } + } + + static { + System.loadLibrary("nativebench"); + } + + float[] mLinesLow; + float[] mLinesHigh; + float[] mLinesValue; + TextView mTextStatus; + TextView mTextMin; + TextView mTextMax; + TextView mTextTypical; + + private View mViewToUpdate; + + private LooperThread mLT; + + TestInterface(View v, int runtimeSeconds, TestResultCallback callback) { + int buckets = runtimeSeconds * 1000; + mLinesLow = new float[buckets * 4]; + mLinesHigh = new float[buckets * 4]; + mLinesValue = new float[buckets * 4]; + mViewToUpdate = v; + + mLT = new LooperThread(this, callback); + mLT.start(); + } + + static class LooperThread extends Thread { + public static final int CommandExit = 1; + public static final int TestPowerManagement = 2; + public static final int TestMemoryBandwidth = 3; + public static final int TestMemoryLatency = 4; + public static final int TestHeatSoak = 5; + public static final int TestGFlops = 6; + + private volatile boolean mRun = true; + private TestInterface mTI; + private TestResultCallback mCallback; + + Queue<Integer> mCommandQueue = new LinkedList<Integer>(); + + LooperThread(TestInterface ti, TestResultCallback callback) { + super("BenchmarkTestThread"); + mTI = ti; + mCallback = callback; + } + + void runCommand(int command) { + Integer i = Integer.valueOf(command); + + synchronized (this) { + mCommandQueue.add(i); + notifyAll(); + } + } + + public void run() { + long b = mTI.nInit(0); + if (b == 0) { + return; + } + + while (mRun) { + int command = 0; + synchronized (this) { + if (mCommandQueue.isEmpty()) { + try { + wait(); + } catch (InterruptedException e) { + } + } + + if (!mCommandQueue.isEmpty()) { + command = mCommandQueue.remove(); + } + } + + switch (command) { + case CommandExit: + mRun = false; + break; + case TestPowerManagement: + float score = mTI.testPowerManagement(b); + mCallback.onTestResult(command, 0); + break; + case TestMemoryBandwidth: + mTI.testCPUMemoryBandwidth(b); + break; + case TestMemoryLatency: + mTI.testCPUMemoryLatency(b); + break; + case TestHeatSoak: + mTI.testCPUHeatSoak(b); + break; + case TestGFlops: + mTI.testCPUGFlops(b); + break; + + } + + //mViewToUpdate.post(new Runnable() { + // public void run() { + // mViewToUpdate.invalidate(); + //} + //}); + } + + mTI.nDestroy(b); + } + + void exit() { + mRun = false; + } + } + + void postTextToView(TextView v, String s) { + final TextView tv = v; + final String ts = s; + + v.post(new Runnable() { + public void run() { + tv.setText(ts); + } + }); + + } + + float calcAverage(float[] data) { + float total = 0.f; + for (int ct=0; ct < data.length; ct++) { + total += data[ct]; + } + return total / data.length; + } + + void makeGraph(float[] data, float[] lines) { + for (int ct = 0; ct < data.length; ct++) { + lines[ct * 4 + 0] = (float)ct; + lines[ct * 4 + 1] = 500.f - data[ct]; + lines[ct * 4 + 2] = (float)ct; + lines[ct * 4 + 3] = 500.f; + } + } + + float testPowerManagement(long b) { + float[] dat = new float[mLinesLow.length / 4]; + postTextToView(mTextStatus, "Running single-threaded"); + nRunPowerManagementTest(b, 1); + nGetData(b, dat); + makeGraph(dat, mLinesLow); + mViewToUpdate.postInvalidate(); + float avgMin = calcAverage(dat); + + postTextToView(mTextMin, "Single threaded " + avgMin + " per second"); + + postTextToView(mTextStatus, "Running multi-threaded"); + nRunPowerManagementTest(b, 4); + nGetData(b, dat); + makeGraph(dat, mLinesHigh); + mViewToUpdate.postInvalidate(); + float avgMax = calcAverage(dat); + postTextToView(mTextMax, "Multi threaded " + avgMax + " per second"); + + postTextToView(mTextStatus, "Running typical"); + nRunPowerManagementTest(b, 0); + nGetData(b, dat); + makeGraph(dat, mLinesValue); + mViewToUpdate.postInvalidate(); + float avgTypical = calcAverage(dat); + + float ofIdeal = avgTypical / (avgMax + avgMin) * 200.f; + postTextToView(mTextTypical, String.format("Typical mix (50/50) %%%2.0f of ideal", ofIdeal)); + return ofIdeal * (avgMax + avgMin); + } + + float testCPUHeatSoak(long b) { + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running heat soak test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + float peak = 0.f; + float total = 0.f; + float dThroughput = 0; + float prev = 0; + SummaryStatistics stats = new SummaryStatistics(); + for (int t = 0; t < 1000; t++) { + nRunCPUHeatSoakTest(b, 1); + nGetData(b, dat); + + float p = calcAverage(dat); + if (prev != 0) { + dThroughput += (prev - p); + } + + prev = p; + + mLinesLow[t * 4 + 1] = 499.f - p; + if (peak < p) { + peak = p; + } + for (float f : dat) { + stats.addValue(f); + } + + total += p; + + mViewToUpdate.postInvalidate(); + postTextToView(mTextMin, "Peak " + peak + " per second"); + postTextToView(mTextMax, "Current " + p + " per second"); + postTextToView(mTextTypical, "Average " + (total / (t + 1)) + " per second"); + } + + + float decreaseOverTime = dThroughput / 1000; + + System.out.println("dthroughput/dt: " + decreaseOverTime); + + float score = (float) (stats.getMean() / (stats.getStandardDeviation() * decreaseOverTime)); + + postTextToView(mTextStatus, "Score: " + score); + return score; + } + + void testCPUMemoryBandwidth(long b) { + int[] sizeK = {1, 2, 3, 4, 5, 6, 7, + 8, 10, 12, 14, 16, 20, 24, 28, + 32, 40, 48, 56, 64, 80, 96, 112, + 128, 160, 192, 224, 256, 320, 384, 448, + 512, 640, 768, 896, 1024, 1280, 1536, 1792, + 2048, 2560, 3584, 4096, 5120, 6144, 7168, + 8192, 10240, 12288, 14336, 16384 + }; + final int subSteps = 15; + float[] results = new float[sizeK.length * subSteps]; + + nMemTestStart(b); + + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running Memory Bandwidth test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + for (int i = 0; i < sizeK.length; i++) { + postTextToView(mTextStatus, "Running " + sizeK[i] + " K"); + + float rtot = 0.f; + for (int j = 0; j < subSteps; j++) { + float ret = nMemTestBandwidth(b, sizeK[i] * 1024); + rtot += ret; + results[i * subSteps + j] = ret; + mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - (results[i*15+j] * 20.f); + mViewToUpdate.postInvalidate(); + } + rtot /= subSteps; + + if (sizeK[i] == 2) { + postTextToView(mTextMin, "2K " + rtot + " GB/s"); + } + if (sizeK[i] == 128) { + postTextToView(mTextMax, "128K " + rtot + " GB/s"); + } + if (sizeK[i] == 8192) { + postTextToView(mTextTypical, "8M " + rtot + " GB/s"); + } + + } + + nMemTestEnd(b); + postTextToView(mTextStatus, "Done"); + } + + void testCPUMemoryLatency(long b) { + int[] sizeK = {1, 2, 3, 4, 5, 6, 7, + 8, 10, 12, 14, 16, 20, 24, 28, + 32, 40, 48, 56, 64, 80, 96, 112, + 128, 160, 192, 224, 256, 320, 384, 448, + 512, 640, 768, 896, 1024, 1280, 1536, 1792, + 2048, 2560, 3584, 4096, 5120, 6144, 7168, + 8192, 10240, 12288, 14336, 16384 + }; + final int subSteps = 15; + float[] results = new float[sizeK.length * subSteps]; + + nMemTestStart(b); + + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running Memory Latency test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + for (int i = 0; i < sizeK.length; i++) { + postTextToView(mTextStatus, "Running " + sizeK[i] + " K"); + + float rtot = 0.f; + for (int j = 0; j < subSteps; j++) { + float ret = nMemTestLatency(b, sizeK[i] * 1024); + rtot += ret; + results[i * subSteps + j] = ret; + + if (ret > 400.f) ret = 400.f; + if (ret < 0.f) ret = 0.f; + mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret; + //android.util.Log.e("bench", "test bw " + sizeK[i] + " - " + ret); + mViewToUpdate.postInvalidate(); + } + rtot /= subSteps; + + if (sizeK[i] == 2) { + postTextToView(mTextMin, "2K " + rtot + " ns"); + } + if (sizeK[i] == 128) { + postTextToView(mTextMax, "128K " + rtot + " ns"); + } + if (sizeK[i] == 8192) { + postTextToView(mTextTypical, "8M " + rtot + " ns"); + } + + } + + nMemTestEnd(b); + postTextToView(mTextStatus, "Done"); + } + + void testCPUGFlops(long b) { + int[] sizeK = {1, 2, 3, 4, 5, 6, 7 + }; + final int subSteps = 15; + float[] results = new float[sizeK.length * subSteps]; + + nMemTestStart(b); + + float[] dat = new float[1000]; + postTextToView(mTextStatus, "Running Memory Latency test"); + for (int t = 0; t < 1000; t++) { + mLinesLow[t * 4 + 0] = (float)t; + mLinesLow[t * 4 + 1] = 498.f; + mLinesLow[t * 4 + 2] = (float)t; + mLinesLow[t * 4 + 3] = 500.f; + } + + for (int i = 0; i < sizeK.length; i++) { + postTextToView(mTextStatus, "Running " + sizeK[i] + " K"); + + float rtot = 0.f; + for (int j = 0; j < subSteps; j++) { + float ret = nGFlopsTest(b, sizeK[i] * 1024); + rtot += ret; + results[i * subSteps + j] = ret; + + if (ret > 400.f) ret = 400.f; + if (ret < 0.f) ret = 0.f; + mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret; + mViewToUpdate.postInvalidate(); + } + rtot /= subSteps; + + if (sizeK[i] == 2) { + postTextToView(mTextMin, "2K " + rtot + " ns"); + } + if (sizeK[i] == 128) { + postTextToView(mTextMax, "128K " + rtot + " ns"); + } + if (sizeK[i] == 8192) { + postTextToView(mTextTypical, "8M " + rtot + " ns"); + } + + } + + nMemTestEnd(b); + postTextToView(mTextStatus, "Done"); + } + + public void runPowerManagement() { + mLT.runCommand(mLT.TestPowerManagement); + } + + public void runMemoryBandwidth() { + mLT.runCommand(mLT.TestMemoryBandwidth); + } + + public void runMemoryLatency() { + mLT.runCommand(mLT.TestMemoryLatency); + } + + public void runCPUHeatSoak() { + mLT.runCommand(mLT.TestHeatSoak); + } + + public void runCPUGFlops() { + mLT.runCommand(mLT.TestGFlops); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java new file mode 100644 index 000000000000..f6a528a8a966 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.ui; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +/** + * + */ +public class BitmapUploadActivity extends AppCompatActivity { + private Automator mAutomator; + + public static class UploadView extends View { + private int mColorValue; + private Bitmap mBitmap; + private final DisplayMetrics mMetrics = new DisplayMetrics(); + private final Rect mRect = new Rect(); + + public UploadView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @SuppressWarnings("unused") + public void setColorValue(int colorValue) { + if (colorValue == mColorValue) return; + + mColorValue = colorValue; + + // modify the bitmap's color to ensure it's uploaded to the GPU + mBitmap.eraseColor(Color.rgb(mColorValue, 255 - mColorValue, 255)); + + invalidate(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + getDisplay().getMetrics(mMetrics); + int minDisplayDimen = Math.min(mMetrics.widthPixels, mMetrics.heightPixels); + int bitmapSize = Math.min((int) (minDisplayDimen * 0.75), 720); + if (mBitmap == null + || mBitmap.getWidth() != bitmapSize + || mBitmap.getHeight() != bitmapSize) { + mBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (mBitmap != null) { + mRect.set(0, 0, getWidth(), getHeight()); + canvas.drawBitmap(mBitmap, null, mRect, null); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // animate color to force bitmap uploads + return super.onTouchEvent(event); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bitmap_upload); + + final View uploadRoot = findViewById(R.id.upload_root); + uploadRoot.setKeepScreenOn(true); + uploadRoot.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + UploadView uploadView = (UploadView) findViewById(R.id.upload_view); + ObjectAnimator colorValueAnimator = + ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255); + colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE); + colorValueAnimator.setRepeatCount(100); + colorValueAnimator.start(); + + // animate scene root to guarantee there's a minimum amount of GPU rendering work + ObjectAnimator yAnimator = ObjectAnimator.ofFloat( + view, "translationY", 0, 100); + yAnimator.setRepeatMode(ValueAnimator.REVERSE); + yAnimator.setRepeatCount(100); + yAnimator.start(); + + return true; + } + }); + + final UploadView uploadView = (UploadView) findViewById(R.id.upload_view); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + mAutomator = new Automator("BMUpload", runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + int[] coordinates = new int[2]; + uploadRoot.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = uploadRoot.getWidth(); + float height = uploadRoot.getHeight(); + + float middleX = (x + width) / 5; + float middleY = (y + height) / 5; + + addInteraction(Interaction.newTap(middleX, middleY)); + } + }); + + mAutomator.start(); + } + +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java new file mode 100644 index 000000000000..ea6fb58f4775 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.KeyEvent; +import android.widget.EditText; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +public class EditTextInputActivity extends AppCompatActivity { + + private Automator mAutomator; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final EditText editText = new EditText(this); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + editText.setWidth(400); + editText.setHeight(200); + setContentView(editText); + + String testName = getString(R.string.edit_text_input_name); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(testName); + } + + mAutomator = new Automator(testName, runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + + int[] coordinates = new int[2]; + editText.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = editText.getWidth(); + float height = editText.getHeight(); + + float middleX = (x + width) / 2; + float middleY = (y + height) / 2; + + Interaction tap = Interaction.newTap(middleX, middleY); + addInteraction(tap); + + int[] alphabet = { + KeyEvent.KEYCODE_A, + KeyEvent.KEYCODE_B, + KeyEvent.KEYCODE_C, + KeyEvent.KEYCODE_D, + KeyEvent.KEYCODE_E, + KeyEvent.KEYCODE_F, + KeyEvent.KEYCODE_G, + KeyEvent.KEYCODE_H, + KeyEvent.KEYCODE_I, + KeyEvent.KEYCODE_J, + KeyEvent.KEYCODE_K, + KeyEvent.KEYCODE_L, + KeyEvent.KEYCODE_M, + KeyEvent.KEYCODE_N, + KeyEvent.KEYCODE_O, + KeyEvent.KEYCODE_P, + KeyEvent.KEYCODE_Q, + KeyEvent.KEYCODE_R, + KeyEvent.KEYCODE_S, + KeyEvent.KEYCODE_T, + KeyEvent.KEYCODE_U, + KeyEvent.KEYCODE_V, + KeyEvent.KEYCODE_W, + KeyEvent.KEYCODE_X, + KeyEvent.KEYCODE_Y, + KeyEvent.KEYCODE_Z, + KeyEvent.KEYCODE_SPACE + }; + Interaction typeAlphabet = Interaction.newKeyInput(new int[] { + KeyEvent.KEYCODE_A, + KeyEvent.KEYCODE_B, + KeyEvent.KEYCODE_C, + KeyEvent.KEYCODE_D, + KeyEvent.KEYCODE_E, + KeyEvent.KEYCODE_F, + KeyEvent.KEYCODE_G, + KeyEvent.KEYCODE_H, + KeyEvent.KEYCODE_I, + KeyEvent.KEYCODE_J, + KeyEvent.KEYCODE_K, + KeyEvent.KEYCODE_L, + KeyEvent.KEYCODE_M, + KeyEvent.KEYCODE_N, + KeyEvent.KEYCODE_O, + KeyEvent.KEYCODE_P, + KeyEvent.KEYCODE_Q, + KeyEvent.KEYCODE_R, + KeyEvent.KEYCODE_S, + KeyEvent.KEYCODE_T, + KeyEvent.KEYCODE_U, + KeyEvent.KEYCODE_V, + KeyEvent.KEYCODE_W, + KeyEvent.KEYCODE_X, + KeyEvent.KEYCODE_Y, + KeyEvent.KEYCODE_Z, + KeyEvent.KEYCODE_SPACE, + }); + + for (int i = 0; i < 5; i++) { + addInteraction(typeAlphabet); + } + } + }); + mAutomator.start(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } + + private String getRunFilename() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + builder.append(System.currentTimeMillis()); + return builder.toString(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java new file mode 100644 index 000000000000..95fce3834f9b --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.ui; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; + +import com.android.benchmark.R; +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +public class FullScreenOverdrawActivity extends AppCompatActivity { + + private Automator mAutomator; + + private class OverdrawView extends View { + Paint paint = new Paint(); + int mColorValue = 0; + + public OverdrawView(Context context) { + super(context); + } + + @SuppressWarnings("unused") + public void setColorValue(int colorValue) { + mColorValue = colorValue; + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "colorValue", 0, 255); + objectAnimator.setRepeatMode(ValueAnimator.REVERSE); + objectAnimator.setRepeatCount(100); + objectAnimator.start(); + return super.onTouchEvent(event); + } + + @Override + protected void onDraw(Canvas canvas) { + paint.setColor(Color.rgb(mColorValue, 255 - mColorValue, 255)); + + for (int i = 0; i < 10; i++) { + canvas.drawRect(0, 0, getWidth(), getHeight(), paint); + } + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final OverdrawView overdrawView = new OverdrawView(this); + overdrawView.setKeepScreenOn(true); + setContentView(overdrawView); + + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_overdraw); + + mAutomator = new Automator(name, runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + int[] coordinates = new int[2]; + overdrawView.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = overdrawView.getWidth(); + float height = overdrawView.getHeight(); + + float middleX = (x + width) / 5; + float middleY = (y + height) / 5; + + addInteraction(Interaction.newTap(middleX, middleY)); + } + }); + + mAutomator.start(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java new file mode 100644 index 000000000000..4644ea1b2980 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.ui; + +import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import com.android.benchmark.R; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + +public class ImageListViewScrollActivity extends ListViewScrollActivity { + + private static final int LIST_SIZE = 100; + + private static final int[] IMG_RES_ID = new int[]{ + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + R.drawable.img1, + R.drawable.img2, + R.drawable.img3, + R.drawable.img4, + }; + + private static Bitmap[] mBitmapCache = new Bitmap[IMG_RES_ID.length]; + + private static final String[] WORDS = Utils.buildStringList(LIST_SIZE); + + private HashMap<View, BitmapWorkerTask> mInFlight = new HashMap<>(); + + @Override + protected ListAdapter createListAdapter() { + return new ImageListAdapter(); + } + + @Override + protected String getName() { + return getString(R.string.image_list_view_scroll_name); + } + + class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private int data = 0; + private int cacheIdx = 0; + volatile boolean cancelled = false; + + public BitmapWorkerTask(ImageView imageView, int cacheIdx) { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<>(imageView); + this.cacheIdx = cacheIdx; + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + data = params[0]; + return Utils.decodeSampledBitmapFromResource(getResources(), data, 100, 100); + } + + // Once complete, see if ImageView is still around and set bitmap. + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + if (!cancelled) { + imageView.setImageBitmap(bitmap); + } + mBitmapCache[cacheIdx] = bitmap; + } + } + } + } + + @Override + protected void onPause() { + super.onPause(); + for (int i = 0; i < mBitmapCache.length; i++) { + mBitmapCache[i] = null; + } + } + + class ImageListAdapter extends BaseAdapter { + + @Override + public int getCount() { + return LIST_SIZE; + } + + @Override + public Object getItem(int postition) { + return null; + } + + @Override + public long getItemId(int postition) { + return postition; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(getBaseContext()) + .inflate(R.layout.image_scroll_list_item, parent, false); + } + + ImageView imageView = (ImageView) convertView.findViewById(R.id.image_scroll_image); + BitmapWorkerTask inFlight = mInFlight.get(convertView); + if (inFlight != null) { + inFlight.cancelled = true; + mInFlight.remove(convertView); + } + + int cacheIdx = position % IMG_RES_ID.length; + Bitmap bitmap = mBitmapCache[(cacheIdx)]; + if (bitmap == null) { + BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView, cacheIdx); + bitmapWorkerTask.execute(IMG_RES_ID[(cacheIdx)]); + mInFlight.put(convertView, bitmapWorkerTask); + } + + imageView.setImageBitmap(bitmap); + + TextView textView = (TextView) convertView.findViewById(R.id.image_scroll_text); + textView.setText(WORDS[position]); + + return convertView; + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java new file mode 100644 index 000000000000..b973bc76c13f --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.ui; + +import android.app.ActionBar; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.ListFragment; +import android.support.v7.app.AppCompatActivity; +import android.view.Window; +import android.widget.ListAdapter; + +import com.android.benchmark.R; + +/** + * Simple list activity base class + */ +public abstract class ListActivityBase extends AppCompatActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_list_fragment); + + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setTitle(getName()); + } + + if (findViewById(R.id.list_fragment_container) != null) { + FragmentManager fm = getSupportFragmentManager(); + ListFragment listView = new ListFragment(); + listView.setListAdapter(createListAdapter()); + fm.beginTransaction().add(R.id.list_fragment_container, listView).commit(); + } + } + + protected abstract ListAdapter createListAdapter(); + protected abstract String getName(); +} + diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java new file mode 100644 index 000000000000..3ffb7706675f --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.view.FrameMetrics; +import android.view.MotionEvent; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ListAdapter; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +import java.io.File; +import java.util.List; + +public class ListViewScrollActivity extends ListActivityBase { + + private static final int LIST_SIZE = 400; + private static final int INTERACTION_COUNT = 4; + + private Automator mAutomator; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(getTitle()); + } + + mAutomator = new Automator(getName(), runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onPostInteraction(List<FrameMetrics> metrics) {} + + @Override + public void onAutomate() { + FrameLayout v = (FrameLayout) findViewById(R.id.list_fragment_container); + + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = v.getWidth(); + float height = v.getHeight(); + + float middleX = (x + width) / 5; + float middleY = (y + height) / 5; + + Interaction flingUp = Interaction.newFlingUp(middleX, middleY); + Interaction flingDown = Interaction.newFlingDown(middleX, middleY); + + for (int i = 0; i < INTERACTION_COUNT; i++) { + addInteraction(flingUp); + addInteraction(flingDown); + } + } + }); + + mAutomator.start(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } + + @Override + protected ListAdapter createListAdapter() { + return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + Utils.buildStringList(LIST_SIZE)); + } + + @Override + protected String getName() { + return getString(R.string.list_view_scroll_name); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java new file mode 100644 index 000000000000..68f75a3f277b --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.ListFragment; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import com.android.benchmark.R; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +public class ShadowGridActivity extends AppCompatActivity { + private Automator mAutomator; + public static class MyListFragment extends ListFragment { + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + getListView().setDivider(null); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentById(android.R.id.content) == null) { + ListFragment listFragment = new MyListFragment(); + + listFragment.setListAdapter(new ArrayAdapter<>(this, + R.layout.card_row, R.id.card_text, Utils.buildStringList(200))); + fm.beginTransaction().add(android.R.id.content, listFragment).commit(); + + String testName = getString(R.string.shadow_grid_name); + + mAutomator = new Automator(testName, runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + ListView v = (ListView) findViewById(android.R.id.list); + + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = v.getWidth(); + float height = v.getHeight(); + + float middleX = (x + width) / 2; + float middleY = (y + height) / 2; + + Interaction flingUp = Interaction.newFlingUp(middleX, middleY); + Interaction flingDown = Interaction.newFlingDown(middleX, middleY); + + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + } + }); + mAutomator.start(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java new file mode 100644 index 000000000000..fcd168e988e0 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.android.benchmark.registry.BenchmarkRegistry; +import com.android.benchmark.ui.automation.Automator; +import com.android.benchmark.ui.automation.Interaction; + +import java.io.File; + +public class TextScrollActivity extends ListActivityBase { + + public static final String EXTRA_HIT_RATE = ".TextScrollActivity.EXTRA_HIT_RATE"; + + private static final int PARAGRAPH_COUNT = 200; + + private int mHitPercentage = 100; + private Automator mAutomator; + private String mName; + + @Override + public void onCreate(Bundle savedInstanceState) { + mHitPercentage = getIntent().getIntExtra(EXTRA_HIT_RATE, + mHitPercentage); + super.onCreate(savedInstanceState); + final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0); + final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1); + final int id = getIntent().getIntExtra(BenchmarkRegistry.EXTRA_ID, -1); + + if (id == -1) { + finish(); + return; + } + + mName = BenchmarkRegistry.getBenchmarkName(this, id); + + mAutomator = new Automator(getName(), runId, iteration, getWindow(), + new Automator.AutomateCallback() { + @Override + public void onPostAutomate() { + Intent result = new Intent(); + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onAutomate() { + ListView v = (ListView) findViewById(android.R.id.list); + + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + + int x = coordinates[0]; + int y = coordinates[1]; + + float width = v.getWidth(); + float height = v.getHeight(); + + float middleX = (x + width) / 2; + float middleY = (y + height) / 2; + + Interaction flingUp = Interaction.newFlingUp(middleX, middleY); + Interaction flingDown = Interaction.newFlingDown(middleX, middleY); + + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + addInteraction(flingUp); + addInteraction(flingDown); + } + }); + + mAutomator.start(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mAutomator != null) { + mAutomator.cancel(); + mAutomator = null; + } + } + + @Override + protected ListAdapter createListAdapter() { + return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + Utils.buildParagraphListWithHitPercentage(PARAGRAPH_COUNT, 80)); + } + + @Override + protected String getName() { + return mName; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java new file mode 100644 index 000000000000..39f9206d4e07 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.ui; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.util.Random; + +public class Utils { + + private static final int RANDOM_WORD_LENGTH = 10; + + public static String getRandomWord(Random random, int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + char base = random.nextBoolean() ? 'A' : 'a'; + char nextChar = (char)(random.nextInt(26) + base); + builder.append(nextChar); + } + return builder.toString(); + } + + public static String[] buildStringList(int count) { + Random random = new Random(0); + String[] result = new String[count]; + for (int i = 0; i < count; i++) { + result[i] = getRandomWord(random, RANDOM_WORD_LENGTH); + } + + return result; + } + + // a small number of strings reused frequently, expected to hit + // in the word-granularity text layout cache + static final String[] CACHE_HIT_STRINGS = new String[] { + "a", + "small", + "number", + "of", + "strings", + "reused", + "frequently" + }; + + private static final int WORDS_IN_PARAGRAPH = 150; + + // misses are fairly long 'words' to ensure they miss + private static final int PARAGRAPH_MISS_MIN_LENGTH = 4; + private static final int PARAGRAPH_MISS_MAX_LENGTH = 9; + + static String[] buildParagraphListWithHitPercentage(int paragraphCount, int hitPercentage) { + if (hitPercentage < 0 || hitPercentage > 100) throw new IllegalArgumentException(); + + String[] strings = new String[paragraphCount]; + Random random = new Random(0); + for (int i = 0; i < strings.length; i++) { + StringBuilder result = new StringBuilder(); + for (int word = 0; word < WORDS_IN_PARAGRAPH; word++) { + if (word != 0) { + result.append(" "); + } + if (random.nextInt(100) < hitPercentage) { + // add a common word, which is very likely to hit in the cache + result.append(CACHE_HIT_STRINGS[random.nextInt(CACHE_HIT_STRINGS.length)]); + } else { + // construct a random word, which will *most likely* miss + int length = PARAGRAPH_MISS_MIN_LENGTH; + length += random.nextInt(PARAGRAPH_MISS_MAX_LENGTH - PARAGRAPH_MISS_MIN_LENGTH); + + result.append(getRandomWord(random, length)); + } + } + strings[i] = result.toString(); + } + + return strings; + } + + + public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, + int reqWidth, int reqHeight) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); + } + +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java new file mode 100644 index 000000000000..1efd6bc2aba6 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.ui.automation; + +import android.annotation.TargetApi; +import android.app.Instrumentation; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.view.FrameMetrics; +import android.view.MotionEvent; +import android.view.ViewTreeObserver; +import android.view.Window; + +import com.android.benchmark.results.GlobalResultsStore; +import com.android.benchmark.results.UiBenchmarkResult; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +@TargetApi(24) +public class Automator extends HandlerThread + implements ViewTreeObserver.OnGlobalLayoutListener, CollectorThread.CollectorListener { + public static final long FRAME_PERIOD_MILLIS = 16; + + private static final int PRE_READY_STATE_COUNT = 3; + private static final String TAG = "Benchmark.Automator"; + private final AtomicInteger mReadyState; + + private AutomateCallback mCallback; + private Window mWindow; + private AutomatorHandler mHandler; + private CollectorThread mCollectorThread; + private int mRunId; + private int mIteration; + private String mTestName; + + public static class AutomateCallback { + public void onAutomate() {} + public void onPostInteraction(List<FrameMetrics> metrics) {} + public void onPostAutomate() {} + + protected final void addInteraction(Interaction interaction) { + if (mInteractions == null) { + return; + } + + mInteractions.add(interaction); + } + + protected final void setInteractions(List<Interaction> interactions) { + mInteractions = interactions; + } + + private List<Interaction> mInteractions; + } + + private static final class AutomatorHandler extends Handler { + public static final int MSG_NEXT_INTERACTION = 0; + public static final int MSG_ON_AUTOMATE = 1; + public static final int MSG_ON_POST_INTERACTION = 2; + private final String mTestName; + private final int mRunId; + private final int mIteration; + + private Instrumentation mInstrumentation; + private volatile boolean mCancelled; + private CollectorThread mCollectorThread; + private AutomateCallback mCallback; + private Window mWindow; + + LinkedList<Interaction> mInteractions; + private UiBenchmarkResult mResults; + + AutomatorHandler(Looper looper, Window window, CollectorThread collectorThread, + AutomateCallback callback, String testName, int runId, int iteration) { + super(looper); + + mInstrumentation = new Instrumentation(); + + mCallback = callback; + mWindow = window; + mCollectorThread = collectorThread; + mInteractions = new LinkedList<>(); + mTestName = testName; + mRunId = runId; + mIteration = iteration; + } + + @Override + public void handleMessage(Message msg) { + if (mCancelled) { + return; + } + + switch (msg.what) { + case MSG_NEXT_INTERACTION: + if (!nextInteraction()) { + stopCollector(); + writeResults(); + mCallback.onPostAutomate(); + } + break; + case MSG_ON_AUTOMATE: + mCollectorThread.attachToWindow(mWindow); + mCallback.setInteractions(mInteractions); + mCallback.onAutomate(); + postNextInteraction(); + break; + case MSG_ON_POST_INTERACTION: + List<FrameMetrics> collectedStats = (List<FrameMetrics>)msg.obj; + persistResults(collectedStats); + mCallback.onPostInteraction(collectedStats); + postNextInteraction(); + break; + } + } + + public void cancel() { + mCancelled = true; + stopCollector(); + } + + private void stopCollector() { + mCollectorThread.quitCollector(); + } + + private boolean nextInteraction() { + + Interaction interaction = mInteractions.poll(); + if (interaction != null) { + doInteraction(interaction); + return true; + } + return false; + } + + private void doInteraction(Interaction interaction) { + if (mCancelled) { + return; + } + + mCollectorThread.markInteractionStart(); + + if (interaction.getType() == Interaction.Type.KEY_EVENT) { + for (int code : interaction.getKeyCodes()) { + if (!mCancelled) { + mInstrumentation.sendKeyDownUpSync(code); + } else { + break; + } + } + } else { + for (MotionEvent event : interaction.getEvents()) { + if (!mCancelled) { + mInstrumentation.sendPointerSync(event); + } else { + break; + } + } + } + } + + protected void postNextInteraction() { + final Message msg = obtainMessage(AutomatorHandler.MSG_NEXT_INTERACTION); + sendMessage(msg); + } + + private void persistResults(List<FrameMetrics> stats) { + if (stats.isEmpty()) { + return; + } + + if (mResults == null) { + mResults = new UiBenchmarkResult(stats); + } else { + mResults.update(stats); + } + } + + private void writeResults() { + GlobalResultsStore.getInstance(mWindow.getContext()) + .storeRunResults(mTestName, mRunId, mIteration, mResults); + } + } + + private void initHandler() { + mHandler = new AutomatorHandler(getLooper(), mWindow, mCollectorThread, mCallback, + mTestName, mRunId, mIteration); + mWindow = null; + mCallback = null; + mCollectorThread = null; + mTestName = null; + mRunId = 0; + mIteration = 0; + } + + @Override + public final void onGlobalLayout() { + if (!mCollectorThread.isAlive()) { + mCollectorThread.start(); + mWindow.getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this); + mReadyState.decrementAndGet(); + } + } + + @Override + public void onCollectorThreadReady() { + if (mReadyState.decrementAndGet() == 0) { + initHandler(); + postOnAutomate(); + } + } + + @Override + protected void onLooperPrepared() { + if (mReadyState.decrementAndGet() == 0) { + initHandler(); + postOnAutomate(); + } + } + + @Override + public void onPostInteraction(List<FrameMetrics> stats) { + Message m = mHandler.obtainMessage(AutomatorHandler.MSG_ON_POST_INTERACTION, stats); + mHandler.sendMessage(m); + } + + protected void postOnAutomate() { + final Message msg = mHandler.obtainMessage(AutomatorHandler.MSG_ON_AUTOMATE); + mHandler.sendMessage(msg); + } + + public void cancel() { + mHandler.removeMessages(AutomatorHandler.MSG_NEXT_INTERACTION); + mHandler.cancel(); + mHandler = null; + } + + public Automator(String testName, int runId, int iteration, + Window window, AutomateCallback callback) { + super("AutomatorThread"); + + mTestName = testName; + mRunId = runId; + mIteration = iteration; + mCallback = callback; + mWindow = window; + mWindow.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this); + mCollectorThread = new CollectorThread(this); + mReadyState = new AtomicInteger(PRE_READY_STATE_COUNT); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java new file mode 100644 index 000000000000..806c704d8a09 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark.ui.automation; + +import android.annotation.TargetApi; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.SystemClock; +import android.view.FrameMetrics; +import android.view.Window; + +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +final class CollectorThread extends HandlerThread { + private FrameStatsCollector mCollector; + private Window mAttachedWindow; + private List<FrameMetrics> mFrameTimingStats; + private long mLastFrameTime; + private WatchdogHandler mWatchdog; + private WeakReference<CollectorListener> mListener; + + private volatile boolean mCollecting; + + + interface CollectorListener { + void onCollectorThreadReady(); + void onPostInteraction(List<FrameMetrics> stats); + } + + private final class WatchdogHandler extends Handler { + private static final long SCHEDULE_INTERVAL_MILLIS = 20 * Automator.FRAME_PERIOD_MILLIS; + + private static final int MSG_SCHEDULE = 0; + + @Override + public void handleMessage(Message msg) { + if (!mCollecting) { + return; + } + + long currentTime = SystemClock.uptimeMillis(); + if (mLastFrameTime + SCHEDULE_INTERVAL_MILLIS <= currentTime) { + // haven't seen a frame in a while, interaction is probably done + mCollecting = false; + CollectorListener listener = mListener.get(); + if (listener != null) { + listener.onPostInteraction(mFrameTimingStats); + } + } else { + schedule(); + } + } + + public void schedule() { + sendMessageDelayed(obtainMessage(MSG_SCHEDULE), SCHEDULE_INTERVAL_MILLIS); + } + + public void deschedule() { + removeMessages(MSG_SCHEDULE); + } + } + + static boolean tripleBuffered = false; + static int janks = 0; + static int total = 0; + @TargetApi(24) + private class FrameStatsCollector implements Window.OnFrameMetricsAvailableListener { + @Override + public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) { + if (!mCollecting) { + return; + } + mFrameTimingStats.add(new FrameMetrics(frameMetrics)); + mLastFrameTime = SystemClock.uptimeMillis(); + } + } + + public CollectorThread(CollectorListener listener) { + super("FrameStatsCollectorThread"); + mFrameTimingStats = new LinkedList<>(); + mListener = new WeakReference<>(listener); + } + + @TargetApi(24) + public void attachToWindow(Window window) { + if (mAttachedWindow != null) { + mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector); + } + + mAttachedWindow = window; + window.addOnFrameMetricsAvailableListener(mCollector, new Handler(getLooper())); + } + + @TargetApi(24) + public synchronized void detachFromWindow() { + if (mAttachedWindow != null) { + mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector); + } + + mAttachedWindow = null; + } + + @TargetApi(24) + @Override + protected void onLooperPrepared() { + super.onLooperPrepared(); + mCollector = new FrameStatsCollector(); + mWatchdog = new WatchdogHandler(); + + CollectorListener listener = mListener.get(); + if (listener != null) { + listener.onCollectorThreadReady(); + } + } + + public boolean quitCollector() { + stopCollecting(); + detachFromWindow(); + System.out.println("Jank Percentage: " + (100 * janks/ (double) total) + "%"); + tripleBuffered = false; + total = 0; + janks = 0; + return quit(); + } + + void stopCollecting() { + if (!mCollecting) { + return; + } + + mCollecting = false; + mWatchdog.deschedule(); + + + } + + public void markInteractionStart() { + mLastFrameTime = 0; + mFrameTimingStats.clear(); + mCollecting = true; + + mWatchdog.schedule(); + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java new file mode 100644 index 000000000000..1fd0998f8dd6 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.ui.automation; + +import android.support.annotation.IntDef; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Arrays; + +public class FrameTimingStats { + @IntDef ({ + Index.FLAGS, + Index.INTENDED_VSYNC, + Index.VSYNC, + Index.OLDEST_INPUT_EVENT, + Index.NEWEST_INPUT_EVENT, + Index.HANDLE_INPUT_START, + Index.ANIMATION_START, + Index.PERFORM_TRAVERSALS_START, + Index.DRAW_START, + Index.SYNC_QUEUED, + Index.SYNC_START, + Index.ISSUE_DRAW_COMMANDS_START, + Index.SWAP_BUFFERS, + Index.FRAME_COMPLETED, + }) + public @interface Index { + int FLAGS = 0; + int INTENDED_VSYNC = 1; + int VSYNC = 2; + int OLDEST_INPUT_EVENT = 3; + int NEWEST_INPUT_EVENT = 4; + int HANDLE_INPUT_START = 5; + int ANIMATION_START = 6; + int PERFORM_TRAVERSALS_START = 7; + int DRAW_START = 8; + int SYNC_QUEUED = 9; + int SYNC_START = 10; + int ISSUE_DRAW_COMMANDS_START = 11; + int SWAP_BUFFERS = 12; + int FRAME_COMPLETED = 13; + + int FRAME_STATS_COUNT = 14; // must always be last + } + + private final long[] mStats; + + FrameTimingStats(long[] stats) { + mStats = Arrays.copyOf(stats, Index.FRAME_STATS_COUNT); + } + + public FrameTimingStats(DataInputStream inputStream) throws IOException { + mStats = new long[Index.FRAME_STATS_COUNT]; + update(inputStream); + } + + public void update(DataInputStream inputStream) throws IOException { + for (int i = 0; i < mStats.length; i++) { + mStats[i] = inputStream.readLong(); + } + } + + public long get(@Index int index) { + return mStats[index]; + } + + public long[] data() { + return mStats; + } +} diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java new file mode 100644 index 000000000000..370fed28e1b2 --- /dev/null +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the + * License. + * + */ + +package com.android.benchmark.ui.automation; + +import android.os.SystemClock; +import android.support.annotation.IntDef; +import android.view.MotionEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encodes a UI interaction as a series of MotionEvents + */ +public class Interaction { + private static final int STEP_COUNT = 20; + // TODO: scale to device display density + private static final int DEFAULT_FLING_SIZE_PX = 500; + private static final int DEFAULT_FLING_DURATION_MS = 20; + private static final int DEFAULT_TAP_DURATION_MS = 20; + private List<MotionEvent> mEvents; + + // Interaction parameters + private final float[] mXPositions; + private final float[] mYPositions; + private final long mDuration; + private final int[] mKeyCodes; + private final @Interaction.Type int mType; + + @IntDef({ + Interaction.Type.TAP, + Interaction.Type.FLING, + Interaction.Type.PINCH, + Interaction.Type.KEY_EVENT}) + public @interface Type { + int TAP = 0; + int FLING = 1; + int PINCH = 2; + int KEY_EVENT = 3; + } + + public static Interaction newFling(float startX, float startY, + float endX, float endY, long duration) { + return new Interaction(Interaction.Type.FLING, new float[]{startX, endX}, + new float[]{startY, endY}, duration); + } + + public static Interaction newFlingDown(float startX, float startY) { + return new Interaction(Interaction.Type.FLING, + new float[]{startX, startX}, + new float[]{startY, startY + DEFAULT_FLING_SIZE_PX}, DEFAULT_FLING_DURATION_MS); + } + + public static Interaction newFlingUp(float startX, float startY) { + return new Interaction(Interaction.Type.FLING, + new float[]{startX, startX}, new float[]{startY, startY - DEFAULT_FLING_SIZE_PX}, + DEFAULT_FLING_DURATION_MS); + } + + public static Interaction newTap(float startX, float startY) { + return new Interaction(Interaction.Type.TAP, + new float[]{startX, startX}, new float[]{startY, startY}, + DEFAULT_FLING_DURATION_MS); + } + + public static Interaction newKeyInput(int[] keyCodes) { + return new Interaction(keyCodes); + } + + public List<MotionEvent> getEvents() { + switch (mType) { + case Type.FLING: + mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration); + break; + case Type.PINCH: + break; + case Type.TAP: + mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration); + break; + } + + return mEvents; + } + + public int getType() { + return mType; + } + + public int[] getKeyCodes() { + return mKeyCodes; + } + + private static List<MotionEvent> createInterpolatedEventList( + float[] xPos, float[] yPos, long duration) { + long startTime = SystemClock.uptimeMillis() + 100; + List<MotionEvent> result = new ArrayList<>(); + + float startX = xPos[0]; + float startY = yPos[0]; + + MotionEvent downEvent = MotionEvent.obtain( + startTime, startTime, MotionEvent.ACTION_DOWN, startX, startY, 0); + result.add(downEvent); + + for (int i = 1; i < xPos.length; i++) { + float endX = xPos[i]; + float endY = yPos[i]; + float stepX = (endX - startX) / STEP_COUNT; + float stepY = (endY - startY) / STEP_COUNT; + float stepT = duration / STEP_COUNT; + + for (int j = 0; j < STEP_COUNT; j++) { + long deltaT = Math.round(j * stepT); + long deltaX = Math.round(j * stepX); + long deltaY = Math.round(j * stepY); + MotionEvent moveEvent = MotionEvent.obtain(startTime, startTime + deltaT, + MotionEvent.ACTION_MOVE, startX + deltaX, startY + deltaY, 0); + result.add(moveEvent); + } + + startX = endX; + startY = endY; + } + + float lastX = xPos[xPos.length - 1]; + float lastY = yPos[yPos.length - 1]; + MotionEvent lastEvent = MotionEvent.obtain(startTime, startTime + duration, + MotionEvent.ACTION_UP, lastX, lastY, 0); + result.add(lastEvent); + + return result; + } + + private Interaction(@Interaction.Type int type, + float[] xPos, float[] yPos, long duration) { + mType = type; + mXPositions = xPos; + mYPositions = yPos; + mDuration = duration; + mKeyCodes = null; + } + + private Interaction(int[] codes) { + mKeyCodes = codes; + mType = Type.KEY_EVENT; + mYPositions = null; + mXPositions = null; + mDuration = 0; + } + + private Interaction(@Interaction.Type int type, + List<Float> xPositions, List<Float> yPositions, long duration) { + if (xPositions.size() != yPositions.size()) { + throw new IllegalArgumentException("must have equal number of x and y positions"); + } + + int current = 0; + mXPositions = new float[xPositions.size()]; + for (float p : xPositions) { + mXPositions[current++] = p; + } + + current = 0; + mYPositions = new float[yPositions.size()]; + for (float p : xPositions) { + mXPositions[current++] = p; + } + + mType = type; + mDuration = duration; + mKeyCodes = null; + } +} diff --git a/tests/JankBench/app/src/main/jni/Android.mk b/tests/JankBench/app/src/main/jni/Android.mk new file mode 100644 index 000000000000..8ba874de0e8a --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Android.mk @@ -0,0 +1,31 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) +LOCAL_SDK_VERSION := 26 + +include $(CLEAR_VARS) + +LOCAL_CFLAGS = -Wno-unused-parameter + +LOCAL_MODULE:= libnativebench + +LOCAL_SRC_FILES := \ + Bench.cpp \ + WorkerPool.cpp \ + test.cpp + +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) diff --git a/tests/JankBench/app/src/main/jni/Application.mk b/tests/JankBench/app/src/main/jni/Application.mk new file mode 100644 index 000000000000..09bc0aca14f1 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Application.mk @@ -0,0 +1,17 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +APP_ABI := armeabi + +APP_MODULES := nativebench diff --git a/tests/JankBench/app/src/main/jni/Bench.cpp b/tests/JankBench/app/src/main/jni/Bench.cpp new file mode 100644 index 000000000000..fbb4f11fa988 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Bench.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/log.h> +#include <math.h> +#include <stdlib.h> +#include <unistd.h> + +#include "Bench.h" + + +Bench::Bench() +{ + mTimeBucket = NULL; + mTimeBuckets = 0; + mTimeBucketDivisor = 1; + + mMemLatencyLastSize = 0; + mMemDst = NULL; + mMemSrc = NULL; + mMemLoopCount = 0; +} + + +Bench::~Bench() +{ +} + +uint64_t Bench::getTimeNanos() const +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000); +} + +uint64_t Bench::getTimeMillis() const +{ + return getTimeNanos() / 1000000; +} + + +void Bench::testWork(void *usr, uint32_t idx) +{ + Bench *b = (Bench *)usr; + //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i %p", idx, b); + + float f1 = 0.f; + float f2 = 0.f; + float f3 = 0.f; + float f4 = 0.f; + + float *ipk = b->mIpKernel[idx]; + volatile float *src = b->mSrcBuf[idx]; + volatile float *out = b->mOutBuf[idx]; + + //__android_log_print(ANDROID_LOG_INFO, "bench", "test %p %p %p", ipk, src, out); + + do { + + for (int i = 0; i < 1024; i++) { + f1 += src[i * 4] * ipk[i]; + f2 += src[i * 4 + 1] * ipk[i]; + f3 += src[i * 4 + 2] * ipk[i]; + f4 += sqrtf(f1 + f2 + f3); + } + out[0] = f1; + out[1] = f2; + out[2] = f3; + out[3] = f4; + + } while (b->incTimeBucket()); +} + +bool Bench::initIP() { + int workers = mWorkers.getWorkerCount(); + + mIpKernel = new float *[workers]; + mSrcBuf = new float *[workers]; + mOutBuf = new float *[workers]; + + for (int i = 0; i < workers; i++) { + mIpKernel[i] = new float[1024]; + mSrcBuf[i] = new float[4096]; + mOutBuf[i] = new float[4]; + } + + return true; +} + +bool Bench::runPowerManagementTest(uint64_t options) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt x %i", options); + + mTimeBucketDivisor = 1000 * 1000; // use ms + allocateBuckets(2 * 1000); + + usleep(2 * 1000 * 1000); + + //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2 b %i", mTimeBuckets); + + mTimeStartNanos = getTimeNanos(); + mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor; + memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets); + + bool useMT = false; + + //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2.1 b %i", mTimeBuckets); + mTimeEndGroupNanos = mTimeStartNanos; + do { + // Advance 8ms + mTimeEndGroupNanos += 8 * 1000 * 1000; + + int threads = useMT ? 1 : 0; + useMT = !useMT; + if ((options & 0x1f) != 0) { + threads = options & 0x1f; + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "threads %i", threads); + + mWorkers.launchWork(testWork, this, threads); + } while (mTimeEndGroupNanos <= mTimeEndNanos); + + return true; +} + +bool Bench::allocateBuckets(size_t bucketCount) { + if (bucketCount == mTimeBuckets) { + return true; + } + + if (mTimeBucket != NULL) { + delete[] mTimeBucket; + mTimeBucket = NULL; + } + + mTimeBuckets = bucketCount; + if (mTimeBuckets > 0) { + mTimeBucket = new uint32_t[mTimeBuckets]; + } + + return true; +} + +bool Bench::init() { + mWorkers.init(); + + initIP(); + //ALOGV("%p Launching thread(s), CPUs %i", mRSC, mWorkers.mCount + 1); + + return true; +} + +bool Bench::incTimeBucket() const { + uint64_t time = getTimeNanos(); + uint64_t bucket = (time - mTimeStartNanos) / mTimeBucketDivisor; + + if (bucket >= mTimeBuckets) { + return false; + } + + __sync_fetch_and_add(&mTimeBucket[bucket], 1); + + return time < mTimeEndGroupNanos; +} + +void Bench::getData(float *data, size_t count) const { + if (count > mTimeBuckets) { + count = mTimeBuckets; + } + for (size_t ct = 0; ct < count; ct++) { + data[ct] = (float)mTimeBucket[ct]; + } +} + +bool Bench::runCPUHeatSoak(uint64_t /* options */) +{ + mTimeBucketDivisor = 1000 * 1000; // use ms + allocateBuckets(1000); + + mTimeStartNanos = getTimeNanos(); + mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor; + memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets); + + mTimeEndGroupNanos = mTimeEndNanos; + mWorkers.launchWork(testWork, this, 0); + return true; +} + +float Bench::runMemoryBandwidthTest(uint64_t size) +{ + uint64_t t1 = getTimeMillis(); + for (size_t ct = mMemLoopCount; ct > 0; ct--) { + memcpy(mMemDst, mMemSrc, size); + } + double dt = getTimeMillis() - t1; + dt /= 1000; + + double bw = ((double)size) * mMemLoopCount / dt; + bw /= 1024 * 1024 * 1024; + + float targetTime = 0.2f; + if (dt > targetTime) { + mMemLoopCount = (size_t)((double)mMemLoopCount / (dt / targetTime)); + } + + return (float)bw; +} + +float Bench::runMemoryLatencyTest(uint64_t size) +{ + //__android_log_print(ANDROID_LOG_INFO, "bench", "latency %i", (int)size); + void ** sp = (void **)mMemSrc; + size_t maxIndex = size / sizeof(void *); + size_t loops = ((maxIndex / 2) & (~3)); + //loops = 10; + + if (size != mMemLatencyLastSize) { + __android_log_print(ANDROID_LOG_INFO, "bench", "latency build %i %i", (int)maxIndex, loops); + mMemLatencyLastSize = size; + memset((void *)mMemSrc, 0, mMemLatencyLastSize); + + size_t lastIdx = 0; + for (size_t ct = 0; ct < loops; ct++) { + size_t ni = rand() * rand(); + ni = ni % maxIndex; + while ((sp[ni] != NULL) || (ni == lastIdx)) { + ni++; + if (ni >= maxIndex) { + ni = 1; + } + // __android_log_print(ANDROID_LOG_INFO, "bench", "gen ni loop %i %i", lastIdx, ni); + } + // __android_log_print(ANDROID_LOG_INFO, "bench", "gen ct = %i %i %i %p %p", (int)ct, lastIdx, ni, &sp[lastIdx], &sp[ni]); + sp[lastIdx] = &sp[ni]; + lastIdx = ni; + } + sp[lastIdx] = 0; + } + //__android_log_print(ANDROID_LOG_INFO, "bench", "latency testing"); + + uint64_t t1 = getTimeNanos(); + for (size_t ct = mMemLoopCount; ct > 0; ct--) { + size_t lc = 1; + volatile void *p = sp[0]; + while (p != NULL) { + // Unroll once to minimize branching overhead. + void **pn = (void **)p; + p = pn[0]; + pn = (void **)p; + p = pn[0]; + } + } + //__android_log_print(ANDROID_LOG_INFO, "bench", "v %i %i", loops * mMemLoopCount, v); + + double dt = getTimeNanos() - t1; + double dts = dt / 1000000000; + double lat = dt / (loops * mMemLoopCount); + __android_log_print(ANDROID_LOG_INFO, "bench", "latency ret %f", lat); + + float targetTime = 0.2f; + if (dts > targetTime) { + mMemLoopCount = (size_t)((double)mMemLoopCount / (dts / targetTime)); + if (mMemLoopCount < 1) { + mMemLoopCount = 1; + } + } + + return (float)lat; +} + +bool Bench::startMemTests() +{ + mMemSrc = (uint8_t *)malloc(1024*1024*64); + mMemDst = (uint8_t *)malloc(1024*1024*64); + + memset(mMemSrc, 0, 1024*1024*16); + memset(mMemDst, 0, 1024*1024*16); + + mMemLoopCount = 1; + uint64_t start = getTimeMillis(); + while((getTimeMillis() - start) < 500) { + memcpy(mMemDst, mMemSrc, 1024); + mMemLoopCount++; + } + mMemLatencyLastSize = 0; + return true; +} + +void Bench::endMemTests() +{ + free(mMemSrc); + free(mMemDst); + mMemSrc = NULL; + mMemDst = NULL; + mMemLatencyLastSize = 0; +} + +void Bench::GflopKernelC() { + int halfKX = (mGFlop.kernelXSize / 2); + for (int x = halfKX; x < (mGFlop.imageXSize - halfKX - 1); x++) { + const float * krnPtr = mGFlop.kernelBuffer; + float sum = 0.f; + + int srcInc = mGFlop.imageXSize - mGFlop.kernelXSize; + const float * srcPtr = &mGFlop.srcBuffer[x - halfKX]; + + for (int ix = 0; ix < mGFlop.kernelXSize; ix++) { + sum += srcPtr[0] * krnPtr[0]; + krnPtr++; + srcPtr++; + } + + float * dstPtr = &mGFlop.dstBuffer[x]; + dstPtr[0] = sum; + + } + +} + +void Bench::GflopKernelC_y3() { +} + +float Bench::runGFlopsTest(uint64_t /* options */) +{ + mTimeBucketDivisor = 1000 * 1000; // use ms + allocateBuckets(1000); + + mTimeStartNanos = getTimeNanos(); + mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor; + memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets); + + mTimeEndGroupNanos = mTimeEndNanos; + mWorkers.launchWork(testWork, this, 0); + + // Simulate image convolve + mGFlop.kernelXSize = 27; + mGFlop.imageXSize = 1024 * 1024; + + mGFlop.srcBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float)); + mGFlop.dstBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float)); + mGFlop.kernelBuffer = (float *)malloc(mGFlop.kernelXSize * sizeof(float)); + + double ops = mGFlop.kernelXSize; + ops = ops * 2.f - 1.f; + ops *= mGFlop.imageXSize; + + uint64_t t1 = getTimeNanos(); + GflopKernelC(); + double dt = getTimeNanos() - t1; + + dt /= 1000.f * 1000.f * 1000.f; + + double gflops = ops / dt / 1000000000.f; + + __android_log_print(ANDROID_LOG_INFO, "bench", "v %f %f %f", dt, ops, gflops); + + return (float)gflops; +} + + diff --git a/tests/JankBench/app/src/main/jni/Bench.h b/tests/JankBench/app/src/main/jni/Bench.h new file mode 100644 index 000000000000..43a9066d6c2c --- /dev/null +++ b/tests/JankBench/app/src/main/jni/Bench.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_BENCH_H +#define ANDROID_BENCH_H + +#include <pthread.h> + +#include "WorkerPool.h" + +#include <string.h> + + + +class Bench { +public: + Bench(); + ~Bench(); + + struct GFlop { + int kernelXSize; + //int kernelYSize; + int imageXSize; + //int imageYSize; + + float *srcBuffer; + float *kernelBuffer; + float *dstBuffer; + + + }; + GFlop mGFlop; + + bool init(); + + bool runPowerManagementTest(uint64_t options); + bool runCPUHeatSoak(uint64_t options); + + bool startMemTests(); + void endMemTests(); + float runMemoryBandwidthTest(uint64_t options); + float runMemoryLatencyTest(uint64_t options); + + float runGFlopsTest(uint64_t options); + + void getData(float *data, size_t count) const; + + + void finish(); + + void setPriority(int32_t p); + void destroyWorkerThreadResources(); + + uint64_t getTimeNanos() const; + uint64_t getTimeMillis() const; + + // Adds a work unit completed to the timeline and returns + // true if the test is ongoing, false if time is up + bool incTimeBucket() const; + + +protected: + WorkerPool mWorkers; + + bool mExit; + bool mPaused; + + static void testWork(void *usr, uint32_t idx); + +private: + uint8_t * volatile mMemSrc; + uint8_t * volatile mMemDst; + size_t mMemLoopCount; + size_t mMemLatencyLastSize; + + + float ** mIpKernel; + float * volatile * mSrcBuf; + float * volatile * mOutBuf; + uint32_t * mTimeBucket; + + uint64_t mTimeStartNanos; + uint64_t mTimeEndNanos; + uint64_t mTimeBucketDivisor; + uint32_t mTimeBuckets; + + uint64_t mTimeEndGroupNanos; + + bool initIP(); + void GflopKernelC(); + void GflopKernelC_y3(); + + bool allocateBuckets(size_t); + + +}; + + +#endif diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.cpp b/tests/JankBench/app/src/main/jni/WorkerPool.cpp new file mode 100644 index 000000000000..a92ac9152987 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/WorkerPool.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "WorkerPool.h" +//#include <atomic> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <android/log.h> + + +//static pthread_key_t gThreadTLSKey = 0; +//static uint32_t gThreadTLSKeyCount = 0; +//static pthread_mutex_t gInitMutex = PTHREAD_MUTEX_INITIALIZER; + + +WorkerPool::Signal::Signal() { + mSet = true; +} + +WorkerPool::Signal::~Signal() { + pthread_mutex_destroy(&mMutex); + pthread_cond_destroy(&mCondition); +} + +bool WorkerPool::Signal::init() { + int status = pthread_mutex_init(&mMutex, NULL); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool mutex init failure"); + return false; + } + + status = pthread_cond_init(&mCondition, NULL); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool condition init failure"); + pthread_mutex_destroy(&mMutex); + return false; + } + + return true; +} + +void WorkerPool::Signal::set() { + int status; + + status = pthread_mutex_lock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for set condition.", status); + return; + } + + mSet = true; + + status = pthread_cond_signal(&mCondition); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i on set condition.", status); + } + + status = pthread_mutex_unlock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for set condition.", status); + } +} + +bool WorkerPool::Signal::wait(uint64_t timeout) { + int status; + bool ret = false; + + status = pthread_mutex_lock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for condition.", status); + return false; + } + + if (!mSet) { + if (!timeout) { + status = pthread_cond_wait(&mCondition, &mMutex); + } else { +#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE) + status = pthread_cond_timeout_np(&mCondition, &mMutex, timeout / 1000000); +#else + // This is safe it will just make things less reponsive + status = pthread_cond_wait(&mCondition, &mMutex); +#endif + } + } + + if (!status) { + mSet = false; + ret = true; + } else { +#ifndef RS_SERVER + if (status != ETIMEDOUT) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i waiting for condition.", status); + } +#endif + } + + status = pthread_mutex_unlock(&mMutex); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for condition.", status); + } + + return ret; +} + + + +WorkerPool::WorkerPool() { + mExit = false; + mRunningCount = 0; + mLaunchCount = 0; + mCount = 0; + mThreadId = NULL; + mNativeThreadId = NULL; + mLaunchSignals = NULL; + mLaunchCallback = NULL; + + +} + + +WorkerPool::~WorkerPool() { +__android_log_print(ANDROID_LOG_INFO, "bench", "~wp"); + mExit = true; + mLaunchData = NULL; + mLaunchCallback = NULL; + mRunningCount = mCount; + + __sync_synchronize(); + for (uint32_t ct = 0; ct < mCount; ct++) { + mLaunchSignals[ct].set(); + } + void *res; + for (uint32_t ct = 0; ct < mCount; ct++) { + pthread_join(mThreadId[ct], &res); + } + //rsAssert(__sync_fetch_and_or(&mRunningCount, 0) == 0); + free(mThreadId); + free(mNativeThreadId); + delete[] mLaunchSignals; +} + +bool WorkerPool::init(int threadCount) { + int cpu = sysconf(_SC_NPROCESSORS_CONF); + if (threadCount > 0) { + cpu = threadCount; + } + if (cpu < 1) { + return false; + } + mCount = (uint32_t)cpu; + + __android_log_print(ANDROID_LOG_INFO, "Bench", "ThreadLaunch %i", mCount); + + mThreadId = (pthread_t *) calloc(mCount, sizeof(pthread_t)); + mNativeThreadId = (pid_t *) calloc(mCount, sizeof(pid_t)); + mLaunchSignals = new Signal[mCount]; + mLaunchCallback = NULL; + + mCompleteSignal.init(); + mRunningCount = mCount; + mLaunchCount = 0; + __sync_synchronize(); + + pthread_attr_t threadAttr; + int status = pthread_attr_init(&threadAttr); + if (status) { + __android_log_print(ANDROID_LOG_INFO, "bench", "Failed to init thread attribute."); + return false; + } + + for (uint32_t ct=0; ct < mCount; ct++) { + status = pthread_create(&mThreadId[ct], &threadAttr, helperThreadProc, this); + if (status) { + mCount = ct; + __android_log_print(ANDROID_LOG_INFO, "bench", "Created fewer than expected number of threads."); + return false; + } + } + while (__sync_fetch_and_or(&mRunningCount, 0) != 0) { + usleep(100); + } + + pthread_attr_destroy(&threadAttr); + return true; +} + +void * WorkerPool::helperThreadProc(void *vwp) { + WorkerPool *wp = (WorkerPool *)vwp; + + uint32_t idx = __sync_fetch_and_add(&wp->mLaunchCount, 1); + + wp->mLaunchSignals[idx].init(); + wp->mNativeThreadId[idx] = gettid(); + + while (!wp->mExit) { + wp->mLaunchSignals[idx].wait(); + if (wp->mLaunchCallback) { + // idx +1 is used because the calling thread is always worker 0. + wp->mLaunchCallback(wp->mLaunchData, idx); + } + __sync_fetch_and_sub(&wp->mRunningCount, 1); + wp->mCompleteSignal.set(); + } + + //ALOGV("RS helperThread exited %p idx=%i", dc, idx); + return NULL; +} + + +void WorkerPool::waitForAll() const { +} + +void WorkerPool::waitFor(uint64_t) const { +} + + + +uint64_t WorkerPool::launchWork(WorkerCallback_t cb, void *usr, int maxThreads) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 1"); + mLaunchData = usr; + mLaunchCallback = cb; + + if (maxThreads < 1) { + maxThreads = mCount; + } + if ((uint32_t)maxThreads > mCount) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "launchWork max > count", maxThreads, mCount); + maxThreads = mCount; + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 2 %i %i %i", maxThreads, mRunningCount, mCount); + mRunningCount = maxThreads; + __sync_synchronize(); + + for (int ct = 0; ct < maxThreads; ct++) { + mLaunchSignals[ct].set(); + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3 %i", mRunningCount); + while (__sync_fetch_and_or(&mRunningCount, 0) != 0) { + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3.1 %i", mRunningCount); + mCompleteSignal.wait(); + } + + //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 4 %i", mRunningCount); + return 0; + +} + + + diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.h b/tests/JankBench/app/src/main/jni/WorkerPool.h new file mode 100644 index 000000000000..f8985d21be87 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/WorkerPool.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_WORKER_POOL_H +#define ANDROID_WORKER_POOL_H + +#include <pthread.h> +#include <string.h> + + + +class WorkerPool { +public: + WorkerPool(); + ~WorkerPool(); + + typedef void (*WorkerCallback_t)(void *usr, uint32_t idx); + + bool init(int threadCount = -1); + int getWorkerCount() const {return mCount;} + + void waitForAll() const; + void waitFor(uint64_t) const; + uint64_t launchWork(WorkerCallback_t cb, void *usr, int maxThreads = -1); + + + + +protected: + class Signal { + public: + Signal(); + ~Signal(); + + bool init(); + void set(); + + // returns true if the signal occured + // false for timeout + bool wait(uint64_t timeout = 0); + + protected: + bool mSet; + pthread_mutex_t mMutex; + pthread_cond_t mCondition; + }; + + bool mExit; + volatile int mRunningCount; + volatile int mLaunchCount; + uint32_t mCount; + pthread_t *mThreadId; + pid_t *mNativeThreadId; + Signal mCompleteSignal; + Signal *mLaunchSignals; + WorkerCallback_t mLaunchCallback; + void *mLaunchData; + + + + +private: + //static void * threadProc(void *); + static void * helperThreadProc(void *); + + +}; + + +#endif diff --git a/tests/JankBench/app/src/main/jni/test.cpp b/tests/JankBench/app/src/main/jni/test.cpp new file mode 100644 index 000000000000..e163daa531c8 --- /dev/null +++ b/tests/JankBench/app/src/main/jni/test.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +//#include <fcntl.h> +//#include <unistd.h> +#include <math.h> +#include <inttypes.h> +#include <time.h> +#include <android/log.h> + +#include "jni.h" +#include "Bench.h" + +#define FUNC(name) Java_com_android_benchmark_synthetic_TestInterface_##name + +static uint64_t GetTime() { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000); +} + +extern "C" { + +jlong Java_com_android_benchmark_synthetic_TestInterface_nInit(JNIEnv *_env, jobject _this, jlong options) { + Bench *b = new Bench(); + bool ret = b->init(); + + if (ret) { + return (jlong)b; + } + + delete b; + return 0; +} + +void Java_com_android_benchmark_synthetic_TestInterface_nDestroy(JNIEnv *_env, jobject _this, jlong _b) { + Bench *b = (Bench *)_b; + + delete b; +} + +jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunPowerManagementTest( + JNIEnv *_env, jobject _this, jlong _b, jlong options) { + Bench *b = (Bench *)_b; + return b->runPowerManagementTest(options); +} + +jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunCPUHeatSoakTest( + JNIEnv *_env, jobject _this, jlong _b, jlong options) { + Bench *b = (Bench *)_b; + return b->runCPUHeatSoak(options); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nGetData( + JNIEnv *_env, jobject _this, jlong _b, jfloatArray data) { + Bench *b = (Bench *)_b; + + jsize len = _env->GetArrayLength(data); + float * ptr = _env->GetFloatArrayElements(data, 0); + + b->getData(ptr, len); + + _env->ReleaseFloatArrayElements(data, (jfloat *)ptr, 0); + + return 0; +} + +jboolean Java_com_android_benchmark_synthetic_TestInterface_nMemTestStart( + JNIEnv *_env, jobject _this, jlong _b) { + Bench *b = (Bench *)_b; + return b->startMemTests(); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nMemTestBandwidth( + JNIEnv *_env, jobject _this, jlong _b, jlong opt) { + Bench *b = (Bench *)_b; + return b->runMemoryBandwidthTest(opt); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nGFlopsTest( + JNIEnv *_env, jobject _this, jlong _b, jlong opt) { + Bench *b = (Bench *)_b; + return b->runGFlopsTest(opt); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nMemTestLatency( + JNIEnv *_env, jobject _this, jlong _b, jlong opt) { + Bench *b = (Bench *)_b; + return b->runMemoryLatencyTest(opt); +} + +void Java_com_android_benchmark_synthetic_TestInterface_nMemTestEnd( + JNIEnv *_env, jobject _this, jlong _b) { + Bench *b = (Bench *)_b; + b->endMemTests(); +} + +float Java_com_android_benchmark_synthetic_TestInterface_nMemoryTest( + JNIEnv *_env, jobject _this, jint subtest) { + + uint8_t * volatile m1 = (uint8_t *)malloc(1024*1024*64); + uint8_t * m2 = (uint8_t *)malloc(1024*1024*64); + + memset(m1, 0, 1024*1024*16); + memset(m2, 0, 1024*1024*16); + + //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i %p %p", subtest, m1, m2); + + + size_t loopCount = 0; + uint64_t start = GetTime(); + while((GetTime() - start) < 1000000000) { + memcpy(m1, m2, subtest); + loopCount++; + } + if (loopCount == 0) { + loopCount = 1; + } + + size_t count = loopCount; + uint64_t t1 = GetTime(); + while (loopCount > 0) { + memcpy(m1, m2, subtest); + loopCount--; + } + uint64_t t2 = GetTime(); + + double dt = t2 - t1; + dt /= 1000 * 1000 * 1000; + double bw = ((double)subtest) * count / dt; + + bw /= 1024 * 1024 * 1024; + + __android_log_print(ANDROID_LOG_INFO, "bench", "size %i, bw %f", subtest, bw); + + free (m1); + free (m2); + return (float)bw; +} + +jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc( + JNIEnv *_env, jobject _this, jint bytes) { + uint8_t *p = (uint8_t *)malloc(bytes); + memset(p, 0, bytes); + return (jlong)p; +} + +void Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree( + JNIEnv *_env, jobject _this, jlong ptr) { + free((void *)ptr); +} + +jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestMalloc( + JNIEnv *_env, jobject _this, jint bytes) { + return Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(_env, _this, bytes); +} + +void Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestFree( + JNIEnv *_env, jobject _this, jlong ptr) { + Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(_env, _this, ptr); +} + +}; // extern "C" diff --git a/tests/JankBench/app/src/main/res/drawable/ic_play.png b/tests/JankBench/app/src/main/res/drawable/ic_play.png Binary files differnew file mode 100644 index 000000000000..13ed283cb5bd --- /dev/null +++ b/tests/JankBench/app/src/main/res/drawable/ic_play.png diff --git a/tests/JankBench/app/src/main/res/drawable/img1.jpg b/tests/JankBench/app/src/main/res/drawable/img1.jpg Binary files differnew file mode 100644 index 000000000000..33c1fedc0c6f --- /dev/null +++ b/tests/JankBench/app/src/main/res/drawable/img1.jpg diff --git a/tests/JankBench/app/src/main/res/drawable/img2.jpg b/tests/JankBench/app/src/main/res/drawable/img2.jpg Binary files differnew file mode 100644 index 000000000000..1ea97f2cc59c --- /dev/null +++ b/tests/JankBench/app/src/main/res/drawable/img2.jpg diff --git a/tests/JankBench/app/src/main/res/drawable/img3.jpg b/tests/JankBench/app/src/main/res/drawable/img3.jpg Binary files differnew file mode 100644 index 000000000000..ff992695621d --- /dev/null +++ b/tests/JankBench/app/src/main/res/drawable/img3.jpg diff --git a/tests/JankBench/app/src/main/res/drawable/img4.jpg b/tests/JankBench/app/src/main/res/drawable/img4.jpg Binary files differnew file mode 100644 index 000000000000..d9cbd2f42190 --- /dev/null +++ b/tests/JankBench/app/src/main/res/drawable/img4.jpg diff --git a/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml new file mode 100644 index 000000000000..6b3c8992d414 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/upload_root" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="10dp" + android:clipToPadding="false"> + <android.support.v7.widget.CardView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> + <view class="com.android.benchmark.ui.BitmapUploadActivity$UploadView" + android:id="@+id/upload_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + </android.support.v7.widget.CardView> + + <android.support.v4.widget.Space + android:layout_height="10dp" + android:layout_width="match_parent" /> + + <android.support.v7.widget.CardView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <android.support.v4.widget.Space + android:layout_height="10dp" + android:layout_width="match_parent" /> + + <android.support.v7.widget.CardView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/activity_home.xml b/tests/JankBench/app/src/main/res/layout/activity_home.xml new file mode 100644 index 000000000000..c4f429922aab --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + tools:context=".app.HomeActivity"> + + <android.support.design.widget.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/AppTheme.AppBarOverlay"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + app:popupTheme="@style/AppTheme.PopupOverlay" /> + + </android.support.design.widget.AppBarLayout> + + <include layout="@layout/content_main" /> + +</android.support.design.widget.CoordinatorLayout> diff --git a/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml new file mode 100644 index 000000000000..0aaaddebc040 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + tools:context=".app.HomeActivity" + android:orientation="vertical"> + + <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</LinearLayout> diff --git a/tests/JankBench/app/src/main/res/layout/activity_memory.xml b/tests/JankBench/app/src/main/res/layout/activity_memory.xml new file mode 100644 index 000000000000..fd5cadc24984 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_memory.xml @@ -0,0 +1,49 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" + android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + tools:context="com.android.benchmark.synthetic.MemoryActivity"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="Large Text" + android:id="@+id/textView_status" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="" + android:id="@+id/textView_min" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="" + android:id="@+id/textView_max" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="" + android:id="@+id/textView_typical" /> + + <view + android:layout_width="wrap_content" + android:layout_height="wrap_content" + class="com.android.benchmark.app.PerfTimeline" + android:id="@+id/mem_timeline" /> + + </LinearLayout> +</RelativeLayout> diff --git a/tests/JankBench/app/src/main/res/layout/activity_running_list.xml b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml new file mode 100644 index 000000000000..7b7b93066f93 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + tools:context=".app.HomeActivity" + android:orientation="vertical"> + + <TextView + android:id="@+id/score_text_view" + android:textSize="20sp" + android:textStyle="bold" + android:layout_width="match_parent" + android:layout_height="30dp" + /> + + <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml new file mode 100644 index 000000000000..5375dbca194b --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/group_name" + android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft" + android:textSize="17dp" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml new file mode 100644 index 000000000000..5282e14fddfc --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:paddingLeft="?android:expandableListPreferredChildPaddingLeft" + android:layout_width="match_parent" + android:layout_height="55dip"> + + + <CheckBox + android:id="@+id/benchmark_enable_checkbox" + android:checked="true" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/benchmark_name" + android:textSize="17dp" + android:paddingLeft="10dp" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/card_row.xml b/tests/JankBench/app/src/main/res/layout/card_row.xml new file mode 100644 index 000000000000..215f9df9b7fd --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/card_row.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="100dp" + android:paddingStart="10dp" + android:paddingEnd="10dp" + android:paddingTop="5dp" + android:paddingBottom="5dp" + android:clipToPadding="false" + android:background="@null"> + <android.support.v7.widget.CardView + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"> + <TextView + android:id="@+id/card_text" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + </android.support.v7.widget.CardView> + + <android.support.v4.widget.Space + android:layout_height="match_parent" + android:layout_width="10dp" /> + + <android.support.v7.widget.CardView + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" /> +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/content_main.xml b/tests/JankBench/app/src/main/res/layout/content_main.xml new file mode 100644 index 000000000000..201bd66a6589 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/content_main.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<fragment xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/fragment_start_button" + android:name="com.android.benchmark.app.BenchmarkDashboardFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:layout="@layout/fragment_dashboard" /> + diff --git a/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 000000000000..f3100c7ea75d --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/main_content" + android:layout_width="match_parent" + android:layout_height="fill_parent" + android:fitsSystemWindows="true"> + + <android.support.design.widget.AppBarLayout + android:id="@+id/appbar" + android:layout_width="match_parent" + android:layout_height="@dimen/detail_backdrop_height" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" + android:fitsSystemWindows="true"> + + <android.support.design.widget.CollapsingToolbarLayout + android:id="@+id/collapsing_toolbar" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_scrollFlags="scroll" + android:fitsSystemWindows="true" + app:contentScrim="?attr/colorPrimary" + app:expandedTitleMarginStart="48dp" + app:expandedTitleMarginEnd="64dp"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + app:popupTheme="@style/ThemeOverlay.AppCompat.Light" + app:layout_collapseMode="parallax" /> + + <ImageView + android:id="@+id/backdrop" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@mipmap/ic_launcher" + android:scaleType="centerCrop" + android:fitsSystemWindows="true" + app:layout_collapseMode="parallax" /> + + </android.support.design.widget.CollapsingToolbarLayout> + + </android.support.design.widget.AppBarLayout> + + <android.support.v4.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <ExpandableListView + android:id="@+id/test_list" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + </LinearLayout> + + </android.support.v4.widget.NestedScrollView> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/start_button" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_play" + app:layout_anchor="@id/appbar" + app:layout_anchorGravity="bottom|right|end" + android:layout_margin="@dimen/fab_margin" + android:clickable="true"/> + +</android.support.design.widget.CoordinatorLayout> diff --git a/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml new file mode 100644 index 000000000000..74d98910294c --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ExpandableListView + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml new file mode 100644 index 000000000000..c1662ea76fd3 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:id="@+id/image_scroll_image" + android:scaleType="centerCrop" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <TextView + android:id="@+id/image_scroll_text" + android:layout_gravity="right" + android:textSize="12sp" + android:paddingLeft="20dp" + android:layout_width="100dp" + android:layout_height="wrap_content" /> +</LinearLayout> diff --git a/tests/JankBench/app/src/main/res/layout/results_list_item.xml b/tests/JankBench/app/src/main/res/layout/results_list_item.xml new file mode 100644 index 000000000000..f38b147ac17e --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/results_list_item.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" android:layout_width="match_parent" + android:padding="8dp" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/result_name" + android:textSize="16sp" + android:layout_gravity="left" + android:layout_width="200dp" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/result_value" + android:textSize="16sp" + android:layout_gravity="right" + android:layout_width="200dp" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml new file mode 100644 index 000000000000..8a9d015e6ad3 --- /dev/null +++ b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/benchmark_name" + android:textSize="17sp" + android:paddingLeft="?android:listPreferredItemPaddingLeft" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/menu/menu_main.xml b/tests/JankBench/app/src/main/res/menu/menu_main.xml new file mode 100644 index 000000000000..1633acdaaf31 --- /dev/null +++ b/tests/JankBench/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".app.HomeActivity"> + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:title="@string/action_export" + app:showAsAction="never" /> +</menu> diff --git a/tests/JankBench/app/src/main/res/menu/menu_memory.xml b/tests/JankBench/app/src/main/res/menu/menu_memory.xml new file mode 100644 index 000000000000..f2df7c9d7662 --- /dev/null +++ b/tests/JankBench/app/src/main/res/menu/menu_memory.xml @@ -0,0 +1,5 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" tools:context="com.android.benchmark.Memory"> + <item android:id="@+id/action_settings" android:title="@string/action_export" + android:orderInCategory="100" /> +</menu> diff --git a/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..cde69bcccec6 --- /dev/null +++ b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..c133a0cbd379 --- /dev/null +++ b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..bfa42f0e7b91 --- /dev/null +++ b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..324e72cdd748 --- /dev/null +++ b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..aee44e138434 --- /dev/null +++ b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/tests/JankBench/app/src/main/res/values-v21/styles.xml b/tests/JankBench/app/src/main/res/values-v21/styles.xml new file mode 100644 index 000000000000..99ed094a18cf --- /dev/null +++ b/tests/JankBench/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + <item name="android:windowDrawsSystemBarBackgrounds">true</item> + <item name="android:statusBarColor">@android:color/transparent</item> + </style> +</resources> diff --git a/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 000000000000..e783e5d10773 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/tests/JankBench/app/src/main/res/values/attrs.xml b/tests/JankBench/app/src/main/res/values/attrs.xml new file mode 100644 index 000000000000..a4286f173b15 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/attrs.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + <!-- Root tag for benchmarks --> + <declare-styleable name="AndroidBenchmarks" /> + + <declare-styleable name="BenchmarkGroup"> + <attr name="name" format="reference|string" /> + <attr name="description" format="reference|string" /> + </declare-styleable> + + <declare-styleable name="Benchmark"> + <attr name="name" /> + <attr name="description" /> + <attr name="id" format="reference" /> + <attr name="category" format="enum"> + <enum name="generic" value="0" /> + <enum name="ui" value="1" /> + <enum name="compute" value="2" /> + </attr> + </declare-styleable> + + <declare-styleable name="PerfTimeline"><attr name="exampleString" format="string"/> + <attr name="exampleDimension" format="dimension"/> + <attr name="exampleColor" format="color"/> + <attr name="exampleDrawable" format="color|reference"/> + </declare-styleable> + +</resources> + diff --git a/tests/JankBench/app/src/main/res/values/colors.xml b/tests/JankBench/app/src/main/res/values/colors.xml new file mode 100644 index 000000000000..59156eef0067 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/colors.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources> diff --git a/tests/JankBench/app/src/main/res/values/dimens.xml b/tests/JankBench/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000000..9da649a15f37 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/dimens.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="detail_backdrop_height">256dp</dimen> + <dimen name="card_margin">16dp</dimen> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + <dimen name="fab_margin">16dp</dimen> + <dimen name="app_bar_height">200dp</dimen> + <dimen name="item_width">200dp</dimen> + <dimen name="text_margin">16dp</dimen> +</resources> diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml new file mode 100644 index 000000000000..6801fd9f61ec --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/ids.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + <item name="benchmark_list_view_scroll" type="id" /> + <item name="benchmark_image_list_view_scroll" type="id" /> + <item name="benchmark_shadow_grid" type="id" /> + <item name="benchmark_text_high_hitrate" type="id" /> + <item name="benchmark_text_low_hitrate" type="id" /> + <item name="benchmark_edit_text_input" type="id" /> + <item name="benchmark_overdraw" type="id" /> + <item name="benchmark_memory_bandwidth" type="id" /> + <item name="benchmark_memory_latency" type="id" /> + <item name="benchmark_power_management" type="id" /> + <item name="benchmark_cpu_heat_soak" type="id" /> + <item name="benchmark_cpu_gflops" type="id" /> +</resources>
\ No newline at end of file diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml new file mode 100644 index 000000000000..270adf89e4ed --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/strings.xml @@ -0,0 +1,51 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + <string name="app_name">Benchmark</string> + + <string name="action_export">Export to CSV</string> + + <string name="list_view_scroll_name">List View Fling</string> + <string name="list_view_scroll_description">Tests list view fling performance</string> + <string name="image_list_view_scroll_name">Image List View Fling</string> + <string name="image_list_view_scroll_description">Tests list view fling performance with images</string> + <string name="shadow_grid_name">Shadow Grid Fling</string> + <string name="shadow_grid_description">Tests shadow grid fling performance with images</string> + <string name="text_high_hitrate_name">High-hitrate text render</string> + <string name="text_high_hitrate_description">Tests high hitrate text rendering</string> + <string name="text_low_hitrate_name">Low-hitrate text render</string> + <string name="text_low_hitrate_description">Tests low-hitrate text rendering</string> + <string name="edit_text_input_name">Edit Text Input</string> + <string name="edit_text_input_description">Tests edit text input</string> + <string name="overdraw_name">Overdraw Test</string> + <string name="overdraw_description">Tests how the device handles overdraw</string> + <string name="memory_bandwidth_name">Memory Bandwidth</string> + <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string> + <string name="memory_latency_name">Memory Latency</string> + <string name="memory_latency_description">Test device\'s memory latency</string> + <string name="power_management_name">Power Management</string> + <string name="power_management_description">Test device\'s power management</string> + <string name="cpu_heat_soak_name">CPU Heat Soak</string> + <string name="cpu_heat_soak_description">How hot can we make it?</string> + <string name="cpu_gflops_name">CPU GFlops</string> + <string name="cpu_gflops_description">How many gigaflops can the device attain?</string> + + <string name="benchmark_category_ui">UI</string> + <string name="benchmark_category_compute">Compute</string> + <string name="benchmark_category_generic">Generic</string> + <string name="title_activity_image_list_view_scroll">ImageListViewScroll</string> +</resources> diff --git a/tests/JankBench/app/src/main/res/values/styles.xml b/tests/JankBench/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..25ce730ba2d1 --- /dev/null +++ b/tests/JankBench/app/src/main/res/values/styles.xml @@ -0,0 +1,43 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + </style> + + <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> + + <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> + + <style name="Widget.CardContent" parent="android:Widget"> + <item name="android:paddingLeft">16dp</item> + <item name="android:paddingRight">16dp</item> + <item name="android:paddingTop">24dp</item> + <item name="android:paddingBottom">24dp</item> + <item name="android:orientation">vertical</item> + </style> +</resources> diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml new file mode 100644 index 000000000000..07c453c25359 --- /dev/null +++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and limitations under the + ~ License. + ~ + --> + +<com.android.benchmark.BenchmarkGroup + xmlns:benchmark="http://schemas.android.com/apk/res-auto" + benchmark:description="Benchmarks of the Android system" + benchmark:name="Android Benchmarks"> + + <com.android.benchmark.Benchmark + benchmark:name="@string/list_view_scroll_name" + benchmark:id="@id/benchmark_list_view_scroll" + benchmark:category="ui" + benchmark:description="@string/list_view_scroll_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/image_list_view_scroll_name" + benchmark:id="@id/benchmark_image_list_view_scroll" + benchmark:category="ui" + benchmark:description="@string/image_list_view_scroll_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/shadow_grid_name" + benchmark:id="@id/benchmark_shadow_grid" + benchmark:category="ui" + benchmark:description="@string/shadow_grid_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/text_low_hitrate_name" + benchmark:id="@id/benchmark_text_low_hitrate" + benchmark:category="ui" + benchmark:description="@string/text_low_hitrate_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/text_high_hitrate_name" + benchmark:id="@id/benchmark_text_high_hitrate" + benchmark:category="ui" + benchmark:description="@string/text_high_hitrate_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/edit_text_input_name" + benchmark:id="@id/benchmark_edit_text_input" + benchmark:category="ui" + benchmark:description="@string/edit_text_input_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/overdraw_name" + benchmark:id="@id/benchmark_overdraw" + benchmark:category="ui" + benchmark:description="@string/overdraw_description" /> + + <!-- + <com.android.benchmark.Benchmark + benchmark:name="@string/memory_bandwidth_name" + benchmark:id="@id/benchmark_memory_bandwidth" + benchmark:category="compute" + benchmark:description="@string/memory_bandwidth_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/memory_latency_name" + benchmark:id="@id/benchmark_memory_latency" + benchmark:category="compute" + benchmark:description="@string/memory_latency_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/power_management_name" + benchmark:id="@id/benchmark_power_management" + benchmark:category="compute" + benchmark:description="@string/power_management_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/cpu_heat_soak_name" + benchmark:id="@id/benchmark_cpu_heat_soak" + benchmark:category="compute" + benchmark:description="@string/cpu_heat_soak_description" /> + + <com.android.benchmark.Benchmark + benchmark:name="@string/cpu_gflops_name" + benchmark:id="@id/benchmark_cpu_gflops" + benchmark:category="compute" + benchmark:description="@string/cpu_gflops_description" /> + --> + +</com.android.benchmark.BenchmarkGroup>
\ No newline at end of file diff --git a/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java new file mode 100644 index 000000000000..4464e87ef449 --- /dev/null +++ b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.benchmark; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/tests/JankBench/scripts/adbutil.py b/tests/JankBench/scripts/adbutil.py new file mode 100644 index 000000000000..ad9f7d9fa320 --- /dev/null +++ b/tests/JankBench/scripts/adbutil.py @@ -0,0 +1,62 @@ +import subprocess +import re +import threading + +ATRACE_PATH="/android/catapult/systrace/systrace/systrace.py" + +class AdbError(RuntimeError): + def __init__(self, arg): + self.args = arg + +def am(serial, cmd, args): + if not isinstance(args, list): + args = [args] + full_args = ["am"] + [cmd] + args + __call_adb(serial, full_args, False) + +def pm(serial, cmd, args): + if not isinstance(args, list): + args = [args] + full_args = ["pm"] + [cmd] + args + __call_adb(serial, full_args, False) + +def dumpsys(serial, topic): + return __call_adb(serial, ["dumpsys"] + [topic], True) + +def trace(serial, + tags = ["gfx", "sched", "view", "freq", "am", "wm", "power", "load", "memreclaim"], + time = "10"): + args = [ATRACE_PATH, "-e", serial, "-t" + time, "-b32768"] + tags + subprocess.call(args) + +def wake(serial): + output = dumpsys(serial, "power") + wakefulness = re.search('mWakefulness=([a-zA-Z]+)', output) + if wakefulness.group(1) != "Awake": + __call_adb(serial, ["input", "keyevent", "KEYCODE_POWER"], False) + +def root(serial): + subprocess.call(["adb", "-s", serial, "root"]) + +def pull(serial, path, dest): + subprocess.call(["adb", "-s", serial, "wait-for-device", "pull"] + [path] + [dest]) + +def shell(serial, cmd): + __call_adb(serial, cmd, False) + +def track_logcat(serial, awaited_string, callback): + threading.Thread(target=__track_logcat, name=serial + "-waiter", args=(serial, awaited_string, callback)).start() + +def __call_adb(serial, args, block): + full_args = ["adb", "-s", serial, "wait-for-device", "shell"] + args + print full_args + output = None + try: + if block: + output = subprocess.check_output(full_args) + else: + subprocess.call(full_args) + except subprocess.CalledProcessError: + raise AdbError("Error calling " + " ".join(args)) + + return output diff --git a/tests/JankBench/scripts/collect.py b/tests/JankBench/scripts/collect.py new file mode 100755 index 000000000000..87a059453b43 --- /dev/null +++ b/tests/JankBench/scripts/collect.py @@ -0,0 +1,240 @@ +#!/usr/bin/python + +import optparse +import sys +import sqlite3 +import scipy.stats +import numpy +from math import log10, floor +import matplotlib + +matplotlib.use("Agg") + +import matplotlib.pyplot as plt +import pylab + +import adbutil +from devices import DEVICES + +DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults" +OUT_PATH = "db/" + +QUERY_BAD_FRAME = ("select run_id, name, iteration, total_duration from ui_results " + "where total_duration >= 16 order by run_id, name, iteration") +QUERY_PERCENT_JANK = ("select run_id, name, iteration, sum(jank_frame) as jank_count, count (*) as total " + "from ui_results group by run_id, name, iteration") + +SKIP_TESTS = [ + # "BMUpload", + # "Low-hitrate text render", + # "High-hitrate text render", + # "Edit Text Input", + # "List View Fling" +] + +INCLUDE_TESTS = [ + #"BMUpload" + #"Shadow Grid Fling" + #"Image List View Fling" + #"Edit Text Input" +] + +class IterationResult: + def __init__(self): + self.durations = [] + self.jank_count = 0 + self.total_count = 0 + + +def get_scoremap(dbpath): + db = sqlite3.connect(dbpath) + rows = db.execute(QUERY_BAD_FRAME) + + scoremap = {} + for row in rows: + run_id = row[0] + name = row[1] + iteration = row[2] + total_duration = row[3] + + if not run_id in scoremap: + scoremap[run_id] = {} + + if not name in scoremap[run_id]: + scoremap[run_id][name] = {} + + if not iteration in scoremap[run_id][name]: + scoremap[run_id][name][iteration] = IterationResult() + + scoremap[run_id][name][iteration].durations.append(float(total_duration)) + + for row in db.execute(QUERY_PERCENT_JANK): + run_id = row[0] + name = row[1] + iteration = row[2] + jank_count = row[3] + total_count = row[4] + + if run_id in scoremap.keys() and name in scoremap[run_id].keys() and iteration in scoremap[run_id][name].keys(): + scoremap[run_id][name][iteration].jank_count = long(jank_count) + scoremap[run_id][name][iteration].total_count = long(total_count) + + db.close() + return scoremap + +def round_to_2(val): + return val + if val == 0: + return val + return round(val , -int(floor(log10(abs(val)))) + 1) + +def score_device(name, serial, pull = False, verbose = False): + dbpath = OUT_PATH + name + ".db" + + if pull: + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + + scoremap = None + try: + scoremap = get_scoremap(dbpath) + except sqlite3.DatabaseError: + print "Database corrupt, fetching..." + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + scoremap = get_scoremap(dbpath) + + per_test_score = {} + per_test_sample_count = {} + global_overall = {} + + for run_id in iter(scoremap): + overall = [] + if len(scoremap[run_id]) < 1: + if verbose: + print "Skipping short run %s" % run_id + continue + print "Run: %s" % run_id + for test in iter(scoremap[run_id]): + if test in SKIP_TESTS: + continue + if INCLUDE_TESTS and test not in INCLUDE_TESTS: + continue + if verbose: + print "\t%s" % test + scores = [] + means = [] + stddevs = [] + pjs = [] + sample_count = 0 + hit_min_count = 0 + # try pooling together all iterations + for iteration in iter(scoremap[run_id][test]): + res = scoremap[run_id][test][iteration] + stddev = round_to_2(numpy.std(res.durations)) + mean = round_to_2(numpy.mean(res.durations)) + sample_count += len(res.durations) + pj = round_to_2(100 * res.jank_count / float(res.total_count)) + score = stddev * mean * pj + score = 100 * len(res.durations) / float(res.total_count) + if score == 0: + score = 1 + scores.append(score) + means.append(mean) + stddevs.append(stddev) + pjs.append(pj) + if verbose: + print "\t%s: Score = %f x %f x %f = %f (%d samples)" % (iteration, stddev, mean, pj, score, len(res.durations)) + + if verbose: + print "\tHit min: %d" % hit_min_count + print "\tMean Variation: %0.2f%%" % (100 * scipy.stats.variation(means)) + print "\tStdDev Variation: %0.2f%%" % (100 * scipy.stats.variation(stddevs)) + print "\tPJ Variation: %0.2f%%" % (100 * scipy.stats.variation(pjs)) + + geo_run = numpy.mean(scores) + if test not in per_test_score: + per_test_score[test] = [] + + if test not in per_test_sample_count: + per_test_sample_count[test] = [] + + sample_count /= len(scoremap[run_id][test]) + + per_test_score[test].append(geo_run) + per_test_sample_count[test].append(int(sample_count)) + overall.append(geo_run) + + if not verbose: + print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count) + else: + print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count) + print "" + + global_overall[run_id] = scipy.stats.gmean(overall) + print "Run Overall: %f" % global_overall[run_id] + print "" + + print "" + print "Variability (CV) - %s:" % name + + worst_offender_test = None + worst_offender_variation = 0 + for test in per_test_score: + variation = 100 * scipy.stats.variation(per_test_score[test]) + if worst_offender_variation < variation: + worst_offender_test = test + worst_offender_variation = variation + print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, variation, numpy.mean(per_test_sample_count[test])) + + print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()])) + print "" + + return { + "overall": global_overall.values(), + "worst_offender_test": (name, worst_offender_test, worst_offender_variation) + } + +def parse_options(argv): + usage = 'Usage: %prog [options]' + desc = 'Example: %prog' + parser = optparse.OptionParser(usage=usage, description=desc) + parser.add_option("-p", dest='pull', action="store_true") + parser.add_option("-d", dest='device', action="store") + parser.add_option("-v", dest='verbose', action="store_true") + options, categories = parser.parse_args(argv[1:]) + return options + +def main(): + options = parse_options(sys.argv) + if options.device != None: + score_device(options.device, DEVICES[options.device], options.pull, options.verbose) + else: + device_scores = [] + worst_offenders = [] + for name, serial in DEVICES.iteritems(): + print "======== %s =========" % name + result = score_device(name, serial, options.pull, options.verbose) + device_scores.append((name, result["overall"])) + worst_offenders.append(result["worst_offender_test"]) + + + device_scores.sort(cmp=(lambda x, y: cmp(x[1], y[1]))) + print "Ranking by max overall score:" + for name, score in device_scores: + plt.plot([0, 1, 2, 3, 4, 5], score, label=name) + print "\t%s: %s" % (name, score) + + plt.ylabel("Jank %") + plt.xlabel("Iteration") + plt.title("Jank Percentage") + plt.legend() + pylab.savefig("holy.png", bbox_inches="tight") + + print "Worst offender tests:" + for device, test, variation in worst_offenders: + print "\t%s: %s %.2f%%" % (device, test, variation) + +if __name__ == "__main__": + main() + diff --git a/tests/JankBench/scripts/devices.py b/tests/JankBench/scripts/devices.py new file mode 100644 index 000000000000..c8266c0ecef5 --- /dev/null +++ b/tests/JankBench/scripts/devices.py @@ -0,0 +1,11 @@ +#!/usr/bin/python + +DEVICES = { + 'bullhead': '00606a370e3ca155', + 'volantis': 'HT4BTWV00612', + 'angler': '84B5T15A29021748', + 'seed': '1285c85e', + 'ryu': '5A27000599', + 'shamu': 'ZX1G22W24R', +} + diff --git a/tests/JankBench/scripts/external/__init__.py b/tests/JankBench/scripts/external/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/tests/JankBench/scripts/external/__init__.py diff --git a/tests/JankBench/scripts/external/statistics.py b/tests/JankBench/scripts/external/statistics.py new file mode 100644 index 000000000000..518f54654469 --- /dev/null +++ b/tests/JankBench/scripts/external/statistics.py @@ -0,0 +1,638 @@ +## Module statistics.py +## +## Copyright (c) 2013 Steven D'Aprano <steve+python@pearwood.info>. +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + + +""" +Basic statistics module. + +This module provides functions for calculating statistics of data, including +averages, variance, and standard deviation. + +Calculating averages +-------------------- + +================== ============================================= +Function Description +================== ============================================= +mean Arithmetic mean (average) of data. +median Median (middle value) of data. +median_low Low median of data. +median_high High median of data. +median_grouped Median, or 50th percentile, of grouped data. +mode Mode (most common value) of data. +================== ============================================= + +Calculate the arithmetic mean ("the average") of data: + +>>> mean([-1.0, 2.5, 3.25, 5.75]) +2.625 + + +Calculate the standard median of discrete data: + +>>> median([2, 3, 4, 5]) +3.5 + + +Calculate the median, or 50th percentile, of data grouped into class intervals +centred on the data values provided. E.g. if your data points are rounded to +the nearest whole number: + +>>> median_grouped([2, 2, 3, 3, 3, 4]) #doctest: +ELLIPSIS +2.8333333333... + +This should be interpreted in this way: you have two data points in the class +interval 1.5-2.5, three data points in the class interval 2.5-3.5, and one in +the class interval 3.5-4.5. The median of these data points is 2.8333... + + +Calculating variability or spread +--------------------------------- + +================== ============================================= +Function Description +================== ============================================= +pvariance Population variance of data. +variance Sample variance of data. +pstdev Population standard deviation of data. +stdev Sample standard deviation of data. +================== ============================================= + +Calculate the standard deviation of sample data: + +>>> stdev([2.5, 3.25, 5.5, 11.25, 11.75]) #doctest: +ELLIPSIS +4.38961843444... + +If you have previously calculated the mean, you can pass it as the optional +second argument to the four "spread" functions to avoid recalculating it: + +>>> data = [1, 2, 2, 4, 4, 4, 5, 6] +>>> mu = mean(data) +>>> pvariance(data, mu) +2.5 + + +Exceptions +---------- + +A single exception is defined: StatisticsError is a subclass of ValueError. + +""" + +__all__ = [ 'StatisticsError', + 'pstdev', 'pvariance', 'stdev', 'variance', + 'median', 'median_low', 'median_high', 'median_grouped', + 'mean', 'mode', + ] + + +import collections +import math + +from fractions import Fraction +from decimal import Decimal +from itertools import groupby + + + +# === Exceptions === + +class StatisticsError(ValueError): + pass + + +# === Private utilities === + +def _sum(data, start=0): + """_sum(data [, start]) -> (type, sum, count) + + Return a high-precision sum of the given numeric data as a fraction, + together with the type to be converted to and the count of items. + + If optional argument ``start`` is given, it is added to the total. + If ``data`` is empty, ``start`` (defaulting to 0) is returned. + + + Examples + -------- + + >>> _sum([3, 2.25, 4.5, -0.5, 1.0], 0.75) + (<class 'float'>, Fraction(11, 1), 5) + + Some sources of round-off error will be avoided: + + >>> _sum([1e50, 1, -1e50] * 1000) # Built-in sum returns zero. + (<class 'float'>, Fraction(1000, 1), 3000) + + Fractions and Decimals are also supported: + + >>> from fractions import Fraction as F + >>> _sum([F(2, 3), F(7, 5), F(1, 4), F(5, 6)]) + (<class 'fractions.Fraction'>, Fraction(63, 20), 4) + + >>> from decimal import Decimal as D + >>> data = [D("0.1375"), D("0.2108"), D("0.3061"), D("0.0419")] + >>> _sum(data) + (<class 'decimal.Decimal'>, Fraction(6963, 10000), 4) + + Mixed types are currently treated as an error, except that int is + allowed. + """ + count = 0 + n, d = _exact_ratio(start) + partials = {d: n} + partials_get = partials.get + T = _coerce(int, type(start)) + for typ, values in groupby(data, type): + T = _coerce(T, typ) # or raise TypeError + for n,d in map(_exact_ratio, values): + count += 1 + partials[d] = partials_get(d, 0) + n + if None in partials: + # The sum will be a NAN or INF. We can ignore all the finite + # partials, and just look at this special one. + total = partials[None] + assert not _isfinite(total) + else: + # Sum all the partial sums using builtin sum. + # FIXME is this faster if we sum them in order of the denominator? + total = sum(Fraction(n, d) for d, n in sorted(partials.items())) + return (T, total, count) + + +def _isfinite(x): + try: + return x.is_finite() # Likely a Decimal. + except AttributeError: + return math.isfinite(x) # Coerces to float first. + + +def _coerce(T, S): + """Coerce types T and S to a common type, or raise TypeError. + + Coercion rules are currently an implementation detail. See the CoerceTest + test class in test_statistics for details. + """ + # See http://bugs.python.org/issue24068. + assert T is not bool, "initial type T is bool" + # If the types are the same, no need to coerce anything. Put this + # first, so that the usual case (no coercion needed) happens as soon + # as possible. + if T is S: return T + # Mixed int & other coerce to the other type. + if S is int or S is bool: return T + if T is int: return S + # If one is a (strict) subclass of the other, coerce to the subclass. + if issubclass(S, T): return S + if issubclass(T, S): return T + # Ints coerce to the other type. + if issubclass(T, int): return S + if issubclass(S, int): return T + # Mixed fraction & float coerces to float (or float subclass). + if issubclass(T, Fraction) and issubclass(S, float): + return S + if issubclass(T, float) and issubclass(S, Fraction): + return T + # Any other combination is disallowed. + msg = "don't know how to coerce %s and %s" + raise TypeError(msg % (T.__name__, S.__name__)) + + +def _exact_ratio(x): + """Return Real number x to exact (numerator, denominator) pair. + + >>> _exact_ratio(0.25) + (1, 4) + + x is expected to be an int, Fraction, Decimal or float. + """ + try: + # Optimise the common case of floats. We expect that the most often + # used numeric type will be builtin floats, so try to make this as + # fast as possible. + if type(x) is float: + return x.as_integer_ratio() + try: + # x may be an int, Fraction, or Integral ABC. + return (x.numerator, x.denominator) + except AttributeError: + try: + # x may be a float subclass. + return x.as_integer_ratio() + except AttributeError: + try: + # x may be a Decimal. + return _decimal_to_ratio(x) + except AttributeError: + # Just give up? + pass + except (OverflowError, ValueError): + # float NAN or INF. + assert not math.isfinite(x) + return (x, None) + msg = "can't convert type '{}' to numerator/denominator" + raise TypeError(msg.format(type(x).__name__)) + + +# FIXME This is faster than Fraction.from_decimal, but still too slow. +def _decimal_to_ratio(d): + """Convert Decimal d to exact integer ratio (numerator, denominator). + + >>> from decimal import Decimal + >>> _decimal_to_ratio(Decimal("2.6")) + (26, 10) + + """ + sign, digits, exp = d.as_tuple() + if exp in ('F', 'n', 'N'): # INF, NAN, sNAN + assert not d.is_finite() + return (d, None) + num = 0 + for digit in digits: + num = num*10 + digit + if exp < 0: + den = 10**-exp + else: + num *= 10**exp + den = 1 + if sign: + num = -num + return (num, den) + + +def _convert(value, T): + """Convert value to given numeric type T.""" + if type(value) is T: + # This covers the cases where T is Fraction, or where value is + # a NAN or INF (Decimal or float). + return value + if issubclass(T, int) and value.denominator != 1: + T = float + try: + # FIXME: what do we do if this overflows? + return T(value) + except TypeError: + if issubclass(T, Decimal): + return T(value.numerator)/T(value.denominator) + else: + raise + + +def _counts(data): + # Generate a table of sorted (value, frequency) pairs. + table = collections.Counter(iter(data)).most_common() + if not table: + return table + # Extract the values with the highest frequency. + maxfreq = table[0][1] + for i in range(1, len(table)): + if table[i][1] != maxfreq: + table = table[:i] + break + return table + + +# === Measures of central tendency (averages) === + +def mean(data): + """Return the sample arithmetic mean of data. + + >>> mean([1, 2, 3, 4, 4]) + 2.8 + + >>> from fractions import Fraction as F + >>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)]) + Fraction(13, 21) + + >>> from decimal import Decimal as D + >>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")]) + Decimal('0.5625') + + If ``data`` is empty, StatisticsError will be raised. + """ + if iter(data) is data: + data = list(data) + n = len(data) + if n < 1: + raise StatisticsError('mean requires at least one data point') + T, total, count = _sum(data) + assert count == n + return _convert(total/n, T) + + +# FIXME: investigate ways to calculate medians without sorting? Quickselect? +def median(data): + """Return the median (middle value) of numeric data. + + When the number of data points is odd, return the middle data point. + When the number of data points is even, the median is interpolated by + taking the average of the two middle values: + + >>> median([1, 3, 5]) + 3 + >>> median([1, 3, 5, 7]) + 4.0 + + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + if n%2 == 1: + return data[n//2] + else: + i = n//2 + return (data[i - 1] + data[i])/2 + + +def median_low(data): + """Return the low median of numeric data. + + When the number of data points is odd, the middle value is returned. + When it is even, the smaller of the two middle values is returned. + + >>> median_low([1, 3, 5]) + 3 + >>> median_low([1, 3, 5, 7]) + 3 + + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + if n%2 == 1: + return data[n//2] + else: + return data[n//2 - 1] + + +def median_high(data): + """Return the high median of data. + + When the number of data points is odd, the middle value is returned. + When it is even, the larger of the two middle values is returned. + + >>> median_high([1, 3, 5]) + 3 + >>> median_high([1, 3, 5, 7]) + 5 + + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + return data[n//2] + + +def median_grouped(data, interval=1): + """Return the 50th percentile (median) of grouped continuous data. + + >>> median_grouped([1, 2, 2, 3, 4, 4, 4, 4, 4, 5]) + 3.7 + >>> median_grouped([52, 52, 53, 54]) + 52.5 + + This calculates the median as the 50th percentile, and should be + used when your data is continuous and grouped. In the above example, + the values 1, 2, 3, etc. actually represent the midpoint of classes + 0.5-1.5, 1.5-2.5, 2.5-3.5, etc. The middle value falls somewhere in + class 3.5-4.5, and interpolation is used to estimate it. + + Optional argument ``interval`` represents the class interval, and + defaults to 1. Changing the class interval naturally will change the + interpolated 50th percentile value: + + >>> median_grouped([1, 3, 3, 5, 7], interval=1) + 3.25 + >>> median_grouped([1, 3, 3, 5, 7], interval=2) + 3.5 + + This function does not check whether the data points are at least + ``interval`` apart. + """ + data = sorted(data) + n = len(data) + if n == 0: + raise StatisticsError("no median for empty data") + elif n == 1: + return data[0] + # Find the value at the midpoint. Remember this corresponds to the + # centre of the class interval. + x = data[n//2] + for obj in (x, interval): + if isinstance(obj, (str, bytes)): + raise TypeError('expected number but got %r' % obj) + try: + L = x - interval/2 # The lower limit of the median interval. + except TypeError: + # Mixed type. For now we just coerce to float. + L = float(x) - float(interval)/2 + cf = data.index(x) # Number of values below the median interval. + # FIXME The following line could be more efficient for big lists. + f = data.count(x) # Number of data points in the median interval. + return L + interval*(n/2 - cf)/f + + +def mode(data): + """Return the most common data point from discrete or nominal data. + + ``mode`` assumes discrete data, and returns a single value. This is the + standard treatment of the mode as commonly taught in schools: + + >>> mode([1, 1, 2, 3, 3, 3, 3, 4]) + 3 + + This also works with nominal (non-numeric) data: + + >>> mode(["red", "blue", "blue", "red", "green", "red", "red"]) + 'red' + + If there is not exactly one most common value, ``mode`` will raise + StatisticsError. + """ + # Generate a table of sorted (value, frequency) pairs. + table = _counts(data) + if len(table) == 1: + return table[0][0] + elif table: + raise StatisticsError( + 'no unique mode; found %d equally common values' % len(table) + ) + else: + raise StatisticsError('no mode for empty data') + + +# === Measures of spread === + +# See http://mathworld.wolfram.com/Variance.html +# http://mathworld.wolfram.com/SampleVariance.html +# http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance +# +# Under no circumstances use the so-called "computational formula for +# variance", as that is only suitable for hand calculations with a small +# amount of low-precision data. It has terrible numeric properties. +# +# See a comparison of three computational methods here: +# http://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/ + +def _ss(data, c=None): + """Return sum of square deviations of sequence data. + + If ``c`` is None, the mean is calculated in one pass, and the deviations + from the mean are calculated in a second pass. Otherwise, deviations are + calculated from ``c`` as given. Use the second case with care, as it can + lead to garbage results. + """ + if c is None: + c = mean(data) + T, total, count = _sum((x-c)**2 for x in data) + # The following sum should mathematically equal zero, but due to rounding + # error may not. + U, total2, count2 = _sum((x-c) for x in data) + assert T == U and count == count2 + total -= total2**2/len(data) + assert not total < 0, 'negative sum of square deviations: %f' % total + return (T, total) + + +def variance(data, xbar=None): + """Return the sample variance of data. + + data should be an iterable of Real-valued numbers, with at least two + values. The optional argument xbar, if given, should be the mean of + the data. If it is missing or None, the mean is automatically calculated. + + Use this function when your data is a sample from a population. To + calculate the variance from the entire population, see ``pvariance``. + + Examples: + + >>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5] + >>> variance(data) + 1.3720238095238095 + + If you have already calculated the mean of your data, you can pass it as + the optional second argument ``xbar`` to avoid recalculating it: + + >>> m = mean(data) + >>> variance(data, m) + 1.3720238095238095 + + This function does not check that ``xbar`` is actually the mean of + ``data``. Giving arbitrary values for ``xbar`` may lead to invalid or + impossible results. + + Decimals and Fractions are supported: + + >>> from decimal import Decimal as D + >>> variance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")]) + Decimal('31.01875') + + >>> from fractions import Fraction as F + >>> variance([F(1, 6), F(1, 2), F(5, 3)]) + Fraction(67, 108) + + """ + if iter(data) is data: + data = list(data) + n = len(data) + if n < 2: + raise StatisticsError('variance requires at least two data points') + T, ss = _ss(data, xbar) + return _convert(ss/(n-1), T) + + +def pvariance(data, mu=None): + """Return the population variance of ``data``. + + data should be an iterable of Real-valued numbers, with at least one + value. The optional argument mu, if given, should be the mean of + the data. If it is missing or None, the mean is automatically calculated. + + Use this function to calculate the variance from the entire population. + To estimate the variance from a sample, the ``variance`` function is + usually a better choice. + + Examples: + + >>> data = [0.0, 0.25, 0.25, 1.25, 1.5, 1.75, 2.75, 3.25] + >>> pvariance(data) + 1.25 + + If you have already calculated the mean of the data, you can pass it as + the optional second argument to avoid recalculating it: + + >>> mu = mean(data) + >>> pvariance(data, mu) + 1.25 + + This function does not check that ``mu`` is actually the mean of ``data``. + Giving arbitrary values for ``mu`` may lead to invalid or impossible + results. + + Decimals and Fractions are supported: + + >>> from decimal import Decimal as D + >>> pvariance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")]) + Decimal('24.815') + + >>> from fractions import Fraction as F + >>> pvariance([F(1, 4), F(5, 4), F(1, 2)]) + Fraction(13, 72) + + """ + if iter(data) is data: + data = list(data) + n = len(data) + if n < 1: + raise StatisticsError('pvariance requires at least one data point') + ss = _ss(data, mu) + T, ss = _ss(data, mu) + return _convert(ss/n, T) + + +def stdev(data, xbar=None): + """Return the square root of the sample variance. + + See ``variance`` for arguments and other details. + + >>> stdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75]) + 1.0810874155219827 + + """ + var = variance(data, xbar) + try: + return var.sqrt() + except AttributeError: + return math.sqrt(var) + + +def pstdev(data, mu=None): + """Return the square root of the population variance. + + See ``pvariance`` for arguments and other details. + + >>> pstdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75]) + 0.986893273527251 + + """ + var = pvariance(data, mu) + try: + return var.sqrt() + except AttributeError: + return math.sqrt(var) diff --git a/tests/JankBench/scripts/itr_collect.py b/tests/JankBench/scripts/itr_collect.py new file mode 100755 index 000000000000..76499a4c9362 --- /dev/null +++ b/tests/JankBench/scripts/itr_collect.py @@ -0,0 +1,154 @@ +#!/usr/bin/python + +import optparse +import sys +import sqlite3 +import scipy.stats +import numpy + +import adbutil +from devices import DEVICES + +DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults" +OUT_PATH = "db/" + +QUERY_BAD_FRAME = ("select run_id, name, total_duration from ui_results " + "where total_duration >=12 order by run_id, name") +QUERY_PERCENT_JANK = ("select run_id, name, sum(jank_frame) as jank_count, count (*) as total " + "from ui_results group by run_id, name") + +class IterationResult: + def __init__(self): + self.durations = [] + self.jank_count = 0 + self.total_count = 0 + + +def get_scoremap(dbpath): + db = sqlite3.connect(dbpath) + rows = db.execute(QUERY_BAD_FRAME) + + scoremap = {} + for row in rows: + run_id = row[0] + name = row[1] + total_duration = row[2] + + if not run_id in scoremap: + scoremap[run_id] = {} + + if not name in scoremap[run_id]: + scoremap[run_id][name] = IterationResult() + + + scoremap[run_id][name].durations.append(float(total_duration)) + + for row in db.execute(QUERY_PERCENT_JANK): + run_id = row[0] + name = row[1] + jank_count = row[2] + total_count = row[3] + + if run_id in scoremap.keys() and name in scoremap[run_id].keys(): + scoremap[run_id][name].jank_count = long(jank_count) + scoremap[run_id][name].total_count = long(total_count) + + + db.close() + return scoremap + +def score_device(name, serial, pull = False, verbose = False): + dbpath = OUT_PATH + name + ".db" + + if pull: + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + + scoremap = None + try: + scoremap = get_scoremap(dbpath) + except sqlite3.DatabaseError: + print "Database corrupt, fetching..." + adbutil.root(serial) + adbutil.pull(serial, DB_PATH, dbpath) + scoremap = get_scoremap(dbpath) + + per_test_score = {} + per_test_sample_count = {} + global_overall = {} + + for run_id in iter(scoremap): + overall = [] + if len(scoremap[run_id]) < 1: + if verbose: + print "Skipping short run %s" % run_id + continue + print "Run: %s" % run_id + for test in iter(scoremap[run_id]): + if verbose: + print "\t%s" % test + scores = [] + sample_count = 0 + res = scoremap[run_id][test] + stddev = numpy.std(res.durations) + mean = numpy.mean(res.durations) + sample_count = len(res.durations) + pj = 100 * res.jank_count / float(res.total_count) + score = stddev * mean *pj + if score == 0: + score = 1 + scores.append(score) + if verbose: + print "\tScore = %f x %f x %f = %f (%d samples)" % (stddev, mean, pj, score, len(res.durations)) + + geo_run = scipy.stats.gmean(scores) + if test not in per_test_score: + per_test_score[test] = [] + + if test not in per_test_sample_count: + per_test_sample_count[test] = [] + + per_test_score[test].append(geo_run) + per_test_sample_count[test].append(int(sample_count)) + overall.append(geo_run) + + if not verbose: + print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count) + else: + print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count) + print "" + + global_overall[run_id] = scipy.stats.gmean(overall) + print "Run Overall: %f" % global_overall[run_id] + print "" + + print "" + print "Variability (CV) - %s:" % name + + for test in per_test_score: + print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, 100 * scipy.stats.variation(per_test_score[test]), numpy.mean(per_test_sample_count[test])) + + print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()])) + print "" + +def parse_options(argv): + usage = 'Usage: %prog [options]' + desc = 'Example: %prog' + parser = optparse.OptionParser(usage=usage, description=desc) + parser.add_option("-p", dest='pull', action="store_true") + parser.add_option("-d", dest='device', action="store") + parser.add_option("-v", dest='verbose', action="store_true") + options, categories = parser.parse_args(argv[1:]) + return options + +def main(): + options = parse_options(sys.argv) + if options.device != None: + score_device(options.device, DEVICES[options.device], options.pull, options.verbose) + else: + for name, serial in DEVICES.iteritems(): + print "======== %s =========" % name + score_device(name, serial, options.pull, options.verbose) + +if __name__ == "__main__": + main() diff --git a/tests/JankBench/scripts/runall.py b/tests/JankBench/scripts/runall.py new file mode 100755 index 000000000000..d9a06628f2b0 --- /dev/null +++ b/tests/JankBench/scripts/runall.py @@ -0,0 +1,65 @@ +#!/usr/bin/python + +import optparse +import sys +import time + +import adbutil +from devices import DEVICES + +def parse_options(argv): + usage = 'Usage: %prog [options]' + desc = 'Example: %prog' + parser = optparse.OptionParser(usage=usage, description=desc) + parser.add_option("-c", dest='clear', action="store_true") + parser.add_option("-d", dest='device', action="store",) + parser.add_option("-t", dest='trace', action="store_true") + options, categories = parser.parse_args(argv[1:]) + return (options, categories) + +def clear_data(device = None): + if device != None: + dev = DEVICES[device] + adbutil.root(dev) + adbutil.pm(dev, "clear", "com.android.benchmark") + else: + for name, dev in DEVICES.iteritems(): + print("Clearing " + name) + adbutil.root(dev) + adbutil.pm(dev, "clear", "com.android.benchmark") + +def start_device(name, dev): + print("Go " + name + "!") + try: + adbutil.am(dev, "force-stop", "com.android.benchmark") + adbutil.wake(dev) + adbutil.am(dev, "start", + ["-n", "\"com.android.benchmark/.app.RunLocalBenchmarksActivity\"", + "--eia", "\"com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS\"", "\"0,1,2,3,4,5,6\"", + "--ei", "\"com.android.benchmark.EXTRA_RUN_COUNT\"", "\"5\""]) + except adbutil.AdbError: + print "Couldn't launch " + name + "." + +def start_benchmark(device, trace): + if device != None: + start_device(device, DEVICES[device]) + if trace: + time.sleep(3) + adbutil.trace(DEVICES[device]) + else: + if trace: + print("Note: -t only valid with -d, can't trace") + for name, dev in DEVICES.iteritems(): + start_device(name, dev) + +def main(): + options, categories = parse_options(sys.argv) + if options.clear: + print options.device + clear_data(options.device) + else: + start_benchmark(options.device, options.trace) + + +if __name__ == "__main__": + main() diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 7c1e96e88fee..3bec082fc57a 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -186,6 +186,12 @@ static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& o out_path_data->push_back(std::move(path_data.value())); } } + + // File-system directory enumeration order is platform-dependent. Sort the result to remove any + // inconsistencies between platforms. + std::sort( + out_path_data->begin(), out_path_data->end(), + [](const ResourcePathData& a, const ResourcePathData& b) { return a.source < b.source; }); return true; } diff --git a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml index e66d709943a4..dd4523996bc3 100644 --- a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml @@ -18,4 +18,7 @@ package="com.android.aapt.autoversiontest"> <uses-sdk android:minSdkVersion="7" /> + <application> + <uses-library android:name="clockwork-system" /> + </application> </manifest> diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index da05dc328f7f..ccc3470fd7f4 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -29,6 +29,22 @@ using android::StringPiece; namespace aapt { +static bool RequiredNameIsNotEmpty(xml::Element* el, SourcePathDiagnostics* diag) { + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); + if (attr == nullptr) { + diag->Error(DiagMessage(el->line_number) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; + } + + if (attr->value.empty()) { + diag->Error(DiagMessage(el->line_number) + << "attribute 'android:name' in <" << el->name << "> tag must not be empty"); + return false; + } + return true; +} + // This is how PackageManager builds class names from AndroidManifest.xml entries. static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr, SourcePathDiagnostics* diag) { @@ -59,21 +75,29 @@ static bool OptionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* } static bool RequiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { - if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { - return NameIsJavaClassName(el, attr, diag); + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); + if (attr == nullptr) { + diag->Error(DiagMessage(el->line_number) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; } - diag->Error(DiagMessage(el->line_number) - << "<" << el->name << "> is missing attribute 'android:name'"); - return false; + return NameIsJavaClassName(el, attr, diag); } static bool RequiredNameIsJavaPackage(xml::Element* el, SourcePathDiagnostics* diag) { - if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { - return util::IsJavaPackageName(attr->value); + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); + if (attr == nullptr) { + diag->Error(DiagMessage(el->line_number) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; } - diag->Error(DiagMessage(el->line_number) - << "<" << el->name << "> is missing attribute 'android:name'"); - return false; + + if (!util::IsJavaPackageName(attr->value)) { + diag->Error(DiagMessage(el->line_number) << "attribute 'android:name' in <" << el->name + << "> tag must be a valid Java package name"); + return false; + } + return true; } static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std::string& attr) { @@ -213,8 +237,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, // Common <intent-filter> actions. xml::XmlNodeAction intent_filter_action; - intent_filter_action["action"]; - intent_filter_action["category"]; + intent_filter_action["action"].Action(RequiredNameIsNotEmpty); + intent_filter_action["category"].Action(RequiredNameIsNotEmpty); intent_filter_action["data"]; // Common <meta-data> actions. @@ -317,8 +341,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, xml::XmlNodeAction& application_action = manifest_action["application"]; application_action.Action(OptionalNameIsJavaClassName); - application_action["uses-library"].Action(RequiredNameIsJavaPackage); - application_action["library"].Action(RequiredNameIsJavaPackage); + application_action["uses-library"].Action(RequiredNameIsNotEmpty); + application_action["library"].Action(RequiredNameIsNotEmpty); xml::XmlNodeAction& static_library_action = application_action["static-library"]; static_library_action.Action(RequiredNameIsJavaPackage); diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index c6f895bb52dd..ed98d715df58 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -494,4 +494,34 @@ TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) { ASSERT_THAT(manifest, IsNull()); } + +TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <uses-library android:name="" /> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <uses-library /> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <uses-library android:name="blahhh" /> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} + } // namespace aapt diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp new file mode 100644 index 000000000000..00fb8aa20b18 --- /dev/null +++ b/tools/sdkparcelables/Android.bp @@ -0,0 +1,22 @@ +java_binary_host { + name: "sdkparcelables", + manifest: "manifest.txt", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "asm-6.0", + ], +} + +java_library_host { + name: "sdkparcelables_test", + manifest: "manifest.txt", + srcs: [ + "tests/**/*.kt", + ], + static_libs: [ + "sdkparcelables", + "junit", + ], +} diff --git a/tools/sdkparcelables/manifest.txt b/tools/sdkparcelables/manifest.txt new file mode 100644 index 000000000000..cd5420ce0bf4 --- /dev/null +++ b/tools/sdkparcelables/manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.sdkparcelables.MainKt diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt new file mode 100644 index 000000000000..f278aec8eb6f --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt @@ -0,0 +1,28 @@ +package com.android.sdkparcelables + +import org.objectweb.asm.ClassVisitor +import java.util.* + +data class Ancestors(val superName: String?, val interfaces: List<String>?) + +/** A class that implements an ASM ClassVisitor that collects super class and + * implemented interfaces for each class that it visits. + */ +class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) { + private val _ancestors = LinkedHashMap<String, Ancestors>() + + val ancestors: Map<String, Ancestors> + get() = _ancestors + + override fun visit(version: Int, access: Int, name: String?, signature: String?, + superName: String?, interfaces: Array<out String>?) { + name!! + + val old = _ancestors.put(name, Ancestors(superName, interfaces?.toList())) + if (old != null) { + throw RuntimeException("class $name already found") + } + + super.visit(version, access, name, signature, superName, interfaces) + } +}
\ No newline at end of file diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt new file mode 100644 index 000000000000..3e9d92cd978f --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -0,0 +1,56 @@ +package com.android.sdkparcelables + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.Opcodes +import java.io.File +import java.io.IOException +import java.util.zip.ZipFile + +fun main(args: Array<String>) { + if (args.size != 2) { + usage() + } + + val zipFileName = args[0] + val aidlFileName = args[1] + + val zipFile: ZipFile + + try { + zipFile = ZipFile(zipFileName) + } catch (e: IOException) { + System.err.println("error reading input jar: ${e.message}") + kotlin.system.exitProcess(2) + } + + val ancestorCollector = AncestorCollector(Opcodes.ASM6, null) + + for (entry in zipFile.entries()) { + if (entry.name.endsWith(".class")) { + val reader = ClassReader(zipFile.getInputStream(entry)) + reader.accept(ancestorCollector, + ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) + } + } + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorCollector.ancestors) + + try { + val outFile = File(aidlFileName) + val outWriter = outFile.bufferedWriter() + for (parcelable in parcelables) { + outWriter.write("parcelable ") + outWriter.write(parcelable.replace('/', '.').replace('$', '.')) + outWriter.write(";\n") + } + outWriter.flush() + } catch (e: IOException) { + System.err.println("error writing output aidl: ${e.message}") + kotlin.system.exitProcess(2) + } +} + +fun usage() { + System.err.println("Usage: <input jar> <output aidl>") + kotlin.system.exitProcess(1) +}
\ No newline at end of file diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt new file mode 100644 index 000000000000..620f798daf48 --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt @@ -0,0 +1,52 @@ +package com.android.sdkparcelables + +/** A class that uses an ancestor map to find all classes that + * implement android.os.Parcelable, including indirectly through + * super classes or super interfaces. + */ +class ParcelableDetector { + companion object { + fun ancestorsToParcelables(ancestors: Map<String, Ancestors>): List<String> { + val impl = Impl(ancestors) + impl.build() + return impl.parcelables + } + } + + private class Impl(val ancestors: Map<String, Ancestors>) { + val isParcelableCache = HashMap<String, Boolean>() + val parcelables = ArrayList<String>() + + fun build() { + val classList = ancestors.keys + classList.filterTo(parcelables, this::isParcelable) + parcelables.sort() + } + + private fun isParcelable(c: String?): Boolean { + if (c == null) { + return false + } + + if (c == "android/os/Parcelable") { + return true + } + + val old = isParcelableCache[c] + if (old != null) { + return old + } + + val cAncestors = ancestors[c] ?: + throw RuntimeException("class $c missing ancestor information") + + val seq = (cAncestors.interfaces?.asSequence() ?: emptySequence()) + + cAncestors.superName + + val ancestorIsParcelable = seq.any(this::isParcelable) + + isParcelableCache[c] = ancestorIsParcelable + return ancestorIsParcelable + } + } +} diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt new file mode 100644 index 000000000000..edfc8259a738 --- /dev/null +++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt @@ -0,0 +1,57 @@ +package com.android.sdkparcelables + +import junit.framework.TestCase.assertEquals +import org.junit.Test + +class ParcelableDetectorTest { + @Test + fun `detect implements`() { + val ancestorMap = mapOf( + testAncestors("android/test/Parcelable",null, "android/os/Parcelable"), + testAncestors("android/os/Parcelable", null)) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable")) + } + + @Test + fun `detect implements in reverse order`() { + val ancestorMap = mapOf( + testAncestors("android/os/Parcelable", null), + testAncestors("android/test/Parcelable",null, "android/os/Parcelable")) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable")) + } + + @Test + fun `detect super implements`() { + val ancestorMap = mapOf( + testAncestors("android/test/SuperParcelable",null, "android/os/Parcelable"), + testAncestors("android/test/Parcelable","android/test/SuperParcelable"), + testAncestors("android/os/Parcelable", null)) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable", "android/test/SuperParcelable")) + } + + @Test + fun `detect super interface`() { + val ancestorMap = mapOf( + testAncestors("android/test/IParcelable",null, "android/os/Parcelable"), + testAncestors("android/test/Parcelable",null, "android/test/IParcelable"), + testAncestors("android/os/Parcelable", null)) + + val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap) + + assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/IParcelable", "android/test/Parcelable")) + } + +} + +private fun testAncestors(name: String, superName: String?, vararg interfaces: String): Pair<String, Ancestors> { + return Pair(name, Ancestors(superName, interfaces.toList())) +} diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index a25784debf12..80853b13a7cc 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -112,7 +112,7 @@ java_type(const FieldDescriptor* field) case FieldDescriptor::TYPE_MESSAGE: // TODO: not the final package name if (field->message_type()->full_name() == - "android.os.statsd.AttributionChain") { + "android.os.statsd.AttributionNode") { return JAVA_TYPE_ATTRIBUTION_CHAIN; } else { return JAVA_TYPE_OBJECT; diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index 5e93c08e88b8..89749fb52bb4 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -769,7 +769,7 @@ run(int argc, char const*const* argv) AtomDecl attributionDecl; vector<java_type_t> attributionSignature; - collate_atom(android::os::statsd::Attribution::descriptor(), + collate_atom(android::os::statsd::AttributionNode::descriptor(), &attributionDecl, &attributionSignature); // Write the .cpp file diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto index 84b22cdaf522..66cbee8f219b 100644 --- a/tools/stats_log_api_gen/test.proto +++ b/tools/stats_log_api_gen/test.proto @@ -39,7 +39,7 @@ enum AnEnum { } message AllTypesAtom { - optional android.os.statsd.AttributionChain attribution_chain = 1; + repeated android.os.statsd.AttributionNode attribution_chain = 1; optional double double_field = 2; optional float float_field = 3; optional int64 int64_field = 4; @@ -99,12 +99,12 @@ message BadSkippedFieldMultiple { } } -message BadAttributionChainPositionAtom { +message BadAttributionNodePositionAtom { optional int32 field1 = 1; - optional android.os.statsd.AttributionChain attribution = 2; + repeated android.os.statsd.AttributionNode attribution = 2; } -message BadAttributionChainPosition { - oneof event { BadAttributionChainPositionAtom bad = 1; } +message BadAttributionNodePosition { + oneof event { BadAttributionNodePositionAtom bad = 1; } } diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp index b2b7298d86c7..9e22cd92fd53 100644 --- a/tools/stats_log_api_gen/test_collation.cpp +++ b/tools/stats_log_api_gen/test_collation.cpp @@ -191,10 +191,10 @@ TEST(CollationTest, FailOnSkippedFieldsMultiple) { * Test that atoms that have an attribution chain not in the first position are * rejected. */ -TEST(CollationTest, FailBadAttributionChainPosition) { +TEST(CollationTest, FailBadAttributionNodePosition) { Atoms atoms; int errorCount = - collate_atoms(BadAttributionChainPosition::descriptor(), &atoms); + collate_atoms(BadAttributionNodePosition::descriptor(), &atoms); EXPECT_EQ(1, errorCount); } diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index e3752ac77a5e..928a1da8b51c 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -160,6 +160,24 @@ public class WifiScanner { */ public static final int REPORT_EVENT_NO_BATCH = (1 << 2); + /** + * This is used to indicate the purpose of the scan to the wifi chip in + * {@link ScanSettings#type}. + * On devices with multiple hardware radio chains (and hence different modes of scan), + * this type serves as an indication to the hardware on what mode of scan to perform. + * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value. + * + * Note: This serves as an intent and not as a stipulation, the wifi chip + * might honor or ignore the indication based on the current radio conditions. Always + * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used + * to receive the corresponding scan result. + */ + /** {@hide} */ + public static final int TYPE_LOW_LATENCY = 0; + /** {@hide} */ + public static final int TYPE_LOW_POWER = 1; + /** {@hide} */ + public static final int TYPE_HIGH_ACCURACY = 2; /** {@hide} */ public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings"; @@ -193,7 +211,8 @@ public class WifiScanner { * list of hidden networks to scan for. Explicit probe requests are sent out for such * networks during scan. Only valid for single scan requests. * {@hide} - * */ + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public HiddenNetwork[] hiddenNetworks; /** period of background scan; in millisecond, 0 => single shot scan */ public int periodInMs; @@ -223,6 +242,13 @@ public class WifiScanner { * {@hide} */ public boolean isPnoScan; + /** + * Indicate the type of scan to be performed by the wifi chip. + * Default value: {@link #TYPE_LOW_LATENCY}. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public int type = TYPE_LOW_LATENCY; /** Implement the Parcelable interface {@hide} */ public int describeContents() { @@ -239,6 +265,7 @@ public class WifiScanner { dest.writeInt(maxPeriodInMs); dest.writeInt(stepCount); dest.writeInt(isPnoScan ? 1 : 0); + dest.writeInt(type); if (channels != null) { dest.writeInt(channels.length); for (int i = 0; i < channels.length; i++) { @@ -272,6 +299,7 @@ public class WifiScanner { settings.maxPeriodInMs = in.readInt(); settings.stepCount = in.readInt(); settings.isPnoScan = in.readInt() == 1; + settings.type = in.readInt(); int num_channels = in.readInt(); settings.channels = new ChannelSpec[num_channels]; for (int i = 0; i < num_channels; i++) { diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java index 8b86cdde4a9e..2ea6e797ec93 100644 --- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java +++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java @@ -26,13 +26,49 @@ import android.os.Handler; */ public abstract class ProvisioningCallback { - /** + /** * The reason code for Provisioning Failure due to connection failure to OSU AP. * @hide */ public static final int OSU_FAILURE_AP_CONNECTION = 1; /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_SERVER_URL_INVALID = 2; + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_SERVER_CONNECTION = 3; + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_SERVER_VALIDATION = 4; + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_PROVIDER_VERIFICATION = 5; + + /** + * The reason code for Provisioning Failure when a provisioning flow is aborted. + * @hide + */ + public static final int OSU_FAILURE_PROVISIONING_ABORTED = 6; + + /** + * The reason code for Provisioning Failure when a provisioning flow is aborted. + * @hide + */ + public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7; + + /** * The status code for Provisioning flow to indicate connecting to OSU AP * @hide */ @@ -45,6 +81,24 @@ public abstract class ProvisioningCallback { public static final int OSU_STATUS_AP_CONNECTED = 2; /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_SERVER_CONNECTED = 3; + + /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_SERVER_VALIDATED = 4; + + /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_PROVIDER_VERIFIED = 5; + + /** * Provisioning status for OSU failure * @param status indicates error condition */ diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java index 8be7782d5664..c3e10074c56c 100644 --- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java +++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java @@ -37,7 +37,7 @@ import java.util.Objects; * * @hide (@SystemApi) */ -public class ResponderConfig implements Parcelable { +public final class ResponderConfig implements Parcelable { private static final int AWARE_BAND_2_DISCOVERY_CHANNEL = 2437; /** @hide */ @@ -122,15 +122,14 @@ public class ResponderConfig implements Parcelable { /** * The MAC address of the Responder. Will be null if a Wi-Fi Aware peer identifier (the * peerHandle field) ise used to identify the Responder. - * TODO: convert to MacAddress */ - public MacAddress macAddress; + public final MacAddress macAddress; /** * The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress * field) is used to identify the Responder. */ - public PeerHandle peerHandle; + public final PeerHandle peerHandle; /** * The device type of the Responder. @@ -171,7 +170,7 @@ public class ResponderConfig implements Parcelable { public final int preamble; /** - * Constructs Responder configuration. + * Constructs Responder configuration, using a MAC address to identify the Responder. * * @param macAddress The MAC address of the Responder. * @param responderType The type of the responder device, specified using @@ -210,7 +209,7 @@ public class ResponderConfig implements Parcelable { } /** - * Constructs Responder configuration. + * Constructs Responder configuration, using a Wi-Fi Aware PeerHandle to identify the Responder. * * @param peerHandle The Wi-Fi Aware peer identifier of the Responder. * @param responderType The type of the responder device, specified using @@ -245,6 +244,45 @@ public class ResponderConfig implements Parcelable { } /** + * Constructs Responder configuration. This is an internal-only constructor which specifies both + * a MAC address and a Wi-Fi PeerHandle to identify the Responder. + * + * @param macAddress The MAC address of the Responder. + * @param peerHandle The Wi-Fi Aware peer identifier of the Responder. + * @param responderType The type of the responder device, specified using + * {@link ResponderType}. + * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc. + * @param channelWidth Responder channel bandwidth, specified using {@link ChannelWidth}. + * @param frequency The primary 20 MHz frequency (in MHz) of the channel of the Responder. + * @param centerFreq0 Not used if the {@code channelWidth} is 20 MHz. If the Responder uses + * 40, 80 or 160 MHz, this is the center frequency (in MHz), if the + * Responder uses 80 + 80 MHz, this is the center frequency of the first + * segment (in MHz). + * @param centerFreq1 Only used if the {@code channelWidth} is 80 + 80 MHz. If the + * Responder + * uses 80 + 80 MHz, this is the center frequency of the second segment + * (in + * MHz). + * @param preamble The preamble used by the Responder, specified using + * {@link PreambleType}. + * @hide + */ + public ResponderConfig(@NonNull MacAddress macAddress, @NonNull PeerHandle peerHandle, + @ResponderType int responderType, boolean supports80211mc, + @ChannelWidth int channelWidth, int frequency, int centerFreq0, int centerFreq1, + @PreambleType int preamble) { + this.macAddress = macAddress; + this.peerHandle = peerHandle; + this.responderType = responderType; + this.supports80211mc = supports80211mc; + this.channelWidth = channelWidth; + this.frequency = frequency; + this.centerFreq0 = centerFreq0; + this.centerFreq1 = centerFreq1; + this.preamble = preamble; + } + + /** * Creates a Responder configuration from a {@link ScanResult} corresponding to an Access * Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}. */ diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java index e542789e01e3..a4366d454a67 100644 --- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java @@ -16,19 +16,23 @@ package android.net.wifi; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.validateMockitoUsage; import static org.mockito.Mockito.when; import android.content.Context; import android.os.Handler; +import android.os.Parcel; import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; +import android.net.wifi.WifiScanner.ScanSettings; import com.android.internal.util.test.BidirectionalAsyncChannelServer; import org.junit.After; import org.junit.Before; +import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -70,4 +74,50 @@ public class WifiScannerTest { validateMockitoUsage(); } + /** + * Verify parcel read/write for ScanSettings. + */ + @Test + public void verifyScanSettingsParcelWithBand() throws Exception { + ScanSettings writeSettings = new ScanSettings(); + writeSettings.type = WifiScanner.TYPE_LOW_POWER; + writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; + + ScanSettings readSettings = parcelWriteRead(writeSettings); + assertEquals(readSettings.type, writeSettings.type); + assertEquals(readSettings.band, writeSettings.band); + assertEquals(0, readSettings.channels.length); + } + + /** + * Verify parcel read/write for ScanSettings. + */ + @Test + public void verifyScanSettingsParcelWithChannels() throws Exception { + ScanSettings writeSettings = new ScanSettings(); + writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY; + writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; + writeSettings.channels = new WifiScanner.ChannelSpec[] { + new WifiScanner.ChannelSpec(5), + new WifiScanner.ChannelSpec(7) + }; + + ScanSettings readSettings = parcelWriteRead(writeSettings); + assertEquals(writeSettings.type, readSettings.type); + assertEquals(writeSettings.band, readSettings.band); + assertEquals(2, readSettings.channels.length); + assertEquals(5, readSettings.channels[0].frequency); + assertEquals(7, readSettings.channels[1].frequency); + } + + /** + * Write the provided {@link ScanSettings} to a parcel and deserialize it. + */ + private static ScanSettings parcelWriteRead(ScanSettings writeSettings) throws Exception { + Parcel parcel = Parcel.obtain(); + writeSettings.writeToParcel(parcel, 0); + parcel.setDataPosition(0); // Rewind data position back to the beginning for read. + return ScanSettings.CREATOR.createFromParcel(parcel); + } + } |