diff options
264 files changed, 10608 insertions, 4424 deletions
diff --git a/Android.bp b/Android.bp index 32bd40861839..e096c9df5662 100644 --- a/Android.bp +++ b/Android.bp @@ -535,6 +535,7 @@ java_defaults { "telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl", "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl", + "telephony/java/android/telephony/ims/aidl/IRcs.aidl", "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl", @@ -611,7 +612,6 @@ java_defaults { "telephony/java/com/android/internal/telephony/euicc/ISetDefaultSmdpAddressCallback.aidl", "telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl", "telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl", - "telephony/java/com/android/internal/telephony/rcs/IRcs.aidl", "wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl", "wifi/java/android/net/wifi/INetworkRequestUserSelectionCallback.aidl", "wifi/java/android/net/wifi/ISoftApCallback.aidl", diff --git a/api/current.txt b/api/current.txt index 725548302e4e..bb6b889c9a14 100644 --- a/api/current.txt +++ b/api/current.txt @@ -91,6 +91,7 @@ package android { field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS"; field public static final java.lang.String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS"; + field public static final java.lang.String CALL_COMPANION_APP = "android.permission.CALL_COMPANION_APP"; field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS"; @@ -703,6 +704,7 @@ package android { field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e field public static final int hardwareAccelerated = 16843475; // 0x10102d3 field public static final int hasCode = 16842764; // 0x101000c + field public static final int hasFragileUserData = 16844192; // 0x10105a0 field public static final deprecated int headerAmPmTextAppearance = 16843936; // 0x10104a0 field public static final int headerBackground = 16843055; // 0x101012f field public static final deprecated int headerDayOfMonthTextAppearance = 16843927; // 0x1010497 @@ -5949,6 +5951,15 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); + method public int describeContents(); + method public android.app.RemoteAction getUserAction(); + method public java.lang.CharSequence getUserMessage(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR; + } + public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); method public android.app.RemoteAction clone(); @@ -5974,6 +5985,7 @@ package android.app { method public java.util.Set<java.lang.String> getAllowedDataTypes(); method public java.lang.CharSequence[] getChoices(); method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String); + method public int getEditChoicesBeforeSending(); method public android.os.Bundle getExtras(); method public java.lang.CharSequence getLabel(); method public java.lang.String getResultKey(); @@ -5983,6 +5995,9 @@ package android.app { method public static void setResultsSource(android.content.Intent, int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR; + field public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; // 0x0 + field public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; // 0x1 + field public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; // 0x2 field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results"; field public static final int SOURCE_CHOICE = 1; // 0x1 @@ -5997,6 +6012,7 @@ package android.app { method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean); method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean); method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]); + method public android.app.RemoteInput.Builder setEditChoicesBeforeSending(int); method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence); } @@ -6434,6 +6450,13 @@ package android.app.admin { field public static final android.os.Parcelable.Creator<android.app.admin.ConnectEvent> CREATOR; } + public class DelegatedAdminReceiver extends android.content.BroadcastReceiver { + ctor public DelegatedAdminReceiver(); + method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String); + method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int); + method public void onReceive(android.content.Context, android.content.Intent); + } + public final class DeviceAdminInfo implements android.os.Parcelable { ctor public DeviceAdminInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public int describeContents(); @@ -6496,11 +6519,13 @@ package android.app.admin { method public void onUserStarted(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserStopped(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserSwitched(android.content.Context, android.content.Intent, android.os.UserHandle); + field public static final java.lang.String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; field public static final java.lang.String ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING"; field public static final java.lang.String ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING"; + field public static final java.lang.String ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE"; field public static final java.lang.String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"; field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING"; field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"; @@ -6574,6 +6599,7 @@ package android.app.admin { method public java.lang.CharSequence getOrganizationName(android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName); method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); + method public int getPasswordComplexity(); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -6745,10 +6771,13 @@ package android.app.admin { field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions"; field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall"; field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install"; + field public static final java.lang.String DELEGATION_CERT_SELECTION = "delegation-cert-selection"; field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app"; field public static final java.lang.String DELEGATION_INSTALL_EXISTING_PACKAGE = "delegation-install-existing-package"; field public static final java.lang.String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages"; + field public static final java.lang.String DELEGATION_NETWORK_LOGGING = "delegation-network-logging"; field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access"; + field public static final java.lang.String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation"; field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant"; field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2 field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3 @@ -6827,6 +6856,10 @@ package android.app.admin { field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1 field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2 field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning"; + field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000 + field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000 + field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000 + field public static final int PASSWORD_COMPLEXITY_NONE = 0; // 0x0 field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000 field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000 field public static final int PASSWORD_QUALITY_BIOMETRIC_WEAK = 32768; // 0x8000 @@ -7615,13 +7648,16 @@ package android.app.usage { method public java.lang.String getPackageName(); method public java.lang.String getShortcutId(); method public long getTimeStamp(); + field public static final int ACTIVITY_PAUSED = 2; // 0x2 + field public static final int ACTIVITY_RESUMED = 1; // 0x1 + field public static final int ACTIVITY_STOPPED = 23; // 0x17 field public static final int CONFIGURATION_CHANGE = 5; // 0x5 field public static final int FOREGROUND_SERVICE_START = 19; // 0x13 field public static final int FOREGROUND_SERVICE_STOP = 20; // 0x14 field public static final int KEYGUARD_HIDDEN = 18; // 0x12 field public static final int KEYGUARD_SHOWN = 17; // 0x11 - field public static final int MOVE_TO_BACKGROUND = 2; // 0x2 - field public static final int MOVE_TO_FOREGROUND = 1; // 0x1 + field public static final deprecated int MOVE_TO_BACKGROUND = 2; // 0x2 + field public static final deprecated int MOVE_TO_FOREGROUND = 1; // 0x1 field public static final int NONE = 0; // 0x0 field public static final int SCREEN_INTERACTIVE = 15; // 0xf field public static final int SCREEN_NON_INTERACTIVE = 16; // 0x10 @@ -7638,9 +7674,11 @@ package android.app.usage { method public long getLastTimeForegroundServiceUsed(); method public long getLastTimeStamp(); method public long getLastTimeUsed(); + method public long getLastTimeVisible(); method public java.lang.String getPackageName(); method public long getTotalTimeForegroundServiceUsed(); method public long getTotalTimeInForeground(); + method public long getTotalTimeVisible(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.usage.UsageStats> CREATOR; } @@ -11189,6 +11227,15 @@ package android.content.pm { field public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1024; // 0x400 } + public final class ModuleInfo implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getName(); + method public java.lang.String getPackageName(); + method public boolean isHidden(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.content.pm.ModuleInfo> CREATOR; + } + public class PackageInfo implements android.os.Parcelable { ctor public PackageInfo(); method public int describeContents(); @@ -11405,6 +11452,7 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); + method public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int); method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public abstract java.lang.String getInstallerPackageName(java.lang.String); method public abstract byte[] getInstantAppCookie(); @@ -11412,6 +11460,7 @@ package android.content.pm { method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String); method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String); + method public android.content.pm.ModuleInfo getModuleInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract java.lang.String getNameForUid(int); method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int); method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -22829,9 +22878,10 @@ package android.location { ctor public SettingInjectorService(java.lang.String); method public final android.os.IBinder onBind(android.content.Intent); method protected abstract boolean onGetEnabled(); - method protected abstract deprecated java.lang.String onGetSummary(); + method protected abstract java.lang.String onGetSummary(); method public final void onStart(android.content.Intent, int); method public final int onStartCommand(android.content.Intent, int, int); + method public static final void refreshSettings(android.content.Context); field public static final java.lang.String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged"; field public static final java.lang.String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService"; field public static final java.lang.String ATTRIBUTES_NAME = "injected-location-setting"; @@ -23963,6 +24013,7 @@ package android.media { method public void releaseOutputBuffer(int, boolean); method public void releaseOutputBuffer(int, long); method public void reset(); + method public void setAudioPresentation(android.media.AudioPresentation); method public void setCallback(android.media.MediaCodec.Callback, android.os.Handler); method public void setCallback(android.media.MediaCodec.Callback); method public void setInputSurface(android.view.Surface); @@ -24953,6 +25004,7 @@ package android.media { field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2 field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3 field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0 + field public static final int MUXER_OUTPUT_OGG = 4; // 0x4 field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } @@ -25408,6 +25460,7 @@ package android.media { field public static final int AMR_WB = 2; // 0x2 field public static final int DEFAULT = 0; // 0x0 field public static final int HE_AAC = 4; // 0x4 + field public static final int OPUS = 7; // 0x7 field public static final int VORBIS = 6; // 0x6 } @@ -25458,6 +25511,7 @@ package android.media { field public static final int DEFAULT = 0; // 0x0 field public static final int MPEG_2_TS = 8; // 0x8 field public static final int MPEG_4 = 2; // 0x2 + field public static final int OGG = 11; // 0xb field public static final deprecated int RAW_AMR = 3; // 0x3 field public static final int THREE_GPP = 1; // 0x1 field public static final int WEBM = 9; // 0x9 @@ -35936,9 +35990,11 @@ package android.provider { } public final class CalendarContract { + method public static boolean startViewCalendarEventInManagedProfile(android.content.Context, long, long, long, boolean, int); field public static final java.lang.String ACCOUNT_TYPE_LOCAL = "LOCAL"; field public static final java.lang.String ACTION_EVENT_REMINDER = "android.intent.action.EVENT_REMINDER"; field public static final java.lang.String ACTION_HANDLE_CUSTOM_EVENT = "android.provider.calendar.action.HANDLE_CUSTOM_EVENT"; + field public static final java.lang.String ACTION_VIEW_WORK_CALENDAR_EVENT = "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT"; field public static final java.lang.String AUTHORITY = "com.android.calendar"; field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"; field public static final android.net.Uri CONTENT_URI; @@ -35946,6 +36002,7 @@ package android.provider { field public static final java.lang.String EXTRA_EVENT_ALL_DAY = "allDay"; field public static final java.lang.String EXTRA_EVENT_BEGIN_TIME = "beginTime"; field public static final java.lang.String EXTRA_EVENT_END_TIME = "endTime"; + field public static final java.lang.String EXTRA_EVENT_ID = "id"; } public static final class CalendarContract.Attendees implements android.provider.BaseColumns android.provider.CalendarContract.AttendeesColumns android.provider.CalendarContract.EventsColumns { @@ -38476,10 +38533,13 @@ package android.provider { } public static final class Telephony.CarrierId implements android.provider.BaseColumns { + method public static android.net.Uri getPreciseCarrierIdUriForSubscriptionId(int); method public static android.net.Uri getUriForSubscriptionId(int); field public static final java.lang.String CARRIER_ID = "carrier_id"; field public static final java.lang.String CARRIER_NAME = "carrier_name"; field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String PRECISE_CARRIER_ID = "precise_carrier_id"; + field public static final java.lang.String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name"; } public static final class Telephony.Carriers implements android.provider.BaseColumns { @@ -40625,13 +40685,16 @@ package android.service.carrier { public class CarrierIdentifier implements android.os.Parcelable { ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String); + ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, int); ctor public CarrierIdentifier(byte[], java.lang.String, java.lang.String); method public int describeContents(); + method public int getCarrierId(); method public java.lang.String getGid1(); method public java.lang.String getGid2(); method public java.lang.String getImsi(); method public java.lang.String getMcc(); method public java.lang.String getMnc(); + method public int getPreciseCarrierId(); method public java.lang.String getSpn(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.carrier.CarrierIdentifier> CREATOR; @@ -43123,12 +43186,14 @@ package android.telecom { field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT"; field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; + field public static final java.lang.String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED"; field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS"; field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE"; field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS"; field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS"; field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING"; field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI"; + field public static final java.lang.String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"; field public static final int PRESENTATION_ALLOWED = 1; // 0x1 field public static final int PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int PRESENTATION_RESTRICTED = 2; // 0x2 @@ -43316,6 +43381,7 @@ package android.telephony { field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool"; field public static final java.lang.String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app"; + field public static final java.lang.String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string"; field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int"; field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int"; @@ -44185,6 +44251,7 @@ package android.telephony { method public java.util.List<android.telephony.CellInfo> getAllCellInfo(); method public int getCallState(); method public android.os.PersistableBundle getCarrierConfig(); + method public int getCarrierIdFromSimMccMnc(); method public deprecated android.telephony.CellLocation getCellLocation(); method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(); method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int); @@ -44222,6 +44289,8 @@ package android.telephony { method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); method public java.lang.String getSimOperatorName(); + method public int getSimPreciseCarrierId(); + method public java.lang.CharSequence getSimPreciseCarrierIdName(); method public java.lang.String getSimSerialNumber(); method public int getSimState(); method public int getSimState(int); @@ -44278,6 +44347,7 @@ package android.telephony { field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION"; field public static final java.lang.String ACTION_SMS_APP_SERVICE = "android.telephony.action.SMS_APP_SERVICE"; field public static final java.lang.String ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED"; + field public static final java.lang.String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED"; field public static final int APPTYPE_CSIM = 4; // 0x4 field public static final int APPTYPE_ISIM = 5; // 0x5 field public static final int APPTYPE_RUIM = 3; // 0x3 @@ -44310,6 +44380,8 @@ package android.telephony { field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT"; field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT"; field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE"; + field public static final java.lang.String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID"; + field public static final java.lang.String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.PRECISE_CARRIER_NAME"; field public static final java.lang.String EXTRA_STATE = "state"; field public static final java.lang.String EXTRA_STATE_IDLE; field public static final java.lang.String EXTRA_STATE_OFFHOOK; @@ -52504,19 +52576,19 @@ package android.view.textclassifier { method public int describeContents(); method public android.app.Person getAuthor(); method public android.os.Bundle getExtras(); + method public java.time.ZonedDateTime getReferenceTime(); method public java.lang.CharSequence getText(); - method public java.time.ZonedDateTime getTime(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; field public static final android.app.Person PERSON_USER_LOCAL; + field public static final android.app.Person PERSON_USER_REMOTE; } public static final class ConversationActions.Message.Builder { - ctor public ConversationActions.Message.Builder(); + ctor public ConversationActions.Message.Builder(android.app.Person); method public android.view.textclassifier.ConversationActions.Message build(); - method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person); - method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle); + method public android.view.textclassifier.ConversationActions.Message.Builder setReferenceTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence); } @@ -52699,6 +52771,7 @@ package android.view.textclassifier { method public default android.view.textclassifier.ConversationActions suggestConversationActions(android.view.textclassifier.ConversationActions.Request); method public default android.view.textclassifier.TextSelection suggestSelection(android.view.textclassifier.TextSelection.Request); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList); + field public static final java.lang.String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER"; field public static final java.lang.String HINT_TEXT_IS_EDITABLE = "android.text_is_editable"; field public static final java.lang.String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable"; field public static final android.view.textclassifier.TextClassifier NO_OP; diff --git a/api/system-current.txt b/api/system-current.txt index 6c2b30f6a725..da6840c88a25 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -205,6 +205,7 @@ package android { } public static final class R.dimen { + field public static final int config_mediaMetadataBitmapMaxSize = 17104904; // 0x1050008 field public static final int config_restrictedIconSize = 17104903; // 0x1050007 } @@ -899,6 +900,7 @@ package android.app.usage { } public static final class UsageEvents.Event { + method public int getInstanceId(); method public java.lang.String getNotificationChannelId(); field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc field public static final int NOTIFICATION_SEEN = 10; // 0xa @@ -1007,7 +1009,7 @@ package android.content { method public void setDetectNotResponding(long); } - public abstract class ContentResolver { + public abstract class ContentResolver implements android.content.ContentInterface { method public android.graphics.drawable.Drawable getTypeDrawable(java.lang.String); } @@ -1214,6 +1216,7 @@ package android.content.pm { method public abstract boolean arePermissionsIndividuallyControlled(); method public boolean canSuspendPackage(java.lang.String); method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String); + method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.dex.ArtManager getArtManager(); method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int); method public java.lang.CharSequence getHarmfulAppWarning(java.lang.String); @@ -1225,11 +1228,13 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String); method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int); method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); - method public java.lang.String getWellbeingPackageName(); method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle); + method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, android.os.UserHandle); + method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, android.os.UserHandle); + method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, android.os.UserHandle); method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback); method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName); @@ -3966,6 +3971,19 @@ package android.os { field public static final java.lang.String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; } + public class Binder implements android.os.IBinder { + method public static final long clearCallingWorkSource(); + method public static final int getCallingWorkSourceUid(); + method public static final void restoreCallingWorkSource(long); + method public static final long setCallingWorkSourceUid(int); + method public static void setProxyTransactListener(android.os.Binder.ProxyTransactListener); + } + + public static abstract interface Binder.ProxyTransactListener { + method public abstract void onTransactEnded(java.lang.Object); + method public abstract java.lang.Object onTransactStarted(android.os.IBinder, int); + } + public final class ConfigUpdate { field public static final java.lang.String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB"; field public static final java.lang.String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS"; @@ -5850,6 +5868,7 @@ package android.telephony { method public void enableVideoCalling(boolean); method public java.lang.String getAidForAppType(int); method public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int); + method public int getCardIdForDefaultEuicc(); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int); method public java.lang.String getCdmaMdn(); @@ -5916,6 +5935,7 @@ package android.telephony { field public static final java.lang.String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE"; field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; + field public static final int INVALID_CARD_ID = -1; // 0xffffffff field public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000L; // 0xea60L field public static final int NETWORK_MODE_CDMA_EVDO = 4; // 0x4 field public static final int NETWORK_MODE_CDMA_NO_EVDO = 5; // 0x5 @@ -7236,6 +7256,7 @@ package android.view { package android.view.accessibility { public final class AccessibilityManager { + method public int getAccessibilityWindowId(android.os.IBinder); method public void performAccessibilityShortcut(); } diff --git a/api/test-current.txt b/api/test-current.txt index 8d8999e6a30b..627ef22a5d56 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -362,6 +362,7 @@ package android.content.pm { method public abstract java.lang.String getPermissionControllerPackageName(); method public abstract java.lang.String getServicesSystemSharedLibraryPackageName(); method public abstract java.lang.String getSharedSystemSharedLibraryPackageName(); + method public java.lang.String getWellbeingPackageName(); method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 7fa05be29b9d..04173b217dcb 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -424,13 +424,14 @@ void StatsService::print_cmd_help(int out) { dprintf(out, "\n be removed from memory and disk!\n"); dprintf(out, "\n"); dprintf(out, - "usage: adb shell cmd stats dump-report [UID] NAME [--include_current_bucket] " - "[--proto]\n"); + "usage: adb shell cmd stats dump-report [UID] NAME [--keep_data] " + "[--include_current_bucket] [--proto]\n"); dprintf(out, " Dump all metric data for a configuration.\n"); dprintf(out, " UID The uid of the configuration. It is only possible to pass\n"); dprintf(out, " the UID parameter on eng builds. If UID is omitted the\n"); dprintf(out, " calling uid is used.\n"); dprintf(out, " NAME The name of the configuration\n"); + dprintf(out, " --keep_data Do NOT erase the data upon dumping it.\n"); dprintf(out, " --proto Print proto binary.\n"); dprintf(out, "\n"); dprintf(out, "\n"); @@ -590,6 +591,7 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) { bool good = false; bool proto = false; bool includeCurrentBucket = false; + bool eraseData = true; int uid; string name; if (!std::strcmp("--proto", args[argCount-1].c_str())) { @@ -600,6 +602,10 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) { includeCurrentBucket = true; argCount -= 1; } + if (!std::strcmp("--keep_data", args[argCount-1].c_str())) { + eraseData = false; + argCount -= 1; + } if (argCount == 2) { // Automatically pick the UID uid = IPCThreadState::self()->getCallingUid(); @@ -627,7 +633,7 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) { if (good) { vector<uint8_t> data; mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), getElapsedRealtimeNs(), - includeCurrentBucket, true /* erase_data */, ADB_DUMP, &data); + includeCurrentBucket, eraseData, ADB_DUMP, &data); if (proto) { for (size_t i = 0; i < data.size(); i ++) { dprintf(out, "%c", data[i]); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 6518c61600aa..410bd198a227 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -2394,16 +2394,23 @@ message KernelWakelock { } /** - * Pulls low power state information. This includes platform and subsystem sleep state information, - * PowerStatePlatformSleepState, PowerStateVoter or PowerStateSubsystemSleepState as defined in + * Pulls low power state information. If power.stats HAL is not available, this + * includes platform and subsystem sleep state information, + * PowerStatePlatformSleepState, PowerStateVoter or PowerStateSubsystemSleepState + * as defined in: * hardware/interfaces/power/1.0/types.hal * hardware/interfaces/power/1.1/types.hal + * If power.stats HAL is available, this includes PowerEntityStateResidencyResult + * as defined in: + * hardware/interfaces/power/stats/1.0/types.hal */ message SubsystemSleepState { // Subsystem name optional string subsystem_name = 1; // For PlatformLowPowerStats (hal 1.0), this is the voter name, which could be empty. // For SubsystemLowPowerStats (hal 1.1), this is the sleep state name. + // For PowerEntityStateResidencyResult (hal power/stats/1.0) this is the + // powerEntityStateName from the corresponding PowerEntityStateInfo. optional string subname = 2; // The number of times it entered, or voted for entering the sleep state optional uint64 count = 3; diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp index 4501b64ad47e..c8c392016e52 100644 --- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp +++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp @@ -19,6 +19,8 @@ #include <android/hardware/power/1.0/IPower.h> #include <android/hardware/power/1.1/IPower.h> +#include <android/hardware/power/stats/1.0/IPowerStats.h> + #include <fcntl.h> #include <hardware/power.h> #include <hardware_legacy/power.h> @@ -42,9 +44,12 @@ using android::hardware::hidl_vec; using android::hardware::power::V1_0::IPower; using android::hardware::power::V1_0::PowerStatePlatformSleepState; using android::hardware::power::V1_0::PowerStateVoter; -using android::hardware::power::V1_0::Status; using android::hardware::power::V1_1::PowerStateSubsystem; using android::hardware::power::V1_1::PowerStateSubsystemSleepState; +using android::hardware::power::stats::V1_0::PowerEntityInfo; +using android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult; +using android::hardware::power::stats::V1_0::PowerEntityStateSpace; + using android::hardware::Return; using android::hardware::Void; @@ -55,44 +60,209 @@ namespace android { namespace os { namespace statsd { +std::function<bool(vector<shared_ptr<LogEvent>>* data)> gPuller = {}; + sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr; sp<android::hardware::power::V1_1::IPower> gPowerHalV1_1 = nullptr; +sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr; + +std::unordered_map<uint32_t, std::string> gEntityNames = {}; +std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> gStateNames = {}; + std::mutex gPowerHalMutex; -bool gPowerHalExists = true; -bool getPowerHal() { - if (gPowerHalExists && gPowerHalV1_0 == nullptr) { - gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService(); - if (gPowerHalV1_0 != nullptr) { - gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0); - ALOGI("Loaded power HAL service"); - } else { - ALOGW("Couldn't load power HAL service"); - gPowerHalExists = false; +// The caller must be holding gPowerHalMutex. +void deinitPowerStatsLocked() { + gPowerHalV1_0 = nullptr; + gPowerHalV1_1 = nullptr; + gPowerStatsHalV1_0 = nullptr; +} + +struct PowerHalDeathRecipient : virtual public hardware::hidl_death_recipient { + virtual void serviceDied(uint64_t cookie, + const wp<android::hidl::base::V1_0::IBase>& who) override { + // The HAL just died. Reset all handles to HAL services. + std::lock_guard<std::mutex> lock(gPowerHalMutex); + deinitPowerStatsLocked(); + } +}; + +sp<PowerHalDeathRecipient> gDeathRecipient = new PowerHalDeathRecipient(); + +SubsystemSleepStatePuller::SubsystemSleepStatePuller() : + StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) { +} + +// The caller must be holding gPowerHalMutex. +bool checkResultLocked(const Return<void> &ret, const char* function) { + if (!ret.isOk()) { + ALOGE("%s failed: requested HAL service not available. Description: %s", + function, ret.description().c_str()); + if (ret.isDeadObject()) { + deinitPowerStatsLocked(); + } + return false; + } + return true; +} + +// The caller must be holding gPowerHalMutex. +// gPowerStatsHalV1_0 must not be null +bool initializePowerStats() { + using android::hardware::power::stats::V1_0::Status; + + // Clear out previous content if we are re-initializing + gEntityNames.clear(); + gStateNames.clear(); + + Return<void> ret; + ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) { + if (status != Status::SUCCESS) { + ALOGE("Error getting power entity info"); + return; + } + + // construct lookup table of powerEntityId to power entity name + for (auto info : infos) { + gEntityNames.emplace(info.powerEntityId, info.powerEntityName); } + }); + if (!checkResultLocked(ret, __func__)) { + return false; } - return gPowerHalV1_0 != nullptr; + + ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) { + if (status != Status::SUCCESS) { + ALOGE("Error getting state info"); + return; + } + + // construct lookup table of powerEntityId, powerEntityStateId to power entity state name + for (auto stateSpace : stateSpaces) { + std::unordered_map<uint32_t, std::string> stateNames = {}; + for (auto state : stateSpace.states) { + stateNames.emplace(state.powerEntityStateId, + state.powerEntityStateName); + } + gStateNames.emplace(stateSpace.powerEntityId, stateNames); + } + }); + if (!checkResultLocked(ret, __func__)) { + return false; + } + + return (!gEntityNames.empty()) && (!gStateNames.empty()); } -SubsystemSleepStatePuller::SubsystemSleepStatePuller() : StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) { +// The caller must be holding gPowerHalMutex. +bool getPowerStatsHalLocked() { + if(gPowerStatsHalV1_0 == nullptr) { + gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService(); + if (gPowerStatsHalV1_0 == nullptr) { + ALOGE("Unable to get power.stats HAL service."); + return false; + } + + // Link death recipient to power.stats service handle + hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0); + if (!linked.isOk()) { + ALOGE("Transaction error in linking to power.stats HAL death: %s", + linked.description().c_str()); + deinitPowerStatsLocked(); + return false; + } else if (!linked) { + ALOGW("Unable to link to power.stats HAL death notifications"); + // We should still continue even though linking failed + } + return initializePowerStats(); + } + return true; } -bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - std::lock_guard<std::mutex> lock(gPowerHalMutex); +// The caller must be holding gPowerHalMutex. +bool getIPowerStatsDataLocked(vector<shared_ptr<LogEvent>>* data) { + using android::hardware::power::stats::V1_0::Status; - if (!getPowerHal()) { - ALOGE("Power Hal not loaded"); + if(!getPowerStatsHalLocked()) { return false; } int64_t wallClockTimestampNs = getWallClockNs(); int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - data->clear(); + // Get power entity state residency data + bool success = false; + Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({}, + [&data, &success, wallClockTimestampNs, elapsedTimestampNs] + (auto results, auto status) { + if (status == Status::NOT_SUPPORTED) { + ALOGW("getPowerEntityStateResidencyData is not supported"); + success = false; + return; + } + + for(auto result : results) { + for(auto stateResidency : result.stateResidencyData) { + auto statePtr = make_shared<LogEvent>( + android::util::SUBSYSTEM_SLEEP_STATE, + wallClockTimestampNs, elapsedTimestampNs); + statePtr->write(gEntityNames.at(result.powerEntityId)); + statePtr->write(gStateNames.at(result.powerEntityId) + .at(stateResidency.powerEntityStateId)); + statePtr->write(stateResidency.totalStateEntryCount); + statePtr->write(stateResidency.totalTimeInStateMs); + statePtr->init(); + data->emplace_back(statePtr); + } + } + success = true; + }); + // Intentionally not returning early here. + // bool success determines if this succeeded or not. + checkResultLocked(ret, __func__); - Return<void> ret; + return success; +} + +// The caller must be holding gPowerHalMutex. +bool getPowerHalLocked() { + if(gPowerHalV1_0 == nullptr) { + gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService(); + if(gPowerHalV1_0 == nullptr) { + ALOGE("Unable to get power HAL service."); + return false; + } + gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0); + + // Link death recipient to power service handle + hardware::Return<bool> linked = gPowerHalV1_0->linkToDeath(gDeathRecipient, 0); + if (!linked.isOk()) { + ALOGE("Transaction error in linking to power HAL death: %s", + linked.description().c_str()); + gPowerHalV1_0 = nullptr; + return false; + } else if (!linked) { + ALOGW("Unable to link to power. death notifications"); + // We should still continue even though linking failed + } + } + return true; +} + +// The caller must be holding gPowerHalMutex. +bool getIPowerDataLocked(vector<shared_ptr<LogEvent>>* data) { + using android::hardware::power::V1_0::Status; + + if(!getPowerHalLocked()) { + return false; + } + + int64_t wallClockTimestampNs = getWallClockNs(); + int64_t elapsedTimestampNs = getElapsedRealtimeNs(); + Return<void> ret; ret = gPowerHalV1_0->getPlatformLowPowerStats( - [&data, wallClockTimestampNs, elapsedTimestampNs](hidl_vec<PowerStatePlatformSleepState> states, Status status) { + [&data, wallClockTimestampNs, elapsedTimestampNs] + (hidl_vec<PowerStatePlatformSleepState> states, Status status) { if (status != Status::SUCCESS) return; for (size_t i = 0; i < states.size(); i++) { @@ -128,9 +298,7 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) } } }); - if (!ret.isOk()) { - ALOGE("getLowPowerStats() failed: power HAL service not available"); - gPowerHalV1_0 = nullptr; + if (!checkResultLocked(ret, __func__)) { return false; } @@ -139,35 +307,68 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0); if (gPowerHal_1_1 != nullptr) { ret = gPowerHal_1_1->getSubsystemLowPowerStats( - [&data, wallClockTimestampNs, elapsedTimestampNs](hidl_vec<PowerStateSubsystem> subsystems, Status status) { - if (status != Status::SUCCESS) return; - - if (subsystems.size() > 0) { - for (size_t i = 0; i < subsystems.size(); i++) { - const PowerStateSubsystem& subsystem = subsystems[i]; - for (size_t j = 0; j < subsystem.states.size(); j++) { - const PowerStateSubsystemSleepState& state = - subsystem.states[j]; - auto subsystemStatePtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - subsystemStatePtr->write(subsystem.name); - subsystemStatePtr->write(state.name); - subsystemStatePtr->write(state.totalTransitions); - subsystemStatePtr->write(state.residencyInMsecSinceBoot); - subsystemStatePtr->init(); - data->push_back(subsystemStatePtr); - VLOG("subsystemstate: %s, %s, %lld, %lld, %lld", - subsystem.name.c_str(), state.name.c_str(), - (long long)state.residencyInMsecSinceBoot, - (long long)state.totalTransitions, - (long long)state.lastEntryTimestampMs); - } - } + [&data, wallClockTimestampNs, elapsedTimestampNs] + (hidl_vec<PowerStateSubsystem> subsystems, Status status) { + if (status != Status::SUCCESS) return; + + if (subsystems.size() > 0) { + for (size_t i = 0; i < subsystems.size(); i++) { + const PowerStateSubsystem& subsystem = subsystems[i]; + for (size_t j = 0; j < subsystem.states.size(); j++) { + const PowerStateSubsystemSleepState& state = + subsystem.states[j]; + auto subsystemStatePtr = make_shared<LogEvent>( + android::util::SUBSYSTEM_SLEEP_STATE, + wallClockTimestampNs, elapsedTimestampNs); + subsystemStatePtr->write(subsystem.name); + subsystemStatePtr->write(state.name); + subsystemStatePtr->write(state.totalTransitions); + subsystemStatePtr->write(state.residencyInMsecSinceBoot); + subsystemStatePtr->init(); + data->push_back(subsystemStatePtr); + VLOG("subsystemstate: %s, %s, %lld, %lld, %lld", + subsystem.name.c_str(), state.name.c_str(), + (long long)state.residencyInMsecSinceBoot, + (long long)state.totalTransitions, + (long long)state.lastEntryTimestampMs); } - }); + } + } + }); } - return true; + return true; +} + +// The caller must be holding gPowerHalMutex. +std::function<bool(vector<shared_ptr<LogEvent>>* data)> getPullerLocked() { + std::function<bool(vector<shared_ptr<LogEvent>>* data)> ret = {}; + + // First see if power.stats HAL is available. Fall back to power HAL if + // power.stats HAL is unavailable. + if(android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) { + ALOGI("Using power.stats HAL"); + ret = getIPowerStatsDataLocked; + } else if(android::hardware::power::V1_0::IPower::getService() != nullptr) { + ALOGI("Using power HAL"); + ret = getIPowerDataLocked; + } + + return ret; +} + +bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { + std::lock_guard<std::mutex> lock(gPowerHalMutex); + + if(!gPuller) { + gPuller = getPullerLocked(); + } + + if(gPuller) { + return gPuller(data); + } + + ALOGE("Unable to load Power Hal or power.stats HAL"); + return false; } } // namespace statsd diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 355df2986a0b..237f8b902015 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -240,6 +240,49 @@ TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) { EXPECT_EQ(2, report.annotation(0).field_int32()); } +TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) { + // Setup a simple config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = wakelockAcquireMatcher; + + auto countMetric = config.add_count_metric(); + countMetric->set_id(123456); + countMetric->set_what(wakelockAcquireMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + + ConfigKey cfgKey; + sp<StatsLogProcessor> processor = CreateStatsLogProcessor(1, 1, config, cfgKey); + + std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; + auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 2); + processor->OnLogEvent(event.get()); + + vector<uint8_t> bytes; + ConfigMetricsReportList output; + + // Dump report WITHOUT erasing data. + processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, &bytes); + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).metrics_size(), 1); + EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); + + // Dump report WITH erasing data. There should be data since we didn't previously erase it. + processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, &bytes); + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).metrics_size(), 1); + EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); + + // Dump report again. There should be no data since we erased it. + processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, &bytes); + output.ParseFromArray(bytes.data(), bytes.size()); + bool noData = (output.reports_size() == 0) || (output.reports(0).metrics_size() == 0); + EXPECT_TRUE(noData); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tools/localtools/Android.bp b/cmds/statsd/tools/localtools/Android.bp new file mode 100644 index 000000000000..75a57a3f3068 --- /dev/null +++ b/cmds/statsd/tools/localtools/Android.bp @@ -0,0 +1,25 @@ +java_binary_host { + name: "statsd_localdrive", + manifest: "localdrive_manifest.txt", + srcs: [ + "src/com/android/statsd/shelltools/localdrive/*.java", + "src/com/android/statsd/shelltools/Utils.java", + ], + static_libs: [ + "platformprotos", + "guava", + ], +} + +java_binary_host { + name: "statsd_testdrive", + manifest: "testdrive_manifest.txt", + srcs: [ + "src/com/android/statsd/shelltools/testdrive/*.java", + "src/com/android/statsd/shelltools/Utils.java", + ], + static_libs: [ + "platformprotos", + "guava", + ], +}
\ No newline at end of file diff --git a/cmds/statsd/tools/localtools/localdrive_manifest.txt b/cmds/statsd/tools/localtools/localdrive_manifest.txt new file mode 100644 index 000000000000..035cea1134bc --- /dev/null +++ b/cmds/statsd/tools/localtools/localdrive_manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.statsd.shelltools.localdrive.LocalDrive diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java new file mode 100644 index 000000000000..597377e34ac3 --- /dev/null +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.statsd.shelltools; + +import com.android.os.StatsLog.ConfigMetricsReportList; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.logging.ConsoleHandler; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * Utilities for local use of statsd. + */ +public class Utils { + + public static final String CMD_UPDATE_CONFIG = "cmd stats config update"; + public static final String CMD_DUMP_REPORT = "cmd stats dump-report"; + public static final String CMD_REMOVE_CONFIG = "cmd stats config remove"; + + public static final String SHELL_UID = "2000"; // Use shell, even if rooted. + + /** + * Runs adb shell command with output directed to outputFile if non-null. + */ + public static void runCommand(File outputFile, Logger logger, String... commands) + throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(commands); + if (outputFile != null && outputFile.exists() && outputFile.canWrite()) { + pb.redirectOutput(outputFile); + } + Process process = pb.start(); + + // Capture any errors + StringBuilder err = new StringBuilder(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream())); + for (String line = br.readLine(); line != null; line = br.readLine()) { + err.append(line).append('\n'); + } + logger.severe(err.toString()); + + // Check result + if (process.waitFor() == 0) { + logger.fine("Adb command successful."); + } else { + logger.severe("Abnormal adb shell cmd termination for: " + String.join(",", commands)); + throw new RuntimeException("Error running adb command: " + err.toString()); + } + } + + /** + * Dumps the report from the device and converts it to a ConfigMetricsReportList. + * Erases the data if clearData is true. + */ + public static ConfigMetricsReportList getReportList(long configId, boolean clearData, + Logger logger) throws IOException, InterruptedException { + try { + File outputFile = File.createTempFile("statsdret", ".bin"); + outputFile.deleteOnExit(); + runCommand( + outputFile, + logger, + "adb", + "shell", + CMD_DUMP_REPORT, + SHELL_UID, + String.valueOf(configId), + clearData ? "" : "--keep_data", + "--include_current_bucket", + "--proto"); + ConfigMetricsReportList reportList = + ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile)); + return reportList; + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + logger.severe("Failed to fetch and parse the statsd output report. " + + "Perhaps there is not a valid statsd config for the requested " + + "uid=" + SHELL_UID + + ", configId=" + configId + + "."); + throw (e); + } + } + + public static void setUpLogger(Logger logger, boolean debug) { + ConsoleHandler handler = new ConsoleHandler(); + handler.setFormatter(new LocalToolsFormatter()); + logger.setUseParentHandlers(false); + if (debug) { + handler.setLevel(Level.ALL); + logger.setLevel(Level.ALL); + } + logger.addHandler(handler); + } + + public static class LocalToolsFormatter extends Formatter { + public String format(LogRecord record) { + return record.getMessage() + "\n"; + } + } +} diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java new file mode 100644 index 000000000000..08074ede9d31 --- /dev/null +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.statsd.shelltools.localdrive; + +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.os.StatsLog.ConfigMetricsReport; +import com.android.os.StatsLog.ConfigMetricsReportList; +import com.android.statsd.shelltools.Utils; + +import com.google.common.io.Files; +import com.google.protobuf.TextFormat; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.logging.Logger; + +/** + * Tool for using statsd locally. Can upload a config and get the data. Handles + * both binary and human-readable protos. + * To make: make statsd_localdrive + * To run: statsd_localdrive (i.e. ./out/host/linux-x86/bin/statsd_localdrive) + */ +public class LocalDrive { + private static final boolean DEBUG = false; + + public static final long DEFAULT_CONFIG_ID = 56789; + + public static final String BINARY_FLAG = "--binary"; + public static final String CLEAR_DATA = "--clear"; + public static final String NO_UID_MAP_FLAG = "--no-uid-map"; + + public static final String HELP_STRING = + "Usage:\n\n" + + + "statsd_local upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + + " Uploads the given statsd config file (in binary or human-readable-text format).\n" + + " If a config with this id already exists, removes it first.\n" + + " CONFIG_FILE Location of config file on host.\n" + + " CONFIG_ID Long ID to associate with this config. If absent, uses " + + DEFAULT_CONFIG_ID + ".\n" + + " --binary Config is in binary format; otherwise, assumed human-readable text.\n" + + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID + "\n" + + + "statsd_local update CONFIG_FILE [CONFIG_ID] [--binary]\n" + + " Same as upload, but does not remove the old config first (if it already exists).\n" + + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID + "\n" + + + "statsd_local get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + + " Prints the output statslog data (in binary or human-readable-text format).\n" + + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + + " --binary Output should be in binary, instead of default human-readable text.\n" + + " Binary output can be redirected as usual (e.g. > FILENAME).\n" + + " --no-uid-map Do not include the uid-map (the very lengthy uid<-->pkgName map).\n" + + " --clear Erase the data from statsd afterwards. Does not remove the config.\n" + + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID [--keep_data] + // --include_current_bucket --proto + "\n" + + + "statsd_local remove [CONFIG_ID]\n" + + " Removes the config.\n" + + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + + // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID + "\n" + + + "statsd_local clear [CONFIG_ID]\n" + + " Clears the data associated with the config.\n" + + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID + // --include_current_bucket --proto + ""; + + + private static final Logger sLogger = Logger.getLogger(LocalDrive.class.getName()); + + /** Usage: make statsd_localdrive && statsd_localdrive */ + public static void main(String[] args) { + Utils.setUpLogger(sLogger, DEBUG); + + if (args.length > 0) { + switch (args[0]) { + case "clear": + cmdClear(args); + return; + case "get-data": + cmdGetData(args); + return; + case "remove": + cmdRemove(args); + return; + case "update": + cmdUpdate(args); + return; + case "upload": + cmdUpload(args); + return; + } + } + printHelp(); + } + + private static void printHelp() { + sLogger.info(HELP_STRING); + } + + // upload CONFIG_FILE [CONFIG_ID] [--binary] + private static boolean cmdUpload(String[] args) { + return updateConfig(args, true); + } + + // update CONFIG_FILE [CONFIG_ID] [--binary] + private static boolean cmdUpdate(String[] args) { + return updateConfig(args, false); + } + + private static boolean updateConfig(String[] args, boolean removeOldConfig) { + int argCount = args.length - 1; // Used up one for upload/update. + + // Get CONFIG_FILE + if (argCount < 1) { + sLogger.severe("No config file provided."); + printHelp(); + return false; + } + final String origConfigLocation = args[1]; + if (!new File(origConfigLocation).exists()) { + sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation); + return false; + } + argCount--; + + // Get --binary + boolean binary = contains(args, 2, BINARY_FLAG); + if (binary) argCount --; + + // Get CONFIG_ID + long configId; + try { + configId = getConfigId(argCount < 1, args, 2); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("updateConfig with %s %d %b %b", + origConfigLocation, configId, binary, removeOldConfig)); + + // Remove the old config. + if (removeOldConfig) { + try { + Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, + Utils.SHELL_UID, String.valueOf(configId)); + Utils.getReportList(configId, true /* clearData */, sLogger); + } catch (InterruptedException | IOException e) { + sLogger.severe("Failed to remove config: " + e.getMessage()); + return false; + } + } + + // Upload the config. + String configLocation; + if (binary) { + configLocation = origConfigLocation; + } else { + StatsdConfig.Builder builder = StatsdConfig.newBuilder(); + try { + TextFormat.merge(new FileReader(origConfigLocation), builder); + } catch (IOException e) { + sLogger.severe("Failed to read config file " + origConfigLocation + ": " + + e.getMessage()); + return false; + } + + try { + File tempConfigFile = File.createTempFile("statsdconfig", ".config"); + tempConfigFile.deleteOnExit(); + Files.write(builder.build().toByteArray(), tempConfigFile); + configLocation = tempConfigFile.getAbsolutePath(); + } catch (IOException e) { + sLogger.severe("Failed to write temp config file: " + e.getMessage()); + return false; + } + } + String remotePath = "/data/local/tmp/statsdconfig.config"; + try { + Utils.runCommand(null, sLogger, "adb", "push", configLocation, remotePath); + Utils.runCommand(null, sLogger, "adb", "shell", "cat", remotePath, "|", + Utils.CMD_UPDATE_CONFIG, Utils.SHELL_UID, String.valueOf(configId)); + } catch (InterruptedException | IOException e) { + sLogger.severe("Failed to update config: " + e.getMessage()); + return false; + } + return true; + } + + // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map] + private static boolean cmdGetData(String[] args) { + boolean binary = contains(args, 1, BINARY_FLAG); + boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG); + boolean clearData = contains(args, 1, CLEAR_DATA); + + // Get CONFIG_ID + int argCount = args.length - 1; // Used up one for get-data. + if (binary) argCount--; + if (noUidMap) argCount--; + if (clearData) argCount--; + long configId; + try { + configId = getConfigId(argCount < 1, args, 1); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("cmdGetData with %d %b %b %b", + configId, clearData, binary, noUidMap)); + + // Get the StatsLog + // Even if the args request no modifications, we still parse it to make sure it's valid. + ConfigMetricsReportList reportList; + try { + reportList = Utils.getReportList(configId, clearData, sLogger); + } catch (IOException | InterruptedException e) { + sLogger.severe("Failed to get report list: " + e.getMessage()); + return false; + } + if (noUidMap) { + ConfigMetricsReportList.Builder builder + = ConfigMetricsReportList.newBuilder(reportList); + // Clear the reports, then add them back without their UidMap. + builder.clearReports(); + for (ConfigMetricsReport report : reportList.getReportsList()) { + builder.addReports(ConfigMetricsReport.newBuilder(report).clearUidMap()); + } + reportList = builder.build(); + } + + if (!binary) { + sLogger.info(reportList.toString()); + } else { + try { + System.out.write(reportList.toByteArray()); + } catch (IOException e) { + sLogger.severe("Failed to output binary statslog proto: " + + e.getMessage()); + return false; + } + } + return true; + } + + // clear [CONFIG_ID] + private static boolean cmdClear(String[] args) { + // Get CONFIG_ID + long configId; + try { + configId = getConfigId(false, args, 1); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("cmdClear with %d", configId)); + + try { + Utils.getReportList(configId, true /* clearData */, sLogger); + } catch (IOException | InterruptedException e) { + sLogger.severe("Failed to get report list: " + e.getMessage()); + return false; + } + return true; + } + + // remove [CONFIG_ID] + private static boolean cmdRemove(String[] args) { + // Get CONFIG_ID + long configId; + try { + configId = getConfigId(false, args, 1); + } catch (NumberFormatException e) { + sLogger.severe("Invalid config id provided."); + printHelp(); + return false; + } + sLogger.fine(String.format("cmdRemove with %d", configId)); + + try { + Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, + Utils.SHELL_UID, String.valueOf(configId)); + } catch (InterruptedException | IOException e) { + sLogger.severe("Failed to remove config: " + e.getMessage()); + return false; + } + return true; + } + + /** + * Searches through the array to see if it contains (precisely) the given value, starting + * at the given firstIdx. + */ + private static boolean contains(String[] array, int firstIdx, String value) { + if (value == null) return false; + if (firstIdx < 0) return false; + for (int i = firstIdx; i < array.length; i++) { + if (value.equals(array[i])) { + return true; + } + } + return false; + } + + /** + * Gets the config id from args[idx], or returns DEFAULT_CONFIG_ID if args[idx] does not exist. + * If justUseDefault, overrides and just uses DEFAULT_CONFIG_ID instead. + */ + private static long getConfigId(boolean justUseDefault, String[] args, int idx) + throws NumberFormatException { + if (justUseDefault || args.length <= idx || idx < 0) { + return DEFAULT_CONFIG_ID; + } + try { + return Long.valueOf(args[idx]); + } catch (NumberFormatException e) { + sLogger.severe("Bad config id provided: " + args[idx]); + throw e; + } + } +} diff --git a/cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index cc4e386bfdf0..f7bd44aeab62 100644 --- a/cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.statsd.testdrive; +package com.android.statsd.shelltools.testdrive; import com.android.internal.os.StatsdConfigProto.AtomMatcher; import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; @@ -21,20 +21,15 @@ import com.android.internal.os.StatsdConfigProto.StatsdConfig; import com.android.os.AtomsProto.Atom; import com.android.os.StatsLog.ConfigMetricsReport; import com.android.os.StatsLog.ConfigMetricsReportList; +import com.android.statsd.shelltools.Utils; import com.google.common.io.Files; import com.google.protobuf.TextFormat; import com.google.protobuf.TextFormat.ParseException; -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.util.logging.ConsoleHandler; -import java.util.logging.Formatter; import java.util.logging.Level; -import java.util.logging.LogRecord; import java.util.logging.Logger; public class TestDrive { @@ -42,10 +37,6 @@ public class TestDrive { public static final int PULL_ATOM_START = 10000; public static final long ATOM_MATCHER_ID = 1234567; - public static final String UPDATE_CONFIG_CMD = "cmd stats config update"; - public static final String DUMP_REPORT_CMD = "cmd stats dump-report"; - public static final String REMOVE_CONFIG_CMD = "cmd stats config remove"; - public static final String CONFIG_UID = "2000"; // shell uid public static final long CONFIG_ID = 54321; private static boolean mIsPushedAtom = false; @@ -53,6 +44,9 @@ public class TestDrive { private static final Logger logger = Logger.getLogger(TestDrive.class.getName()); public static void main(String[] args) { + TestDrive testDrive = new TestDrive(); + Utils.setUpLogger(logger, false); + if (args.length != 1) { logger.log(Level.SEVERE, "Usage: ./test_drive <atomId>"); return; @@ -70,12 +64,6 @@ public class TestDrive { } mIsPushedAtom = atomId < PULL_ATOM_START; - TestDrive testDrive = new TestDrive(); - TestDriveFormatter formatter = new TestDriveFormatter(); - ConsoleHandler handler = new ConsoleHandler(); - handler.setFormatter(formatter); - logger.addHandler(handler); - logger.setUseParentHandlers(false); try { StatsdConfig config = testDrive.createConfig(atomId); @@ -109,55 +97,21 @@ public class TestDrive { configFile.deleteOnExit(); Files.write(config.toByteArray(), configFile); String remotePath = "/data/local/tmp/" + configFile.getName(); - runCommand(null, "adb", "push", configFile.getAbsolutePath(), remotePath); - runCommand( - null, "adb", "shell", "cat", remotePath, "|", UPDATE_CONFIG_CMD, + Utils.runCommand(null, logger, "adb", "push", configFile.getAbsolutePath(), remotePath); + Utils.runCommand(null, logger, + "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, String.valueOf(CONFIG_ID)); } private void removeConfig() { try { - runCommand(null, "adb", "shell", REMOVE_CONFIG_CMD, String.valueOf(CONFIG_ID)); + Utils.runCommand(null, logger, + "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to remove config: " + e.getMessage()); } } - // Runs a shell command. Output should go to outputFile. Returns error string. - private String runCommand(File outputFile, String... commands) - throws IOException, InterruptedException { - // Run macro on target - ProcessBuilder pb = new ProcessBuilder(commands); - // pb.redirectErrorStream(true); - - if (outputFile != null && outputFile.exists() && outputFile.canWrite()) { - pb.redirectOutput(outputFile); - } - Process process = pb.start(); - - // capture any errors - StringBuilder out = new StringBuilder(); - // Read output - BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream())); - String line = null, previous = null; - while ((line = br.readLine()) != null) { - if (!line.equals(previous)) { - previous = line; - out.append(line).append('\n'); - logger.fine(line); - } - } - - // Check result - if (process.waitFor() == 0) { - logger.fine("Success!"); - } else { - // Abnormal termination: Log command parameters and output and throw ExecutionException - logger.log(Level.SEVERE, out.toString()); - } - return out.toString(); - } - private StatsdConfig createConfig(int atomId) { try { if (mIsPushedAtom) { @@ -210,37 +164,8 @@ public class TestDrive { return builder; } - private ConfigMetricsReportList getReportList() throws Exception { - try { - File outputFile = File.createTempFile("statsdret", ".bin"); - outputFile.deleteOnExit(); - runCommand( - outputFile, - "adb", - "shell", - DUMP_REPORT_CMD, - String.valueOf(CONFIG_ID), - "--include_current_bucket", - "--proto"); - ConfigMetricsReportList reportList = - ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile)); - return reportList; - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - logger.log( - Level.SEVERE, - "Failed to fetch and parse the statsd output report. " - + "Perhaps there is not a valid statsd config for the requested " - + "uid=" - + CONFIG_UID - + ", id=" - + CONFIG_ID - + "."); - throw (e); - } - } - private void dumpMetrics() throws Exception { - ConfigMetricsReportList reportList = getReportList(); + ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, logger); // We may get multiple reports. Take the last one. ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); // Really should be only one metric. @@ -294,9 +219,4 @@ public class TestDrive { + "\n" + "hash_strings_in_metric_report: false"; - public static class TestDriveFormatter extends Formatter { - public String format(LogRecord record) { - return record.getMessage() + "\n"; - } - } } diff --git a/cmds/statsd/tools/localtools/testdrive_manifest.txt b/cmds/statsd/tools/localtools/testdrive_manifest.txt new file mode 100644 index 000000000000..625ebfa4312a --- /dev/null +++ b/cmds/statsd/tools/localtools/testdrive_manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.statsd.shelltools.testdrive.TestDrive diff --git a/cmds/statsd/tools/statsd-testdrive/Android.bp b/cmds/statsd/tools/statsd-testdrive/Android.bp deleted file mode 100644 index f566bc7f2a53..000000000000 --- a/cmds/statsd/tools/statsd-testdrive/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -java_binary_host { - name: "statsd_testdrive", - manifest: "manifest.txt", - srcs: [ - "src/**/*.java", - ], - static_libs: [ - "platformprotos", - "guava", - ], -} diff --git a/cmds/statsd/tools/statsd-testdrive/manifest.txt b/cmds/statsd/tools/statsd-testdrive/manifest.txt deleted file mode 100644 index 0266d1143245..000000000000 --- a/cmds/statsd/tools/statsd-testdrive/manifest.txt +++ /dev/null @@ -1 +0,0 @@ -Main-class: com.android.statsd.testdrive.TestDrive diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index bacb991012fd..458c7decab5a 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -2861,7 +2861,6 @@ Lcom/android/internal/telephony/dataconnection/DataConnection;->mActivatingState Lcom/android/internal/telephony/dataconnection/DataConnection;->mActiveState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcActiveState; Lcom/android/internal/telephony/dataconnection/DataConnection;->mConnectionParams:Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams; Lcom/android/internal/telephony/dataconnection/DataConnection;->mDataRegState:I -Lcom/android/internal/telephony/dataconnection/DataConnection;->mDcFailCause:Lcom/android/internal/telephony/dataconnection/DcFailCause; Lcom/android/internal/telephony/dataconnection/DataConnection;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker; Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingErrorCreatingConnection:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectionErrorCreatingConnection; Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectingState; @@ -2872,10 +2871,8 @@ Lcom/android/internal/telephony/dataconnection/DataConnection;->mLinkProperties: Lcom/android/internal/telephony/dataconnection/DataConnection;->mNetworkInfo:Landroid/net/NetworkInfo; Lcom/android/internal/telephony/dataconnection/DataConnection;->mPhone:Lcom/android/internal/telephony/Phone; Lcom/android/internal/telephony/dataconnection/DataConnection;->mRilRat:I -Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllDisconnectCompleted(Lcom/android/internal/telephony/dataconnection/DcFailCause;)V Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllOfConnected(Ljava/lang/String;)V Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllOfDisconnectDcRetrying(Ljava/lang/String;)V -Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyConnectCompleted(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;Lcom/android/internal/telephony/dataconnection/DcFailCause;Z)V Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyDisconnectCompleted(Lcom/android/internal/telephony/dataconnection/DataConnection$DisconnectParams;Z)V Lcom/android/internal/telephony/dataconnection/DataConnection;->onConnect(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;)V Lcom/android/internal/telephony/dataconnection/DataConnection;->tearDownData(Ljava/lang/Object;)V @@ -2884,22 +2881,6 @@ Lcom/android/internal/telephony/dataconnection/DcController;->lr(Ljava/lang/Stri Lcom/android/internal/telephony/dataconnection/DcController;->mDcListActiveByCid:Ljava/util/HashMap; Lcom/android/internal/telephony/dataconnection/DcController;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker; Lcom/android/internal/telephony/dataconnection/DcController;->mDcTesterDeactivateAll:Lcom/android/internal/telephony/dataconnection/DcTesterDeactivateAll; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->ACTIVATION_REJECT_GGSN:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->ACTIVATION_REJECT_UNSPECIFIED:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->APN_TYPE_CONFLICT:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->INSUFFICIENT_RESOURCES:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->MISSING_UNKNOWN_APN:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->NSAPI_IN_USE:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_IPV4_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_IPV6_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_SINGLE_BEARER_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->OPERATOR_BARRED:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->PROTOCOL_ERRORS:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_NOT_SUBSCRIBED:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_NOT_SUPPORTED:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_OUT_OF_ORDER:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->UNKNOWN_PDP_ADDRESS_TYPE:Lcom/android/internal/telephony/dataconnection/DcFailCause; -Lcom/android/internal/telephony/dataconnection/DcFailCause;->USER_AUTHENTICATION:Lcom/android/internal/telephony/dataconnection/DcFailCause; Lcom/android/internal/telephony/dataconnection/DcTracker$RecoveryAction;->isAggressiveRecovery(I)Z Lcom/android/internal/telephony/dataconnection/DcTracker;->cancelReconnectAlarm(Lcom/android/internal/telephony/dataconnection/ApnContext;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->cleanUpAllConnections(Ljava/lang/String;)V diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 5e445d14a08b..b584d5d4c3b4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2545,7 +2545,7 @@ public class Activity extends ContextThemeWrapper * picture-in-picture. * * @return true if the system successfully put this activity into picture-in-picture mode or was - * already in picture-in-picture mode (@see {@link #isInPictureInPictureMode()). If the device + * already in picture-in-picture mode (see {@link #isInPictureInPictureMode()}). If the device * does not support picture-in-picture, return false. */ public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) { diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index af3da0cbf5ee..b42d53ad10f6 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -196,8 +196,26 @@ public abstract class ActivityManagerInternal { public abstract void updateOomAdj(); public abstract void updateCpuStats(); - public abstract void updateUsageStats( + + /** + * Update battery stats on activity usage. + * @param activity + * @param uid + * @param userId + * @param started + */ + public abstract void updateBatteryStats( ComponentName activity, int uid, int userId, boolean resumed); + + /** + * Update UsageStats of the activity. + * @param activity + * @param userId + * @param event + * @param appToken ActivityRecord's appToken. + */ + public abstract void updateActivityUsageStats( + ComponentName activity, int userId, int event, IBinder appToken); public abstract void updateForegroundTimeIfOnBattery( String packageName, int uid, long cpuTimeDiff); public abstract void sendForegroundProfileChanged(int userId); @@ -288,6 +306,6 @@ public abstract class ActivityManagerInternal { public abstract void setDebugFlagsForStartingActivity(ActivityInfo aInfo, int startFlags, ProfilerInfo profilerInfo, Object wmLock); - /** Checks if process running with given pid has access to full external storage or not */ - public abstract boolean isAppStorageSandboxed(int pid, int uid); + /** Returns mount mode for process running with given pid */ + public abstract int getStorageMountMode(int pid, int uid); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 67d9ad6e93c6..2b81c86e1b0d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -44,6 +44,7 @@ import android.content.pm.InstantAppInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; +import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; @@ -791,6 +792,16 @@ public class ApplicationPackageManager extends PackageManager { throw new NameNotFoundException("No shared userid for user:"+sharedUserName); } + @Override + public List<ModuleInfo> getInstalledModules(int flags) { + return null; + } + + @Override + public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException { + return null; + } + @SuppressWarnings("unchecked") @Override public List<PackageInfo> getInstalledPackages(int flags) { diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java index 6747004e8186..7cc3dab5866c 100644 --- a/core/java/android/app/RecoverableSecurityException.java +++ b/core/java/android/app/RecoverableSecurityException.java @@ -16,15 +16,13 @@ package android.app; -import android.content.ContentProvider; -import android.content.ContentResolver; +import android.annotation.NonNull; import android.content.Context; -import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.Preconditions; +import java.util.Objects; /** * Specialization of {@link SecurityException} that contains additional @@ -35,18 +33,11 @@ import com.android.internal.util.Preconditions; * authentication credentials, or granting access. * <p> * If the receiving app is actively involved with the user, it should present - * the contained recovery details to help the user make forward progress. The - * {@link #showAsDialog(Activity)} and - * {@link #showAsNotification(Context, String)} methods are provided as a - * convenience, but receiving apps are encouraged to use - * {@link #getUserMessage()} and {@link #getUserAction()} to integrate in a more - * natural way if relevant. + * the contained recovery details to help the user make forward progress. * <p class="note"> * Note: legacy code that receives this exception may treat it as a general * {@link SecurityException}, and thus there is no guarantee that the messages * contained will be shown to the end user. - * - * @hide */ public final class RecoverableSecurityException extends SecurityException implements Parcelable { private static final String TAG = "RecoverableSecurityException"; @@ -78,53 +69,28 @@ public final class RecoverableSecurityException extends SecurityException implem * apps that observe {@link Activity#RESULT_OK} may choose to * immediately retry their operation. */ - public RecoverableSecurityException(Throwable cause, CharSequence userMessage, - RemoteAction userAction) { + public RecoverableSecurityException(@NonNull Throwable cause, @NonNull CharSequence userMessage, + @NonNull RemoteAction userAction) { super(cause.getMessage()); - mUserMessage = Preconditions.checkNotNull(userMessage); - mUserAction = Preconditions.checkNotNull(userAction); - } - - /** {@hide} */ - @Deprecated - public RecoverableSecurityException(Throwable cause, CharSequence userMessage, - CharSequence userActionTitle, PendingIntent userAction) { - this(cause, userMessage, - new RemoteAction( - Icon.createWithResource("android", - com.android.internal.R.drawable.ic_restart), - userActionTitle, userActionTitle, userAction)); + mUserMessage = Objects.requireNonNull(userMessage); + mUserAction = Objects.requireNonNull(userAction); } /** * Return short message describing the issue for end user audiences, which * may be shown in a notification or dialog. */ - public CharSequence getUserMessage() { + public @NonNull CharSequence getUserMessage() { return mUserMessage; } /** * Return primary action that will initiate the recovery. */ - public RemoteAction getUserAction() { + public @NonNull RemoteAction getUserAction() { return mUserAction; } - /** @removed */ - @Deprecated - public void showAsNotification(Context context) { - final NotificationManager nm = context.getSystemService(NotificationManager.class); - - // Create a channel per-sender, since we don't want one poorly behaved - // remote app to cause all of our notifications to be blocked - final String channelId = TAG + "_" + mUserAction.getActionIntent().getCreatorUid(); - nm.createNotificationChannel(new NotificationChannel(channelId, TAG, - NotificationManager.IMPORTANCE_DEFAULT)); - - showAsNotification(context, channelId); - } - /** * Convenience method that will show a very simple notification populated * with the details from this exception. @@ -142,6 +108,7 @@ public final class RecoverableSecurityException extends SecurityException implem * @param channelId the {@link NotificationChannel} to use, which must have * been already created using * {@link NotificationManager#createNotificationChannel}. + * @hide */ public void showAsNotification(Context context, String channelId) { final NotificationManager nm = context.getSystemService(NotificationManager.class); @@ -167,6 +134,8 @@ public final class RecoverableSecurityException extends SecurityException implem * <p> * This method will only display the most recent exception from any single * remote UID; dialogs from older exceptions will always be replaced. + * + * @hide */ public void showAsDialog(Activity activity) { final LocalDialog dialog = new LocalDialog(); diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 85fe99d95969..392921e813f9 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -93,6 +93,22 @@ public final class RemoteInput implements Parcelable { /** The user selected one of the choices from {@link #getChoices}. */ public static final int SOURCE_CHOICE = 1; + /** @hide */ + @IntDef(prefix = {"EDIT_CHOICES_BEFORE_SENDING_"}, + value = {EDIT_CHOICES_BEFORE_SENDING_AUTO, EDIT_CHOICES_BEFORE_SENDING_DISABLED, + EDIT_CHOICES_BEFORE_SENDING_ENABLED}) + @Retention(RetentionPolicy.SOURCE) + public @interface EditChoicesBeforeSending {} + + /** The platform will determine whether choices will be edited before being sent to the app. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; + + /** Tapping on a choice should send the input immediately, without letting the user edit it. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; + + /** Tapping on a choice should let the user edit the input before it is sent to the app. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; + // Flags bitwise-ored to mFlags private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; @@ -103,17 +119,25 @@ public final class RemoteInput implements Parcelable { private final CharSequence mLabel; private final CharSequence[] mChoices; private final int mFlags; + @EditChoicesBeforeSending private final int mEditChoicesBeforeSending; private final Bundle mExtras; private final ArraySet<String> mAllowedDataTypes; private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, - int flags, Bundle extras, ArraySet<String> allowedDataTypes) { + int flags, int editChoicesBeforeSending, Bundle extras, + ArraySet<String> allowedDataTypes) { this.mResultKey = resultKey; this.mLabel = label; this.mChoices = choices; this.mFlags = flags; + this.mEditChoicesBeforeSending = editChoicesBeforeSending; this.mExtras = extras; this.mAllowedDataTypes = allowedDataTypes; + if (getEditChoicesBeforeSending() == EDIT_CHOICES_BEFORE_SENDING_ENABLED + && !getAllowFreeFormInput()) { + throw new IllegalArgumentException( + "setEditChoicesBeforeSending requires setAllowFreeFormInput"); + } } /** @@ -149,7 +173,7 @@ public final class RemoteInput implements Parcelable { /** * Returns true if the input only accepts data, meaning {@link #getAllowFreeFormInput} - * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes is + * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes} is * non-null and not empty. */ public boolean isDataOnly() { @@ -169,6 +193,15 @@ public final class RemoteInput implements Parcelable { } /** + * Gets whether tapping on a choice should let the user edit the input before it is sent to the + * app. + */ + @EditChoicesBeforeSending + public int getEditChoicesBeforeSending() { + return mEditChoicesBeforeSending; + } + + /** * Get additional metadata carried around with this remote input. */ public Bundle getExtras() { @@ -185,6 +218,8 @@ public final class RemoteInput implements Parcelable { private CharSequence mLabel; private CharSequence[] mChoices; private int mFlags = DEFAULT_FLAGS; + @EditChoicesBeforeSending + private int mEditChoicesBeforeSending = EDIT_CHOICES_BEFORE_SENDING_AUTO; /** * Create a builder object for {@link RemoteInput} objects. @@ -269,7 +304,20 @@ public final class RemoteInput implements Parcelable { */ @NonNull public Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) { - setFlag(mFlags, allowFreeFormTextInput); + setFlag(FLAG_ALLOW_FREE_FORM_INPUT, allowFreeFormTextInput); + return this; + } + + /** + * Specifies whether tapping on a choice should let the user edit the input before it is + * sent to the app. The default is {@link #EDIT_CHOICES_BEFORE_SENDING_AUTO}. + * + * It cannot be used if {@link #setAllowFreeFormInput} has been set to false. + */ + @NonNull + public Builder setEditChoicesBeforeSending( + @EditChoicesBeforeSending int editChoicesBeforeSending) { + mEditChoicesBeforeSending = editChoicesBeforeSending; return this; } @@ -312,8 +360,8 @@ public final class RemoteInput implements Parcelable { */ @NonNull public RemoteInput build() { - return new RemoteInput( - mResultKey, mLabel, mChoices, mFlags, mExtras, mAllowedDataTypes); + return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mEditChoicesBeforeSending, + mExtras, mAllowedDataTypes); } } @@ -322,6 +370,7 @@ public final class RemoteInput implements Parcelable { mLabel = in.readCharSequence(); mChoices = in.readCharSequenceArray(); mFlags = in.readInt(); + mEditChoicesBeforeSending = in.readInt(); mExtras = in.readBundle(); mAllowedDataTypes = (ArraySet<String>) in.readArraySet(null); } @@ -507,6 +556,7 @@ public final class RemoteInput implements Parcelable { out.writeCharSequence(mLabel); out.writeCharSequenceArray(mChoices); out.writeInt(mFlags); + out.writeInt(mEditChoicesBeforeSending); out.writeBundle(mExtras); out.writeArraySet(mAllowedDataTypes); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 43e183665435..f4fd5d132069 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -154,7 +154,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccCardManager; import android.telephony.euicc.EuiccManager; -import android.telephony.rcs.RcsManager; +import android.telephony.ims.RcsManager; import android.util.ArrayMap; import android.util.Log; import android.view.ContextThemeWrapper; diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 257122d3abe0..2990b577e09d 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -673,6 +673,15 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } /** + * Returns true if the windowingMode represents a split window. + * @hide + */ + public static boolean isSplitScreenWindowingMode(int windowingMode) { + return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + } + + /** * Returns true if the windows associated with this window configuration can receive input keys. * @hide */ diff --git a/core/java/android/app/admin/DelegatedAdminReceiver.java b/core/java/android/app/admin/DelegatedAdminReceiver.java new file mode 100644 index 000000000000..960538251c5f --- /dev/null +++ b/core/java/android/app/admin/DelegatedAdminReceiver.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import static android.app.admin.DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS; +import static android.app.admin.DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI; +import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT; +import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.security.KeyChain; +import android.util.Log; + +/** + * Base class for delegated apps to handle callbacks related to their delegated capabilities. + * + * <p>Delegated apps are apps that receive additional capabilities from the profile owner or + * device owner apps. Some of these capabilities involve the framework calling into the apps. + * To receive these callbacks, delegated apps should subclass this class and override the + * appropriate methods here. The subclassed receiver needs to be published in the app's + * manifest, with appropriate intent filters to mark which callbacks the receiver is interested + * in. An app can have multiple receivers as long as they listen for disjoint set of callbacks. + * For the manifest definitions, it must be protected by the + * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission to ensure only + * the system can trigger these callbacks. + * + * <p>The callback methods happen on the main thread of the process. Thus long running + * operations must be done on another thread. Note that because a receiver + * is done once returning from its onReceive function, such long-running operations + * should probably be done in a {@link Service}. + * + * @see DevicePolicyManager#setDelegatedScopes + * @see DeviceAdminReceiver + */ +public class DelegatedAdminReceiver extends BroadcastReceiver { + private static final String TAG = "DelegatedAdminReceiver"; + + /** + * Allows this receiver to select the alias for a private key and certificate pair for + * authentication. If this method returns null, the default {@link android.app.Activity} will + * be shown that lets the user pick a private key and certificate pair. + * + * <p> This callback is only applicable if the delegated app has + * {@link DevicePolicyManager#DELEGATION_CERT_SELECTION} capability. Additionally, it must + * declare an intent fitler for {@link DeviceAdminReceiver#ACTION_CHOOSE_PRIVATE_KEY_ALIAS} + * in the receiver's manifest in order to receive this callback. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param uid The uid asking for the private key and certificate pair. + * @param uri The URI to authenticate, may be null. + * @param alias The alias preselected by the client, or null. + * @return The private key alias to return and grant access to. + * @see KeyChain#choosePrivateKeyAlias + */ + public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri, + String alias) { + return null; + } + + /** + * Called each time a new batch of network logs can be retrieved. This callback method will only + * ever be called when network logging is enabled. The logs can only be retrieved while network + * logging is enabled. + * + * <p>If a secondary user or profile is created, this callback won't be received until all users + * become affiliated again (even if network logging is enabled). It will also no longer be + * possible to retrieve the network logs batch with the most recent {@code batchToken} provided + * by this callback. See {@link DevicePolicyManager#setAffiliationIds}. + * + * <p> This callback is only applicable if the delegated app has + * {@link DevicePolicyManager#DELEGATION_NETWORK_LOGGING} capability. Additionally, it must + * declare an intent fitler for {@link DeviceAdminReceiver#ACTION_NETWORK_LOGS_AVAILABLE} in the + * receiver's manifest in order to receive this callback. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param batchToken The token representing the current batch of network logs. + * @param networkLogsCount The total count of events in the current batch of network logs. + * @see DevicePolicyManager#retrieveNetworkLogs + */ + public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken, + int networkLogsCount) { + } + + /** + * Intercept delegated device administrator broadcasts. Implementations should not override + * this method; implement the convenience callbacks for each action instead. + */ + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) { + int uid = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, -1); + Uri uri = intent.getParcelableExtra(EXTRA_CHOOSE_PRIVATE_KEY_URI); + String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS); + String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, uri, alias); + setResultData(chosenAlias); + } else if (ACTION_NETWORK_LOGS_AVAILABLE.equals(action)) { + long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1); + int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0); + onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount); + } else { + Log.w(TAG, "Unhandled broadcast: " + action); + } + } +} diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 6fb0d7ec33ae..5a7124e1fdc6 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -296,7 +296,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Broadcast action: notify that a new batch of network logs is ready to be collected. * @see DeviceAdminReceiver#onNetworkLogsAvailable - * @hide + * @see DelegatedAdminReceiver#onNetworkLogsAvailable */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @BroadcastBehavior(explicitOnly = true) @@ -425,7 +425,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver { */ public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1; - /** @hide */ + /** + * Broadcast action: notify that some app is attempting to choose a KeyChain key. + * @see DeviceAdminReceiver#onChoosePrivateKeyAlias + * @see DelegatedAdminReceiver#onChoosePrivateKeyAlias + */ public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 81eac5a413a6..03e5933d300d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,7 @@ package android.app.admin; +import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; import android.annotation.IntDef; @@ -66,6 +67,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManager.UserOperationException; import android.os.UserManager.UserOperationResult; +import android.provider.CalendarContract; import android.provider.ContactsContract.Directory; import android.provider.Settings; import android.security.AttestedKeyPair; @@ -1381,6 +1383,73 @@ public class DevicePolicyManager { = "android.app.action.SET_NEW_PASSWORD"; /** + * Constant for {@link #getPasswordComplexity()}: no password. + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + */ + public static final int PASSWORD_COMPLEXITY_NONE = 0; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>pattern + * <li>PIN with repeating (4444) or ordered (1234, 4321, 2468) sequences + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_SOMETHING + * @see #PASSWORD_QUALITY_NUMERIC + */ + public static final int PASSWORD_COMPLEXITY_LOW = 0x10000; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at + * least 4 + * <li>alphabetic, length at least 4 + * <li>alphanumeric, length at least 4 + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX + * @see #PASSWORD_QUALITY_ALPHABETIC + * @see #PASSWORD_QUALITY_ALPHANUMERIC + */ + public static final int PASSWORD_COMPLEXITY_MEDIUM = 0x30000; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at + * least 4 + * <li>alphabetic, length at least 6 + * <li>alphanumeric, length at least 6 + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX + * @see #PASSWORD_QUALITY_ALPHABETIC + * @see #PASSWORD_QUALITY_ALPHANUMERIC + */ + public static final int PASSWORD_COMPLEXITY_HIGH = 0x50000; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PASSWORD_COMPLEXITY_"}, value = { + PASSWORD_COMPLEXITY_NONE, + PASSWORD_COMPLEXITY_LOW, + PASSWORD_COMPLEXITY_MEDIUM, + PASSWORD_COMPLEXITY_HIGH, + }) + public @interface PasswordComplexity {} + + /** * Activity action: have the user enter a new password for the parent profile. * If the intent is launched from within a managed profile, this will trigger * entering a new password for the parent of the profile. In all other cases @@ -1546,12 +1615,46 @@ public class DevicePolicyManager { /** * Delegation of management of uninstalled packages. This scope grants access to the - * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs. + * {@link #setKeepUninstalledPackages} and {@link #getKeepUninstalledPackages} APIs. */ public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages"; /** + * Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and + * {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving + * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer + * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback. + * There can be at most one app that has this delegation. + * If another app already had delegated network logging access, + * it will lose the delegation when a new app is delegated. + * + * <p> Can only be granted by Device Owner. + */ + public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging"; + + /** + * Grants access to selection of KeyChain certificates on behalf of requesting apps. + * Once granted the app will start receiving + * DelegatedAdminReceiver.onChoosePrivateKeyAlias. The caller (PO/DO) will + * no longer receive {@link DeviceAdminReceiver#onChoosePrivateKeyAlias}. + * There can be at most one app that has this delegation. + * If another app already had delegated certificate selection access, + * it will lose the delegation when a new app is delegated. + * + * <p> Can be granted by Device Owner or Profile Owner. + */ + public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection"; + + + /** + * Delegation of silent APK installation via {@link android.content.pm.PackageInstaller} APIs. + * + * <p> Can only be delegated by Device Owner. + */ + public static final String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation"; + + /** * No management for current user in-effect. This is the default. * @hide */ @@ -3071,6 +3174,33 @@ public class DevicePolicyManager { } /** + * Returns how complex the current user's screen lock is. + * + * <p>Note that when called from a profile which uses an unified challenge with its parent, the + * screen lock complexity of the parent will be returned. However, this API does not support + * explicitly querying the parent profile screen lock complexity via {@link + * #getParentProfileInstance}. + * + * @throws IllegalStateException if the user is not unlocked. + * @throws SecurityException if the calling application does not have the permission + * {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY} + */ + @PasswordComplexity + @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) + public int getPasswordComplexity() { + throwIfParentInstance("getPasswordComplexity"); + if (mService == null) { + return PASSWORD_COMPLEXITY_NONE; + } + + try { + return mService.getPasswordComplexity(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * When called by a profile owner of a managed profile returns true if the profile uses unified * challenge with its parent user. * @@ -9238,7 +9368,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner to control the network logging feature. + * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to + * control the network logging feature. * * <p> Network logs contain DNS lookup and connect() library call events. The following library * functions are recorded while network logging is active: @@ -9275,16 +9406,17 @@ public class DevicePolicyManager { * all users to become affiliated. Therefore it's recommended that affiliation ids are set for * new users as soon as possible after provisioning via {@link #setAffiliationIds}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by a delegated app. * @param enabled whether network logging should be enabled or not. * @throws SecurityException if {@code admin} is not a device owner. * @see #setAffiliationIds * @see #retrieveNetworkLogs */ - public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { + public void setNetworkLoggingEnabled(@Nullable ComponentName admin, boolean enabled) { throwIfParentInstance("setNetworkLoggingEnabled"); try { - mService.setNetworkLoggingEnabled(admin, enabled); + mService.setNetworkLoggingEnabled(admin, mContext.getPackageName(), enabled); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -9294,7 +9426,8 @@ public class DevicePolicyManager { * Return whether network logging is enabled by a device owner. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only - * be {@code null} if the caller has MANAGE_USERS permission. + * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING} + * or has MANAGE_USERS permission. * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner and caller has * no MANAGE_USERS permission @@ -9302,14 +9435,15 @@ public class DevicePolicyManager { public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) { throwIfParentInstance("isNetworkLoggingEnabled"); try { - return mService.isNetworkLoggingEnabled(admin); + return mService.isNetworkLoggingEnabled(admin, mContext.getPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Called by device owner to retrieve the most recent batch of network logging events. + * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve + * the most recent batch of network logging events. * A device owner has to provide a batchToken provided as part of * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the * token of the most recent available batch of logs, {@code null} will be returned. @@ -9328,7 +9462,8 @@ public class DevicePolicyManager { * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See * {@link DevicePolicyManager#setAffiliationIds}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by a delegated app. * @param batchToken A token of the batch to retrieve * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns * {@code null} if the batch represented by batchToken is no longer available or if @@ -9338,11 +9473,11 @@ public class DevicePolicyManager { * @see #setAffiliationIds * @see DeviceAdminReceiver#onNetworkLogsAvailable */ - public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin, + public @Nullable List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin, long batchToken) { throwIfParentInstance("retrieveNetworkLogs"); try { - return mService.retrieveNetworkLogs(admin, batchToken); + return mService.retrieveNetworkLogs(admin, mContext.getPackageName(), batchToken); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -10308,4 +10443,33 @@ public class DevicePolicyManager { } return false; } + + /** + * Starts an activity to view calendar events in the managed profile. + * + * @param eventId the id of the event to be viewed. + * @param start the start time of the event. + * @param end the end time of the event. + * @param allDay if the event is an all-day event. + * @param flags flags to be set for the intent + * @return {@code true} if the activity is started successfully. {@code false} otherwise. + * + * @see CalendarContract#startViewCalendarEventInManagedProfile(Context, String, long, long, + * long, boolean, int) + * + * @hide + */ + public boolean startViewCalendarEventInManagedProfile(long eventId, long start, long end, + boolean allDay, int flags) { + throwIfParentInstance("startViewCalendarEventInManagedProfile"); + if (mService != null) { + try { + return mService.startViewCalendarEventInManagedProfile(mContext.getPackageName(), + eventId, start, end, allDay, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index de9297897158..8765760b216b 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -118,6 +118,12 @@ public abstract class DevicePolicyManagerInternal { public abstract boolean isUserAffiliatedWithDevice(int userId); /** + * Returns whether the calling package can install or uninstall packages without user + * interaction. + */ + public abstract boolean canSilentlyInstallPackage(String callerPackage, int callerUid); + + /** * Reports that a profile has changed to use a unified or separate credential. * * @param userId User ID of the profile. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 1ff414619be1..568becfcdd1a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -82,6 +82,7 @@ interface IDevicePolicyManager { boolean isActivePasswordSufficient(int userHandle, boolean parent); boolean isProfileActivePasswordSufficientForParent(int userHandle); + int getPasswordComplexity(); boolean isUsingUnifiedPassword(in ComponentName admin); int getCurrentFailedPasswordAttempts(int userHandle, boolean parent); int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent); @@ -366,9 +367,9 @@ interface IDevicePolicyManager { void setBackupServiceEnabled(in ComponentName admin, boolean enabled); boolean isBackupServiceEnabled(in ComponentName admin); - void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); - boolean isNetworkLoggingEnabled(in ComponentName admin); - List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, long batchToken); + void setNetworkLoggingEnabled(in ComponentName admin, in String packageName, boolean enabled); + boolean isNetworkLoggingEnabled(in ComponentName admin, in String packageName); + List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, in String packageName, long batchToken); boolean bindDeviceAdminServiceAsUser(in ComponentName admin, IApplicationThread caller, IBinder token, in Intent service, @@ -431,4 +432,6 @@ interface IDevicePolicyManager { boolean isManagedKiosk(); boolean isUnattendedManagedKiosk(); + + boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags); } diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index 5fee853275fb..8b41755f6dec 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -16,8 +16,14 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +41,8 @@ public class PasswordMetrics implements Parcelable { // consider it a complex PIN/password. public static final int MAX_ALLOWED_SEQUENCE = 3; + // TODO(b/120536847): refactor isActivePasswordSufficient logic so that the actual password + // quality is not overwritten public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; public int length = 0; public int letters = 0; @@ -46,6 +54,10 @@ public class PasswordMetrics implements Parcelable { public PasswordMetrics() {} + public PasswordMetrics(int quality) { + this.quality = quality; + } + public PasswordMetrics(int quality, int length) { this.quality = quality; this.length = length; @@ -173,6 +185,15 @@ public class PasswordMetrics implements Parcelable { && this.nonLetter == o.nonLetter; } + private boolean satisfiesBucket(PasswordMetrics... bucket) { + for (PasswordMetrics metrics : bucket) { + if (this.quality == metrics.quality) { + return this.length >= metrics.length; + } + } + return false; + } + /* * Returns the maximum length of a sequential characters. A sequence is defined as * monotonically increasing characters with a constant interval or the same character repeated. @@ -254,4 +275,99 @@ public class PasswordMetrics implements Parcelable { return 0; } } + + /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */ + @PasswordComplexity + public int determineComplexity() { + for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) { + if (satisfiesBucket(bucket.getMetrics())) { + return bucket.mComplexityLevel; + } + } + return PASSWORD_COMPLEXITY_NONE; + } + + /** + * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}. + */ + public static class PasswordComplexityBucket { + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket HIGH = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_HIGH, + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 8)); + + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket MEDIUM = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_MEDIUM, + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 4)); + + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket LOW = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_LOW, + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)); + + /** + * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}. + */ + private static final PasswordComplexityBucket NONE = + new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics()); + + /** Array containing all buckets from high to low. */ + private static final PasswordComplexityBucket[] BUCKETS = + new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW}; + + @PasswordComplexity + private final int mComplexityLevel; + private final PasswordMetrics[] mMetrics; + + private PasswordComplexityBucket(@PasswordComplexity int complexityLevel, + PasswordMetrics... metrics) { + this.mComplexityLevel = complexityLevel; + this.mMetrics = metrics; + } + + /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */ + public PasswordMetrics[] getMetrics() { + return mMetrics; + } + + /** Returns the bucket that {@code complexityLevel} represents. */ + public static PasswordComplexityBucket complexityLevelToBucket( + @PasswordComplexity int complexityLevel) { + for (PasswordComplexityBucket bucket : BUCKETS) { + if (bucket.mComplexityLevel == complexityLevel) { + return bucket; + } + } + return NONE; + } + } } diff --git a/core/java/android/app/usage/EventList.java b/core/java/android/app/usage/EventList.java index aaae57e526a0..a79ad2fc8607 100644 --- a/core/java/android/app/usage/EventList.java +++ b/core/java/android/app/usage/EventList.java @@ -103,4 +103,21 @@ public class EventList { } return result; } + + /** + * Remove events of certain type on or after a timestamp. + * @param type The type of event to remove. + * @param timeStamp the timeStamp on or after which to remove the event. + */ + public void removeOnOrAfter(int type, long timeStamp) { + for (int i = mEvents.size() - 1; i >= 0; i--) { + UsageEvents.Event event = mEvents.get(i); + if (event.mTimeStamp < timeStamp) { + break; + } + if (event.mEventType == type) { + mEvents.remove(i); + } + } + } } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 3a5975aea628..a06213d77a68 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -50,13 +50,27 @@ public final class UsageEvents implements Parcelable { public static final int NONE = 0; /** + * @deprecated by {@link #ACTIVITY_RESUMED} + */ + @Deprecated + public static final int MOVE_TO_FOREGROUND = 1; + + /** * An event type denoting that an {@link android.app.Activity} moved to the foreground. * This event has a package name and class name associated with it and can be retrieved * using {@link #getPackageName()} and {@link #getClassName()}. * If a package has multiple activities, this event is reported for each activity that moves * to foreground. + * This event is corresponding to {@link android.app.Activity#onResume()} of the + * activity's lifecycle. */ - public static final int MOVE_TO_FOREGROUND = 1; + public static final int ACTIVITY_RESUMED = MOVE_TO_FOREGROUND; + + /** + * @deprecated by {@link #ACTIVITY_PAUSED} + */ + @Deprecated + public static final int MOVE_TO_BACKGROUND = 2; /** * An event type denoting that an {@link android.app.Activity} moved to the background. @@ -64,19 +78,21 @@ public final class UsageEvents implements Parcelable { * using {@link #getPackageName()} and {@link #getClassName()}. * If a package has multiple activities, this event is reported for each activity that moves * to background. + * This event is corresponding to {@link android.app.Activity#onPause()} of the activity's + * lifecycle. */ - public static final int MOVE_TO_BACKGROUND = 2; + public static final int ACTIVITY_PAUSED = MOVE_TO_BACKGROUND; /** * An event type denoting that a component was in the foreground when the stats - * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND}. + * rolled-over. This is effectively treated as a {@link #ACTIVITY_PAUSED}. * {@hide} */ public static final int END_OF_DAY = 3; /** * An event type denoting that a component was in the foreground the previous day. - * This is effectively treated as a {@link #MOVE_TO_FOREGROUND}. + * This is effectively treated as a {@link #ACTIVITY_RESUMED}. * {@hide} */ public static final int CONTINUE_PREVIOUS_DAY = 4; @@ -207,10 +223,31 @@ public final class UsageEvents implements Parcelable { public static final int ROLLOVER_FOREGROUND_SERVICE = 22; /** + * An activity becomes invisible on the UI, corresponding to + * {@link android.app.Activity#onStop()} of the activity's lifecycle. + */ + public static final int ACTIVITY_STOPPED = 23; + + /** + * An activity object is destroyed, corresponding to + * {@link android.app.Activity#onDestroy()} of the activity's lifecycle. + * {@hide} + */ + public static final int ACTIVITY_DESTROYED = 24; + + /** + * The event type demoting that a flush of UsageStatsDatabase to file system. Before the + * flush all usage stats need to be updated to latest timestamp to make sure the most + * up to date stats are persisted. + * @hide + */ + public static final int FLUSH_TO_DISK = 25; + + /** * Keep in sync with the greatest event type value. * @hide */ - public static final int MAX_EVENT_TYPE = 22; + public static final int MAX_EVENT_TYPE = 25; /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -240,6 +277,12 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage public String mClass; + + /** + * {@hide} + */ + public int mInstanceId; + /** * {@hide} */ @@ -311,9 +354,16 @@ public final class UsageEvents implements Parcelable { } /** @hide */ + public Event(int type, long timeStamp) { + mEventType = type; + mTimeStamp = timeStamp; + } + + /** @hide */ public Event(Event orig) { mPackage = orig.mPackage; mClass = orig.mClass; + mInstanceId = orig.mInstanceId; mTimeStamp = orig.mTimeStamp; mEventType = orig.mEventType; mConfiguration = orig.mConfiguration; @@ -342,6 +392,16 @@ public final class UsageEvents implements Parcelable { } /** + * An activity can be instantiated multiple times, this is the unique activity instance ID. + * For non-activity class, instance ID is always zero. + * @hide + */ + @SystemApi + public int getInstanceId() { + return mInstanceId; + } + + /** * The time at which this event occurred, measured in milliseconds since the epoch. * <p/> * See {@link System#currentTimeMillis()}. @@ -352,12 +412,14 @@ public final class UsageEvents implements Parcelable { /** * The event type. - * - * @see #MOVE_TO_BACKGROUND - * @see #MOVE_TO_FOREGROUND + * @see #ACTIVITY_PAUSED + * @see #ACTIVITY_RESUMED * @see #CONFIGURATION_CHANGE * @see #USER_INTERACTION * @see #STANDBY_BUCKET_CHANGED + * @see #FOREGROUND_SERVICE_START + * @see #FOREGROUND_SERVICE_STOP + * @see #ACTIVITY_STOPPED */ public int getEventType() { return mEventType; @@ -576,6 +638,7 @@ public final class UsageEvents implements Parcelable { } p.writeInt(packageIndex); p.writeInt(classIndex); + p.writeInt(event.mInstanceId); p.writeInt(event.mEventType); p.writeLong(event.mTimeStamp); @@ -618,6 +681,7 @@ public final class UsageEvents implements Parcelable { } else { eventOut.mClass = null; } + eventOut.mInstanceId = p.readInt(); eventOut.mEventType = p.readInt(); eventOut.mTimeStamp = p.readLong(); diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index 73426e495037..8fb7f4cb4d99 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -16,13 +16,15 @@ package android.app.usage; -import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; +import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; import static android.app.usage.UsageEvents.Event.END_OF_DAY; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; -import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND; -import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; import android.annotation.SystemApi; @@ -31,6 +33,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import android.util.SparseIntArray; /** * Contains usage statistics for an app package for a specific @@ -57,13 +60,20 @@ public final class UsageStats implements Parcelable { public long mEndTimeStamp; /** - * Last time used by the user with an explicit action (notification, activity launch) + * Last time an activity is at foreground (have focus), this is corresponding to + * {@link android.app.usage.UsageEvents.Event#ACTIVITY_RESUMED} event. * {@hide} */ @UnsupportedAppUsage public long mLastTimeUsed; /** + * Last time an activity is visible. + * @hide + */ + public long mLastTimeVisible; + + /** * Total time this package's activity is in foreground. * {@hide} */ @@ -71,6 +81,12 @@ public final class UsageStats implements Parcelable { public long mTotalTimeInForeground; /** + * Total time this package's activity is visible. + * {@hide} + */ + public long mTotalTimeVisible; + + /** * Last time foreground service is started. * {@hide} */ @@ -93,31 +109,32 @@ public final class UsageStats implements Parcelable { */ public int mAppLaunchCount; - /** Last activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event. + /** Last activity ACTIVITY_RESUMED or ACTIVITY_PAUSED event. * {@hide} - * @deprecated use {@link #mLastForegroundActivityEventMap} instead. + * @deprecated use {@link #mActivities} instead. */ @UnsupportedAppUsage @Deprecated public int mLastEvent; /** - * If an activity is in foreground, it has one entry in this map. - * When activity moves to background, it is removed from this map. - * Key is activity class name. - * Value is last time this activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event. + * If an activity is visible(onStart(), onPause() states) or in foreground (onResume() state), + * it has one entry in this map. When an activity becomes invisible (onStop() or onDestroy()), + * it is removed from this map. + * Key is instanceId of the activity (ActivityRecode appToken hashCode).. + * Value is this activity's last event, one of ACTIVITY_RESUMED or + * ACTIVITY_PAUSED. * {@hide} */ - public ArrayMap<String, Integer> mLastForegroundActivityEventMap = new ArrayMap<>(); - + public SparseIntArray mActivities = new SparseIntArray(); /** * If a foreground service is started, it has one entry in this map. - * When a foreground service is stopped, it is removed from this map. + * When a foreground service is stopped, it is removed from this set. * Key is foreground service class name. - * Value is last foreground service FOREGROUND_SERVICE_START ot FOREGROUND_SERVICE_STOP event. + * Value is the foreground service's last event, it is FOREGROUND_SERVICE_START. * {@hide} */ - public ArrayMap<String, Integer> mLastForegroundServiceEventMap = new ArrayMap<>(); + public ArrayMap<String, Integer> mForegroundServices = new ArrayMap<>(); /** * {@hide} @@ -135,14 +152,16 @@ public final class UsageStats implements Parcelable { mBeginTimeStamp = stats.mBeginTimeStamp; mEndTimeStamp = stats.mEndTimeStamp; mLastTimeUsed = stats.mLastTimeUsed; + mLastTimeVisible = stats.mLastTimeVisible; mLastTimeForegroundServiceUsed = stats.mLastTimeForegroundServiceUsed; mTotalTimeInForeground = stats.mTotalTimeInForeground; + mTotalTimeVisible = stats.mTotalTimeVisible; mTotalTimeForegroundServiceUsed = stats.mTotalTimeForegroundServiceUsed; mLaunchCount = stats.mLaunchCount; mAppLaunchCount = stats.mAppLaunchCount; mLastEvent = stats.mLastEvent; - mLastForegroundActivityEventMap = stats.mLastForegroundActivityEventMap; - mLastForegroundServiceEventMap = stats.mLastForegroundServiceEventMap; + mActivities = stats.mActivities; + mForegroundServices = stats.mForegroundServices; mChooserCounts = stats.mChooserCounts; } @@ -191,6 +210,14 @@ public final class UsageStats implements Parcelable { } /** + * Get the last time this package's activity is visible in the UI, measured in milliseconds + * since the epoch. + */ + public long getLastTimeVisible() { + return mLastTimeVisible; + } + + /** * Get the total time this package spent in the foreground, measured in milliseconds. */ public long getTotalTimeInForeground() { @@ -198,6 +225,13 @@ public final class UsageStats implements Parcelable { } /** + * Get the total time this package's activity is visible in the UI, measured in milliseconds. + */ + public long getTotalTimeVisible() { + return mTotalTimeVisible; + } + + /** * Get the last time this package's foreground service was used, measured in milliseconds since * the epoch. * <p/> @@ -224,6 +258,20 @@ public final class UsageStats implements Parcelable { return mAppLaunchCount; } + private void mergeEventMap(SparseIntArray left, SparseIntArray right) { + final int size = right.size(); + for (int i = 0; i < size; i++) { + final int instanceId = right.keyAt(i); + final int event = right.valueAt(i); + final int index = left.indexOfKey(instanceId); + if (index >= 0) { + left.put(instanceId, Math.max(left.valueAt(index), event)); + } else { + left.put(instanceId, event); + } + } + } + private void mergeEventMap(ArrayMap<String, Integer> left, ArrayMap<String, Integer> right) { final int size = right.size(); for (int i = 0; i < size; i++) { @@ -255,15 +303,17 @@ public final class UsageStats implements Parcelable { if (right.mBeginTimeStamp > mBeginTimeStamp) { // Even though incoming UsageStat begins after this one, its last time used fields // may somehow be empty or chronologically preceding the older UsageStat. - mergeEventMap(mLastForegroundActivityEventMap, right.mLastForegroundActivityEventMap); - mergeEventMap(mLastForegroundServiceEventMap, right.mLastForegroundServiceEventMap); + mergeEventMap(mActivities, right.mActivities); + mergeEventMap(mForegroundServices, right.mForegroundServices); mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed); + mLastTimeVisible = Math.max(mLastTimeVisible, right.mLastTimeVisible); mLastTimeForegroundServiceUsed = Math.max(mLastTimeForegroundServiceUsed, right.mLastTimeForegroundServiceUsed); } mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp); mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); mTotalTimeInForeground += right.mTotalTimeInForeground; + mTotalTimeVisible += right.mTotalTimeVisible; mTotalTimeForegroundServiceUsed += right.mTotalTimeForegroundServiceUsed; mLaunchCount += right.mLaunchCount; mAppLaunchCount += right.mAppLaunchCount; @@ -290,36 +340,76 @@ public final class UsageStats implements Parcelable { } /** - * Tell if an event indicate activity is in foreground or not. - * @param event the activity event. - * @return true if activity is in foreground, false otherwise. - * @hide + * Tell if any activity is in foreground. + * @return */ - private boolean isActivityInForeground(int event) { - return event == MOVE_TO_FOREGROUND - || event == CONTINUE_PREVIOUS_DAY; + private boolean hasForegroundActivity() { + final int size = mActivities.size(); + for (int i = 0; i < size; i++) { + if (mActivities.valueAt(i) == ACTIVITY_RESUMED) { + return true; + } + } + return false; } /** - * Tell if an event indicate foreground sevice is started or not. - * @param event the foreground service event. - * @return true if foreground service is started, false if stopped. - * @hide + * Tell if any activity is visible. + * @return */ - private boolean isForegroundServiceStarted(int event) { - return event == FOREGROUND_SERVICE_START - || event == CONTINUING_FOREGROUND_SERVICE; + private boolean hasVisibleActivity() { + final int size = mActivities.size(); + for (int i = 0; i < size; i++) { + final int type = mActivities.valueAt(i); + if (type == ACTIVITY_RESUMED + || type == ACTIVITY_PAUSED) { + return true; + } + } + return false; } /** - * If any activity in foreground or any foreground service is started, the app is considered in - * use. - * @return true if in use, false otherwise. - * @hide + * Tell if any foreground service is started. + * @return + */ + private boolean anyForegroundServiceStarted() { + return !mForegroundServices.isEmpty(); + } + + /** + * Increment total time in foreground and update last time in foreground. + * @param timeStamp current timestamp. + */ + private void incrementTimeUsed(long timeStamp) { + if (timeStamp > mLastTimeUsed) { + mTotalTimeInForeground += timeStamp - mLastTimeUsed; + mLastTimeUsed = timeStamp; + } + } + + /** + * Increment total time visible and update last time visible. + * @param timeStamp current timestmap. */ - private boolean isAppInUse() { - return !mLastForegroundActivityEventMap.isEmpty() - || !mLastForegroundServiceEventMap.isEmpty(); + private void incrementTimeVisible(long timeStamp) { + if (timeStamp > mLastTimeVisible) { + mTotalTimeVisible += timeStamp - mLastTimeVisible; + mLastTimeVisible = timeStamp; + } + } + + /** + * Increment total time foreground service is used and update last time foreground service is + * used. + * @param timeStamp current timestamp. + */ + private void incrementServiceTimeUsed(long timeStamp) { + if (timeStamp > mLastTimeForegroundServiceUsed) { + mTotalTimeForegroundServiceUsed += + timeStamp - mLastTimeForegroundServiceUsed; + mLastTimeForegroundServiceUsed = timeStamp; + } } /** @@ -327,33 +417,63 @@ public final class UsageStats implements Parcelable { * @param className className of the activity. * @param timeStamp timeStamp of the event. * @param eventType type of the event. + * @param instanceId hashCode of the ActivityRecord's appToken. * @hide */ - private void updateForegroundActivity(String className, long timeStamp, int eventType) { - if (eventType != MOVE_TO_BACKGROUND - && eventType != MOVE_TO_FOREGROUND - && eventType != END_OF_DAY) { + private void updateActivity(String className, long timeStamp, int eventType, int instanceId) { + if (eventType != ACTIVITY_RESUMED + && eventType != ACTIVITY_PAUSED + && eventType != ACTIVITY_STOPPED + && eventType != ACTIVITY_DESTROYED) { return; } - final Integer lastEvent = mLastForegroundActivityEventMap.get(className); - if (lastEvent != null) { - if (isActivityInForeground(lastEvent)) { - if (timeStamp > mLastTimeUsed) { - mTotalTimeInForeground += timeStamp - mLastTimeUsed; + // update usage. + final int index = mActivities.indexOfKey(instanceId); + if (index >= 0) { + final int lastEvent = mActivities.valueAt(index); + switch (lastEvent) { + case ACTIVITY_RESUMED: + incrementTimeUsed(timeStamp); + incrementTimeVisible(timeStamp); + break; + case ACTIVITY_PAUSED: + incrementTimeVisible(timeStamp); + break; + default: + break; + } + } + + // update current event. + switch(eventType) { + case ACTIVITY_RESUMED: + if (!hasVisibleActivity()) { + // this is the first visible activity. + mLastTimeUsed = timeStamp; + mLastTimeVisible = timeStamp; + } else if (!hasForegroundActivity()) { + // this is the first foreground activity. mLastTimeUsed = timeStamp; } - } - if (eventType == MOVE_TO_BACKGROUND) { - mLastForegroundActivityEventMap.remove(className); - } else { - mLastForegroundActivityEventMap.put(className, eventType); - } - } else if (eventType == MOVE_TO_FOREGROUND) { - if (!isAppInUse()) { - mLastTimeUsed = timeStamp; - } - mLastForegroundActivityEventMap.put(className, eventType); + mActivities.put(instanceId, eventType); + break; + case ACTIVITY_PAUSED: + if (!hasVisibleActivity()) { + // this is the first visible activity. + mLastTimeVisible = timeStamp; + } + mActivities.put(instanceId, eventType); + break; + case ACTIVITY_STOPPED: + mActivities.put(instanceId, eventType); + break; + case ACTIVITY_DESTROYED: + // remove activity from the map. + mActivities.delete(instanceId); + break; + default: + break; } } @@ -366,80 +486,97 @@ public final class UsageStats implements Parcelable { */ private void updateForegroundService(String className, long timeStamp, int eventType) { if (eventType != FOREGROUND_SERVICE_STOP - && eventType != FOREGROUND_SERVICE_START - && eventType != ROLLOVER_FOREGROUND_SERVICE) { + && eventType != FOREGROUND_SERVICE_START) { return; } - final Integer lastEvent = mLastForegroundServiceEventMap.get(className); + final Integer lastEvent = mForegroundServices.get(className); + // update usage. if (lastEvent != null) { - if (isForegroundServiceStarted(lastEvent)) { - if (timeStamp > mLastTimeForegroundServiceUsed) { - mTotalTimeForegroundServiceUsed += - timeStamp - mLastTimeForegroundServiceUsed; + switch (lastEvent) { + case FOREGROUND_SERVICE_START: + case CONTINUING_FOREGROUND_SERVICE: + incrementServiceTimeUsed(timeStamp); + break; + default: + break; + } + } + + // update current event. + switch (eventType) { + case FOREGROUND_SERVICE_START: + if (!anyForegroundServiceStarted()) { mLastTimeForegroundServiceUsed = timeStamp; } - } - if (eventType == FOREGROUND_SERVICE_STOP) { - mLastForegroundServiceEventMap.remove(className); - } else { - mLastForegroundServiceEventMap.put(className, eventType); - } - } else if (eventType == FOREGROUND_SERVICE_START) { - if (!isAppInUse()) { - mLastTimeForegroundServiceUsed = timeStamp; - } - mLastForegroundServiceEventMap.put(className, eventType); + mForegroundServices.put(className, eventType); + break; + case FOREGROUND_SERVICE_STOP: + mForegroundServices.remove(className); + break; + default: + break; } } /** * Update the UsageStats by a activity or foreground service event. - * @param className class name of a activity or foreground service, could be null to mark - * END_OF_DAY or rollover. + * @param className class name of a activity or foreground service, could be null to if this + * is sent to all activities/services in this package. * @param timeStamp Epoch timestamp in milliseconds. * @param eventType event type as in {@link UsageEvents.Event} + * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken. + * if className is not an activity, instanceId is not used. * @hide */ - public void update(String className, long timeStamp, int eventType) { + public void update(String className, long timeStamp, int eventType, int instanceId) { switch(eventType) { - case MOVE_TO_BACKGROUND: - case MOVE_TO_FOREGROUND: - updateForegroundActivity(className, timeStamp, eventType); + case ACTIVITY_RESUMED: + case ACTIVITY_PAUSED: + case ACTIVITY_STOPPED: + case ACTIVITY_DESTROYED: + updateActivity(className, timeStamp, eventType, instanceId); break; case END_OF_DAY: - // END_OF_DAY means updating all activities. - final int size = mLastForegroundActivityEventMap.size(); - for (int i = 0; i < size; i++) { - final String name = mLastForegroundActivityEventMap.keyAt(i); - updateForegroundActivity(name, timeStamp, eventType); + // END_OF_DAY updates all activities. + if (hasForegroundActivity()) { + incrementTimeUsed(timeStamp); + } + if (hasVisibleActivity()) { + incrementTimeVisible(timeStamp); } break; - case CONTINUE_PREVIOUS_DAY: - mLastTimeUsed = timeStamp; - mLastForegroundActivityEventMap.put(className, eventType); - break; - case FOREGROUND_SERVICE_STOP: case FOREGROUND_SERVICE_START: + case FOREGROUND_SERVICE_STOP: updateForegroundService(className, timeStamp, eventType); break; case ROLLOVER_FOREGROUND_SERVICE: - // ROLLOVER_FOREGROUND_SERVICE means updating all foreground services. - final int size2 = mLastForegroundServiceEventMap.size(); - for (int i = 0; i < size2; i++) { - final String name = mLastForegroundServiceEventMap.keyAt(i); - updateForegroundService(name, timeStamp, eventType); + // ROLLOVER_FOREGROUND_SERVICE updates all foreground services. + if (anyForegroundServiceStarted()) { + incrementServiceTimeUsed(timeStamp); } break; case CONTINUING_FOREGROUND_SERVICE: mLastTimeForegroundServiceUsed = timeStamp; - mLastForegroundServiceEventMap.put(className, eventType); + mForegroundServices.put(className, eventType); + break; + case FLUSH_TO_DISK: + // update usage of all active activities/services. + if (hasForegroundActivity()) { + incrementTimeUsed(timeStamp); + } + if (hasVisibleActivity()) { + incrementTimeVisible(timeStamp); + } + if (anyForegroundServiceStarted()) { + incrementServiceTimeUsed(timeStamp); + } break; default: break; } mEndTimeStamp = timeStamp; - if (eventType == MOVE_TO_FOREGROUND) { + if (eventType == ACTIVITY_RESUMED) { mLaunchCount += 1; } } @@ -455,8 +592,10 @@ public final class UsageStats implements Parcelable { dest.writeLong(mBeginTimeStamp); dest.writeLong(mEndTimeStamp); dest.writeLong(mLastTimeUsed); + dest.writeLong(mLastTimeVisible); dest.writeLong(mLastTimeForegroundServiceUsed); dest.writeLong(mTotalTimeInForeground); + dest.writeLong(mTotalTimeVisible); dest.writeLong(mTotalTimeForegroundServiceUsed); dest.writeInt(mLaunchCount); dest.writeInt(mAppLaunchCount); @@ -477,21 +616,26 @@ public final class UsageStats implements Parcelable { } dest.writeBundle(allCounts); - final Bundle foregroundActivityEventBundle = new Bundle(); - final int foregroundEventSize = mLastForegroundActivityEventMap.size(); - for (int i = 0; i < foregroundEventSize; i++) { - foregroundActivityEventBundle.putInt(mLastForegroundActivityEventMap.keyAt(i), - mLastForegroundActivityEventMap.valueAt(i)); + writeSparseIntArray(dest, mActivities); + dest.writeBundle(eventMapToBundle(mForegroundServices)); + } + + private void writeSparseIntArray(Parcel dest, SparseIntArray arr) { + final int size = arr.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + dest.writeInt(arr.keyAt(i)); + dest.writeInt(arr.valueAt(i)); } - dest.writeBundle(foregroundActivityEventBundle); + } - final Bundle foregroundServiceEventBundle = new Bundle(); - final int foregroundServiceEventSize = mLastForegroundServiceEventMap.size(); - for (int i = 0; i < foregroundServiceEventSize; i++) { - foregroundServiceEventBundle.putInt(mLastForegroundServiceEventMap.keyAt(i), - mLastForegroundServiceEventMap.valueAt(i)); + private Bundle eventMapToBundle(ArrayMap<String, Integer> eventMap) { + final Bundle bundle = new Bundle(); + final int size = eventMap.size(); + for (int i = 0; i < size; i++) { + bundle.putInt(eventMap.keyAt(i), eventMap.valueAt(i)); } - dest.writeBundle(foregroundServiceEventBundle); + return bundle; } public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() { @@ -502,8 +646,10 @@ public final class UsageStats implements Parcelable { stats.mBeginTimeStamp = in.readLong(); stats.mEndTimeStamp = in.readLong(); stats.mLastTimeUsed = in.readLong(); + stats.mLastTimeVisible = in.readLong(); stats.mLastTimeForegroundServiceUsed = in.readLong(); stats.mTotalTimeInForeground = in.readLong(); + stats.mTotalTimeVisible = in.readLong(); stats.mTotalTimeForegroundServiceUsed = in.readLong(); stats.mLaunchCount = in.readInt(); stats.mAppLaunchCount = in.readInt(); @@ -527,12 +673,21 @@ public final class UsageStats implements Parcelable { } } } - readBundleToEventMap(stats.mLastForegroundActivityEventMap, in.readBundle()); - readBundleToEventMap(stats.mLastForegroundServiceEventMap, in.readBundle()); + readSparseIntArray(in, stats.mActivities); + readBundleToEventMap(in.readBundle(), stats.mForegroundServices); return stats; } - private void readBundleToEventMap(ArrayMap<String, Integer> eventMap, Bundle bundle) { + private void readSparseIntArray(Parcel in, SparseIntArray arr) { + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final int key = in.readInt(); + final int value = in.readInt(); + arr.put(key, value); + } + } + + private void readBundleToEventMap(Bundle bundle, ArrayMap<String, Integer> eventMap) { if (bundle != null) { for (String className : bundle.keySet()) { final int event = bundle.getInt(className); diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 1a656ab39373..2edad350e18e 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -37,9 +37,12 @@ public abstract class UsageStatsManagerInternal { * @param component The component for which this event occurred. * @param userId The user id to which the component belongs to. * @param eventType The event that occurred. Valid values can be found at - * {@link UsageEvents} + * {@link UsageEvents} + * @param instanceId For activity, hashCode of ActivityRecord's appToken. + * For non-activity, it is not used. */ - public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType); + public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType, + int instanceId); /** * Reports an event to the UsageStatsManager. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index d7d3cb543af9..b39010d9d14e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4462,7 +4462,7 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an - * {@link android.telephony.rcs.RcsManager}. + * {@link android.telephony.ims.RcsManager}. * @hide */ public static final String TELEPHONY_RCS_SERVICE = "ircs"; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 07d6e4785759..c361ac12667e 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -631,6 +631,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_PROFILEABLE_BY_SHELL = 1 << 23; + /** + * Indicates whether this package requires access to non-SDK APIs. + * Only system apps and tests are allowed to use this property. + * @hide + */ + public static final int PRIVATE_FLAG_HAS_FRAGILE_USER_DATA = 1 << 24; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = { PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE, @@ -655,6 +662,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_STATIC_SHARED_LIBRARY, PRIVATE_FLAG_VENDOR, PRIVATE_FLAG_VIRTUAL_PRELOAD, + PRIVATE_FLAG_HAS_FRAGILE_USER_DATA, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlags {} @@ -1730,6 +1738,17 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & PRIVATE_FLAG_USES_NON_SDK_API) != 0; } + /** + * Whether an app needs to keep the app data on uninstall. + * + * @return {@code true} if the app indicates that it needs to keep the app data + * + * @hide + */ + public boolean hasFragileUserData() { + return (privateFlags & PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) != 0; + } + private boolean isAllowedToUseHiddenApis() { if (isSignedWithPlatformKey()) { return true; diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java new file mode 100644 index 000000000000..07e640b1ba61 --- /dev/null +++ b/core/java/android/content/pm/ModuleInfo.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Information you can retrieve about a particular system + * module. + */ +public final class ModuleInfo implements Parcelable { + + // NOTE: When adding new data members be sure to update the copy-constructor, Parcel + // constructor, and writeToParcel. + + /** Public name of this module. */ + private String mName; + + /** The package name of this module. */ + private String mPackageName; + + /** Whether or not this module is hidden from the user. */ + private boolean mHidden; + + // TODO: Decide whether we need an additional metadata bundle to support out of band + // updates to ModuleInfo. + // + // private Bundle mMetadata; + + /** @hide */ + public ModuleInfo() { + } + + /** @hide */ + public ModuleInfo(ModuleInfo orig) { + mName = orig.mName; + mPackageName = orig.mPackageName; + mHidden = orig.mHidden; + } + + /** @hide Sets the public name of this module. */ + public ModuleInfo setName(String name) { + mName = name; + return this; + } + + /** Gets the public name of this module. */ + public @Nullable String getName() { + return mName; + } + + /** @hide Sets the package name of this module. */ + public ModuleInfo setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + /** Gets the package name of this module. */ + public @Nullable String getPackageName() { + return mPackageName; + } + + /** @hide Sets whether or not this package is hidden. */ + public ModuleInfo setHidden(boolean hidden) { + mHidden = hidden; + return this; + } + + /** Gets whether or not this package is hidden. */ + public boolean isHidden() { + return mHidden; + } + + /** Returns a string representation of this object. */ + public String toString() { + return "ModuleInfo{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + mName + "}"; + } + + /** Describes the kinds of special objects contained in this object. */ + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + int hashCode = 0; + hashCode = 31 * hashCode + Objects.hashCode(mName); + hashCode = 31 * hashCode + Objects.hashCode(mPackageName); + hashCode = 31 * hashCode + Boolean.hashCode(mHidden); + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ModuleInfo)) { + return false; + } + final ModuleInfo other = (ModuleInfo) obj; + return Objects.equals(mName, other.mName) + && Objects.equals(mPackageName, other.mPackageName) + && mHidden == other.mHidden; + } + + /** Flattens this object into the given {@link Parcel}. */ + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeString(mName); + dest.writeString(mPackageName); + dest.writeBoolean(mHidden); + } + + private ModuleInfo(Parcel source) { + mName = source.readString(); + mPackageName = source.readString(); + mHidden = source.readBoolean(); + } + + public static final Parcelable.Creator<ModuleInfo> CREATOR = + new Parcelable.Creator<ModuleInfo>() { + public ModuleInfo createFromParcel(Parcel source) { + return new ModuleInfo(source); + } + public ModuleInfo[] newArray(int size) { + return new ModuleInfo[size]; + } + }; +} diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 96c30f156105..f81eb7642443 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -68,7 +68,14 @@ import java.util.List; * {@link PackageInstaller.Session}, which any app can create. Once the session * is created, the installer can stream one or more APKs into place until it * decides to either commit or destroy the session. Committing may require user - * intervention to complete the installation. + * intervention to complete the installation, unless the caller falls into one of the + * following categories, in which case the installation will complete automatically. + * <ul> + * <li>the device owner + * <li>the affiliated profile owner + * <li>the device owner delegated app with + * {@link android.app.admin.DevicePolicyManager#DELEGATION_PACKAGE_INSTALLATION} + * </ul> * <p> * Sessions can install brand new apps, upgrade existing apps, or add new splits * into an existing app. @@ -481,6 +488,8 @@ public class PackageInstaller { * <li>the current "installer of record" for the package * <li>the device owner * <li>the affiliated profile owner + * <li>the device owner delegated app with + * {@link android.app.admin.DevicePolicyManager#DELEGATION_PACKAGE_INSTALLATION} * </ul> * * @param packageName The package to uninstall. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index da39b6394f8f..566017b7372e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -220,6 +220,12 @@ public abstract class PackageManager { /** @hide */ @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModuleInfoFlags {} + + /** @hide */ + @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = { GET_META_DATA, }) @Retention(RetentionPolicy.SOURCE) @@ -3377,6 +3383,33 @@ public abstract class PackageManager { @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; /** + * Retrieve all of the information we know about a particular + * package/application, for a specific user. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of an + * application. + * @param flags Additional option flags to modify the data returned. + * @return An {@link ApplicationInfo} containing information about the + * package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if + * the package is not found in the list of installed applications, + * the application information is retrieved from the list of + * uninstalled applications (which includes installed applications + * as well as applications with data directory i.e. applications + * which had been deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, + @ApplicationInfoFlags int flags, @NonNull UserHandle user) + throws NameNotFoundException { + return getApplicationInfoAsUser(packageName, flags, user.getIdentifier()); + } + + /** * Retrieve all of the information we know about a particular activity * class. * @@ -3440,6 +3473,35 @@ public abstract class PackageManager { @ComponentInfoFlags int flags) throws NameNotFoundException; /** + * Retrieve information for a particular module. + * + * @param packageName The name of the module. + * @param flags Additional option flags to modify the data returned. + * @return A {@link ModuleInfo} object containing information about the + * module. + * @throws NameNotFoundException if a module with the given name cannot be + * found on the system. + */ + public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags) + throws NameNotFoundException { + throw new UnsupportedOperationException( + "getModuleInfo not implemented in subclass"); + } + + /** + * Return a List of all modules that are installed. + * + * @param flags Additional option flags to modify the data returned. + * @return A {@link List} of {@link ModuleInfo} objects, one for each installed + * module, containing information about the module. In the unlikely case + * there are no installed modules, an empty list is returned. + */ + public @NonNull List<ModuleInfo> getInstalledModules(@ModuleInfoFlags int flags) { + throw new UnsupportedOperationException( + "getInstalledModules not implemented in subclass"); + } + + /** * Return a List of all packages that are installed for the current user. * * @param flags Additional option flags to modify the data returned. @@ -4203,6 +4265,32 @@ public abstract class PackageManager { @ResolveInfoFlags int flags, @UserIdInt int userId); /** + * Retrieve all activities that can be performed for the given intent, for a + * specific user. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags to modify the data returned. The + * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the + * resolution to only those activities that support the + * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set + * {@link #MATCH_ALL} to prevent any filtering of the results. + * @param user The user being queried. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching activity, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveActivity}. If there are no matching activities, an + * empty list is returned. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @NonNull UserHandle user) { + return queryIntentActivitiesAsUser(intent, flags, user.getIdentifier()); + } + + /** * Retrieve a set of activities that should be presented to the user as * similar options. This is like {@link #queryIntentActivities}, except it * also allows you to supply a list of more explicit Intents that you would @@ -4334,6 +4422,27 @@ public abstract class PackageManager { @ResolveInfoFlags int flags, @UserIdInt int userId); /** + * Retrieve all services that can match the given intent for a given user. + * + * @param intent The desired intent as per resolveService(). + * @param flags Additional option flags to modify the data returned. + * @param user The user being queried. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching service, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveService}. If there are no matching services, an + * empty list or null is returned. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @NonNull UserHandle user) { + return queryIntentServicesAsUser(intent, flags, user.getIdentifier()); + } + + /** * Retrieve all providers that can match the given intent. * * @param intent An intent containing all of the desired specification @@ -4355,6 +4464,26 @@ public abstract class PackageManager { * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. + * @param user The user being queried. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching provider, ordered from best to worst. If there are + * no matching services, an empty list or null is returned. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @NonNull UserHandle user) { + return queryIntentContentProvidersAsUser(intent, flags, user.getIdentifier()); + } + + /** + * Retrieve all providers that can match the given intent. + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags to modify the data returned. * @return Returns a List of ResolveInfo objects containing one entry for * each matching provider, ordered from best to worst. If there are * no matching services, an empty list or null is returned. @@ -6448,7 +6577,7 @@ public abstract class PackageManager { * * @hide */ - @SystemApi + @TestApi public String getWellbeingPackageName() { throw new UnsupportedOperationException( "getWellbeingPackageName not implemented in subclass"); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d0de9a1d2a76..61a74ded02d0 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3710,6 +3710,12 @@ public class PackageParser { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_hasFragileUserData, + false)) { + ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA; + } + if (outError[0] == null) { CharSequence pname; if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) { diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index 1d9330d4dfed..9d37d9939127 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -28,6 +28,21 @@ import android.hardware.face.FaceManager; */ public interface BiometricFaceConstants { // + // Accessibility constants + // + /** + * Require the user to look at the device during enrollment and + * authentication. Note this is to accommodate people who have limited + * vision. + */ + public static final int FEATURE_REQUIRE_ATTENTION = 1; + /** + * Require a diverse set of poses during enrollment. Note this is to + * accommodate people with limited mobility. + */ + public static final int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2; + + // // Error messages from face authentication hardware during initialization, enrollment, // authentication or removal. Must agree with the list in HAL h file // diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 9d61f028bc91..1af9cdebf6bc 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -16,6 +16,7 @@ package android.hardware.display; +import android.annotation.Nullable; import android.hardware.SensorManager; import android.os.Handler; import android.os.PowerManager; @@ -195,6 +196,44 @@ public abstract class DisplayManagerInternal { public abstract void onOverlayChanged(); /** + * Get the attributes available for display color sampling. + * @param displayId id of the display to collect the sample from. + * + * @return The attributes the display supports, or null if sampling is not supported. + */ + @Nullable + public abstract DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes( + int displayId); + + /** + * Enable or disable the collection of color samples. + * + * @param displayId id of the display to collect the sample from. + * @param componentMask a bitmask of the color channels to collect samples for, or zero for all + * available. + * @param maxFrames maintain a ringbuffer of the last maxFrames. + * @param enable True to enable, False to disable. + * + * @return True if sampling was enabled, false if failure. + */ + public abstract boolean setDisplayedContentSamplingEnabled( + int displayId, boolean enable, int componentMask, int maxFrames); + + /** + * Accesses the color histogram statistics of displayed frames on devices that support sampling. + * + * @param displayId id of the display to collect the sample from + * @param maxFrames limit the statistics to the last maxFrames number of frames. + * @param timestamp discard statistics that were collected prior to timestamp, where timestamp + * is given as CLOCK_MONOTONIC. + * @return The statistics representing a histogram of the color distribution of the frames + * displayed on-screen, or null if sampling is not supported. + */ + @Nullable + public abstract DisplayedContentSample getDisplayedContentSample( + int displayId, long maxFrames, long timestamp); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the diff --git a/core/java/android/hardware/display/DisplayedContentSample.java b/core/java/android/hardware/display/DisplayedContentSample.java new file mode 100644 index 000000000000..0610377c648a --- /dev/null +++ b/core/java/android/hardware/display/DisplayedContentSample.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +/** + * @hide + */ +public final class DisplayedContentSample { + private long mNumFrames; + private long[] mSamplesComponent0; + private long[] mSamplesComponent1; + private long[] mSamplesComponent2; + private long[] mSamplesComponent3; + + /** + * Construct an object representing a color histogram of pixels that were displayed on screen. + * + * @param numFrames The number of frames represented by this sample. + * @param mSamplesComponent0 is a histogram counting how many times a pixel of a given value + * was displayed onscreen for FORMAT_COMPONENT_0. The buckets of the histogram are evenly + * weighted, the number of buckets is device specific. + * eg, for RGBA_8888, if sampleComponent0 is {10, 6, 4, 1} this means that 10 red pixels were + * displayed onscreen in range 0x00->0x3F, 6 red pixels were displayed onscreen in range + * 0x40->0x7F, etc. + * @param mSamplesComponent1 is the same sample definition as sampleComponent0, but for the + * second component of format. + * @param mSamplesComponent2 is the same sample definition as sampleComponent0, but for the + * third component of format. + * @param mSamplesComponent3 is the same sample definition as sampleComponent0, but for the + * fourth component of format. + */ + public DisplayedContentSample(long numFrames, + long[] sampleComponent0, + long[] sampleComponent1, + long[] sampleComponent2, + long[] sampleComponent3) { + mNumFrames = numFrames; + mSamplesComponent0 = sampleComponent0; + mSamplesComponent1 = sampleComponent1; + mSamplesComponent2 = sampleComponent2; + mSamplesComponent3 = sampleComponent3; + } + + public enum ColorComponent { + CHANNEL0, + CHANNEL1, + CHANNEL2, + CHANNEL3, + } + + /** + * Returns a color histogram according to component channel. + * + * @param component the component to return, according to the PixelFormat ordering + * (eg, for RGBA, CHANNEL0 is R, CHANNEL1 is G, etc). + * + * @return an evenly weighted histogram counting how many times a pixel was + * displayed onscreen that fell into the corresponding bucket, with the first entry + * corresponding to the normalized 0.0 value, and the last corresponding to the 1.0 + * value for that PixelFormat component. + */ + public long[] getSampleComponent(ColorComponent component) { + switch (component) { + case CHANNEL0: return mSamplesComponent0; + case CHANNEL1: return mSamplesComponent1; + case CHANNEL2: return mSamplesComponent2; + case CHANNEL3: return mSamplesComponent3; + default: throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Return the number of frames this sample was collected over. + * + * @return the number of frames that this sample was collected over. + */ + public long getNumFrames() { + return mNumFrames; + } +} diff --git a/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java new file mode 100644 index 000000000000..aad68d9bcf09 --- /dev/null +++ b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java @@ -0,0 +1,67 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +/** + * @hide + */ +public final class DisplayedContentSamplingAttributes { + private int mPixelFormat; + private int mDataspace; + private int mComponentMask; + + /* Creates the attributes reported by the display hardware about what capabilities + * are present. + * + * NOTE: the format and ds constants must match the values from graphics/common/x.x/types.hal + * @param format the format that the display hardware samples in. + * @param ds the dataspace in use when sampling. + * @param componentMask a mask of which of the format components are supported. + */ + public DisplayedContentSamplingAttributes(int format, int ds, int componentMask) { + mPixelFormat = format; + mDataspace = ds; + mComponentMask = componentMask; + } + + /* Returns the pixel format that the display hardware uses when sampling. + * + * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal + * @return the format that the samples were collected in. + */ + public int getPixelFormat() { + return mPixelFormat; + } + + /* Returns the dataspace that the display hardware uses when sampling. + * + * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal + * @return the dataspace that the samples were collected in. + */ + public int getDataspace() { + return mDataspace; + } + + /* Returns a mask of which components can be collected by the sampling engine. + * + * @return a mask of the components which are supported by the engine. The lowest + * bit corresponds to the lowest component (ie, 0x1 corresponds to A for RGBA). + */ + public int getComponentMask() { + return mComponentMask; + } +} diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 322863a6577d..bac23b3c00f9 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -207,11 +207,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public void enroll(byte[] token, CancellationSignal cancel, int flags, - int userId, EnrollmentCallback callback) { - if (userId == UserHandle.USER_CURRENT) { - userId = getCurrentUserId(); - } + public void enroll(byte[] token, CancellationSignal cancel, + EnrollmentCallback callback, int[] disabledFeatures) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); } @@ -228,8 +225,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService != null) { try { mEnrollmentCallback = callback; - mService.enroll(mToken, token, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); + mService.enroll(mToken, token, mServiceReceiver, + mContext.getOpPackageName(), disabledFeatures); } catch (RemoteException e) { Log.w(TAG, "Remote exception in enroll: ", e); if (callback != null) { @@ -284,10 +281,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public void setRequireAttention(boolean requireAttention, byte[] token) { + public void setFeature(int feature, boolean enabled, byte[] token) { if (mService != null) { try { - mService.setRequireAttention(requireAttention, token); + mService.setFeature(feature, enabled, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -298,11 +295,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public boolean getRequireAttention(byte[] token) { + public boolean getFeature(int feature) { boolean result = true; if (mService != null) { try { - mService.getRequireAttention(token); + result = mService.getFeature(feature); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index a15dcec3b276..a1c88f81e3e7 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -50,8 +50,8 @@ interface IFaceService { int callingUid, int callingPid, int callingUserId, boolean fromClient); // Start face enrollment - void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver, - int flags, String opPackageName); + void enroll(IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, + String opPackageName, in int [] disabledFeatures); // Cancel enrollment in progress void cancelEnrollment(IBinder token); @@ -98,9 +98,9 @@ interface IFaceService { // Enumerate all faces void enumerate(IBinder token, int userId, IFaceServiceReceiver receiver); - int setRequireAttention(boolean requireAttention, in byte [] token); + int setFeature(int feature, boolean enabled, in byte [] token); - boolean getRequireAttention(in byte [] token); + boolean getFeature(int feature); void userActivity(); } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index d45fa11de639..9939a3c8f36d 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; @@ -399,6 +400,9 @@ public class Binder implements IBinder { * reasons, we only support one UID. This UID represents the original user responsible for the * binder calls. * + * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after setting the + * worksource. + * * <p>A typical use case would be * <pre> * long token = Binder.setCallingWorkSourceUid(uid); @@ -417,6 +421,7 @@ public class Binder implements IBinder { * @hide **/ @CriticalNative + @SystemApi public static final native long setCallingWorkSourceUid(int workSource); /** @@ -430,6 +435,7 @@ public class Binder implements IBinder { * @hide */ @CriticalNative + @SystemApi public static final native int getCallingWorkSourceUid(); /** @@ -438,10 +444,24 @@ public class Binder implements IBinder { * <p>The work source will be propagated for future outgoing binder transactions * executed on this thread. * + * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after clearing the + * worksource. + * + * <p>A typical use case would be + * <pre> + * long token = Binder.clearCallingWorkSource(); + * try { + * // Call an API. + * } finally { + * Binder.restoreCallingWorkSource(token); + * } + * </pre> + * * @return token to restore original work source. * @hide **/ @CriticalNative + @SystemApi public static final native long clearCallingWorkSource(); /** @@ -461,6 +481,7 @@ public class Binder implements IBinder { * @hide **/ @CriticalNative + @SystemApi public static final native void restoreCallingWorkSource(long token); /** @@ -601,6 +622,7 @@ public class Binder implements IBinder { * See {@link setProxyTransactListener}. * @hide */ + @SystemApi public interface ProxyTransactListener { /** * Called before onTransact. @@ -663,6 +685,7 @@ public class Binder implements IBinder { * <li>Never execute another binder transaction inside the listener. * @hide */ + @SystemApi public static void setProxyTransactListener(@Nullable ProxyTransactListener listener) { BinderProxy.setTransactListener(listener); } diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 7abe913312a1..124d7b174739 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -16,7 +16,6 @@ package android.os; -import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -71,7 +70,7 @@ public class GraphicsEnvironment { */ public void setup(Context context, Bundle coreSettings) { setupGpuLayers(context, coreSettings); - setupAngle(context, context.getPackageName()); + setupAngle(context, coreSettings, context.getPackageName()); chooseDriver(context, coreSettings); } @@ -213,10 +212,9 @@ public class GraphicsEnvironment { } - private static List<String> getGlobalSettingsString(Context context, String globalSetting) { + private static List<String> getGlobalSettingsString(Bundle bundle, String globalSetting) { List<String> valueList = null; - ContentResolver contentResolver = context.getContentResolver(); - String settingsValue = Settings.Global.getString(contentResolver, globalSetting); + String settingsValue = bundle.getString(globalSetting); if (settingsValue != null) { valueList = new ArrayList<>(Arrays.asList(settingsValue.split(","))); @@ -238,23 +236,18 @@ public class GraphicsEnvironment { return -1; } - private static String getDriverForPkg(Context context, String packageName) { - try { - ContentResolver contentResolver = context.getContentResolver(); - int allUseAngle = Settings.Global.getInt(contentResolver, - Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); - if (allUseAngle == 1) { - return sDriverMap.get(OpenGlDriverChoice.ANGLE); - } - } catch (Settings.SettingNotFoundException e) { - // Do nothing and move on + private static String getDriverForPkg(Bundle bundle, String packageName) { + String allUseAngle = + bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); + if ((allUseAngle != null) && allUseAngle.equals("1")) { + return sDriverMap.get(OpenGlDriverChoice.ANGLE); } List<String> globalSettingsDriverPkgs = - getGlobalSettingsString(context, + getGlobalSettingsString(bundle, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS); List<String> globalSettingsDriverValues = - getGlobalSettingsString(context, + getGlobalSettingsString(bundle, Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES); // Make sure we have a good package name @@ -285,8 +278,8 @@ public class GraphicsEnvironment { /** * Pass ANGLE details down to trigger enable logic */ - private void setupAngle(Context context, String packageName) { - String devOptIn = getDriverForPkg(context, packageName); + private void setupAngle(Context context, Bundle bundle, String packageName) { + String devOptIn = getDriverForPkg(bundle, packageName); if (DEBUG) { Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 17ce79b83aa8..0a60764428dc 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1515,11 +1515,11 @@ public class UserManager { * background user; the result here does not distinguish between the two. * * <p>Note prior to Android Nougat MR1 (SDK version <= 24; - * {@link android.os.Build.VERSION_CODES#N), this API required a system permission + * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission * in order to check other profile's status. * Since Android Nougat MR1 (SDK version >= 25; - * {@link android.os.Build.VERSION_CODES#N_MR1)), the restriction has been relaxed, and now - * it'll accept any {@link UserHandle} within the same profile group as the caller. + * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now + * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller. * * @param user The user to retrieve the running state for. */ @@ -1544,11 +1544,11 @@ public class UserManager { * (but is not yet fully stopped, and still running some code). * * <p>Note prior to Android Nougat MR1 (SDK version <= 24; - * {@link android.os.Build.VERSION_CODES#N), this API required a system permission + * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission * in order to check other profile's status. * Since Android Nougat MR1 (SDK version >= 25; - * {@link android.os.Build.VERSION_CODES#N_MR1)), the restriction has been relaxed, and now - * it'll accept any {@link UserHandle} within the same profile group as the caller. + * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now + * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller. * * @param user The user to retrieve the running state for. */ diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 7fd0a4b66d66..f136cd6699a7 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -398,6 +398,8 @@ public class ZygoteProcess { argsForZygote.add("--mount-external-write"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) { argsForZygote.add("--mount-external-full"); + } else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) { + argsForZygote.add("--mount-external-installer"); } argsForZygote.add("--target-sdk-version=" + targetSdkVersion); diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index 865b8f8482bd..c167ea18f0c5 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -16,6 +16,7 @@ package android.provider; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.UnsupportedAppUsage; @@ -41,6 +42,8 @@ import android.text.format.DateUtils; import android.text.format.Time; import android.util.Log; +import com.android.internal.util.Preconditions; + /** * <p> * The contract between the calendar provider and applications. Contains @@ -129,6 +132,13 @@ public final class CalendarContract { "android.provider.calendar.action.HANDLE_CUSTOM_EVENT"; /** + * Action used to help apps show calendar events in the managed profile. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VIEW_WORK_CALENDAR_EVENT = + "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT"; + + /** * Intent Extras key: {@link EventsColumns#CUSTOM_APP_URI} for the event in * the {@link #ACTION_HANDLE_CUSTOM_EVENT} intent */ @@ -153,6 +163,11 @@ public final class CalendarContract { public static final String EXTRA_EVENT_ALL_DAY = "allDay"; /** + * Intent Extras key: The id of an event. + */ + public static final String EXTRA_EVENT_ID = "id"; + + /** * This authority is used for writing to or querying from the calendar * provider. Note: This is set at first run and cannot be changed without * breaking apps that access the provider. @@ -195,6 +210,43 @@ public final class CalendarContract { private CalendarContract() {} /** + * Starts an activity to view calendar events in the managed profile. + * + * When this API is called, the system will attempt to start an activity + * in the managed profile with an intent targeting the same caller package. + * The intent will have its action set to + * {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras + * corresponding to the API's arguments. A calendar app intending to support + * cross profile events viewing should handle this intent, parse the arguments + * and show the appropriate UI. + * + * @param context the context. + * @param eventId the id of the event to be viewed. Will be put into {@link #EXTRA_EVENT_ID} + * field of the intent. + * @param start the start time of the event. Will be put into {@link #EXTRA_EVENT_BEGIN_TIME} + * field of the intent. + * @param end the end time of the event. Will be put into {@link #EXTRA_EVENT_END_TIME} field + * of the intent. + * @param allDay if the event is an all-day event. Will be put into + * {@link #EXTRA_EVENT_ALL_DAY} field of the intent. + * @param flags flags to be set on the intent via {@link Intent#setFlags} + * @return {@code true} if the activity is started successfully. {@code false} otherwise. + * + * @see #EXTRA_EVENT_ID + * @see #EXTRA_EVENT_BEGIN_TIME + * @see #EXTRA_EVENT_END_TIME + * @see #EXTRA_EVENT_ALL_DAY + */ + public static boolean startViewCalendarEventInManagedProfile(@NonNull Context context, + long eventId, long start, long end, boolean allDay, int flags) { + Preconditions.checkNotNull(context, "Context is null"); + final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + return dpm.startViewCalendarEventInManagedProfile(eventId, start, + end, allDay, flags); + } + + /** * Generic columns for use by sync adapters. The specific functions of these * columns are private to the sync adapter. Other clients of the API should * not attempt to either read or write this column. These columns are @@ -695,7 +747,7 @@ public final class CalendarContract { public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars"); /** - * The content:// style URL for querying Calendars table in the work profile. Appending a + * The content:// style URL for querying Calendars table in the managed profile. Appending a * calendar id using {@link ContentUris#withAppendedId(Uri, long)} will * specify a single calendar. * @@ -715,9 +767,9 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a work profile, or cross profile calendar is disabled in Settings, or this uri is - * queried from a package that is not whitelisted by profile owner of the work profile via - * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * queried from a package that is not whitelisted by profile owner of the managed profile + * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED @@ -1673,7 +1725,7 @@ public final class CalendarContract { Uri.parse("content://" + AUTHORITY + "/events"); /** - * The content:// style URL for querying Events table in the work profile. Appending an + * The content:// style URL for querying Events table in the managed profile. Appending an * event id using {@link ContentUris#withAppendedId(Uri, long)} will * specify a single event. * @@ -1706,9 +1758,9 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a work profile, or cross profile calendar is disabled in Settings, or this uri is - * queried from a package that is not whitelisted by profile owner of the work profile via - * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * queried from a package that is not whitelisted by profile owner of the managed profile + * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED @@ -1896,7 +1948,7 @@ public final class CalendarContract { Uri.parse("content://" + AUTHORITY + "/instances/searchbyday"); /** - * The content:// style URL for querying an instance range in the work profile. + * The content:// style URL for querying an instance range in the managed profile. * It supports similar semantics as {@link #CONTENT_URI}. * * <p>The following columns plus the columns that are whitelisted by @@ -1916,9 +1968,9 @@ public final class CalendarContract { * projection of the query to this uri that are not contained in the above list. * * <p>This uri will return an empty cursor if the calling user is not a parent profile - * of a work profile, or cross profile calendar for the work profile is disabled in + * of a managed profile, or cross profile calendar for the managed profile is disabled in * Settings, or this uri is queried from a package that is not whitelisted by - * profile owner of the work profile via + * profile owner of the managed profile via * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. * * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) @@ -1929,7 +1981,7 @@ public final class CalendarContract { /** * The content:// style URL for querying an instance range by Julian - * Day in the work profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI} + * Day in the managed profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI} * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}. */ public static final Uri ENTERPRISE_CONTENT_BY_DAY_URI = @@ -1937,7 +1989,7 @@ public final class CalendarContract { /** * The content:// style URL for querying an instance range with a search - * term in the work profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI} + * term in the managed profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI} * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}. */ public static final Uri ENTERPRISE_CONTENT_SEARCH_URI = @@ -1945,7 +1997,7 @@ public final class CalendarContract { /** * The content:// style URL for querying an instance range with a search - * term in the work profile. It supports similar semantics as + * term in the managed profile. It supports similar semantics as * {@link #CONTENT_SEARCH_BY_DAY_URI} and performs similar checks as * {@link #ENTERPRISE_CONTENT_URI}. */ diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java index e930f401ecd5..568ca0f6b56e 100644 --- a/core/java/android/service/carrier/CarrierIdentifier.java +++ b/core/java/android/service/carrier/CarrierIdentifier.java @@ -71,10 +71,8 @@ public class CarrierIdentifier implements Parcelable { * @param gid2 group id level 2 * @param carrierid carrier unique identifier {@link TelephonyManager#getSimCarrierId()}, used * to uniquely identify the carrier and look up the carrier configurations. - * @param preciseCarrierId precise carrier identifier {@link TelephonyManager#getSimPreciseCarrierId()} - * @hide - * - * TODO: expose this to public API + * @param preciseCarrierId precise carrier identifier + * {@link TelephonyManager#getSimPreciseCarrierId()} */ public CarrierIdentifier(String mcc, String mnc, @Nullable String spn, @Nullable String imsi, @Nullable String gid1, @Nullable String gid2, @@ -155,16 +153,16 @@ public class CarrierIdentifier implements Parcelable { } /** - * Get the carrier id {@link TelephonyManager#getSimCarrierId() } - * @hide + * Returns the carrier id. + * @see TelephonyManager#getSimCarrierId() */ public int getCarrierId() { return mCarrierId; } /** - * Get the precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()} - * @hide + * Returns the precise carrier id. + * @see TelephonyManager#getSimPreciseCarrierId() */ public int getPreciseCarrierId() { return mPreciseCarrierId; diff --git a/core/java/android/view/InputEventCompatProcessor.java b/core/java/android/view/InputEventCompatProcessor.java new file mode 100644 index 000000000000..ff8407a40cf5 --- /dev/null +++ b/core/java/android/view/InputEventCompatProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.Context; +import android.os.Build; + +import java.util.ArrayList; +import java.util.List; + +/** + * Compatibility processor for InputEvents that allows events to be adjusted before and + * after it is sent to the application. + * + * {@hide} + */ +public class InputEventCompatProcessor { + + protected Context mContext; + protected int mTargetSdkVersion; + + /** List of events to be used to return the processed events */ + private List<InputEvent> mProcessedEvents; + + public InputEventCompatProcessor(Context context) { + mContext = context; + mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; + mProcessedEvents = new ArrayList<>(); + } + + /** + * Processes the InputEvent for compatibility before it is sent to the app, allowing for the + * generation of more than one event if necessary. + * + * @param e The InputEvent to process + * @return The list of adjusted events, or null if no adjustments are needed. Do not keep a + * reference to the output as the list is reused. + */ + public List<InputEvent> processInputEventForCompatibility(InputEvent e) { + if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) { + mProcessedEvents.clear(); + MotionEvent motion = (MotionEvent) e; + final int mask = + MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY; + final int buttonState = motion.getButtonState(); + final int compatButtonState = (buttonState & mask) >> 4; + if (compatButtonState != 0) { + motion.setButtonState(buttonState | compatButtonState); + } + mProcessedEvents.add(motion); + return mProcessedEvents; + } + return null; + } + + /** + * Processes the InputEvent for compatibility before it is finished by calling + * InputEventReceiver#finishInputEvent(). + * + * @param e The InputEvent to process + * @return The InputEvent to finish, or null if it should not be finished + */ + public InputEvent processInputEventBeforeFinish(InputEvent e) { + // No changes needed + return e; + } +} diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ab010855b896..a006e5de283e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -36,6 +36,8 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.display.DisplayedContentSample; +import android.hardware.display.DisplayedContentSamplingAttributes; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -129,6 +131,12 @@ public class SurfaceControl implements Parcelable { int width, int height); private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs( IBinder displayToken); + private static native DisplayedContentSamplingAttributes + nativeGetDisplayedContentSamplingAttributes(IBinder displayToken); + private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken, + boolean enable, int componentMask, int maxFrames); + private static native DisplayedContentSample nativeGetDisplayedContentSample( + IBinder displayToken, long numFrames, long timestamp); private static native int nativeGetActiveConfig(IBinder displayToken); private static native boolean nativeSetActiveConfig(IBinder displayToken, int id); private static native int[] nativeGetDisplayColorModes(IBinder displayToken); @@ -1164,6 +1172,45 @@ public class SurfaceControl implements Parcelable { return nativeGetActiveConfig(displayToken); } + /** + * @hide + */ + public static DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes( + IBinder displayToken) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + return nativeGetDisplayedContentSamplingAttributes(displayToken); + } + + /** + * @hide + */ + public static boolean setDisplayedContentSamplingEnabled( + IBinder displayToken, boolean enable, int componentMask, int maxFrames) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + final int maxColorComponents = 4; + if ((componentMask >> maxColorComponents) != 0) { + throw new IllegalArgumentException("invalid componentMask when enabling sampling"); + } + return nativeSetDisplayedContentSamplingEnabled( + displayToken, enable, componentMask, maxFrames); + } + + /** + * @hide + */ + public static DisplayedContentSample getDisplayedContentSample( + IBinder displayToken, long maxFrames, long timestamp) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + return nativeGetDisplayedContentSample(displayToken, maxFrames, timestamp); + } + + public static boolean setActiveConfig(IBinder displayToken, int id) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e36e2588f3d7..9fe0ddc110c2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -124,6 +124,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Queue; import java.util.concurrent.CountDownLatch; @@ -543,6 +544,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mNeedsRendererSetup; + private final InputEventCompatProcessor mInputCompatProcessor; + /** * Consistency verifier for debugging purposes. */ @@ -598,6 +601,25 @@ public final class ViewRootImpl implements ViewParent, mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); + String processorOverrideName = context.getResources().getString( + R.string.config_inputEventCompatProcessorOverrideClassName); + if (processorOverrideName.isEmpty()) { + // No compatibility processor override, using default. + mInputCompatProcessor = new InputEventCompatProcessor(context); + } else { + InputEventCompatProcessor compatProcessor = null; + try { + final Class<? extends InputEventCompatProcessor> klass = + (Class<? extends InputEventCompatProcessor>) Class.forName( + processorOverrideName); + compatProcessor = klass.getConstructor(Context.class).newInstance(context); + } catch (Exception e) { + Log.e(TAG, "Unable to create the InputEventCompatProcessor. ", e); + } finally { + mInputCompatProcessor = compatProcessor; + } + } + if (!sCompatibilityDone) { sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P; @@ -7166,6 +7188,7 @@ public final class ViewRootImpl implements ViewParent, public static final int FLAG_FINISHED_HANDLED = 1 << 3; public static final int FLAG_RESYNTHESIZED = 1 << 4; public static final int FLAG_UNHANDLED = 1 << 5; + public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6; public QueuedInputEvent mNext; @@ -7258,7 +7281,6 @@ public final class ViewRootImpl implements ViewParent, @UnsupportedAppUsage void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { - adjustInputEventForCompatibility(event); QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); // Always enqueue the input event in order, regardless of its time stamp. @@ -7361,7 +7383,22 @@ public final class ViewRootImpl implements ViewParent, if (q.mReceiver != null) { boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; - q.mReceiver.finishInputEvent(q.mEvent, handled); + boolean modified = (q.mFlags & QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY) != 0; + if (modified) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventBeforeFinish"); + InputEvent processedEvent; + try { + processedEvent = + mInputCompatProcessor.processInputEventBeforeFinish(q.mEvent); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + if (processedEvent != null) { + q.mReceiver.finishInputEvent(processedEvent, handled); + } + } else { + q.mReceiver.finishInputEvent(q.mEvent, handled); + } } else { q.mEvent.recycleIfNeededAfterDispatch(); } @@ -7369,19 +7406,6 @@ public final class ViewRootImpl implements ViewParent, recycleQueuedInputEvent(q); } - private void adjustInputEventForCompatibility(InputEvent e) { - if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) { - MotionEvent motion = (MotionEvent) e; - final int mask = - MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY; - final int buttonState = motion.getButtonState(); - final int compatButtonState = (buttonState & mask) >> 4; - if (compatButtonState != 0) { - motion.setButtonState(buttonState | compatButtonState); - } - } - } - static boolean isTerminalInputEvent(InputEvent event) { if (event instanceof KeyEvent) { final KeyEvent keyEvent = (KeyEvent)event; @@ -7452,7 +7476,28 @@ public final class ViewRootImpl implements ViewParent, @Override public void onInputEvent(InputEvent event) { - enqueueInputEvent(event, this, 0, true); + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility"); + List<InputEvent> processedEvents; + try { + processedEvents = + mInputCompatProcessor.processInputEventForCompatibility(event); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + if (processedEvents != null) { + if (processedEvents.isEmpty()) { + // InputEvent consumed by mInputCompatProcessor + finishInputEvent(event, true); + } else { + for (int i = 0; i < processedEvents.size(); i++) { + enqueueInputEvent( + processedEvents.get(i), this, + QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true); + } + } + } else { + enqueueInputEvent(event, this, 0, true); + } } @Override diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 88b9c8096fe1..c5c1bcae232a 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1004,6 +1004,36 @@ public final class AccessibilityManager { } /** + * Returns accessibility window id from window token. Accessibility window id is the one + * returned from AccessibilityWindowInfo.getId(). Only available for the system process. + * + * @param windowToken Window token to find accessibility window id. + * @return Accessibility window id for the window token. + * AccessibilityWindowInfo.UNDEFINED_WINDOW_ID if accessibility window id not available for + * the token. + * @hide + */ + @SystemApi + public int getAccessibilityWindowId(IBinder windowToken) { + if (windowToken == null) { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + } + try { + return service.getAccessibilityWindowId(windowToken); + } catch (RemoteException e) { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + } + + /** * Sets the current state and notifies listeners, if necessary. * * @param stateFlags The state flags. diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 2767a82e5dba..38dac94340bb 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -76,5 +76,8 @@ interface IAccessibilityManager { // System process only boolean sendFingerprintGesture(int gestureKeyCode); + // System process only + int getAccessibilityWindowId(IBinder windowToken); + long getRecommendedTimeoutMillis(); } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 1889692ea831..cc0264ac0bc9 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -31,7 +31,9 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; +import android.util.TimeUtils; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; @@ -98,6 +100,12 @@ public final class ContentCaptureManager { */ public static final int STATE_DISABLED = 3; + /** + * Handler message used to flush the buffer. + */ + private static final int MSG_FLUSH = 1; + + private static final String BG_THREAD_NAME = "intel_svc_streamer_thread"; /** @@ -106,6 +114,12 @@ public final class ContentCaptureManager { // TODO(b/111276913): use settings private static final int MAX_BUFFER_SIZE = 100; + /** + * Frequency the buffer is flushed if stale. + */ + // TODO(b/111276913): use settings + private static final int FLUSHING_FREQUENCY_MS = 5_000; + @NonNull private final AtomicBoolean mDisabled = new AtomicBoolean(); @@ -136,6 +150,9 @@ public final class ContentCaptureManager { // held at the Application level private final Handler mHandler; + // Used just for debugging purposes (on dump) + private long mNextFlush; + /** @hide */ public ContentCaptureManager(@NonNull Context context, @Nullable IContentCaptureManager service) { @@ -207,9 +224,17 @@ public final class ContentCaptureManager { mEvents = new ArrayList<>(MAX_BUFFER_SIZE); } mEvents.add(event); + final int numberEvents = mEvents.size(); - if (numberEvents < MAX_BUFFER_SIZE && !forceFlush) { - // Buffering events, return right away... + + // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are + // buffered (either total or per autofillid). For + // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer + // "a" and "b" then send "abc". + final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE; + + if (bufferEvent && !forceFlush) { + handleScheduleFlush(); return; } @@ -236,10 +261,38 @@ public final class ContentCaptureManager { return; } + handleForceFlush(); + } + + private void handleScheduleFlush() { + if (mHandler.hasMessages(MSG_FLUSH)) { + // "Renew" the flush message by removing the previous one + mHandler.removeMessages(MSG_FLUSH); + } + mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS; + if (VERBOSE) { + Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush); + } + mHandler.sendMessageDelayed( + obtainMessage(ContentCaptureManager::handleFlushIfNeeded, this).setWhat(MSG_FLUSH), + FLUSHING_FREQUENCY_MS); + } + + private void handleFlushIfNeeded() { + if (mEvents.isEmpty()) { + if (VERBOSE) Log.v(TAG, "Nothing to flush"); + return; + } + handleForceFlush(); + } + + private void handleForceFlush() { + final int numberEvents = mEvents.size(); try { if (DEBUG) { Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName()); } + mHandler.removeMessages(MSG_FLUSH); mService.sendEvents(mContext.getUserId(), mId, mEvents); // TODO(b/111276913): decide whether we should clear or set it to null, as each has // its own advantages: clearing will save extra allocations while the session is @@ -307,6 +360,7 @@ public final class ContentCaptureManager { mApplicationToken = null; mComponentName = null; mEvents = null; + mHandler.removeMessages(MSG_FLUSH); } /** @@ -443,7 +497,7 @@ public final class ContentCaptureManager { pw.print(prefix2); pw.print("component name: "); pw.println(mComponentName.flattenToShortString()); } - if (mEvents != null) { + if (mEvents != null && !mEvents.isEmpty()) { final int numberEvents = mEvents.size(); pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents); pw.print('/'); pw.println(MAX_BUFFER_SIZE); @@ -455,6 +509,9 @@ public final class ContentCaptureManager { pw.println(); } } + pw.print(prefix2); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS); + pw.print(prefix2); pw.print("next flush: "); + TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println(); } } diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index 797b861e9e70..b41096c74bf7 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -16,7 +16,6 @@ package android.view.textclassifier; -import android.annotation.NonNull; import android.app.Person; import android.text.TextUtils; import android.util.ArrayMap; @@ -30,6 +29,7 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -57,9 +57,9 @@ public final class ActionsSuggestionsHelper { * </ul> * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0. */ - @NonNull public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages( - @NonNull List<ConversationActions.Message> messages) { + List<ConversationActions.Message> messages, + Function<CharSequence, String> languageDetector) { List<ConversationActions.Message> messagesWithText = messages.stream() .filter(message -> !TextUtils.isEmpty(message.getText())) @@ -67,31 +67,18 @@ public final class ActionsSuggestionsHelper { if (messagesWithText.isEmpty()) { return new ActionsSuggestionsModel.ConversationMessage[0]; } - int size = messagesWithText.size(); - // If the last message (the most important one) does not have the Person object, we will - // just use the last message and consider this message is sent from a remote user. - ConversationActions.Message lastMessage = messages.get(size - 1); - boolean useLastMessageOnly = lastMessage.getAuthor() == null; - if (useLastMessageOnly) { - return new ActionsSuggestionsModel.ConversationMessage[]{ - new ActionsSuggestionsModel.ConversationMessage( - FIRST_NON_LOCAL_USER, - lastMessage.getText().toString(), - 0, - null)}; - } - - // Encode the messages in the reverse order, stop whenever the Person object is missing. Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>(); PersonEncoder personEncoder = new PersonEncoder(); + int size = messagesWithText.size(); for (int i = size - 1; i >= 0; i--) { ConversationActions.Message message = messagesWithText.get(i); - if (message.getAuthor() == null) { - break; - } + long referenceTime = message.getReferenceTime() == null + ? 0 + : message.getReferenceTime().toInstant().toEpochMilli(); nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage( personEncoder.encode(message.getAuthor()), - message.getText().toString(), 0, null)); + message.getText().toString(), referenceTime, + languageDetector.apply(message.getText()))); } return nativeMessages.toArray( new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]); diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 04b94b0e03ad..04924c9a59c4 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -349,17 +349,31 @@ public final class ConversationActions implements Parcelable { /** * Represents the local user. * - * @see Builder#setAuthor(Person) + * @see Builder#Builder(Person) */ public static final Person PERSON_USER_LOCAL = new Person.Builder() .setKey("text-classifier-conversation-actions-local-user") .build(); + /** + * Represents the remote user. + * <p> + * If possible, you are suggested to create a {@link Person} object that can identify + * the remote user better, so that the underlying model could differentiate between + * different remote users. + * + * @see Builder#Builder(Person) + */ + public static final Person PERSON_USER_REMOTE = + new Person.Builder() + .setKey("text-classifier-conversation-actions-remote-user") + .build(); + @Nullable private final Person mAuthor; @Nullable - private final ZonedDateTime mComposeTime; + private final ZonedDateTime mReferenceTime; @Nullable private final CharSequence mText; @NonNull @@ -367,18 +381,18 @@ public final class ConversationActions implements Parcelable { private Message( @Nullable Person author, - @Nullable ZonedDateTime composeTime, + @Nullable ZonedDateTime referenceTime, @Nullable CharSequence text, @NonNull Bundle bundle) { mAuthor = author; - mComposeTime = composeTime; + mReferenceTime = referenceTime; mText = text; mExtras = Preconditions.checkNotNull(bundle); } private Message(Parcel in) { mAuthor = in.readParcelable(null); - mComposeTime = + mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse( @@ -390,9 +404,9 @@ public final class ConversationActions implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mAuthor, flags); - parcel.writeInt(mComposeTime != null ? 1 : 0); - if (mComposeTime != null) { - parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + parcel.writeInt(mReferenceTime != null ? 1 : 0); + if (mReferenceTime != null) { + parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); } parcel.writeCharSequence(mText); parcel.writeBundle(mExtras); @@ -417,15 +431,18 @@ public final class ConversationActions implements Parcelable { }; /** Returns the person that composed the message. */ - @Nullable + @NonNull public Person getAuthor() { return mAuthor; } - /** Returns the compose time of the message. */ + /** + * Returns the reference time of the message, for example it could be the compose or send + * time of this message. + */ @Nullable - public ZonedDateTime getTime() { - return mComposeTime; + public ZonedDateTime getReferenceTime() { + return mReferenceTime; } /** Returns the text of the message. */ @@ -451,34 +468,38 @@ public final class ConversationActions implements Parcelable { @Nullable private Person mAuthor; @Nullable - private ZonedDateTime mComposeTime; + private ZonedDateTime mReferenceTime; @Nullable private CharSequence mText; @Nullable private Bundle mExtras; /** - * Sets the person who composed this message. - * <p> - * Use {@link #PERSON_USER_LOCAL} to represent the local user. + * Constructs a builder. + * + * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} + * to represent the local user. If it is not possible to identify the + * remote user that the local user is conversing with, use + * {@link #PERSON_USER_REMOTE} to represent a remote user. */ - @NonNull - public Builder setAuthor(@Nullable Person author) { - mAuthor = author; - return this; + public Builder(@NonNull Person author) { + mAuthor = Preconditions.checkNotNull(author); } - /** Sets the text of this message */ + /** Sets the text of this message. */ @NonNull public Builder setText(@Nullable CharSequence text) { mText = text; return this; } - /** Sets the compose time of this message */ + /** + * Sets the reference time of this message, for example it could be the compose or send + * time of this message. + */ @NonNull - public Builder setComposeTime(@Nullable ZonedDateTime composeTime) { - mComposeTime = composeTime; + public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { + mReferenceTime = referenceTime; return this; } @@ -494,7 +515,7 @@ public final class ConversationActions implements Parcelable { public Message build() { return new Message( mAuthor, - mComposeTime, + mReferenceTime, mText == null ? null : new SpannedString(mText), mExtras == null ? new Bundle() : mExtras.deepCopy()); } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index a2536cbd911c..ea82bf3f3846 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -162,6 +162,14 @@ public interface TextClassifier { TextClassifier NO_OP = new TextClassifier() {}; /** + * Used as a boolean value to indicate the intent is generated by TextClassifier. + * <p> + * All {@link TextClassifier} implementations should set this boolean extra to be true in their + * generated intents. + */ + String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER"; + + /** * Returns suggested text selection start and end indices, recognized entity types, and their * associated confidence scores. The entity types are ordered from highest to lowest scoring. * diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 8e14dfdb7ee3..deda92646e71 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -139,7 +139,7 @@ public final class TextClassifierImpl implements TextClassifier { FACTORY_MODEL_DIR, LANG_ID_FACTORY_MODEL_FILENAME_REGEX, UPDATED_LANG_ID_MODEL_FILE, - fd -> -1, // TODO: Replace this with LangIdModel.getVersion(fd) + LangIdModel::getVersion, fd -> ModelFileManager.ModelFile.LANGUAGE_INDEPENDENT)); mActionsModelFileManager = new ModelFileManager( new ModelFileManager.ModelFileSupplierImpl( @@ -374,7 +374,8 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } ActionsSuggestionsModel.ConversationMessage[] nativeMessages = - ActionsSuggestionsHelper.toNativeMessages(request.getConversation()); + ActionsSuggestionsHelper.toNativeMessages(request.getConversation(), + this::detectLanguageTagsFromText); if (nativeMessages.length == 0) { return mFallback.suggestConversationActions(request); } @@ -407,6 +408,26 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } + @Nullable + private String detectLanguageTagsFromText(CharSequence text) { + TextLanguage.Request request = new TextLanguage.Request.Builder(text).build(); + TextLanguage textLanguage = detectLanguage(request); + int localeHypothesisCount = textLanguage.getLocaleHypothesisCount(); + List<String> languageTags = new ArrayList<>(); + // TODO: Reconsider this and probably make the score threshold configurable. + for (int i = 0; i < localeHypothesisCount; i++) { + ULocale locale = textLanguage.getLocale(i); + if (textLanguage.getConfidenceScore(locale) < 0.5) { + break; + } + languageTags.add(locale.toLanguageTag()); + } + if (languageTags.isEmpty()) { + return LocaleList.getDefault().toLanguageTags(); + } + return String.join(",", languageTags); + } + private Collection<String> resolveActionTypesFromRequest(ConversationActions.Request request) { List<String> defaultActionTypes = request.getHints().contains(ConversationActions.HINT_FOR_NOTIFICATION) @@ -777,6 +798,9 @@ public final class TextClassifierImpl implements TextClassifier { if (foreignText) { insertTranslateAction(actions, context, text); } + actions.forEach( + action -> action.getIntent() + .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true)); return actions; } diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java index 3842f6659704..4da339165655 100644 --- a/core/java/com/android/internal/app/procstats/AssociationState.java +++ b/core/java/com/android/internal/app/procstats/AssociationState.java @@ -445,8 +445,18 @@ public final class AssociationState { } } + public boolean hasProcess(String procName) { + final int NSRC = mSources.size(); + for (int isrc = 0; isrc < NSRC; isrc++) { + if (mSources.keyAt(isrc).mProcess.equals(procName)) { + return true; + } + } + return false; + } + public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix, - long now, long totalTime, boolean dumpDetails, boolean dumpAll) { + long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll) { if (dumpAll) { pw.print(prefix); pw.print("mNumActive="); @@ -456,6 +466,9 @@ public final class AssociationState { for (int isrc = 0; isrc < NSRC; isrc++) { final SourceKey key = mSources.keyAt(isrc); final SourceState src = mSources.valueAt(isrc); + if (reqPackage != null && !reqPackage.equals(key.mProcess)) { + continue; + } pw.print(prefixInner); pw.print("<- "); pw.print(key.mProcess); diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 19d8a836fc4c..9ee583a97b8d 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -1396,10 +1396,10 @@ public final class ProcessStats implements Parcelable { return as; } - // See b/118826162 -- to avoid logspaming, we rate limit the WTF. - private static final long INVERSE_PROC_STATE_WTF_MIN_INTERVAL_MS = 10_000L; - private long mNextInverseProcStateWtfUptime; - private int mSkippedInverseProcStateWtfCount; + // See b/118826162 -- to avoid logspaming, we rate limit the warnings. + private static final long INVERSE_PROC_STATE_WARNING_MIN_INTERVAL_MS = 10_000L; + private long mNextInverseProcStateWarningUptime; + private int mSkippedInverseProcStateWarningCount; public void updateTrackingAssociationsLocked(int curSeq, long now) { final int NUM = mTrackingAssociations.size(); @@ -1423,18 +1423,19 @@ public final class ProcessStats implements Parcelable { act.stopActive(now); if (act.mProcState < procState) { final long nowUptime = SystemClock.uptimeMillis(); - if (mNextInverseProcStateWtfUptime > nowUptime) { - mSkippedInverseProcStateWtfCount++; + if (mNextInverseProcStateWarningUptime > nowUptime) { + mSkippedInverseProcStateWarningCount++; } else { // TODO We still see it during boot related to GMS-core. // b/118826162 - Slog.wtf(TAG, "Tracking association " + act + " whose proc state " + Slog.w(TAG, "Tracking association " + act + " whose proc state " + act.mProcState + " is better than process " + proc + " proc state " + procState - + " (" + mSkippedInverseProcStateWtfCount + " skipped)"); - mSkippedInverseProcStateWtfCount = 0; - mNextInverseProcStateWtfUptime = - nowUptime + INVERSE_PROC_STATE_WTF_MIN_INTERVAL_MS; + + " (" + mSkippedInverseProcStateWarningCount + + " skipped)"); + mSkippedInverseProcStateWarningCount = 0; + mNextInverseProcStateWarningUptime = + nowUptime + INVERSE_PROC_STATE_WARNING_MIN_INTERVAL_MS; } } } @@ -1474,6 +1475,7 @@ public final class ProcessStats implements Parcelable { final int NSRVS = pkgState.mServices.size(); final int NASCS = pkgState.mAssociations.size(); final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + boolean onlyAssociations = false; if (!pkgMatch) { boolean procMatch = false; for (int iproc = 0; iproc < NPROCS; iproc++) { @@ -1484,7 +1486,18 @@ public final class ProcessStats implements Parcelable { } } if (!procMatch) { - continue; + // Check if this app has any associations with the requested + // package, so that if so we print those. + for (int iasc = 0; iasc < NASCS; iasc++) { + AssociationState asc = pkgState.mAssociations.valueAt(iasc); + if (asc.hasProcess(reqPackage)) { + onlyAssociations = true; + break; + } + } + if (!onlyAssociations) { + continue; + } } } if (NPROCS > 0 || NSRVS > 0 || NASCS > 0) { @@ -1502,7 +1515,7 @@ public final class ProcessStats implements Parcelable { pw.print(vers); pw.println(":"); } - if ((section & REPORT_PKG_PROC_STATS) != 0) { + if ((section & REPORT_PKG_PROC_STATS) != 0 && !onlyAssociations) { if (!dumpSummary || dumpAll) { for (int iproc = 0; iproc < NPROCS; iproc++) { ProcessState proc = pkgState.mProcesses.valueAt(iproc); @@ -1549,7 +1562,7 @@ public final class ProcessStats implements Parcelable { now, totalTime); } } - if ((section & REPORT_PKG_SVC_STATS) != 0) { + if ((section & REPORT_PKG_SVC_STATS) != 0 && !onlyAssociations) { for (int isvc = 0; isvc < NSRVS; isvc++) { ServiceState svc = pkgState.mServices.valueAt(isvc); if (!pkgMatch && !reqPackage.equals(svc.getProcessName())) { @@ -1578,7 +1591,9 @@ public final class ProcessStats implements Parcelable { for (int iasc = 0; iasc < NASCS; iasc++) { AssociationState asc = pkgState.mAssociations.valueAt(iasc); if (!pkgMatch && !reqPackage.equals(asc.getProcessName())) { - continue; + if (!onlyAssociations || !asc.hasProcess(reqPackage)) { + continue; + } } if (activeOnly && !asc.isInUse()) { pw.print(" (Not active association: "); @@ -1596,7 +1611,8 @@ public final class ProcessStats implements Parcelable { pw.print(" Process: "); pw.println(asc.getProcessName()); asc.dumpStats(pw, " ", " ", " ", - now, totalTime, dumpDetails, dumpAll); + now, totalTime, onlyAssociations ? reqPackage : null, + dumpDetails, dumpAll); } } } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 65213c0a1085..65b9fad97d89 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -81,6 +81,11 @@ public final class Zygote { public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ; /** Read-write external storage should be mounted. */ public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE; + /** + * Mount mode for package installers which should give them access to + * all obb dirs in addition to their package sandboxes + */ + public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER; /** Read-write external storage should be mounted instead of package sandbox */ public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL; diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 4a94ec4a4071..f182c4d447df 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -656,7 +656,9 @@ class ZygoteConnection { mountExternal = Zygote.MOUNT_EXTERNAL_WRITE; } else if (arg.equals("--mount-external-full")) { mountExternal = Zygote.MOUNT_EXTERNAL_FULL; - } else if (arg.equals("--query-abi-list")) { + } else if (arg.equals("--mount-external-installer")) { + mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER; + } else if (arg.equals("--query-abi-list")) { abiListQuery = true; } else if (arg.equals("--get-pid")) { pidQuery = true; diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index b00e6fdd5e9a..841e5b679f5f 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -25,6 +25,7 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.Environment; import android.os.Process; +import android.os.SystemProperties; import android.os.storage.StorageManager; import android.permission.PermissionManager.SplitPermissionInfo; import android.text.TextUtils; @@ -66,8 +67,12 @@ public class SystemConfig { private static final int ALLOW_PRIVAPP_PERMISSIONS = 0x10; private static final int ALLOW_OEM_PERMISSIONS = 0x20; private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x40; + private static final int ALLOW_ASSOCIATIONS = 0x80; private static final int ALLOW_ALL = ~0; + // property for runtime configuration differentiation + private static final String SKU_PROPERTY = "ro.boot.product.hardware.sku"; + // Group-ids that are given to all packages as read from etc/permissions/*.xml. int[] mGlobalGids; @@ -191,6 +196,12 @@ public class SystemConfig { final ArrayMap<String, ArrayMap<String, Boolean>> mOemPermissions = new ArrayMap<>(); + // Allowed associations between applications. If there are any entries + // for an app, those are the only associations allowed; otherwise, all associations + // are allowed. Allowing an association from app A to app B means app A can not + // associate with any other apps, but does not limit what apps B can associate with. + final ArrayMap<String, ArraySet<String>> mAllowedAssociations = new ArrayMap<>(); + public static SystemConfig getInstance() { synchronized (SystemConfig.class) { if (sInstance == null) { @@ -316,6 +327,10 @@ public class SystemConfig { return Collections.emptyMap(); } + public ArrayMap<String, ArraySet<String>> getAllowedAssociations() { + return mAllowedAssociations; + } + SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( @@ -325,8 +340,9 @@ public class SystemConfig { readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL); - // Vendors are only allowed to customze libs, features and privapp permissions - int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS; + // Vendors are only allowed to customize these + int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS + | ALLOW_ASSOCIATIONS; if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) { // For backward compatibility vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS); @@ -344,8 +360,19 @@ public class SystemConfig { readPermissions(Environment.buildPath( Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag); - // Allow OEM to customize features and OEM permissions - int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS; + String skuProperty = SystemProperties.get(SKU_PROPERTY, ""); + if (!skuProperty.isEmpty()) { + String skuDir = "sku_" + skuProperty; + + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "sysconfig", skuDir), odmPermissionFlag); + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "permissions", skuDir), + odmPermissionFlag); + } + + // Allow OEM to customize these + int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS | ALLOW_ASSOCIATIONS; readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag); readPermissions(Environment.buildPath( @@ -380,6 +407,10 @@ public class SystemConfig { // Iterate over the files in the directory and scan .xml files File platformFile = null; for (File f : libraryDir.listFiles()) { + if (!f.isFile()) { + continue; + } + // We'll read platform.xml last if (f.getPath().endsWith("etc/permissions/platform.xml")) { platformFile = f; @@ -404,6 +435,11 @@ public class SystemConfig { } } + private void logNotAllowedInPartition(String name, File permFile, XmlPullParser parser) { + Slog.w(TAG, "<" + name + "> not allowed in partition of " + + permFile + " at " + parser.getPositionDescription()); + } + private void readPermissionsFromXml(File permFile, int permissionFlag) { FileReader permReader = null; try { @@ -434,14 +470,17 @@ public class SystemConfig { + ": found " + parser.getName() + ", expected 'permissions' or 'config'"); } - boolean allowAll = permissionFlag == ALLOW_ALL; - boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0; - boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0; - boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0; - boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0; - boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) != 0; - boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0; - boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) != 0; + final boolean allowAll = permissionFlag == ALLOW_ALL; + final boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0; + final boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0; + final boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0; + final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0; + final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) + != 0; + final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0; + final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) + != 0; + final boolean allowAssociations = (permissionFlag & ALLOW_ASSOCIATIONS) != 0; while (true) { XmlUtils.nextElement(parser); if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -449,297 +488,425 @@ public class SystemConfig { } String name = parser.getName(); - if ("group".equals(name) && allowAll) { - String gidStr = parser.getAttributeValue(null, "gid"); - if (gidStr != null) { - int gid = android.os.Process.getGidForName(gidStr); - mGlobalGids = appendInt(mGlobalGids, gid); - } else { - Slog.w(TAG, "<group> without gid in " + permFile + " at " - + parser.getPositionDescription()); - } - + if (name == null) { XmlUtils.skipCurrentTag(parser); continue; - } else if ("permission".equals(name) && allowPermissions) { - String perm = parser.getAttributeValue(null, "name"); - if (perm == null) { - Slog.w(TAG, "<permission> without name in " + permFile + " at " - + parser.getPositionDescription()); + } + switch (name) { + case "group": { + if (allowAll) { + String gidStr = parser.getAttributeValue(null, "gid"); + if (gidStr != null) { + int gid = android.os.Process.getGidForName(gidStr); + mGlobalGids = appendInt(mGlobalGids, gid); + } else { + Slog.w(TAG, "<" + name + "> without gid in " + permFile + " at " + + parser.getPositionDescription()); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } XmlUtils.skipCurrentTag(parser); - continue; - } - perm = perm.intern(); - readPermission(parser, perm); - - } else if ("assign-permission".equals(name) && allowPermissions) { - String perm = parser.getAttributeValue(null, "name"); - if (perm == null) { - Slog.w(TAG, "<assign-permission> without name in " + permFile + " at " - + parser.getPositionDescription()); + } break; + case "permission": { + if (allowPermissions) { + String perm = parser.getAttributeValue(null, "name"); + if (perm == null) { + Slog.w(TAG, "<" + name + "> without name in " + permFile + " at " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + break; + } + perm = perm.intern(); + readPermission(parser, perm); + } else { + logNotAllowedInPartition(name, permFile, parser); + XmlUtils.skipCurrentTag(parser); + } + } break; + case "assign-permission": { + if (allowPermissions) { + String perm = parser.getAttributeValue(null, "name"); + if (perm == null) { + Slog.w(TAG, "<" + name + "> without name in " + permFile + + " at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + break; + } + String uidStr = parser.getAttributeValue(null, "uid"); + if (uidStr == null) { + Slog.w(TAG, "<" + name + "> without uid in " + permFile + + " at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + break; + } + int uid = Process.getUidForName(uidStr); + if (uid < 0) { + Slog.w(TAG, "<" + name + "> with unknown uid \"" + + uidStr + " in " + permFile + " at " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + break; + } + perm = perm.intern(); + ArraySet<String> perms = mSystemPermissions.get(uid); + if (perms == null) { + perms = new ArraySet<String>(); + mSystemPermissions.put(uid, perms); + } + perms.add(perm); + } else { + logNotAllowedInPartition(name, permFile, parser); + } XmlUtils.skipCurrentTag(parser); - continue; - } - String uidStr = parser.getAttributeValue(null, "uid"); - if (uidStr == null) { - Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at " - + parser.getPositionDescription()); + } break; + case "split-permission": { + if (allowPermissions) { + readSplitPermission(parser, permFile); + } else { + logNotAllowedInPartition(name, permFile, parser); + XmlUtils.skipCurrentTag(parser); + } + } break; + case "library": { + if (allowLibs) { + String lname = parser.getAttributeValue(null, "name"); + String lfile = parser.getAttributeValue(null, "file"); + String ldependency = parser.getAttributeValue(null, "dependency"); + if (lname == null) { + Slog.w(TAG, "<" + name + "> without name in " + permFile + " at " + + parser.getPositionDescription()); + } else if (lfile == null) { + Slog.w(TAG, "<" + name + "> without file in " + permFile + " at " + + parser.getPositionDescription()); + } else { + //Log.i(TAG, "Got library " + lname + " in " + lfile); + SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile, + ldependency == null ? new String[0] : ldependency.split(":")); + mSharedLibraries.put(lname, entry); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } XmlUtils.skipCurrentTag(parser); - continue; - } - int uid = Process.getUidForName(uidStr); - if (uid < 0) { - Slog.w(TAG, "<assign-permission> with unknown uid \"" - + uidStr + " in " + permFile + " at " - + parser.getPositionDescription()); + } break; + case "feature": { + if (allowFeatures) { + String fname = parser.getAttributeValue(null, "name"); + int fversion = XmlUtils.readIntAttribute(parser, "version", 0); + boolean allowed; + if (!lowRam) { + allowed = true; + } else { + String notLowRam = parser.getAttributeValue(null, "notLowRam"); + allowed = !"true".equals(notLowRam); + } + if (fname == null) { + Slog.w(TAG, "<" + name + "> without name in " + permFile + " at " + + parser.getPositionDescription()); + } else if (allowed) { + addFeature(fname, fversion); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } XmlUtils.skipCurrentTag(parser); - continue; - } - perm = perm.intern(); - ArraySet<String> perms = mSystemPermissions.get(uid); - if (perms == null) { - perms = new ArraySet<String>(); - mSystemPermissions.put(uid, perms); - } - perms.add(perm); - XmlUtils.skipCurrentTag(parser); - - } else if ("split-permission".equals(name) && allowPermissions) { - readSplitPermission(parser, permFile); - } else if ("library".equals(name) && allowLibs) { - String lname = parser.getAttributeValue(null, "name"); - String lfile = parser.getAttributeValue(null, "file"); - String ldependency = parser.getAttributeValue(null, "dependency"); - if (lname == null) { - Slog.w(TAG, "<library> without name in " + permFile + " at " - + parser.getPositionDescription()); - } else if (lfile == null) { - Slog.w(TAG, "<library> without file in " + permFile + " at " - + parser.getPositionDescription()); - } else { - //Log.i(TAG, "Got library " + lname + " in " + lfile); - SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile, - ldependency == null ? new String[0] : ldependency.split(":")); - mSharedLibraries.put(lname, entry); - } - XmlUtils.skipCurrentTag(parser); - continue; - } else if ("feature".equals(name) && allowFeatures) { - String fname = parser.getAttributeValue(null, "name"); - int fversion = XmlUtils.readIntAttribute(parser, "version", 0); - boolean allowed; - if (!lowRam) { - allowed = true; - } else { - String notLowRam = parser.getAttributeValue(null, "notLowRam"); - allowed = !"true".equals(notLowRam); - } - if (fname == null) { - Slog.w(TAG, "<feature> without name in " + permFile + " at " - + parser.getPositionDescription()); - } else if (allowed) { - addFeature(fname, fversion); - } - XmlUtils.skipCurrentTag(parser); - continue; - - } else if ("unavailable-feature".equals(name) && allowFeatures) { - String fname = parser.getAttributeValue(null, "name"); - if (fname == null) { - Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at " - + parser.getPositionDescription()); - } else { - mUnavailableFeatures.add(fname); - } - XmlUtils.skipCurrentTag(parser); - continue; - - } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<allow-in-power-save-except-idle> without package in " - + permFile + " at " + parser.getPositionDescription()); - } else { - mAllowInPowerSaveExceptIdle.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - continue; - - } else if ("allow-in-power-save".equals(name) && allowAll) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at " - + parser.getPositionDescription()); - } else { - mAllowInPowerSave.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - continue; - - } else if ("allow-in-data-usage-save".equals(name) && allowAll) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<allow-in-data-usage-save> without package in " + permFile - + " at " + parser.getPositionDescription()); - } else { - mAllowInDataUsageSave.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - continue; - - } else if ("allow-unthrottled-location".equals(name) && allowAll) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<allow-unthrottled-location> without package in " - + permFile + " at " + parser.getPositionDescription()); - } else { - mAllowUnthrottledLocation.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - continue; - - } else if ("allow-implicit-broadcast".equals(name) && allowAll) { - String action = parser.getAttributeValue(null, "action"); - if (action == null) { - Slog.w(TAG, "<allow-implicit-broadcast> without action in " + permFile - + " at " + parser.getPositionDescription()); - } else { - mAllowImplicitBroadcasts.add(action); - } - XmlUtils.skipCurrentTag(parser); - continue; - - } else if ("app-link".equals(name) && allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<app-link> without package in " + permFile + " at " - + parser.getPositionDescription()); - } else { - mLinkedApps.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile - + " at " + parser.getPositionDescription()); - } else { - mSystemUserWhitelistedApps.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile - + " at " + parser.getPositionDescription()); - } else { - mSystemUserBlacklistedApps.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - String clsname = parser.getAttributeValue(null, "class"); - if (pkgname == null) { - Slog.w(TAG, "<default-enabled-vr-app without package in " + permFile - + " at " + parser.getPositionDescription()); - } else if (clsname == null) { - Slog.w(TAG, "<default-enabled-vr-app without class in " + permFile - + " at " + parser.getPositionDescription()); - } else { - mDefaultVrComponents.add(new ComponentName(pkgname, clsname)); - } - XmlUtils.skipCurrentTag(parser); - } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) { - String serviceName = parser.getAttributeValue(null, "service"); - if (serviceName == null) { - Slog.w(TAG, "<backup-transport-whitelisted-service> without service in " - + permFile + " at " + parser.getPositionDescription()); - } else { - ComponentName cn = ComponentName.unflattenFromString(serviceName); - if (cn == null) { - Slog.w(TAG, - "<backup-transport-whitelisted-service> with invalid service name " - + serviceName + " in "+ permFile - + " at " + parser.getPositionDescription()); + } break; + case "unavailable-feature": { + if (allowFeatures) { + String fname = parser.getAttributeValue(null, "name"); + if (fname == null) { + Slog.w(TAG, "<" + name + "> without name in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mUnavailableFeatures.add(fname); + } } else { - mBackupTransportWhitelist.add(cn); + logNotAllowedInPartition(name, permFile, parser); } - } - XmlUtils.skipCurrentTag(parser); - } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name) - && allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - String carrierPkgname = parser.getAttributeValue(null, "carrierAppPackage"); - if (pkgname == null || carrierPkgname == null) { - Slog.w(TAG, "<disabled-until-used-preinstalled-carrier-associated-app" - + " without package or carrierAppPackage in " + permFile + " at " - + parser.getPositionDescription()); - } else { - List<String> associatedPkgs = - mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get( - carrierPkgname); - if (associatedPkgs == null) { - associatedPkgs = new ArrayList<>(); - mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put( - carrierPkgname, associatedPkgs); + XmlUtils.skipCurrentTag(parser); + } break; + case "allow-in-power-save-except-idle": { + if (allowAll) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowInPowerSaveExceptIdle.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); } - associatedPkgs.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - } else if ("disabled-until-used-preinstalled-carrier-app".equals(name) - && allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, - "<disabled-until-used-preinstalled-carrier-app> without " - + "package in " + permFile + " at " - + parser.getPositionDescription()); - } else { - mDisabledUntilUsedPreinstalledCarrierApps.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - } else if ("privapp-permissions".equals(name) && allowPrivappPermissions) { - // privapp permissions from system, vendor, product and product_services - // partitions are stored separately. This is to prevent xml files in the vendor - // partition from granting permissions to priv apps in the system partition and - // vice versa. - boolean vendor = permFile.toPath().startsWith( - Environment.getVendorDirectory().toPath() + "/") - || permFile.toPath().startsWith( - Environment.getOdmDirectory().toPath() + "/"); - boolean product = permFile.toPath().startsWith( - Environment.getProductDirectory().toPath() + "/"); - boolean productServices = permFile.toPath().startsWith( - Environment.getProductServicesDirectory().toPath() + "/"); - if (vendor) { - readPrivAppPermissions(parser, mVendorPrivAppPermissions, - mVendorPrivAppDenyPermissions); - } else if (product) { - readPrivAppPermissions(parser, mProductPrivAppPermissions, - mProductPrivAppDenyPermissions); - } else if (productServices) { - readPrivAppPermissions(parser, mProductServicesPrivAppPermissions, - mProductServicesPrivAppDenyPermissions); - } else { - readPrivAppPermissions(parser, mPrivAppPermissions, - mPrivAppDenyPermissions); - } - } else if ("oem-permissions".equals(name) && allowOemPermissions) { - readOemPermissions(parser); - } else if ("hidden-api-whitelisted-app".equals(name) && allowApiWhitelisting) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<hidden-api-whitelisted-app> without package in " + permFile - + " at " + parser.getPositionDescription()); - } else { - mHiddenApiPackageWhitelist.add(pkgname); - } - XmlUtils.skipCurrentTag(parser); - } else { - Slog.w(TAG, "Tag " + name + " is unknown or not allowed in " - + permFile.getParent()); - XmlUtils.skipCurrentTag(parser); - continue; + XmlUtils.skipCurrentTag(parser); + } break; + case "allow-in-power-save": { + if (allowAll) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowInPowerSave.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "allow-in-data-usage-save": { + if (allowAll) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowInDataUsageSave.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "allow-unthrottled-location": { + if (allowAll) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowUnthrottledLocation.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "allow-implicit-broadcast": { + if (allowAll) { + String action = parser.getAttributeValue(null, "action"); + if (action == null) { + Slog.w(TAG, "<" + name + "> without action in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowImplicitBroadcasts.add(action); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "app-link": { + if (allowAppConfigs) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mLinkedApps.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "system-user-whitelisted-app": { + if (allowAppConfigs) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mSystemUserWhitelistedApps.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "system-user-blacklisted-app": { + if (allowAppConfigs) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mSystemUserBlacklistedApps.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "default-enabled-vr-app": { + if (allowAppConfigs) { + String pkgname = parser.getAttributeValue(null, "package"); + String clsname = parser.getAttributeValue(null, "class"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else if (clsname == null) { + Slog.w(TAG, "<" + name + "> without class in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mDefaultVrComponents.add(new ComponentName(pkgname, clsname)); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "backup-transport-whitelisted-service": { + if (allowFeatures) { + String serviceName = parser.getAttributeValue(null, "service"); + if (serviceName == null) { + Slog.w(TAG, "<" + name + "> without service in " + + permFile + " at " + parser.getPositionDescription()); + } else { + ComponentName cn = ComponentName.unflattenFromString(serviceName); + if (cn == null) { + Slog.w(TAG, "<" + name + "> with invalid service name " + + serviceName + " in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mBackupTransportWhitelist.add(cn); + } + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "disabled-until-used-preinstalled-carrier-associated-app": { + if (allowAppConfigs) { + String pkgname = parser.getAttributeValue(null, "package"); + String carrierPkgname = parser.getAttributeValue(null, + "carrierAppPackage"); + if (pkgname == null || carrierPkgname == null) { + Slog.w(TAG, "<" + name + + "> without package or carrierAppPackage in " + permFile + + " at " + parser.getPositionDescription()); + } else { + List<String> associatedPkgs = + mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get( + carrierPkgname); + if (associatedPkgs == null) { + associatedPkgs = new ArrayList<>(); + mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put( + carrierPkgname, associatedPkgs); + } + associatedPkgs.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "disabled-until-used-preinstalled-carrier-app": { + if (allowAppConfigs) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, + "<" + name + "> without " + + "package in " + permFile + " at " + + parser.getPositionDescription()); + } else { + mDisabledUntilUsedPreinstalledCarrierApps.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "privapp-permissions": { + if (allowPrivappPermissions) { + // privapp permissions from system, vendor, product and product_services + // partitions are stored separately. This is to prevent xml files in + // the vendor partition from granting permissions to priv apps in the + // system partition and vice versa. + boolean vendor = permFile.toPath().startsWith( + Environment.getVendorDirectory().toPath() + "/") + || permFile.toPath().startsWith( + Environment.getOdmDirectory().toPath() + "/"); + boolean product = permFile.toPath().startsWith( + Environment.getProductDirectory().toPath() + "/"); + boolean productServices = permFile.toPath().startsWith( + Environment.getProductServicesDirectory().toPath() + "/"); + if (vendor) { + readPrivAppPermissions(parser, mVendorPrivAppPermissions, + mVendorPrivAppDenyPermissions); + } else if (product) { + readPrivAppPermissions(parser, mProductPrivAppPermissions, + mProductPrivAppDenyPermissions); + } else if (productServices) { + readPrivAppPermissions(parser, mProductServicesPrivAppPermissions, + mProductServicesPrivAppDenyPermissions); + } else { + readPrivAppPermissions(parser, mPrivAppPermissions, + mPrivAppDenyPermissions); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + XmlUtils.skipCurrentTag(parser); + } + } break; + case "oem-permissions": { + if (allowOemPermissions) { + readOemPermissions(parser); + } else { + logNotAllowedInPartition(name, permFile, parser); + XmlUtils.skipCurrentTag(parser); + } + } break; + case "hidden-api-whitelisted-app": { + if (allowApiWhitelisting) { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mHiddenApiPackageWhitelist.add(pkgname); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + case "allow-association": { + if (allowAssociations) { + String target = parser.getAttributeValue(null, "target"); + if (target == null) { + Slog.w(TAG, "<" + name + "> without target in " + permFile + + " at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + break; + } + String allowed = parser.getAttributeValue(null, "allowed"); + if (allowed == null) { + Slog.w(TAG, "<" + name + "> without allowed in " + permFile + + " at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + break; + } + target = target.intern(); + allowed = allowed.intern(); + ArraySet<String> associations = mAllowedAssociations.get(target); + if (associations == null) { + associations = new ArraySet<>(); + mAllowedAssociations.put(target, associations); + } + Slog.i(TAG, "Adding association: " + target + " <- " + allowed); + associations.add(allowed); + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; + default: { + Slog.w(TAG, "Tag " + name + " is unknown in " + + permFile + " at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + } break; } } } catch (XmlPullParserException e) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 8962e1d8c727..43f8d00bb5f1 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -161,6 +161,7 @@ cc_library_shared { "android/graphics/pdf/PdfUtils.cpp", "android/graphics/text/LineBreaker.cpp", "android/graphics/text/MeasuredText.cpp", + "android_media_AudioEffectDescriptor.cpp", "android_media_AudioRecord.cpp", "android_media_AudioSystem.cpp", "android_media_AudioTrack.cpp", @@ -263,6 +264,7 @@ cc_library_shared { "libEGL", "libGLESv1_CM", "libGLESv2", + "libGLESv3", "libvulkan", "libziparchive", "libETC1", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f9879ccc18c9..687b1055c3d6 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -103,6 +103,7 @@ extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env); extern int register_android_hardware_UsbRequest(JNIEnv *env); extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env); +extern int register_android_media_AudioEffectDescriptor(JNIEnv *env); extern int register_android_media_AudioRecord(JNIEnv *env); extern int register_android_media_AudioSystem(JNIEnv *env); extern int register_android_media_AudioTrack(JNIEnv *env); @@ -1456,6 +1457,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_hardware_UsbDeviceConnection), REG_JNI(register_android_hardware_UsbRequest), REG_JNI(register_android_hardware_location_ActivityRecognitionHardware), + REG_JNI(register_android_media_AudioEffectDescriptor), REG_JNI(register_android_media_AudioSystem), REG_JNI(register_android_media_AudioRecord), REG_JNI(register_android_media_AudioTrack), diff --git a/core/jni/android_media_AudioEffectDescriptor.cpp b/core/jni/android_media_AudioEffectDescriptor.cpp new file mode 100644 index 000000000000..5175a05c4c3b --- /dev/null +++ b/core/jni/android_media_AudioEffectDescriptor.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core_jni_helpers.h" +#include "android_media_AudioErrors.h" +#include "media/AudioEffect.h" + +using namespace android; + +static jclass gAudioEffectDescriptorClass; +static jmethodID gAudioEffectDescriptorCstor; + +namespace android { + +jclass audioEffectDescriptorClass() { + return gAudioEffectDescriptorClass; +} + +jint convertAudioEffectDescriptorFromNative(JNIEnv* env, jobject* jDescriptor, + const effect_descriptor_t* nDescriptor) +{ + jstring jType; + jstring jUuid; + jstring jConnect; + jstring jName; + jstring jImplementor; + char str[EFFECT_STRING_LEN_MAX]; + + if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK) + == EFFECT_FLAG_TYPE_AUXILIARY) { + jConnect = env->NewStringUTF("Auxiliary"); + } else if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK) + == EFFECT_FLAG_TYPE_INSERT) { + jConnect = env->NewStringUTF("Insert"); + } else if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK) + == EFFECT_FLAG_TYPE_PRE_PROC) { + jConnect = env->NewStringUTF("Pre Processing"); + } else { + return (jint) AUDIO_JAVA_BAD_VALUE; + } + + AudioEffect::guidToString(&nDescriptor->type, str, EFFECT_STRING_LEN_MAX); + jType = env->NewStringUTF(str); + + AudioEffect::guidToString(&nDescriptor->uuid, str, EFFECT_STRING_LEN_MAX); + jUuid = env->NewStringUTF(str); + + jName = env->NewStringUTF(nDescriptor->name); + jImplementor = env->NewStringUTF(nDescriptor->implementor); + + *jDescriptor = env->NewObject(gAudioEffectDescriptorClass, + gAudioEffectDescriptorCstor, + jType, + jUuid, + jConnect, + jName, + jImplementor); + env->DeleteLocalRef(jType); + env->DeleteLocalRef(jUuid); + env->DeleteLocalRef(jConnect); + env->DeleteLocalRef(jName); + env->DeleteLocalRef(jImplementor); + + return (jint) AUDIO_JAVA_SUCCESS; +} + +void convertAudioEffectDescriptorVectorFromNative(JNIEnv *env, jobjectArray *jDescriptors, + const std::vector<effect_descriptor_t>& nDescriptors) +{ + jobjectArray temp = env->NewObjectArray(nDescriptors.size(), + audioEffectDescriptorClass(), NULL); + size_t actualSize = 0; + for (size_t i = 0; i < nDescriptors.size(); i++) { + jobject jdesc; + if (convertAudioEffectDescriptorFromNative(env, + &jdesc, + &nDescriptors[i]) + != AUDIO_JAVA_SUCCESS) { + continue; + } + + env->SetObjectArrayElement(temp, actualSize++, jdesc); + env->DeleteLocalRef(jdesc); + } + + *jDescriptors = env->NewObjectArray(actualSize, audioEffectDescriptorClass(), NULL); + for (size_t i = 0; i < actualSize; i++) { + env->SetObjectArrayElement(*jDescriptors, + i, + env->GetObjectArrayElement(temp, i)); + } + env->DeleteLocalRef(temp); +} + +}; // namespace android + +int register_android_media_AudioEffectDescriptor(JNIEnv* env) { + jclass audioEffectDescriptorClass = + FindClassOrDie(env, "android/media/audiofx/AudioEffect$Descriptor"); + gAudioEffectDescriptorClass = + MakeGlobalRefOrDie(env, audioEffectDescriptorClass); + gAudioEffectDescriptorCstor = + GetMethodIDOrDie(env, + audioEffectDescriptorClass, + "<init>", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + env->DeleteLocalRef(audioEffectDescriptorClass); + return 0; +} diff --git a/core/jni/android_media_AudioEffectDescriptor.h b/core/jni/android_media_AudioEffectDescriptor.h new file mode 100644 index 000000000000..d07188c8bdb1 --- /dev/null +++ b/core/jni/android_media_AudioEffectDescriptor.h @@ -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. + */ + +#ifndef ANDROID_MEDIA_AUDIOEFFECT_DESCRIPTOR_H +#define ANDROID_MEDIA_AUDIOEFFECT_DESCRIPTOR_H + +#include <system/audio.h> +#include <system/audio_effect.h> + +#include "jni.h" + +namespace android { + +// Conversion from C effect_descriptor_t to Java AudioEffect.Descriptor object + +extern jclass audioEffectDescriptorClass(); + +extern jint convertAudioEffectDescriptorFromNative(JNIEnv *env, jobject *jDescriptor, + const effect_descriptor_t *nDescriptor); + +extern void convertAudioEffectDescriptorVectorFromNative(JNIEnv *env, jobjectArray *jDescriptors, + const std::vector<effect_descriptor_t>& nDescriptors); +} // namespace android + +#endif
\ No newline at end of file diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 4f8bbc1396c8..3329e2047085 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -919,7 +919,7 @@ static jlong android_os_Binder_clearCallingWorkSource() return IPCThreadState::self()->clearCallingWorkSource(); } -static void android_os_Binder_restoreCallingWorkSource(long token) +static void android_os_Binder_restoreCallingWorkSource(jlong token) { IPCThreadState::self()->restoreCallingWorkSource(token); } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 377e65c33dd0..102a0b7b8957 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -1137,7 +1137,7 @@ static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid UniqueFile file = MakeUniqueFile(status_path.c_str(), "re"); char line[256]; - while (fgets(line, sizeof(line), file.get())) { + while (file != nullptr && fgets(line, sizeof(line), file.get())) { jlong v; if ( sscanf(line, "VmRSS: %" SCNd64 " kB", &v) == 1) { rss[0] = v; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ea6e0178bd9c..c745c160e143 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -37,6 +37,7 @@ #include <stdio.h> #include <system/graphics.h> #include <ui/DisplayInfo.h> +#include <ui/DisplayedFrameStats.h> #include <ui/FrameStats.h> #include <ui/GraphicTypes.h> #include <ui/HdrCapabilities.h> @@ -97,6 +98,16 @@ static struct { jmethodID builder; } gGraphicBufferClassInfo; +static struct { + jclass clazz; + jmethodID ctor; +} gDisplayedContentSampleClassInfo; + +static struct { + jclass clazz; + jmethodID ctor; +} gDisplayedContentSamplingAttributesClassInfo; + // ---------------------------------------------------------------------------- static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) { @@ -398,6 +409,73 @@ static jobject nativeGetBuiltInDisplay(JNIEnv* env, jclass clazz, jint id) { return javaObjectForIBinder(env, token); } +static jobject nativeGetDisplayedContentSamplingAttributes(JNIEnv* env, jclass clazz, + jobject tokenObj) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + + ui::PixelFormat format; + ui::Dataspace dataspace; + uint8_t componentMask; + status_t err = SurfaceComposerClient::getDisplayedContentSamplingAttributes( + token, &format, &dataspace, &componentMask); + if (err != OK) { + return nullptr; + } + return env->NewObject(gDisplayedContentSamplingAttributesClassInfo.clazz, + gDisplayedContentSamplingAttributesClassInfo.ctor, + format, dataspace, componentMask); +} + +static jboolean nativeSetDisplayedContentSamplingEnabled(JNIEnv* env, jclass clazz, + jobject tokenObj, jboolean enable, jint componentMask, jint maxFrames) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + return SurfaceComposerClient::setDisplayContentSamplingEnabled( + token, enable, componentMask, maxFrames); +} + +static jobject nativeGetDisplayedContentSample(JNIEnv* env, jclass clazz, jobject tokenObj, + jlong maxFrames, jlong timestamp) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + + DisplayedFrameStats stats; + status_t err = SurfaceComposerClient::getDisplayedContentSample( + token, maxFrames, timestamp, &stats); + if (err != OK) { + return nullptr; + } + + jlongArray histogramComponent0 = env->NewLongArray(stats.component_0_sample.size()); + jlongArray histogramComponent1 = env->NewLongArray(stats.component_1_sample.size()); + jlongArray histogramComponent2 = env->NewLongArray(stats.component_2_sample.size()); + jlongArray histogramComponent3 = env->NewLongArray(stats.component_3_sample.size()); + if ((histogramComponent0 == nullptr) || + (histogramComponent1 == nullptr) || + (histogramComponent2 == nullptr) || + (histogramComponent3 == nullptr)) { + return JNI_FALSE; + } + + env->SetLongArrayRegion(histogramComponent0, 0, + stats.component_0_sample.size(), + reinterpret_cast<jlong*>(stats.component_0_sample.data())); + env->SetLongArrayRegion(histogramComponent1, 0, + stats.component_1_sample.size(), + reinterpret_cast<jlong*>(stats.component_1_sample.data())); + env->SetLongArrayRegion(histogramComponent2, 0, + stats.component_2_sample.size(), + reinterpret_cast<jlong*>(stats.component_2_sample.data())); + env->SetLongArrayRegion(histogramComponent3, 0, + stats.component_3_sample.size(), + reinterpret_cast<jlong*>(stats.component_3_sample.data())); + return env->NewObject(gDisplayedContentSampleClassInfo.clazz, + gDisplayedContentSampleClassInfo.ctor, + stats.numFrames, + histogramComponent0, + histogramComponent1, + histogramComponent2, + histogramComponent3); +} + static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure) { ScopedUtfChars name(env, nameObj); @@ -955,6 +1033,14 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeCaptureLayers }, {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V", (void*)nativeSetInputWindowInfo }, + {"nativeGetDisplayedContentSamplingAttributes", + "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;", + (void*)nativeGetDisplayedContentSamplingAttributes }, + {"nativeSetDisplayedContentSamplingEnabled", "(Landroid/os/IBinder;ZII)Z", + (void*)nativeSetDisplayedContentSamplingEnabled }, + {"nativeGetDisplayedContentSample", + "(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;", + (void*)nativeGetDisplayedContentSample }, }; int register_android_view_SurfaceControl(JNIEnv* env) @@ -1009,6 +1095,18 @@ int register_android_view_SurfaceControl(JNIEnv* env) gGraphicBufferClassInfo.builder = GetStaticMethodIDOrDie(env, graphicsBufferClazz, "createFromExisting", "(IIIIJ)Landroid/graphics/GraphicBuffer;"); + jclass displayedContentSampleClazz = FindClassOrDie(env, + "android/hardware/display/DisplayedContentSample"); + gDisplayedContentSampleClassInfo.clazz = MakeGlobalRefOrDie(env, displayedContentSampleClazz); + gDisplayedContentSampleClassInfo.ctor = GetMethodIDOrDie(env, + displayedContentSampleClazz, "<init>", "(J[J[J[J[J)V"); + + jclass displayedContentSamplingAttributesClazz = FindClassOrDie(env, + "android/hardware/display/DisplayedContentSamplingAttributes"); + gDisplayedContentSamplingAttributesClassInfo.clazz = MakeGlobalRefOrDie(env, + displayedContentSamplingAttributesClazz); + gDisplayedContentSamplingAttributesClassInfo.ctor = GetMethodIDOrDie(env, + displayedContentSamplingAttributesClazz, "<init>", "(III)V"); return err; } diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp index b708735616c4..24bafca9c386 100644 --- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp +++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp @@ -35,7 +35,6 @@ #include "bpf/BpfUtils.h" #include "netdbpf/BpfNetworkStats.h" -using android::bpf::hasBpfSupport; using android::bpf::parseBpfNetworkStatsDetail; using android::bpf::stats_line; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 4aa88e7558cd..7032081ef6a0 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -99,7 +99,8 @@ enum MountExternalKind { MOUNT_EXTERNAL_DEFAULT = 1, MOUNT_EXTERNAL_READ = 2, MOUNT_EXTERNAL_WRITE = 3, - MOUNT_EXTERNAL_FULL = 4, + MOUNT_EXTERNAL_INSTALLER = 4, + MOUNT_EXTERNAL_FULL = 5, }; // Must match values in com.android.internal.os.Zygote. @@ -446,29 +447,35 @@ static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::st return true; } -static bool mountPkgSpecificDir(const std::string& mntSourceRoot, - const std::string& mntTargetRoot, const std::string& packageName, - const char* dirName, std::string* error_msg) { - std::string mntSourceDir = StringPrintf("%s/Android/%s/%s", - mntSourceRoot.c_str(), dirName, packageName.c_str()); - std::string mntTargetDir = StringPrintf("%s/Android/%s/%s", - mntTargetRoot.c_str(), dirName, packageName.c_str()); - if (TEMP_FAILURE_RETRY(mount(mntSourceDir.c_str(), mntTargetDir.c_str(), +static bool bindMount(const std::string& sourceDir, const std::string& targetDir, + std::string* error_msg) { + if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(), nullptr, MS_BIND | MS_REC, nullptr)) == -1) { *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s", - mntSourceDir.c_str(), mntTargetDir.c_str(), strerror(errno)); + sourceDir.c_str(), targetDir.c_str(), strerror(errno)); return false; } - if (TEMP_FAILURE_RETRY(mount(nullptr, mntTargetDir.c_str(), + if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(), nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) { - *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", mntTargetDir.c_str()); + *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str()); return false; } return true; } +static bool mountPkgSpecificDir(const std::string& mntSourceRoot, + const std::string& mntTargetRoot, const std::string& packageName, + const char* dirName, std::string* error_msg) { + std::string mntSourceDir = StringPrintf("%s/Android/%s/%s", + mntSourceRoot.c_str(), dirName, packageName.c_str()); + std::string mntTargetDir = StringPrintf("%s/Android/%s/%s", + mntTargetRoot.c_str(), dirName, packageName.c_str()); + return bindMount(mntSourceDir, mntTargetDir, error_msg); +} + static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames, - const std::vector<std::string>& volumeLabels, userid_t userId, std::string* error_msg) { + const std::vector<std::string>& volumeLabels, bool mountAllObbs, + userid_t userId, std::string* error_msg) { for (auto& label : volumeLabels) { std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str()); std::string mntTarget = StringPrintf("/storage/%s", label.c_str()); @@ -479,7 +486,14 @@ static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames, for (auto& package : packageNames) { mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg); mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg); - mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg); + if (!mountAllObbs) { + mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg); + } + } + if (mountAllObbs) { + StringAppendF(&mntSource, "/Android/obb"); + StringAppendF(&mntTarget, "/Android/obb"); + bindMount(mntSource, mntTarget, error_msg); } } return true; @@ -500,7 +514,7 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, storageSource = "/mnt/runtime/read"; } else if (mount_mode == MOUNT_EXTERNAL_WRITE) { storageSource = "/mnt/runtime/write"; - } else if (mount_mode != MOUNT_EXTERNAL_FULL && !force_mount_namespace) { + } else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) { // Sane default of no storage visible return true; } @@ -568,12 +582,28 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, pkgSandboxDir.c_str(), strerror(errno)); return false; } + if (access("/storage/obb_mount", F_OK) == 0) { + if (mount_mode != MOUNT_EXTERNAL_INSTALLER) { + remove("/storage/obb_mount"); + } + } else { + if (mount_mode == MOUNT_EXTERNAL_INSTALLER) { + int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount", + O_RDWR | O_CREAT, 0660)); + if (fd == -1) { + *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s", + strerror(errno)); + return false; + } + close(fd); + } + } // If the sandbox was already created by vold, only then set up the bind mounts for // pkg specific directories. Otherwise, leave as is and bind mounts will be taken // care of by vold later. if (sandboxAlreadyCreated) { if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids, - user_id, error_msg)) { + mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) { return false; } } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index c2bc7bf91be9..7fe3be870994 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -417,7 +417,8 @@ message StateControllerProto { optional int64 start_time_elapsed = 1; optional int64 end_time_elapsed = 2; - optional int32 job_count = 3; + // The number of background jobs that ran during this session. + optional int32 bg_job_count = 3; } message Timer { @@ -428,8 +429,9 @@ message StateControllerProto { optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; - // How many are currently running. Valid only if the device is_active is true. - optional int32 job_count = 4; + // How many background jobs are currently running. Valid only if the device is_active + // is true. + optional int32 bg_job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; } diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto index 3d60a86d86c9..528c1a4134f1 100644 --- a/core/proto/android/server/usagestatsservice.proto +++ b/core/proto/android/server/usagestatsservice.proto @@ -54,6 +54,9 @@ message IntervalStatsProto { // Time attributes stored as an offset of the IntervalStats's beginTime. optional int64 last_time_service_used_ms = 8; optional int64 total_time_service_used_ms = 9; + // Time attributes stored as an offset of the IntervalStats's beginTime. + optional int64 last_time_visible_ms = 10; + optional int64 total_time_visible_ms = 11; } // Stores the relevant information an IntervalStats will have about a Configuration @@ -82,6 +85,9 @@ message IntervalStatsProto { optional string notification_channel = 12; // notification_channel_index contains the index + 1 of the channel name in the string pool optional int32 notification_channel_index = 13; + // If class field is an Activity, instance_id is a unique id of the + // Activity object. + optional int32 instance_id = 14; } // The following fields contain supplemental data used to build IntervalStats, such as a string diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ff73df6cb876..dca15bd49ca4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1120,6 +1120,18 @@ android:description="@string/permdesc_manageOwnCalls" android:protectionLevel="normal" /> + <!--Allows an app which implements the + {@link android.telecom.InCallService InCallService} API to be eligible to be enabled as a + calling companion app. This means that the Telecom framework will bind to the app's + InCallService implementation when there are calls active. The app can use the InCallService + API to view information about calls on the system and control these calls. + <p>Protection level: normal + --> + <permission android:name="android.permission.CALL_COMPANION_APP" + android:label="@string/permlab_callCompanionApp" + android:description="@string/permdesc_callCompanionApp" + android:protectionLevel="normal" /> + <!-- Allows a calling app to continue a call which was started in another app. An example is a video calling app that wants to continue a voice call on the user's mobile network.<p> When the handover of a call from one app to another takes place, there are two devices @@ -2093,10 +2105,9 @@ <!-- @hide Allows an application to cache content. <p>Not for use by third-party applications. - <p>Protection level: signature --> <permission android:name="android.permission.CACHE_CONTENT" - android:protectionLevel="signature" /> + android:protectionLevel="signature|documenter" /> <!-- @SystemApi @hide Allows an application to aggressively allocate disk space. @@ -4685,6 +4696,10 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> + <service android:name="com.android.server.pm.DynamicCodeLoggingService" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + <service android:name="com.android.server.PruneInstantAppsJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index ab4bd0534025..35263a3fa891 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1603,6 +1603,9 @@ <!-- Declares that this application should be invoked without non-SDK API enforcement --> <attr name="usesNonSdkApi" /> + <!-- If {@code true} the user is prompted to keep the app's data on uninstall --> + <attr name="hasFragileUserData" /> + </declare-styleable> <!-- The <code>permission</code> tag declares a security permission that can be used to control access from other packages to specific components or diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 101f92b2097c..97a21a55f67f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -222,6 +222,11 @@ so that applications can still use their own mechanisms. --> <bool name="config_enableAutoPowerModes">false</bool> + <!-- Whether (if true) this is a kind of device that can be moved around (eg. phone/laptop), + or (if false) something for which movement is either not measurable or should not count + toward power states (eg. tv/soundbar). --> + <bool name="config_autoPowerModeUseMotionSensor">true</bool> + <!-- The threshold angle for any motion detection in auto-power save modes. In hundreths of a degree. --> <integer name="config_autoPowerModeThresholdAngle">200</integer> @@ -3592,4 +3597,9 @@ <!-- Component name for default assistant on this device --> <string name="config_defaultAssistantComponentName">#+UNSET</string> + <!-- Class name for the InputEvent compatibility processor override. + Empty string means use the default compatibility processor + (android.view.InputEventCompatProcessor). --> + <string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string> + </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index d480121fc998..eac8b4854994 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2930,6 +2930,7 @@ <public name="dataRetentionTime" /> <public name="selectionDividerHeight" /> <public name="foregroundServiceType" /> + <public name="hasFragileUserData" /> </public-group> <public-group type="drawable" first-id="0x010800b4"> @@ -2970,6 +2971,8 @@ <public-group type="dimen" first-id="0x01050007"> <!-- @hide @SystemApi --> <public name="config_restrictedIconSize" /> + <!-- @hide @SystemApi --> + <public name="config_mediaMetadataBitmapMaxSize" /> </public-group> <!-- =============================================================== diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 0a167ddc7085..cab01f9c027f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1215,6 +1215,15 @@ <string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in order to improve the calling experience.</string> + <!-- Title of an application permission. When granted the app is allowed to be enabled as + a companion app. [CHAR LIMIT=NONE]--> + <string name="permlab_callCompanionApp">see and control calls through the system.</string> + <!-- Description of an application permission. When granted the app is allowed to be enabled as + a companion app. [CHAR LIMIT=NONE]--> + <string name="permdesc_callCompanionApp">Allows the app to see and control ongoing calls on the + device. This includes information such as call numbers for calls and the state of the + calls.</string> + <!-- Title of an application permission. When granted the user is giving access to a third party app to continue a call which originated in another app. For example, the user could be in a voice call over their carrier's mobile network, and a third party video diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d573c09a6538..161e41681486 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -265,6 +265,7 @@ <java-symbol type="integer" name="config_autoPowerModeAnyMotionSensor" /> <java-symbol type="bool" name="config_autoPowerModePreferWristTilt" /> <java-symbol type="bool" name="config_autoPowerModePrefetchLocation" /> + <java-symbol type="bool" name="config_autoPowerModeUseMotionSensor" /> <java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" /> <java-symbol type="bool" name="config_enable_puk_unlock_screen" /> <java-symbol type="bool" name="config_disableLockscreenByDefault" /> @@ -301,6 +302,7 @@ <java-symbol type="bool" name="config_enableWallpaperService" /> <java-symbol type="bool" name="config_checkWallpaperAtBoot" /> <java-symbol type="string" name="config_wallpaperManagerServiceName" /> + <java-symbol type="string" name="config_inputEventCompatProcessorOverrideClassName" /> <java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" /> <java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" /> <java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" /> diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java index d289f1f5defc..9b5b725a3bed 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java @@ -16,9 +16,16 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import android.app.admin.PasswordMetrics.PasswordComplexityBucket; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -164,4 +171,126 @@ public class PasswordMetricsTest { } + + @Test + public void testConstructQuality() { + PasswordMetrics expected = new PasswordMetrics(); + expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; + + PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX); + + assertEquals(expected, actual); + } + + @Test + public void testDetermineComplexity_none() { + assertEquals(PASSWORD_COMPLEXITY_NONE, + PasswordMetrics.computeForPassword("").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowSomething() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowNumeric() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("1234").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("124").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("a!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("a!1").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("1238").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("ab!c").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("ab!1").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("12389647!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("alphabetic!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("alphanumeric123!").determineComplexity()); + } + + @Test + public void testComplexityLevelToBucket_none() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_NONE).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_low() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_LOW).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_medium() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_MEDIUM).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_high() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_HIGH).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity()); + } + } } diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java index 1f047f9e6d10..28aaf1e05644 100644 --- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java +++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java @@ -16,18 +16,22 @@ package android.app.usage; -import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; +import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; import static android.app.usage.UsageEvents.Event.END_OF_DAY; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; -import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND; -import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import android.app.usage.UsageEvents.Event; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -120,10 +124,10 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.mTotalTimeInForeground = 10; - left.mLastForegroundActivityEventMap.put("com.test.activity1", MOVE_TO_FOREGROUND); - left.mLastForegroundActivityEventMap.put("com.test.activity2", MOVE_TO_FOREGROUND); - left.mLastForegroundServiceEventMap.put("com.test.service1", FOREGROUND_SERVICE_START); - left.mLastForegroundServiceEventMap.put("com.test.service2", FOREGROUND_SERVICE_START); + left.mActivities.put(1, Event.ACTIVITY_RESUMED); + left.mActivities.put(2, Event.ACTIVITY_RESUMED); + left.mForegroundServices.put("com.test.service1", FOREGROUND_SERVICE_START); + left.mForegroundServices.put("com.test.service2", FOREGROUND_SERVICE_START); Parcel p = Parcel.obtain(); left.writeToParcel(p, 0); @@ -133,131 +137,182 @@ public class UsageStatsTest { } @Test - public void testForegroundActivity() { + public void testActivity() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND); + left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 200000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mLaunchCount, 1); + assertEquals(left.mTotalTimeInForeground, 0); + assertEquals(left.mTotalTimeVisible, 0); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertFalse(left.mLastForegroundActivityEventMap.containsKey("com.test.activity1")); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 350000 - 200000); + assertEquals(left.mTotalTimeVisible, 350000 - 200000); + + left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 350000); + assertEquals(left.mLastTimeVisible, 400000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 350000 - 200000); + assertEquals(left.mTotalTimeVisible, 400000 - 200000); + + left.update("com.test.activity1", 500000, ACTIVITY_DESTROYED, 1); + assertEquals(left.mLastTimeUsed, 350000); + assertEquals(left.mLastTimeVisible, 400000); + assertTrue(left.mActivities.indexOfKey(1) < 0); + assertEquals(left.mTotalTimeInForeground, 350000 - 200000); + assertEquals(left.mTotalTimeVisible, 400000 - 200000); } @Test - public void testEvent_CONTINUE_PREVIOUS_DAY() { + public void testEvent_END_OF_DAY() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mLaunchCount, 1); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update(null, 350000, END_OF_DAY, 0); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mTotalTimeInForeground, 350000 - 100000); + assertEquals(left.mTotalTimeVisible, 350000 - 100000); } @Test - public void testEvent_END_OF_DAY() { + public void testEvent_ACTIVITY_PAUSED() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); - assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + left.update("com.test.activity1", 100000, ACTIVITY_PAUSED, 1); + assertEquals(left.mLastTimeUsed, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); - left.update(null, 350000, END_OF_DAY); - assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(END_OF_DAY)); - assertEquals(left.mTotalTimeInForeground, 350000 - 100000); + left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mTotalTimeInForeground, 0); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + + left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1); + assertEquals(left.mLastTimeUsed, 300000); + assertEquals(left.mLastTimeVisible, 300000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); + assertEquals(left.mTotalTimeInForeground, 300000 - 200000); + assertEquals(left.mTotalTimeVisible, 300000 - 100000); + + left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 300000); + assertEquals(left.mLastTimeVisible, 400000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 300000 - 200000); + assertEquals(left.mTotalTimeVisible, 400000 - 100000); } @Test - public void testForegroundActivityEventSequence() { + public void testEvent_CHANGE_TO_INVISIBLE() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); - - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); - assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); - assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED); - left.update("com.test.activity1", 450000, MOVE_TO_FOREGROUND); - assertEquals(left.mLastTimeUsed, 450000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); - assertEquals(left.mTotalTimeInForeground, 250000); - - left.update("com.test.activity1", 500000, MOVE_TO_BACKGROUND); - assertEquals(left.mLastTimeUsed, 500000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); - assertEquals(left.mTotalTimeInForeground, 250000 + 50000 /*500000 - 450000*/); + left.update("com.test.activity1", 200000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + + left.update("com.test.activity1", 300000, ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 300000); + assertEquals(left.mLastTimeVisible, 300000); + assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); } @Test - public void testForegroundActivityEventOutOfSequence() { + public void testEvent_ACTIVITY_DESTROYED() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED); - left.update("com.test.activity1", 150000, MOVE_TO_FOREGROUND); - assertEquals(left.mLastTimeUsed, 150000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + left.update("com.test.activity1", 200000, ACTIVITY_DESTROYED, 1); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertTrue(left.mActivities.indexOfKey(1) < 0); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + } + + @Test + public void testActivityEventOutOfOrder() { + left.mPackageName = "com.test"; + left.mBeginTimeStamp = 100000; + + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 100000); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mLaunchCount, 1); - assertEquals(left.mTotalTimeInForeground, 50000 /*150000 - 100000*/); + assertEquals(left.mTotalTimeInForeground, 0); + assertEquals(left.mTotalTimeVisible, 0); - left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND); + left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 200000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mLaunchCount, 2); assertEquals(left.mTotalTimeInForeground, 100000); + assertEquals(left.mTotalTimeVisible, 100000 /*200000 - 100000*/); - left.update("com.test.activity1", 250000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 250000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 250000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 250000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 150000); + assertEquals(left.mTotalTimeVisible, 150000 /*250000 - 100000*/); - left.update("com.test.activity1", 300000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 250000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 300000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 150000); + assertEquals(left.mTotalTimeVisible, 200000 /*300000 - 100000*/); - left.update("com.test.activity1", 350000, MOVE_TO_FOREGROUND); + left.update("com.test.activity1", 350000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mTotalTimeInForeground, 150000); + assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/); - left.update("com.test.activity1", 400000, END_OF_DAY); + left.update("com.test.activity1", 400000, END_OF_DAY, 1); assertEquals(left.mLastTimeUsed, 400000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(END_OF_DAY)); + assertEquals(left.mLastTimeVisible, 400000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mTotalTimeInForeground, 200000); + assertEquals(left.mTotalTimeVisible, 300000 /*400000 - 100000*/); } @Test @@ -265,28 +320,41 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); - left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED); + assertEquals(left.mLaunchCount, 2); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/); + assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/); - left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND); + left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2); assertEquals(left.mLastTimeUsed, 450000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null); + assertEquals(left.mLastTimeVisible, 450000); + assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/); + assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/); + + left.update("com.test.activity1", 550000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 450000); + assertEquals(left.mLastTimeVisible, 550000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 350000); + assertEquals(left.mTotalTimeVisible, 350000 + 100000 /*550000 - 450000*/); - left.update(null, 500000, END_OF_DAY); + left.update("com.test.activity2", 650000, ACTIVITY_STOPPED, 2); assertEquals(left.mLastTimeUsed, 450000); + assertEquals(left.mLastTimeVisible, 650000); + assertEquals(left.mActivities.get(2), ACTIVITY_STOPPED); assertEquals(left.mTotalTimeInForeground, 350000); + assertEquals(left.mTotalTimeVisible, 450000 + 100000 /*650000 - 550000*/); } @Test @@ -294,15 +362,14 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START); + left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 200000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(FOREGROUND_SERVICE_START)); - assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 200000); } @@ -311,15 +378,15 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.service1", 100000, CONTINUING_FOREGROUND_SERVICE); + left.update("com.test.service1", 100000, + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000); } @@ -329,16 +396,15 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); - left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE); + left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), - new Integer(ROLLOVER_FOREGROUND_SERVICE)); + assertEquals(left.mForegroundServices.get("com.test.service1"), + new Integer(CONTINUING_FOREGROUND_SERVICE)); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000); } @@ -348,27 +414,28 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/); - left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START); + left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 450000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(FOREGROUND_SERVICE_START)); assertEquals(left.mTotalTimeForegroundServiceUsed, 250000); - left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 500000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); - assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 50000 /*500000 - 450000*/); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); + assertEquals(left.mTotalTimeForegroundServiceUsed, + 250000 + 50000 /*500000 - 450000*/); } @Test @@ -377,27 +444,27 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); left.update("com.test.service2", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), + assertEquals(left.mForegroundServices.get("com.test.service2"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/); - left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 450000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null); - assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 100000 /*450000 - 350000*/); + assertEquals(left.mForegroundServices.get("com.test.service2"), null); + assertEquals(left.mTotalTimeForegroundServiceUsed, + 250000 + 100000 /*450000 - 350000*/); - left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE); + left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 450000); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000); } @@ -407,76 +474,117 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); - left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2); left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); left.update("com.test.service2", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeUsed, 100000); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED); + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), + assertEquals(left.mForegroundServices.get("com.test.service2"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLaunchCount, 2); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/); + assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/); - left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 400000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 /*400000 - 100000*/); - left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND); + left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2); assertEquals(left.mLastTimeUsed, 450000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null); + assertEquals(left.mLastTimeVisible, 450000); + assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/); + assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/); - left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 500000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null); - assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 + 100000 /*500000 - 400000*/); + assertEquals(left.mForegroundServices.get("com.test.service2"), null); + assertEquals(left.mTotalTimeForegroundServiceUsed, + 300000 + 100000 /*500000 - 400000*/); - left.update(null, 550000, END_OF_DAY); + left.update(null, 550000, END_OF_DAY, 0); assertEquals(left.mLastTimeUsed, 450000); + assertEquals(left.mLastTimeVisible, 550000); assertEquals(left.mTotalTimeInForeground, 350000); - left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE); + assertEquals(left.mTotalTimeVisible, 450000); + + left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 500000); assertEquals(left.mTotalTimeForegroundServiceUsed, 400000); } + @Test + public void testEvent_FLUSH_TO_DISK() { + testClosingEvent(FLUSH_TO_DISK); + } + + private void testClosingEvent(int eventType) { + // When these three closing events are received, all open activities/services need to be + // closed and usage stats are updated. + if (eventType != FLUSH_TO_DISK) { + fail("Closing eventType must be one of FLUSH_TO_DISK"); + } + + left.mPackageName = "com.test"; + left.mBeginTimeStamp = 100000; + + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 100000); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + + left.update("com.test.service1", 150000, FOREGROUND_SERVICE_START, 0); + assertEquals(left.mLastTimeForegroundServiceUsed, 150000); + assertEquals(left.mForegroundServices.get("com.test.service1"), + new Integer(FOREGROUND_SERVICE_START)); + + left.update(null, 200000, eventType, 0); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + assertEquals(left.mLastTimeForegroundServiceUsed, 200000); + assertEquals(left.mTotalTimeForegroundServiceUsed, 200000 - 150000); + } + void compareUsageStats(UsageStats us1, UsageStats us2) { assertEquals(us1.mPackageName, us2.mPackageName); assertEquals(us1.mBeginTimeStamp, us2.mBeginTimeStamp); assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed); + assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible); assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed); assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground); assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed); assertEquals(us1.mAppLaunchCount, us2.mAppLaunchCount); - assertEquals(us1.mLastForegroundActivityEventMap.size(), - us2.mLastForegroundActivityEventMap.size()); - for (int i = 0; i < us1.mLastForegroundActivityEventMap.size(); i++) { - assertEquals(us1.mLastForegroundActivityEventMap.keyAt(i), - us2.mLastForegroundActivityEventMap.keyAt(i)); - assertEquals(us1.mLastForegroundActivityEventMap.valueAt(i), - us2.mLastForegroundActivityEventMap.valueAt(i)); + assertEquals(us1.mActivities.size(), + us2.mActivities.size()); + for (int i = 0; i < us1.mActivities.size(); i++) { + assertEquals(us1.mActivities.keyAt(i), + us2.mActivities.keyAt(i)); + assertEquals(us1.mActivities.valueAt(i), + us2.mActivities.valueAt(i)); } - assertEquals(us1.mLastForegroundServiceEventMap.size(), - us2.mLastForegroundServiceEventMap.size()); - for (int i = 0; i < us1.mLastForegroundServiceEventMap.size(); i++) { - assertEquals(us1.mLastForegroundServiceEventMap.keyAt(i), - us2.mLastForegroundServiceEventMap.keyAt(i)); - assertEquals(us1.mLastForegroundServiceEventMap.valueAt(i), - us2.mLastForegroundServiceEventMap.valueAt(i)); + assertEquals(us1.mForegroundServices.size(), + us2.mForegroundServices.size()); + for (int i = 0; i < us1.mForegroundServices.size(); i++) { + assertEquals(us1.mForegroundServices.keyAt(i), + us2.mForegroundServices.keyAt(i)); + assertEquals(us1.mForegroundServices.valueAt(i), + us2.mForegroundServices.valueAt(i)); } assertEquals(us1.mChooserCounts, us2.mChooserCounts); } diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java index d1dbd3ccba33..5664df6e9744 100644 --- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java +++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java @@ -23,6 +23,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.platform.test.annotations.Presubmit; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; @@ -37,6 +38,7 @@ import org.junit.runner.RunWith; * Test whether Binder calls work source is propagated correctly. */ @LargeTest +@Presubmit @RunWith(AndroidJUnit4.class) public class BinderWorkSourceTest { private static Context sContext; @@ -125,8 +127,10 @@ public class BinderWorkSourceTest { Binder.setCallingWorkSourceUid(UID); long token = Binder.clearCallingWorkSource(); Binder.restoreCallingWorkSource(token); + assertEquals(UID, Binder.getCallingWorkSourceUid()); assertEquals(UID, mService.getIncomingWorkSourceUid()); + // Still the same after the binder transaction. assertEquals(UID, Binder.getCallingWorkSourceUid()); } diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index f0faaf6153b1..4a6c093e3bd1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -16,6 +16,9 @@ package android.view.textclassifier; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; + import static com.google.common.truth.Truth.assertThat; import android.app.Person; @@ -27,16 +30,26 @@ import com.google.android.textclassifier.ActionsSuggestionsModel; import org.junit.Test; import org.junit.runner.RunWith; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; +import java.util.Locale; +import java.util.function.Function; @SmallTest @RunWith(AndroidJUnit4.class) public class ActionsSuggestionsHelperTest { + private static final String LOCALE_TAG = Locale.US.toLanguageTag(); + private static final Function<CharSequence, String> LANGUAGE_DETECTOR = + charSequence -> LOCALE_TAG; + @Test public void testToNativeMessages_emptyInput() { ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages(Collections.emptyList()); + ActionsSuggestionsHelper.toNativeMessages( + Collections.emptyList(), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @@ -44,114 +57,89 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = - new ConversationActions.Message.Builder().build(); + new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( - Collections.singletonList(messageWithoutText)); + Collections.singletonList(messageWithoutText), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @Test - public void testToNativeMessages_missingPersonInFirstMessage() { - ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder() - .setText("first") - .build(); - ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder() - .setText("second") - .setAuthor(new Person.Builder().build()) - .build(); - ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder() - .setText("third") - .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) - .build(); - - ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage)); - - assertThat(conversationMessages).hasLength(2); - assertNativeMessage(conversationMessages[0], secondMessage.getText(), 1); - assertNativeMessage(conversationMessages[1], thirdMessage.getText(), 0); - } + public void testToNativeMessages_userIdEncoding() { + Person userA = new Person.Builder().setName("userA").build(); + Person userB = new Person.Builder().setName("userB").build(); - @Test - public void testToNativeMessages_missingPersonInMiddleOfConversation() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(userB) .setText("first") - .setAuthor(new Person.Builder().setName("first").build()) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(userA) .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_LOCAL) .setText("third") - .setAuthor(new Person.Builder().setName("third").build()) .build(); ConversationActions.Message fourthMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(userA) .setText("fourth") - .setAuthor(new Person.Builder().setName("fourth").build()) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); + Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage), + LANGUAGE_DETECTOR); - assertThat(conversationMessages).hasLength(2); - assertNativeMessage(conversationMessages[0], thirdMessage.getText(), 2); - assertNativeMessage(conversationMessages[1], fourthMessage.getText(), 1); + assertThat(conversationMessages).hasLength(4); + assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0); + assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); + assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0); + assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0); } @Test - public void testToNativeMessages_userIdEncoding() { - Person userA = new Person.Builder().setName("userA").build(); - Person userB = new Person.Builder().setName("userB").build(); - + public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("first") - .setAuthor(userB) + .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("second") - .setAuthor(userA) .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("third") - .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) - .build(); - ConversationActions.Message fourthMessage = - new ConversationActions.Message.Builder() - .setText("fourth") - .setAuthor(userA) + .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); + Arrays.asList(firstMessage, secondMessage, thirdMessage), + LANGUAGE_DETECTOR); - assertThat(conversationMessages).hasLength(4); - assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2); - assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1); - assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0); - assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1); + assertThat(conversationMessages).hasLength(3); + assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000); + assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); + assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000); + } + + private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC")); } private static void assertNativeMessage( ActionsSuggestionsModel.ConversationMessage nativeMessage, CharSequence text, - int userId) { + int userId, + long referenceTimeInMsUtc) { assertThat(nativeMessage.getText()).isEqualTo(text.toString()); assertThat(nativeMessage.getUserId()).isEqualTo(userId); + assertThat(nativeMessage.getLocales()).isEqualTo(LOCALE_TAG); + assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java index bae2be352b4c..aaadefb90ece 100644 --- a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java @@ -56,5 +56,7 @@ public class IntentFactoryTest { Intent intent = labeledIntent.getIntent(); assertThat(intent.getAction()).isEqualTo(Intent.ACTION_DEFINE); assertThat(intent.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(TEXT); + assertThat( + intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue(); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index aec4571252e7..9b5c0347bdb6 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -373,7 +373,10 @@ public class TextClassifierTest { public void testSuggestConversationActions_textReplyOnly_maxThree() { if (isTextClassifierDisabled()) return; ConversationActions.Message message = - new ConversationActions.Message.Builder().setText("Where are you?").build(); + new ConversationActions.Message.Builder( + ConversationActions.Message.PERSON_USER_REMOTE) + .setText("Hello") + .build(); ConversationActions.TypeConfig typeConfig = new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) .setIncludedTypes( diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 0a2f057ccb69..dcf95fdbb438 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -142,6 +142,7 @@ applications that come with the platform <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> <permission name="android.permission.CLEAR_APP_USER_DATA"/> + <permission name="android.permission.PACKAGE_USAGE_STATS"/> </privapp-permissions> <privapp-permissions package="com.android.permissioncontroller"> diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS index 23ec5ab0d1f3..f1903a5a54a7 100644 --- a/libs/androidfw/OWNERS +++ b/libs/androidfw/OWNERS @@ -1,2 +1,3 @@ set noparent toddke@google.com +rtmitchell@google.com
\ No newline at end of file diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 91261aa3e4f9..cf2d8fb3251c 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1622,7 +1622,7 @@ struct ResTable_overlayable_policy_header { struct ResChunk_header header; - enum PolicyFlags { + enum PolicyFlags : uint32_t { // Any overlay can overlay these resources. POLICY_PUBLIC = 0x00000001, diff --git a/location/java/android/location/SettingInjectorService.java b/location/java/android/location/SettingInjectorService.java index fcd2cdec904f..c20177058b68 100644 --- a/location/java/android/location/SettingInjectorService.java +++ b/location/java/android/location/SettingInjectorService.java @@ -17,6 +17,7 @@ package android.location; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; @@ -26,7 +27,7 @@ import android.os.RemoteException; import android.util.Log; /** - * Dynamically specifies the enabled status of a preference injected into + * Dynamically specifies the summary (subtitle) and enabled status of a preference injected into * the list of app settings displayed by the system settings app * <p/> * For use only by apps that are included in the system image, for preferences that affect multiple @@ -71,12 +72,13 @@ import android.util.Log; * </ul> * * To ensure a good user experience, your {@link android.app.Application#onCreate()}, - * and {@link #onGetEnabled()} methods must all be fast. If either is slow, - * it can delay the display of settings values for other apps as well. Note further that these - * methods are called on your app's UI thread. + * {@link #onGetSummary()}, and {@link #onGetEnabled()} methods must all be fast. If any are slow, + * it can delay the display of settings values for other apps as well. Note further that all are + * called on your app's UI thread. * <p/> * For compactness, only one copy of a given setting should be injected. If each account has a - * distinct value for the setting, then only {@code settingsActivity} should display the value for + * distinct value for the setting, then the {@link #onGetSummary()} value should represent a summary + * of the state across all of the accounts and {@code settingsActivity} should display the value for * each account. */ public abstract class SettingInjectorService extends Service { @@ -108,6 +110,14 @@ public abstract class SettingInjectorService extends Service { "android.location.InjectedSettingChanged"; /** + * Name of the bundle key for the string specifying the summary for the setting (e.g., "ON" or + * "OFF"). + * + * @hide + */ + public static final String SUMMARY_KEY = "summary"; + + /** * Name of the bundle key for the string specifying whether the setting is currently enabled. * * @hide @@ -150,36 +160,41 @@ public abstract class SettingInjectorService extends Service { } private void onHandleIntent(Intent intent) { - - boolean enabled; + String summary = null; + boolean enabled = false; try { + summary = onGetSummary(); enabled = onGetEnabled(); - } catch (RuntimeException e) { - // Exception. Send status anyway, so that settings injector can immediately start - // loading the status of the next setting. - sendStatus(intent, true); - throw e; + } finally { + // If exception happens, send status anyway, so that settings injector can immediately + // start loading the status of the next setting. But leave the exception uncaught to + // crash the injector service itself. + sendStatus(intent, summary, enabled); } - - sendStatus(intent, enabled); } /** * Send the enabled values back to the caller via the messenger encoded in the * intent. */ - private void sendStatus(Intent intent, boolean enabled) { + private void sendStatus(Intent intent, String summary, boolean enabled) { + Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY); + // Bail out to avoid crashing GmsCore with incoming malicious Intent. + if (messenger == null) { + return; + } + Message message = Message.obtain(); Bundle bundle = new Bundle(); + bundle.putString(SUMMARY_KEY, summary); bundle.putBoolean(ENABLED_KEY, enabled); message.setData(bundle); if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, mName + ": received " + intent + Log.d(TAG, mName + ": received " + intent + ", summary=" + summary + ", enabled=" + enabled + ", sending message: " + message); } - Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY); try { messenger.send(message); } catch (RemoteException e) { @@ -188,14 +203,12 @@ public abstract class SettingInjectorService extends Service { } /** - * This method is no longer called, because status values are no longer shown for any injected - * setting. - * - * @return ignored + * Returns the {@link android.preference.Preference#getSummary()} value (allowed to be null or + * empty). Should not perform unpredictably-long operations such as network access--see the + * running-time comments in the class-level javadoc. * - * @deprecated not called any more + * @return the {@link android.preference.Preference#getSummary()} value */ - @Deprecated protected abstract String onGetSummary(); /** @@ -217,4 +230,12 @@ public abstract class SettingInjectorService extends Service { * @return the {@link android.preference.Preference#isEnabled()} value */ protected abstract boolean onGetEnabled(); + + /** + * Sends a broadcast to refresh the injected settings on location settings page. + */ + public static final void refreshSettings(Context context) { + Intent intent = new Intent(ACTION_INJECTED_SETTING_CHANGED); + context.sendBroadcast(intent); + } } diff --git a/media/Android.bp b/media/Android.bp new file mode 100644 index 000000000000..d5da6f266952 --- /dev/null +++ b/media/Android.bp @@ -0,0 +1,36 @@ +java_library { + // TODO: include media2.jar in the media apex and add it to the bootclasspath. + name: "media2", + + srcs: [ + ":media2-srcs", + ":framework-media-annotation-srcs", + ], + + static_libs: [ + "mediaplayer2-protos", + ], + + // Make sure that the implementaion only relies on SDK or system APIs. + sdk_version: "system_current", +} + +filegroup { + name: "media2-srcs", + srcs: [ + "java/android/media/CloseGuard.java", + "java/android/media/DataSourceCallback.java", + "java/android/media/DataSourceDesc.java", + "java/android/media/UriDataSourceDesc.java", + "java/android/media/FileDataSourceDesc.java", + "java/android/media/CallbackDataSourceDesc.java", + "java/android/media/VideoSize.java", + "java/android/media/Media2Utils.java", + "java/android/media/MediaPlayer2Utils.java", + "java/android/media/MediaPlayer2.java", + "java/android/media/Media2HTTPService.java", + "java/android/media/Media2HTTPConnection.java", + "java/android/media/RoutingDelegate.java", + "java/android/media/BufferingParams.java", + ], +} diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java index 823af656abaa..fca70747e202 100644 --- a/media/java/android/media/AudioPresentation.java +++ b/media/java/android/media/AudioPresentation.java @@ -254,6 +254,22 @@ public final class AudioPresentation { mLabels.hashCode()); } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName() + " "); + sb.append("{ presentation id=" + mPresentationId); + sb.append(", program id=" + mProgramId); + sb.append(", language=" + mLanguage); + sb.append(", labels=" + mLabels); + sb.append(", mastering indication=" + mMasteringIndication); + sb.append(", audio description=" + mAudioDescriptionAvailable); + sb.append(", spoken subtitles=" + mSpokenSubtitlesAvailable); + sb.append(", dialogue enhancement=" + mDialogueEnhancementAvailable); + sb.append(" }"); + return sb.toString(); + } + /** * A builder class for creating {@link AudioPresentation} objects. */ diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 242ae46f4d33..0375de3cc496 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -27,7 +27,6 @@ import android.media.MediaCodecInfo.CodecCapabilities; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.IHwBinder; import android.os.Looper; import android.os.Message; @@ -1427,6 +1426,24 @@ import java.util.concurrent.locks.ReentrantLock; <td>⎆</td> </tr> <tr> + <td>(29+)</td> + <td>29+</td> + <td>29+</td> + <td>29+</td> + <td>(29+)</td> + <td>(29+)</td> + <td>-</td> + <td class=fn>{@link #setAudioPresentation setAudioPresentation}</td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + </tr> + <tr> <td>-</td> <td>-</td> <td>18+</td> @@ -3260,6 +3277,19 @@ final public class MediaCodec { public native final void setVideoScalingMode(@VideoScalingMode int mode); /** + * Sets the audio presentation. + * @param presentation see {@link AudioPresentation}. In particular, id should be set. + */ + public void setAudioPresentation(@NonNull AudioPresentation presentation) { + if (presentation == null) { + throw new IllegalArgumentException("audio presentation is null"); + } + native_setAudioPresentation(presentation.getPresentationId(), presentation.getProgramId()); + } + + private native void native_setAudioPresentation(int presentationId, int programId); + + /** * Get the component name. If the codec was created by createDecoderByType * or createEncoderByType, what component is chosen is not known beforehand. * @throws IllegalStateException if in the Released state. diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index c91d4d3de442..0fb392bfc0fe 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -18,10 +18,10 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; + import dalvik.system.CloseGuard; import java.io.FileDescriptor; @@ -269,8 +269,10 @@ final public class MediaMuxer { public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2; /** HEIF media file format*/ public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3; + /** Ogg media file format*/ + public static final int MUXER_OUTPUT_OGG = MUXER_OUTPUT_FIRST + 4; /** @hide */ - public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF; + public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_OGG; }; /** @hide */ @@ -279,6 +281,7 @@ final public class MediaMuxer { OutputFormat.MUXER_OUTPUT_WEBM, OutputFormat.MUXER_OUTPUT_3GPP, OutputFormat.MUXER_OUTPUT_HEIF, + OutputFormat.MUXER_OUTPUT_OGG, }) @Retention(RetentionPolicy.SOURCE) public @interface Format {} diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index 9038f72f1476..d4b1c7f868cb 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -3960,7 +3960,12 @@ public class MediaPlayer2 implements AutoCloseable textBounds = new Rect(left, top, right, bottom); } } + return null; + /* TimedText c-tor usage is temporarily commented out. + * TODO(b/117527789): use SUBTITLE path for MEDIA_MIMETYPE_TEXT_3GPP track + * and remove TimedText path from MediaPlayer2. return new TimedText(textChars, textBounds); + */ } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index d4bfd6175a09..8ced021b1025 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -22,17 +22,17 @@ import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.hardware.Camera; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.view.Surface; +import com.android.internal.annotations.GuardedBy; + import java.io.File; import java.io.FileDescriptor; import java.io.IOException; @@ -41,8 +41,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import com.android.internal.annotations.GuardedBy; - /** * Used to record audio and video. The recording control is based on a * simple state machine (see below). @@ -450,6 +448,9 @@ public class MediaRecorder implements AudioRouting /** VP8/VORBIS data in a WEBM container */ public static final int WEBM = 9; + + /** Opus data in a Ogg container */ + public static final int OGG = 11; }; /** @@ -474,6 +475,8 @@ public class MediaRecorder implements AudioRouting public static final int AAC_ELD = 5; /** Ogg Vorbis audio codec */ public static final int VORBIS = 6; + /** Opus audio codec */ + public static final int OPUS = 7; } /** diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 503720939113..257860890437 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -752,6 +752,13 @@ void JMediaCodec::setVideoScalingMode(int mode) { } } +void JMediaCodec::selectAudioPresentation(const int32_t presentationId, const int32_t programId) { + sp<AMessage> msg = new AMessage; + msg->setInt32("audio-presentation-presentation-id", presentationId); + msg->setInt32("audio-presentation-program-id", programId); + (void)mCodec->setParameters(msg); +} + static jthrowable createCodecException( JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) { ScopedLocalRef<jclass> clazz( @@ -1874,6 +1881,18 @@ static void android_media_MediaCodec_setVideoScalingMode( codec->setVideoScalingMode(mode); } +static void android_media_MediaCodec_setAudioPresentation( + JNIEnv *env, jobject thiz, jint presentationId, jint programId) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId); +} + static void android_media_MediaCodec_native_init(JNIEnv *env) { ScopedLocalRef<jclass> clazz( env, env->FindClass("android/media/MediaCodec")); @@ -2183,6 +2202,9 @@ static const JNINativeMethod gMethods[] = { { "setVideoScalingMode", "(I)V", (void *)android_media_MediaCodec_setVideoScalingMode }, + { "native_setAudioPresentation", "(II)V", + (void *)android_media_MediaCodec_setAudioPresentation }, + { "native_init", "()V", (void *)android_media_MediaCodec_native_init }, { "native_setup", "(Ljava/lang/String;ZZ)V", diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 985f55a89254..0a53f1a0e268 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -128,6 +128,8 @@ struct JMediaCodec : public AHandler { void setVideoScalingMode(int mode); + void selectAudioPresentation(const int32_t presentationId, const int32_t programId); + protected: virtual ~JMediaCodec(); diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 693bd8b4b51c..747d4c01867e 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include "android_media_AudioEffect.h" #include <stdio.h> @@ -29,6 +28,10 @@ #include <nativehelper/ScopedUtfChars.h> +#include "android_media_AudioEffect.h" +#include "android_media_AudioEffectDescriptor.h" +#include "android_media_AudioErrors.h" + using namespace android; #define AUDIOEFFECT_SUCCESS 0 @@ -49,8 +52,6 @@ struct fields_t { jmethodID midPostNativeEvent; // event post callback method jfieldID fidNativeAudioEffect; // stores in Java the native AudioEffect object jfieldID fidJniData; // stores in Java additional resources used by the native AudioEffect - jclass clazzDesc; // AudioEffect.Descriptor class - jmethodID midDescCstor; // AudioEffect.Descriptor class constructor }; static fields_t fields; @@ -226,7 +227,6 @@ android_media_AudioEffect_native_init(JNIEnv *env) ALOGV("android_media_AudioEffect_native_init"); fields.clazzEffect = NULL; - fields.clazzDesc = NULL; // Get the AudioEffect class jclass clazz = env->FindClass(kClassPathName); @@ -263,23 +263,6 @@ android_media_AudioEffect_native_init(JNIEnv *env) ALOGE("Can't find AudioEffect.%s", "mJniData"); return; } - - clazz = env->FindClass("android/media/audiofx/AudioEffect$Descriptor"); - if (clazz == NULL) { - ALOGE("Can't find android/media/audiofx/AudioEffect$Descriptor class"); - return; - } - fields.clazzDesc = (jclass)env->NewGlobalRef(clazz); - - fields.midDescCstor - = env->GetMethodID( - fields.clazzDesc, - "<init>", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - if (fields.midDescCstor == NULL) { - ALOGE("Can't find android/media/audiofx/AudioEffect$Descriptor class constructor"); - return; - } } @@ -297,12 +280,6 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t const char *uuidStr = NULL; effect_descriptor_t desc; jobject jdesc; - char str[EFFECT_STRING_LEN_MAX]; - jstring jdescType; - jstring jdescUuid; - jstring jdescConnect; - jstring jdescName; - jstring jdescImplementor; ScopedUtfChars opPackageNameStr(env, opPackageName); @@ -394,41 +371,12 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t // get the effect descriptor desc = lpAudioEffect->descriptor(); - AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX); - jdescType = env->NewStringUTF(str); - - AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX); - jdescUuid = env->NewStringUTF(str); - - if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { - jdescConnect = env->NewStringUTF("Auxiliary"); - } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) { - jdescConnect = env->NewStringUTF("Pre Processing"); - } else { - jdescConnect = env->NewStringUTF("Insert"); - } - - jdescName = env->NewStringUTF(desc.name); - jdescImplementor = env->NewStringUTF(desc.implementor); - - jdesc = env->NewObject(fields.clazzDesc, - fields.midDescCstor, - jdescType, - jdescUuid, - jdescConnect, - jdescName, - jdescImplementor); - env->DeleteLocalRef(jdescType); - env->DeleteLocalRef(jdescUuid); - env->DeleteLocalRef(jdescConnect); - env->DeleteLocalRef(jdescName); - env->DeleteLocalRef(jdescImplementor); - if (jdesc == NULL) { - ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)"); + if (convertAudioEffectDescriptorFromNative(env, &jdesc, &desc) != AUDIO_JAVA_SUCCESS) { goto setup_failure; } env->SetObjectArrayElement(javadesc, 0, jdesc); + env->DeleteLocalRef(jdesc); setAudioEffect(env, thiz, lpAudioEffect); @@ -729,23 +677,16 @@ static jobjectArray android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz __unused) { effect_descriptor_t desc; - char str[EFFECT_STRING_LEN_MAX]; uint32_t totalEffectsCount = 0; uint32_t returnedEffectsCount = 0; uint32_t i = 0; - jstring jdescType; - jstring jdescUuid; - jstring jdescConnect; - jstring jdescName; - jstring jdescImplementor; - jobject jdesc; jobjectArray ret; if (AudioEffect::queryNumberEffects(&totalEffectsCount) != NO_ERROR) { return NULL; } - jobjectArray temp = env->NewObjectArray(totalEffectsCount, fields.clazzDesc, NULL); + jobjectArray temp = env->NewObjectArray(totalEffectsCount, audioEffectDescriptorClass(), NULL); if (temp == NULL) { return temp; } @@ -757,49 +698,18 @@ android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz __unused goto queryEffects_failure; } - if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { - jdescConnect = env->NewStringUTF("Auxiliary"); - } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_INSERT) { - jdescConnect = env->NewStringUTF("Insert"); - } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) { - jdescConnect = env->NewStringUTF("Pre Processing"); - } else { + jobject jdesc; + if (convertAudioEffectDescriptorFromNative(env, &jdesc, &desc) != AUDIO_JAVA_SUCCESS) { continue; } - - AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX); - jdescType = env->NewStringUTF(str); - - AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX); - jdescUuid = env->NewStringUTF(str); - - jdescName = env->NewStringUTF(desc.name); - jdescImplementor = env->NewStringUTF(desc.implementor); - - jdesc = env->NewObject(fields.clazzDesc, - fields.midDescCstor, - jdescType, - jdescUuid, - jdescConnect, - jdescName, - jdescImplementor); - env->DeleteLocalRef(jdescType); - env->DeleteLocalRef(jdescUuid); - env->DeleteLocalRef(jdescConnect); - env->DeleteLocalRef(jdescName); - env->DeleteLocalRef(jdescImplementor); - if (jdesc == NULL) { - ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)"); - goto queryEffects_failure; - } - env->SetObjectArrayElement(temp, returnedEffectsCount++, jdesc); - } + env->DeleteLocalRef(jdesc); + } if (returnedEffectsCount == 0) { goto queryEffects_failure; } - ret = env->NewObjectArray(returnedEffectsCount, fields.clazzDesc, NULL); + ret = env->NewObjectArray(returnedEffectsCount, audioEffectDescriptorClass(), NULL); if (ret == NULL) { goto queryEffects_failure; } @@ -835,51 +745,11 @@ android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz _ } ALOGV("queryDefaultPreProcessing() got %d effects", numEffects); - jobjectArray ret = env->NewObjectArray(numEffects, fields.clazzDesc, NULL); - if (ret == NULL) { - return ret; - } - - char str[EFFECT_STRING_LEN_MAX]; - jstring jdescType; - jstring jdescUuid; - jstring jdescConnect; - jstring jdescName; - jstring jdescImplementor; - jobject jdesc; - - for (uint32_t i = 0; i < numEffects; i++) { - - AudioEffect::guidToString(&descriptors[i].type, str, EFFECT_STRING_LEN_MAX); - jdescType = env->NewStringUTF(str); - AudioEffect::guidToString(&descriptors[i].uuid, str, EFFECT_STRING_LEN_MAX); - jdescUuid = env->NewStringUTF(str); - jdescConnect = env->NewStringUTF("Pre Processing"); - jdescName = env->NewStringUTF(descriptors[i].name); - jdescImplementor = env->NewStringUTF(descriptors[i].implementor); - - jdesc = env->NewObject(fields.clazzDesc, - fields.midDescCstor, - jdescType, - jdescUuid, - jdescConnect, - jdescName, - jdescImplementor); - env->DeleteLocalRef(jdescType); - env->DeleteLocalRef(jdescUuid); - env->DeleteLocalRef(jdescConnect); - env->DeleteLocalRef(jdescName); - env->DeleteLocalRef(jdescImplementor); - if (jdesc == NULL) { - ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)"); - env->DeleteLocalRef(ret); - return NULL; - } + std::vector<effect_descriptor_t> descVector(descriptors.get(), descriptors.get() + numEffects); - env->SetObjectArrayElement(ret, i, jdesc); - } - - return ret; + jobjectArray ret; + convertAudioEffectDescriptorVectorFromNative(env, &ret, descVector); + return ret; } // ---------------------------------------------------------------------------- diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index b7d7b0339376..45de36ed3e4d 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -440,6 +440,7 @@ static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) if (lpVisualizer == 0) { return; } + lpVisualizer->release(); } // delete the JNI data VisualizerJniStorage* lpJniStorage = diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 96113d68c9b8..537aed498cd2 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -235,6 +235,10 @@ LIBANDROID { android_getaddrinfofornetwork; # introduced=23 android_setprocnetwork; # introduced=23 android_setsocknetwork; # introduced=23 + android_res_cancel; # introduced=29 + android_res_nquery; # introduced=29 + android_res_nresult; # introduced=29 + android_res_nsend; # introduced=29 local: *; }; diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt index 9b5a5a1f4b52..be3531da462d 100644 --- a/native/android/libandroid_net.map.txt +++ b/native/android/libandroid_net.map.txt @@ -1,10 +1,15 @@ -# These functions have been part of the NDK since API 24. # They are also all available to vendor code. LIBANDROID_NET { global: + # These functions have been part of the NDK since API 24. + android_getaddrinfofornetwork; # vndk android_setsocknetwork; # vndk android_setprocnetwork; # vndk - android_getaddrinfofornetwork; # vndk + # These functions have been part of the NDK since API 29. + android_res_cancel; # vndk + android_res_nquery; # vndk + android_res_nresult; # vndk + android_res_nsend; # vndk local: *; }; diff --git a/native/android/net.c b/native/android/net.c index 60296a7bd00c..e32b7875b4e7 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -83,3 +83,31 @@ int android_getaddrinfofornetwork(net_handle_t network, return android_getaddrinfofornet(node, service, hints, netid, 0, res); } + +int android_res_nquery(net_handle_t network, + const char *dname, int ns_class, int ns_type) { + unsigned netid; + if (!getnetidfromhandle(network, &netid)) { + return -ENONET; + } + + return resNetworkQuery(netid, dname, ns_class, ns_type); +} + +int android_res_nresult(int fd, int *rcode, unsigned char *answer, int anslen) { + return resNetworkResult(fd, rcode, answer, anslen); +} + +int android_res_nsend(net_handle_t network, + const unsigned char *msg, int msglen) { + unsigned netid; + if (!getnetidfromhandle(network, &netid)) { + return -ENONET; + } + + return resNetworkSend(netid, msg, msglen); +} + +void android_res_cancel(int nsend_fd) { + resNetworkCancel(nsend_fd); +} diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp index f244f9f88684..74d6605a1ffb 100644 --- a/packages/CarSystemUI/Android.bp +++ b/packages/CarSystemUI/Android.bp @@ -26,6 +26,7 @@ android_app { ], static_libs: [ + "CarNotificationLib", "SystemUI-core", "SystemUIPluginLib", "SystemUISharedLib", diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index 452d61df5322..572737f92370 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -28,4 +28,31 @@ <bool name="config_enableRightNavigationBar">false</bool> <bool name="config_enableBottomNavigationBar">true</bool> + <!-- SystemUI Services: The classes of the stuff to start. This is duplicated from core + SystemUi b/c it can't be overlayed at this level for now + --> + <string-array name="config_systemUIServiceComponents" translatable="false"> + <item>com.android.systemui.Dependency</item> + <item>com.android.systemui.util.NotificationChannels</item> + <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item> + <item>com.android.systemui.keyguard.KeyguardViewMediator</item> + <item>com.android.systemui.recents.Recents</item> + <item>com.android.systemui.volume.VolumeUI</item> + <item>com.android.systemui.stackdivider.Divider</item> + <item>com.android.systemui.SystemBars</item> + <item>com.android.systemui.usb.StorageNotification</item> + <item>com.android.systemui.power.PowerUI</item> + <item>com.android.systemui.media.RingtonePlayer</item> + <item>com.android.systemui.keyboard.KeyboardUI</item> + <item>com.android.systemui.pip.PipUI</item> + <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> + <item>@string/config_systemUIVendorServiceComponent</item> + <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> + <item>com.android.systemui.LatencyTester</item> + <item>com.android.systemui.globalactions.GlobalActionsComponent</item> + <item>com.android.systemui.ScreenDecorations</item> + <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item> + <item>com.android.systemui.SliceBroadcastRelayHandler</item> + <item>com.android.systemui.notifications.NotificationsUI</item> + </string-array> </resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java new file mode 100644 index 000000000000..cb92c4259504 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notifications; + +import android.car.Car; +import android.car.CarNotConnectedException; +import android.car.drivingstate.CarUxRestrictionsManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.android.car.notification.CarNotificationListener; +import com.android.car.notification.CarUxRestrictionManagerWrapper; +import com.android.car.notification.NotificationViewController; +import com.android.car.notification.PreprocessingManager; +import com.android.systemui.R; +import com.android.systemui.SystemUI; + +/** + * Standalone SystemUI for displaying Notifications that have been designed to be used in the car + */ +public class NotificationsUI extends SystemUI { + + private static final String TAG = "NotificationsUI"; + private CarNotificationListener mCarNotificationListener; + private CarUxRestrictionsManager mCarUxRestrictionsManager; + private Car mCar; + private ViewGroup mCarNotificationWindow; + private NotificationViewController mNotificationViewController; + private boolean mIsShowing; + private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper = + new CarUxRestrictionManagerWrapper(); + + /** + * Inits the window that hosts the notifications and establishes the connections + * to the car related services. + */ + @Override + public void start() { + WindowManager windowManager = + (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mCarNotificationListener = new CarNotificationListener(); + mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper); + mCar = Car.createCar(mContext, mCarConnectionListener); + mCar.connect(); + + + mCarNotificationWindow = (ViewGroup) View.inflate(mContext, + R.layout.navigation_bar_window, null); + View.inflate(mContext, + com.android.car.notification.R.layout.notification_center_activity, + mCarNotificationWindow); + mCarNotificationWindow.findViewById( + com.android.car.notification.R.id.exit_button_container) + .setOnClickListener(v -> toggleShowingCarNotifications()); + + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + layoutParams.setTitle("Car Notification Window"); + // start in the hidden state + mCarNotificationWindow.setVisibility(View.GONE); + windowManager.addView(mCarNotificationWindow, layoutParams); + mNotificationViewController = new NotificationViewController( + mCarNotificationWindow + .findViewById(com.android.car.notification.R.id.notification_view), + PreprocessingManager.getInstance(mContext), + mCarNotificationListener, + mCarUxRestrictionManagerWrapper + ); + // Add to the SystemUI component registry + putComponent(NotificationsUI.class, this); + } + + /** + * Connection callback to establish UX Restrictions + */ + private ServiceConnection mCarConnectionListener = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + try { + mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager( + Car.CAR_UX_RESTRICTION_SERVICE); + mCarUxRestrictionManagerWrapper + .setCarUxRestrictionsManager(mCarUxRestrictionsManager); + PreprocessingManager preprocessingManager = PreprocessingManager.getInstance( + mContext); + preprocessingManager + .setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); + } catch (CarNotConnectedException e) { + Log.e(TAG, "Car not connected in CarConnectionListener", e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.e(TAG, "Car service disconnected unexpectedly"); + } + }; + + /** + * Toggles the visiblity of the notifications + */ + public void toggleShowingCarNotifications() { + if (mCarNotificationWindow.getVisibility() == View.VISIBLE) { + closeCarNotifications(); + return; + } + openCarNotifications(); + } + + /** + * Hides the notifications + */ + public void closeCarNotifications() { + mCarNotificationWindow.setVisibility(View.GONE); + mNotificationViewController.disable(); + mIsShowing = false; + } + + /** + * Sets the notifications to visible + */ + public void openCarNotifications() { + mCarNotificationWindow.setVisibility(View.VISIBLE); + mNotificationViewController.enable(); + mIsShowing = true; + } + + /** + * Returns {@code true} if notifications are currently on the screen + */ + public boolean isShowing() { + return mIsShowing; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java index 81f7846b357d..0cba351a8a9c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java @@ -21,7 +21,6 @@ import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; -import com.android.keyguard.AlphaOptimizedImageButton; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -34,7 +33,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController; */ class CarNavigationBarView extends LinearLayout { private View mNavButtons; - private AlphaOptimizedImageButton mNotificationsButton; + private CarFacetButton mNotificationsButton; private CarStatusBar mCarStatusBar; private Context mContext; private View mLockScreenButtons; @@ -71,7 +70,7 @@ class CarNavigationBarView extends LinearLayout { } protected void onNotificationsClick(View v) { - mCarStatusBar.togglePanel(); + mCarStatusBar.toggleCarNotifications(); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 2d90f8f0afd9..5da236ceb211 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -35,6 +35,7 @@ import com.android.systemui.R; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.notifications.NotificationsUI; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.car.CarQSFragment; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -587,4 +588,9 @@ public class CarStatusBar extends StatusBar implements private Drawable getDefaultWallpaper() { return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper); } + + public void toggleCarNotifications() { + getComponent(NotificationsUI.class).toggleShowingCarNotifications(); + } + } diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml index ff70e9712bcc..010a810cd791 100644 --- a/packages/ExtServices/AndroidManifest.xml +++ b/packages/ExtServices/AndroidManifest.xml @@ -63,6 +63,13 @@ android:resource="@array/autofill_field_classification_available_algorithms" /> </service> + <service android:name=".sms.FinancialSmsServiceImpl" + android:permission="android.permission.BIND_FINANCIAL_SMS_SERVICE"> + <intent-filter> + <action android:name="android.service.sms.action.FINANCIAL_SERVICE_INTENT" /> + </intent-filter> + </service> + <library android:name="android.ext.services"/> </application> diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index 6f2b6c9dafd4..38df9b0a6fdc 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -18,6 +18,7 @@ package android.ext.services.notification; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.Person; import android.app.RemoteAction; import android.content.Context; import android.os.Bundle; @@ -31,8 +32,14 @@ import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.List; import java.util.stream.Collectors; @@ -50,6 +57,8 @@ public class SmartActionsHelper { private static final int MAX_ACTIONS_PER_LINK = 1; private static final int MAX_SMART_ACTIONS = 3; private static final int MAX_SUGGESTED_REPLIES = 3; + // TODO: Make this configurable. + private static final int MAX_MESSAGES_TO_EXTRACT = 5; private static final ConversationActions.TypeConfig TYPE_CONFIG = new ConversationActions.TypeConfig.Builder().setIncludedTypes( @@ -64,9 +73,6 @@ public class SmartActionsHelper { /** * Adds action adjustments based on the notification contents. - * - * TODO: Once we have a API in {@link TextClassificationManager} to predict smart actions - * from notification text / message, we can replace most of the code here by consuming that API. */ @NonNull ArrayList<Notification.Action> suggestActions(@Nullable Context context, @@ -84,9 +90,13 @@ public class SmartActionsHelper { if (tcm == null) { return EMPTY_ACTION_LIST; } + List<ConversationActions.Message> messages = extractMessages(entry.getNotification()); + if (messages.isEmpty()) { + return EMPTY_ACTION_LIST; + } + // TODO: Move to TextClassifier.suggestConversationActions once it is ready. return suggestActionsFromText( - tcm, - getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS); + tcm, messages.get(messages.size() - 1).getText(), MAX_SMART_ACTIONS); } ArrayList<CharSequence> suggestReplies(@Nullable Context context, @@ -104,14 +114,12 @@ public class SmartActionsHelper { if (tcm == null) { return EMPTY_REPLY_LIST; } - CharSequence text = getMostSalientActionText(entry.getNotification()); - ConversationActions.Message message = - new ConversationActions.Message.Builder() - .setText(text) - .build(); - + List<ConversationActions.Message> messages = extractMessages(entry.getNotification()); + if (messages.isEmpty()) { + return EMPTY_REPLY_LIST; + } ConversationActions.Request request = - new ConversationActions.Request.Builder(Collections.singletonList(message)) + new ConversationActions.Request.Builder(messages) .setMaxSuggestions(MAX_SUGGESTED_REPLIES) .setHints(HINTS) .setTypeConfig(TYPE_CONFIG) @@ -140,10 +148,6 @@ public class SmartActionsHelper { if (!Process.myUserHandle().equals(entry.getSbn().getUser())) { return false; } - if (notification.actions != null - && notification.actions.length >= Notification.MAX_ACTION_BUTTONS) { - return false; - } if ((notification.flags & FLAG_MASK_INELGIBILE_FOR_ACTIONS) != 0) { return false; } @@ -176,21 +180,41 @@ public class SmartActionsHelper { /** Returns the text most salient for action extraction in a notification. */ @Nullable - private CharSequence getMostSalientActionText(@NonNull Notification notification) { - /* If it's messaging style, use the most recent message. */ - // TODO: Use the last few X messages instead and take the Person object into consideration. + private List<ConversationActions.Message> extractMessages(@NonNull Notification notification) { Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); - if (messages != null && messages.length != 0) { - Bundle lastMessage = (Bundle) messages[messages.length - 1]; - CharSequence lastMessageText = - lastMessage.getCharSequence(Notification.MessagingStyle.Message.KEY_TEXT); - if (!TextUtils.isEmpty(lastMessageText)) { - return lastMessageText; + if (messages == null || messages.length == 0) { + return Arrays.asList(new ConversationActions.Message.Builder( + ConversationActions.Message.PERSON_USER_REMOTE) + .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT)) + .build()); + } + Person localUser = notification.extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON); + Deque<ConversationActions.Message> extractMessages = new ArrayDeque<>(); + for (int i = messages.length - 1; i >= 0; i--) { + Notification.MessagingStyle.Message message = + Notification.MessagingStyle.Message.getMessageFromBundle((Bundle) messages[i]); + if (message == null) { + continue; + } + Person senderPerson = message.getSenderPerson(); + // Skip encoding once the sender is missing as it is important to distinguish + // local user and remote user when generating replies. + if (senderPerson == null) { + break; + } + Person author = localUser != null && localUser.equals(senderPerson) + ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson; + extractMessages.push(new ConversationActions.Message.Builder(author) + .setText(message.getText()) + .setReferenceTime( + ZonedDateTime.ofInstant(Instant.ofEpochMilli(message.getTimestamp()), + ZoneOffset.systemDefault())) + .build()); + if (extractMessages.size() >= MAX_MESSAGES_TO_EXTRACT) { + break; } } - - // Fall back to using the normal text. - return notification.extras.getCharSequence(Notification.EXTRA_TEXT); + return new ArrayList<>(extractMessages); } /** Returns a list of actions to act on entities in a given piece of text. */ diff --git a/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java new file mode 100644 index 000000000000..ab71802102ae --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.ext.services.sms; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.database.Cursor; +import android.database.CursorWindow; +import android.net.Uri; +import android.os.Bundle; +import android.service.sms.FinancialSmsService; +import android.util.Log; + +import java.util.ArrayList; +/** + * Service to provide financial apps access to sms messages. + */ +public class FinancialSmsServiceImpl extends FinancialSmsService { + + private static final String TAG = "FinancialSmsServiceImpl"; + private static final String KEY_COLUMN_NAMES = "column_names"; + + @Nullable + @Override + public CursorWindow onGetSmsMessages(@NonNull Bundle params) { + ArrayList<String> columnNames = params.getStringArrayList(KEY_COLUMN_NAMES); + if (columnNames == null || columnNames.size() <= 0) { + return null; + } + + Uri inbox = Uri.parse("content://sms/inbox"); + + try (Cursor cursor = getContentResolver().query(inbox, null, null, null, null); + CursorWindow window = new CursorWindow("FinancialSmsMessages")) { + int messageCount = cursor.getCount(); + if (messageCount > 0 && cursor.moveToFirst()) { + window.setNumColumns(columnNames.size()); + for (int row = 0; row < messageCount; row++) { + if (!window.allocRow()) { + Log.e(TAG, "CursorWindow ran out of memory."); + return null; + } + for (int col = 0; col < columnNames.size(); col++) { + String columnName = columnNames.get(col); + int inboxColumnIndex = cursor.getColumnIndexOrThrow(columnName); + String inboxColumnValue = cursor.getString(inboxColumnIndex); + boolean addedToCursorWindow = window.putString(inboxColumnValue, row, col); + if (!addedToCursorWindow) { + Log.e(TAG, "Failed to add:" + + inboxColumnValue + + ";column:" + + columnName); + return null; + } + } + cursor.moveToNext(); + } + } else { + Log.w(TAG, "No sms messages."); + } + return window; + } catch (Exception e) { + Log.e(TAG, "Failed to get sms messages."); + return null; + } + } +} diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java new file mode 100644 index 000000000000..60d31fca8ddb --- /dev/null +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -0,0 +1,236 @@ +/** + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.ext.services.notification; + +import static com.google.common.truth.Truth.assertAbout; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.Person; +import android.content.Context; +import android.os.Process; +import android.service.notification.StatusBarNotification; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.view.textclassifier.ConversationActions; +import android.view.textclassifier.TextClassificationManager; +import android.view.textclassifier.TextClassifier; + +import com.google.common.truth.FailureStrategy; +import com.google.common.truth.Subject; +import com.google.common.truth.SubjectFactory; + +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.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import javax.annotation.Nullable; + +@RunWith(AndroidJUnit4.class) +public class SmartActionHelperTest { + + private SmartActionsHelper mSmartActionsHelper = new SmartActionsHelper(); + private Context mContext; + @Mock private TextClassifier mTextClassifier; + @Mock private NotificationEntry mNotificationEntry; + @Mock private StatusBarNotification mStatusBarNotification; + private Notification.Builder mNotificationBuilder; + private AssistantSettings mSettings; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = InstrumentationRegistry.getTargetContext(); + + mContext.getSystemService(TextClassificationManager.class) + .setTextClassifier(mTextClassifier); + when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) + .thenReturn(new ConversationActions(Collections.emptyList())); + + when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification); + // The notification is eligible to have smart suggestions. + when(mNotificationEntry.hasInlineReply()).thenReturn(true); + when(mNotificationEntry.isMessaging()).thenReturn(true); + when(mStatusBarNotification.getPackageName()).thenReturn("random.app"); + when(mStatusBarNotification.getUser()).thenReturn(Process.myUserHandle()); + mNotificationBuilder = new Notification.Builder(mContext, "channel"); + mSettings = AssistantSettings.createForTesting( + null, null, Process.myUserHandle().getIdentifier(), null); + mSettings.mGenerateActions = true; + mSettings.mGenerateReplies = true; + } + + @Test + public void testSuggestReplies_notMessagingApp() { + when(mNotificationEntry.isMessaging()).thenReturn(false); + ArrayList<CharSequence> textReplies = + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + assertThat(textReplies).isEmpty(); + } + + @Test + public void testSuggestReplies_noInlineReply() { + when(mNotificationEntry.hasInlineReply()).thenReturn(false); + ArrayList<CharSequence> textReplies = + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + assertThat(textReplies).isEmpty(); + } + + @Test + public void testSuggestReplies_nonMessageStyle() { + Notification notification = mNotificationBuilder.setContentText("Where are you?").build(); + when(mNotificationEntry.getNotification()).thenReturn(notification); + + List<ConversationActions.Message> messages = getMessagesInRequest(); + assertThat(messages).hasSize(1); + MessageSubject.assertThat(messages.get(0)).hasText("Where are you?"); + } + + @Test + public void testSuggestReplies_messageStyle() { + Person me = new Person.Builder().setName("Me").build(); + Person userA = new Person.Builder().setName("A").build(); + Person userB = new Person.Builder().setName("B").build(); + Notification.MessagingStyle style = + new Notification.MessagingStyle(me) + .addMessage("firstMessage", 1000, (Person) null) + .addMessage("secondMessage", 2000, me) + .addMessage("thirdMessage", 3000, userA) + .addMessage("fourthMessage", 4000, userB); + Notification notification = + mNotificationBuilder + .setContentText("You have three new messages") + .setStyle(style) + .build(); + when(mNotificationEntry.getNotification()).thenReturn(notification); + + List<ConversationActions.Message> messages = getMessagesInRequest(); + assertThat(messages).hasSize(3); + + ConversationActions.Message secondMessage = messages.get(0); + MessageSubject.assertThat(secondMessage).hasText("secondMessage"); + MessageSubject.assertThat(secondMessage) + .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL); + MessageSubject.assertThat(secondMessage) + .hasReferenceTime(createZonedDateTimeFromMsUtc(2000)); + + ConversationActions.Message thirdMessage = messages.get(1); + MessageSubject.assertThat(thirdMessage).hasText("thirdMessage"); + MessageSubject.assertThat(thirdMessage).hasPerson(userA); + MessageSubject.assertThat(thirdMessage) + .hasReferenceTime(createZonedDateTimeFromMsUtc(3000)); + + ConversationActions.Message fourthMessage = messages.get(2); + MessageSubject.assertThat(fourthMessage).hasText("fourthMessage"); + MessageSubject.assertThat(fourthMessage).hasPerson(userB); + MessageSubject.assertThat(fourthMessage) + .hasReferenceTime(createZonedDateTimeFromMsUtc(4000)); + } + + @Test + public void testSuggestReplies_messageStyle_noPerson() { + Person me = new Person.Builder().setName("Me").build(); + Notification.MessagingStyle style = + new Notification.MessagingStyle(me).addMessage("message", 1000, (Person) null); + Notification notification = + mNotificationBuilder + .setContentText("You have one new message") + .setStyle(style) + .build(); + when(mNotificationEntry.getNotification()).thenReturn(notification); + + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + + verify(mTextClassifier, never()) + .suggestConversationActions(any(ConversationActions.Request.class)); + } + + private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneOffset.systemDefault()); + } + + private List<ConversationActions.Message> getMessagesInRequest() { + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + + ArgumentCaptor<ConversationActions.Request> argumentCaptor = + ArgumentCaptor.forClass(ConversationActions.Request.class); + verify(mTextClassifier).suggestConversationActions(argumentCaptor.capture()); + ConversationActions.Request request = argumentCaptor.getValue(); + return request.getConversation(); + } + + private static final class MessageSubject + extends Subject<MessageSubject, ConversationActions.Message> { + + private static final SubjectFactory<MessageSubject, ConversationActions.Message> FACTORY = + new SubjectFactory<MessageSubject, ConversationActions.Message>() { + @Override + public MessageSubject getSubject( + @NonNull FailureStrategy failureStrategy, + @NonNull ConversationActions.Message subject) { + return new MessageSubject(failureStrategy, subject); + } + }; + + private MessageSubject( + FailureStrategy failureStrategy, @Nullable ConversationActions.Message subject) { + super(failureStrategy, subject); + } + + private void hasText(String text) { + if (!Objects.equals(text, getSubject().getText().toString())) { + failWithBadResults("has text", text, "has", getSubject().getText()); + } + } + + private void hasPerson(Person person) { + if (!Objects.equals(person, getSubject().getAuthor())) { + failWithBadResults("has author", person, "has", getSubject().getAuthor()); + } + } + + private void hasReferenceTime(ZonedDateTime referenceTime) { + if (!Objects.equals(referenceTime, getSubject().getReferenceTime())) { + failWithBadResults( + "has reference time", + referenceTime, + "has", + getSubject().getReferenceTime()); + } + } + + private static MessageSubject assertThat(ConversationActions.Message message) { + return assertAbout(FACTORY).that(message); + } + } +} diff --git a/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java new file mode 100644 index 000000000000..12575a63d0ad --- /dev/null +++ b/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.ext.services.sms; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; + +import org.junit.Test; + +/** + * Contains the base tests for FinancialSmsServiceImpl. + */ +public class FinancialSmsServiceImplTest { + + private final FinancialSmsServiceImpl mService = new FinancialSmsServiceImpl(); + + @Test + public void testOnGetSmsMessages_nullWithNoParamData() { + assertThat(mService.onGetSmsMessages(new Bundle())).isNull(); + } +} diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 18b86628ea4d..591cf7071e01 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -16,6 +16,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" /> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" /> diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml index 5ecb614e7209..2f8966c0461b 100644 --- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml +++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml @@ -36,12 +36,23 @@ style="@android:style/TextAppearance.Material.Subhead" /> <CheckBox - android:id="@+id/checkbox" + android:id="@+id/clearContributedFiles" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginStart="-8dp" android:paddingLeft="8sp" + android:visibility="gone" + style="@android:style/TextAppearance.Material.Subhead" /> + + <CheckBox + android:id="@+id/keepData" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginStart="-8dp" + android:paddingLeft="8sp" + android:visibility="gone" style="@android:style/TextAppearance.Material.Subhead" /> </LinearLayout>
\ No newline at end of file diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 1d8747a1fda6..1e0ff506cb20 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -121,6 +121,8 @@ <string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string> <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] --> <string name="uninstall_remove_contributed_files">Also remove <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of associated media files.</string> + <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] --> + <string name="uninstall_keep_data">Keep <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of app data.</string> <!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] --> <string name="uninstalling_notification_channel">Running uninstalls</string> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java index d13bb65604af..63d8c5a82519 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java @@ -52,6 +52,7 @@ public class UninstallUninstalling extends Activity implements static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"; static final String EXTRA_CLEAR_CONTRIBUTED_FILES = "com.android.packageinstaller.extra.CLEAR_CONTRIBUTED_FILES"; + static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA"; private int mUninstallId; private ApplicationInfo mAppInfo; @@ -76,6 +77,7 @@ public class UninstallUninstalling extends Activity implements false); boolean clearContributedFiles = getIntent().getBooleanExtra( EXTRA_CLEAR_CONTRIBUTED_FILES, false); + boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false); UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER); // Show dialog, which is the whole UI @@ -101,6 +103,7 @@ public class UninstallUninstalling extends Activity implements int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0; flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0; + flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0; try { ActivityThread.getPackageManager().getPackageInstaller().uninstall( diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 0fa8c9a688c7..54194491d140 100755 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -285,7 +285,7 @@ public class UninstallerActivity extends Activity { fragment.show(ft, "dialog"); } - public void startUninstallProgress(boolean clearContributedFiles) { + public void startUninstallProgress(boolean clearContributedFiles, boolean keepData) { boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager()); @@ -312,6 +312,7 @@ public class UninstallerActivity extends Activity { newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label); newIntent.putExtra(UninstallUninstalling.EXTRA_CLEAR_CONTRIBUTED_FILES, clearContributedFiles); + newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData); newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback); if (returnResult) { @@ -362,6 +363,7 @@ public class UninstallerActivity extends Activity { int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0; flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0; + flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0; ActivityThread.getPackageManager().getPackageInstaller().uninstall( new VersionedPackage(mDialogInfo.appInfo.packageName, diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index e4e12759211d..499da758739e 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -16,21 +16,30 @@ package com.android.packageinstaller.handheld; +import static android.os.storage.StorageManager.convert; import static android.text.format.Formatter.formatFileSize; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; +import android.app.usage.StorageStats; +import android.app.usage.StorageStatsManager; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; +import android.os.storage.StorageVolume; import android.provider.MediaStore; +import android.util.Log; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.TextView; @@ -43,25 +52,120 @@ import java.util.List; public class UninstallAlertDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { + private static final String LOG_TAG = UninstallAlertDialogFragment.class.getSimpleName(); - private CheckBox mClearContributedFiles; + private @Nullable CheckBox mClearContributedFiles; + private @Nullable CheckBox mKeepData; /** - * Get number of bytes of the combined files contributed by the package. + * Get number of bytes of the files contributed by the package. * - * @param pkg The package that might have contibuted files. + * @param pkg The package that might have contributed files. * @param user The user the package belongs to. * * @return The number of bytes. */ - private long getContributedMediaSize(@NonNull String pkg, @NonNull UserHandle user) { + private long getContributedMediaSizeForUser(@NonNull String pkg, @NonNull UserHandle user) { try { return MediaStore.getContributedMediaSize(getContext(), pkg, user); } catch (IOException e) { + Log.e(LOG_TAG, "Cannot determine amount of contributes files for " + pkg + + " (user " + user + ")", e); return 0; } } + /** + * Get number of bytes of the files contributed by the package. + * + * @param pkg The package that might have contributed files. + * @param user The user the package belongs to or {@code null} if files of all users should be + * counted. + * + * @return The number of bytes. + */ + private long getContributedMediaSize(@NonNull String pkg, @Nullable UserHandle user) { + UserManager userManager = getContext().getSystemService(UserManager.class); + + long contributedFileSize = 0; + + if (user == null) { + List<UserInfo> users = userManager.getUsers(); + + int numUsers = users.size(); + for (int i = 0; i < numUsers; i++) { + contributedFileSize += getContributedMediaSizeForUser(pkg, + UserHandle.of(users.get(i).id)); + } + } else { + contributedFileSize = getContributedMediaSizeForUser(pkg, user); + } + + return contributedFileSize; + } + + /** + * Get number of bytes of the app data of the package. + * + * @param pkg The package that might have app data. + * @param user The user the package belongs to + * + * @return The number of bytes. + */ + private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) { + StorageManager storageManager = getContext().getSystemService(StorageManager.class); + StorageStatsManager storageStatsManager = + getContext().getSystemService(StorageStatsManager.class); + + List<StorageVolume> volumes = storageManager.getStorageVolumes(); + long appDataSize = 0; + + int numVolumes = volumes.size(); + for (int i = 0; i < numVolumes; i++) { + StorageStats stats; + try { + stats = storageStatsManager.queryStatsForPackage(convert(volumes.get(i).getUuid()), + pkg, user); + } catch (PackageManager.NameNotFoundException | IOException e) { + Log.e(LOG_TAG, "Cannot determine amount of app data for " + pkg + " on " + + volumes.get(i) + " (user " + user + ")", e); + continue; + } + + appDataSize += stats.getDataBytes(); + } + + return appDataSize; + } + + /** + * Get number of bytes of the app data of the package. + * + * @param pkg The package that might have app data. + * @param user The user the package belongs to or {@code null} if files of all users should be + * counted. + * + * @return The number of bytes. + */ + private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) { + UserManager userManager = getContext().getSystemService(UserManager.class); + + long appDataSize = 0; + + if (user == null) { + List<UserInfo> users = userManager.getUsers(); + + int numUsers = users.size(); + for (int i = 0; i < numUsers; i++) { + appDataSize += getAppDataSizeForUser(pkg, UserHandle.of(users.get(i).id)); + } + } else { + appDataSize = getAppDataSizeForUser(pkg, user); + } + + return appDataSize; + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final PackageManager pm = getActivity().getPackageManager(); @@ -108,30 +212,46 @@ public class UninstallAlertDialogFragment extends DialogFragment implements dialogBuilder.setNegativeButton(android.R.string.cancel, this); String pkg = dialogInfo.appInfo.packageName; - long contributedFileSize = 0; - if (dialogInfo.allUsers) { - List<UserInfo> users = userManager.getUsers(); + long contributedFileSize = getContributedMediaSize(pkg, + dialogInfo.allUsers ? null : dialogInfo.user); - int numUsers = users.size(); - for (int i = 0; i < numUsers; i++) { - UserHandle user = UserHandle.of(users.get(i).id); + boolean suggestToKeepAppData; + try { + PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0); - contributedFileSize += getContributedMediaSize(pkg, user); - } - } else { - contributedFileSize = getContributedMediaSize(pkg, dialogInfo.user); + suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData(); + } catch (PackageManager.NameNotFoundException e) { + Log.e(LOG_TAG, "Cannot check hasFragileUserData for " + pkg, e); + suggestToKeepAppData = false; + } + + long appDataSize = 0; + if (suggestToKeepAppData) { + appDataSize = getAppDataSize(pkg, dialogInfo.allUsers ? null : dialogInfo.user); } - if (contributedFileSize == 0) { + if (contributedFileSize == 0 && appDataSize == 0) { dialogBuilder.setMessage(messageBuilder.toString()); } else { LayoutInflater inflater = getContext().getSystemService(LayoutInflater.class); ViewGroup content = (ViewGroup) inflater.inflate(R.layout.uninstall_content_view, null); ((TextView) content.requireViewById(R.id.message)).setText(messageBuilder.toString()); - mClearContributedFiles = content.requireViewById(R.id.checkbox); - mClearContributedFiles.setText(getString(R.string.uninstall_remove_contributed_files, - formatFileSize(getContext(), contributedFileSize))); + + if (contributedFileSize != 0) { + mClearContributedFiles = content.requireViewById(R.id.clearContributedFiles); + mClearContributedFiles.setVisibility(View.VISIBLE); + mClearContributedFiles.setText( + getString(R.string.uninstall_remove_contributed_files, + formatFileSize(getContext(), contributedFileSize))); + } + + if (appDataSize != 0) { + mKeepData = content.requireViewById(R.id.keepData); + mKeepData.setVisibility(View.VISIBLE); + mKeepData.setText(getString(R.string.uninstall_keep_data, + formatFileSize(getContext(), appDataSize))); + } dialogBuilder.setView(content); } @@ -143,7 +263,8 @@ public class UninstallAlertDialogFragment extends DialogFragment implements public void onClick(DialogInterface dialog, int which) { if (which == Dialog.BUTTON_POSITIVE) { ((UninstallerActivity) getActivity()).startUninstallProgress( - mClearContributedFiles != null && mClearContributedFiles.isChecked()); + mClearContributedFiles != null && mClearContributedFiles.isChecked(), + mKeepData != null && mKeepData.isChecked()); } else { ((UninstallerActivity) getActivity()).dispatchAborted(); } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java index 21d25f5b030f..ac5fd76f5bda 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java @@ -99,7 +99,7 @@ public class UninstallAlertFragment extends GuidedStepFragment { public void onGuidedActionClicked(GuidedAction action) { if (isAdded()) { if (action.getId() == GuidedAction.ACTION_ID_OK) { - ((UninstallerActivity) getActivity()).startUninstallProgress(false); + ((UninstallerActivity) getActivity()).startUninstallProgress(false, false); getActivity().finish(); } else { ((UninstallerActivity) getActivity()).dispatchAborted(); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 2823149a1585..842779d494cd 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -831,6 +831,10 @@ <!-- Toast message shown when setting a new local backup password fails due to the user not supplying the correct existing password. The phrasing here is deliberately quite general. [CHAR LIMIT=80] --> <string name="local_backup_password_toast_validation_failure">Failure setting backup password</string> + <!-- [CHAR LIMIT=30] Location mode screen, temporary summary text to show as the status of a location + setting injected by an external app while the app is being queried for the actual value --> + <string name="loading_injected_setting_summary">Loading\u2026</string> + <!-- Name of each color mode for the display. [CHAR LIMIT=40] --> <string-array name="color_mode_names"> <item>Vibrant (default)</item> diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java index 780fcbab9822..74057be8434b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java @@ -37,6 +37,7 @@ import android.os.Messenger; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.IconDrawableFactory; import android.util.Log; @@ -44,11 +45,16 @@ import android.util.Xml; import androidx.preference.Preference; +import com.android.settingslib.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -102,7 +108,7 @@ public class SettingsInjector { public SettingsInjector(Context context) { mContext = context; mSettings = new HashSet<Setting>(); - mHandler = new StatusLoadingHandler(); + mHandler = new StatusLoadingHandler(mSettings); } /** @@ -165,7 +171,7 @@ public class SettingsInjector { Log.e(TAG, "Can't get ApplicationInfo for " + setting.packageName, e); } preference.setTitle(setting.title); - preference.setSummary(null); + preference.setSummary(R.string.loading_injected_setting_summary); preference.setIcon(appIcon); preference.setOnPreferenceClickListener(new ServiceSettingClickedListener(setting)); } @@ -180,6 +186,7 @@ public class SettingsInjector { final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); final List<UserHandle> profiles = um.getUserProfiles(); ArrayList<Preference> prefs = new ArrayList<>(); + mSettings.clear(); for (UserHandle userHandle : profiles) { if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) { Iterable<InjectedSetting> settings = getSettings(userHandle); @@ -363,31 +370,28 @@ public class SettingsInjector { * SettingInjectorService}, so to reduce memory pressure we don't want to load too many at * once. */ - private final class StatusLoadingHandler extends Handler { + private static final class StatusLoadingHandler extends Handler { + /** + * References all the injected settings. + */ + WeakReference<Set<Setting>> mAllSettings; /** * Settings whose status values need to be loaded. A set is used to prevent redundant loads. */ - private Set<Setting> mSettingsToLoad = new HashSet<Setting>(); + private Deque<Setting> mSettingsToLoad = new ArrayDeque<Setting>(); /** * Settings that are being loaded now and haven't timed out. In practice this should have * zero or one elements. */ - private Set<Setting> mSettingsBeingLoaded = new HashSet<Setting>(); - - /** - * Settings that are being loaded but have timed out. If only one setting has timed out, we - * will go ahead and start loading the next setting so that one slow load won't delay the - * load of the other settings. - */ - private Set<Setting> mTimedOutSettings = new HashSet<Setting>(); - - private boolean mReloadRequested; + private Set<Setting> mSettingsBeingLoaded = new ArraySet<Setting>(); - private StatusLoadingHandler() { + public StatusLoadingHandler(Set<Setting> allSettings) { super(Looper.getMainLooper()); + mAllSettings = new WeakReference<>(allSettings); } + @Override public void handleMessage(Message msg) { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -396,20 +400,24 @@ public class SettingsInjector { // Update state in response to message switch (msg.what) { - case WHAT_RELOAD: - mReloadRequested = true; + case WHAT_RELOAD: { + final Set<Setting> allSettings = mAllSettings.get(); + if (allSettings != null) { + // Reload requested, so must reload all settings + mSettingsToLoad.clear(); + mSettingsToLoad.addAll(allSettings); + } break; + } case WHAT_RECEIVED_STATUS: final Setting receivedSetting = (Setting) msg.obj; receivedSetting.maybeLogElapsedTime(); mSettingsBeingLoaded.remove(receivedSetting); - mTimedOutSettings.remove(receivedSetting); removeMessages(WHAT_TIMEOUT, receivedSetting); break; case WHAT_TIMEOUT: final Setting timedOutSetting = (Setting) msg.obj; mSettingsBeingLoaded.remove(timedOutSetting); - mTimedOutSettings.add(timedOutSetting); if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Timed out after " + timedOutSetting.getElapsedTime() + " millis trying to get status for: " + timedOutSetting); @@ -421,37 +429,22 @@ public class SettingsInjector { // Decide whether to load additional settings based on the new state. Start by seeing // if we have headroom to load another setting. - if (mSettingsBeingLoaded.size() > 0 || mTimedOutSettings.size() > 1) { + if (mSettingsBeingLoaded.size() > 0) { // Don't load any more settings until one of the pending settings has completed. - // To reduce memory pressure, we want to be loading at most one setting (plus at - // most one timed-out setting) at a time. This means we'll be responsible for - // bringing in at most two services. + // To reduce memory pressure, we want to be loading at most one setting. if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "too many services already live for " + msg + ", " + this); } return; } - if (mReloadRequested && mSettingsToLoad.isEmpty() && mSettingsBeingLoaded.isEmpty() - && mTimedOutSettings.isEmpty()) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "reloading because idle and reload requesteed " + msg + ", " + this); - } - // Reload requested, so must reload all settings - mSettingsToLoad.addAll(mSettings); - mReloadRequested = false; - } - - // Remove the next setting to load from the queue, if any - Iterator<Setting> iter = mSettingsToLoad.iterator(); - if (!iter.hasNext()) { + if (mSettingsToLoad.isEmpty()) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "nothing left to do for " + msg + ", " + this); } return; } - Setting setting = iter.next(); - iter.remove(); + Setting setting = mSettingsToLoad.removeFirst(); // Request the status value setting.startService(); @@ -473,21 +466,48 @@ public class SettingsInjector { return "StatusLoadingHandler{" + "mSettingsToLoad=" + mSettingsToLoad + ", mSettingsBeingLoaded=" + mSettingsBeingLoaded + - ", mTimedOutSettings=" + mTimedOutSettings + - ", mReloadRequested=" + mReloadRequested + '}'; } } + private static class MessengerHandler extends Handler { + private WeakReference<Setting> mSettingRef; + private Handler mHandler; + + public MessengerHandler(Setting setting, Handler handler) { + mSettingRef = new WeakReference(setting); + mHandler = handler; + } + + @Override + public void handleMessage(Message msg) { + final Setting setting = mSettingRef.get(); + if (setting == null) { + return; + } + final Preference preference = setting.preference; + Bundle bundle = msg.getData(); + boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true); + String summary = bundle.getString(SettingInjectorService.SUMMARY_KEY, null); + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle); + } + preference.setSummary(summary); + preference.setEnabled(enabled); + mHandler.sendMessage( + mHandler.obtainMessage(WHAT_RECEIVED_STATUS, setting)); + } + } + /** * Represents an injected setting and the corresponding preference. */ protected final class Setting { - public final InjectedSetting setting; public final Preference preference; public long startMillis; + public Setting(InjectedSetting setting, Preference preference) { this.setting = setting; this.preference = preference; @@ -502,20 +522,6 @@ public class SettingsInjector { } /** - * Returns true if they both have the same {@link #setting} value. Ignores mutable - * {@link #preference} and {@link #startMillis} so that it's safe to use in sets. - */ - @Override - public boolean equals(Object o) { - return this == o || o instanceof Setting && setting.equals(((Setting) o).setting); - } - - @Override - public int hashCode() { - return setting.hashCode(); - } - - /** * Starts the service to fetch for the current status for the setting, and updates the * preference when the service replies. */ @@ -529,20 +535,7 @@ public class SettingsInjector { } return; } - Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - Bundle bundle = msg.getData(); - boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle); - } - preference.setSummary(null); - preference.setEnabled(enabled); - mHandler.sendMessage( - mHandler.obtainMessage(WHAT_RECEIVED_STATUS, Setting.this)); - } - }; + Handler handler = new MessengerHandler(this, mHandler); Messenger messenger = new Messenger(handler); Intent intent = setting.getServiceIntent(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java index 4a8ef1e5d17c..924eb047c340 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java @@ -36,12 +36,12 @@ public class ShadowActivityManager { } @Implementation - public static int getCurrentUser() { + protected static int getCurrentUser() { return sCurrentUserId; } @Implementation - public boolean switchUser(int userId) { + protected boolean switchUser(int userId) { mUserSwitchedTo = userId; return true; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java index cae74c888f0a..906dba487734 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java @@ -32,7 +32,7 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto private BluetoothProfile.ServiceListener mServiceListener; @Implementation - public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, + protected boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, int profile) { mServiceListener = listener; return true; @@ -43,7 +43,7 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto } @Implementation - public List<Integer> getSupportedProfiles() { + protected List<Integer> getSupportedProfiles() { return mSupportedProfiles; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java index 3e91641a69ae..d8fc8613d861 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java @@ -34,7 +34,7 @@ public class ShadowDefaultDialerManager { } @Implementation - public static String getDefaultDialerApplication(Context context) { + protected static String getDefaultDialerApplication(Context context) { return sDefaultDialer; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java index dd7b007ca30b..c8c4526e66ff 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java @@ -36,7 +36,7 @@ public class ShadowSmsApplication { } @Implementation - public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) { + protected static ComponentName getDefaultSmsApplication(Context context, boolean update) { return sDefaultSmsApplication; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java index a81e39501a8e..c50d646c0861 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java @@ -21,11 +21,8 @@ import android.content.Context; import android.content.pm.UserInfo; import android.os.UserManager; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; -import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.List; @@ -56,5 +53,4 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager protected List<UserInfo> getProfiles(@UserIdInt int userHandle) { return getProfiles(); } - } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java deleted file mode 100644 index 3455765ce24c..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.testutils.shadow; - -import static org.robolectric.shadow.api.Shadow.directlyOn; - -import com.android.internal.util.XmlUtils; - -import org.robolectric.Robolectric; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.util.ReflectionHelpers; - -@Implements(XmlUtils.class) -public class ShadowXmlUtils { - - @Implementation - public static final int convertValueToInt(CharSequence charSeq, int defaultValue) { - final Class<?> xmlUtilsClass = ReflectionHelpers.loadClass( - Robolectric.class.getClassLoader(), "com.android.internal.util.XmlUtils"); - try { - return directlyOn(xmlUtilsClass, "convertValueToInt", - ReflectionHelpers.ClassParameter.from(CharSequence.class, charSeq), - ReflectionHelpers.ClassParameter.from(int.class, new Integer(defaultValue))); - } catch (NumberFormatException e) { - return defaultValue; - } - } -}
\ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java index 9a169d2663de..83cc39a8a1a9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java @@ -78,8 +78,8 @@ public class UserManagerHelperRoboTest { @Test public void getForegroundUserInfo() { ShadowActivityManager.setCurrentUser(17); - when(mUserManager.getUserInfo(ShadowActivityManager.getCurrentUser())) - .thenReturn(createUserInfoForId(ShadowActivityManager.getCurrentUser())); + when(mUserManager.getUserInfo(ActivityManager.getCurrentUser())) + .thenReturn(createUserInfoForId(ActivityManager.getCurrentUser())); assertThat(mHelper.getForegroundUserInfo().id).isEqualTo(17); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java index 88fef08bfcb7..97de7ef2378a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java @@ -18,9 +18,9 @@ package com.android.settingslib.widget; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index ba69f3bb1bc2..9391737fe23c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -218,88 +218,7 @@ public class NotificationRemoteInputManager implements Dumpable { return false; } - ViewParent p = view.getParent(); - RemoteInputView riv = null; - while (p != null) { - if (p instanceof View) { - View pv = (View) p; - if (pv.isRootNamespace()) { - riv = findRemoteInputView(pv); - break; - } - } - p = p.getParent(); - } - ExpandableNotificationRow row = null; - while (p != null) { - if (p instanceof ExpandableNotificationRow) { - row = (ExpandableNotificationRow) p; - break; - } - p = p.getParent(); - } - - if (row == null) { - return false; - } - - row.setUserExpanded(true); - - if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { - final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); - if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { - mCallback.onLockedRemoteInput(row, view); - return true; - } - if (mUserManager.getUserInfo(userId).isManagedProfile() - && mKeyguardManager.isDeviceLocked(userId)) { - mCallback.onLockedWorkRemoteInput(userId, row, view); - return true; - } - } - - if (riv == null) { - riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); - if (riv == null) { - return false; - } - if (!row.getPrivateLayout().getExpandedChild().isShown()) { - mCallback.onMakeExpandedVisibleForRemoteInput(row, view); - return true; - } - } - - int width = view.getWidth(); - if (view instanceof TextView) { - // Center the reveal on the text which might be off-center from the TextView - TextView tv = (TextView) view; - if (tv.getLayout() != null) { - int innerWidth = (int) tv.getLayout().getLineWidth(0); - innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); - width = Math.min(width, innerWidth); - } - } - int cx = view.getLeft() + width / 2; - int cy = view.getTop() + view.getHeight() / 2; - int w = riv.getWidth(); - int h = riv.getHeight(); - int r = Math.max( - Math.max(cx + cy, cx + (h - cy)), - Math.max((w - cx) + cy, (w - cx) + (h - cy))); - - riv.setRevealParameters(cx, cy, r); - riv.setPendingIntent(pendingIntent); - riv.setRemoteInput(inputs, input); - riv.focusAnimated(); - - return true; - } - - private RemoteInputView findRemoteInputView(View v) { - if (v == null) { - return null; - } - return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + return activateRemoteInput(view, inputs, input, pendingIntent); } }; @@ -355,6 +274,102 @@ public class NotificationRemoteInputManager implements Dumpable { } /** + * Activates a given {@link RemoteInput} + * + * @param view The view of the action button or suggestion chip that was tapped. + * @param inputs The remote inputs that need to be sent to the app. + * @param input The remote input that needs to be activated. + * @param pendingIntent The pending intent to be sent to the app. + * @return Whether the {@link RemoteInput} was activated. + */ + public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input, + PendingIntent pendingIntent) { + + ViewParent p = view.getParent(); + RemoteInputView riv = null; + while (p != null) { + if (p instanceof View) { + View pv = (View) p; + if (pv.isRootNamespace()) { + riv = findRemoteInputView(pv); + break; + } + } + p = p.getParent(); + } + ExpandableNotificationRow row = null; + while (p != null) { + if (p instanceof ExpandableNotificationRow) { + row = (ExpandableNotificationRow) p; + break; + } + p = p.getParent(); + } + + if (row == null) { + return false; + } + + row.setUserExpanded(true); + + if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { + final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); + if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { + mCallback.onLockedRemoteInput(row, view); + return true; + } + if (mUserManager.getUserInfo(userId).isManagedProfile() + && mKeyguardManager.isDeviceLocked(userId)) { + mCallback.onLockedWorkRemoteInput(userId, row, view); + return true; + } + } + + if (riv == null) { + riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); + if (riv == null) { + return false; + } + if (!row.getPrivateLayout().getExpandedChild().isShown()) { + mCallback.onMakeExpandedVisibleForRemoteInput(row, view); + return true; + } + } + + int width = view.getWidth(); + if (view instanceof TextView) { + // Center the reveal on the text which might be off-center from the TextView + TextView tv = (TextView) view; + if (tv.getLayout() != null) { + int innerWidth = (int) tv.getLayout().getLineWidth(0); + innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); + width = Math.min(width, innerWidth); + } + } + int cx = view.getLeft() + width / 2; + int cy = view.getTop() + view.getHeight() / 2; + int w = riv.getWidth(); + int h = riv.getHeight(); + int r = Math.max( + Math.max(cx + cy, cx + (h - cy)), + Math.max((w - cx) + cy, (w - cx) + (h - cy))); + + riv.setRevealParameters(cx, cy, r); + riv.setPendingIntent(pendingIntent); + riv.setRemoteInput(inputs, input); + riv.focusAnimated(); + + return true; + } + + private RemoteInputView findRemoteInputView(View v) { + if (v == null) { + return null; + } + return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + } + + /** * Adds all the notification lifetime extenders. Each extender represents a reason for the * NotificationRemoteInputManager to keep a notification lifetime extended. */ 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 a2a11bbfd650..c7e4d340b7d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -105,10 +105,22 @@ public class NotificationPanelView extends PanelView implements private static final boolean DEBUG = false; - private static final boolean EXPAND_ON_WAKE_UP = SystemProperties.getBoolean( + /** + * If passive interrupts expand the NSSL or not + */ + private static final boolean EXPAND_ON_PASSIVE_INTERRUPT = SystemProperties.getBoolean( "persist.sysui.expand_shade_on_wake_up", true); + /** + * If the notification panel should remain collapsed when the phone wakes up, even if the user + * presses power. + */ + private static final boolean NEVER_EXPAND_WHEN_WAKING_UP = SystemProperties.getBoolean( + "persist.sysui.defer_notifications_on_lock_screen", false); + /** + * If waking up the phone should take you to SHADE_LOCKED instead of KEYGUARD + */ private static final boolean WAKE_UP_TO_SHADE = SystemProperties.getBoolean( - "persist.sysui.go_to_shade_on_wake_up", true); + "persist.sysui.go_to_shade_on_wake_up", false); /** * Fling expanding QS. @@ -2774,10 +2786,12 @@ public class NotificationPanelView extends PanelView implements } public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation, - boolean passiveInterrupted) { + boolean passivelyInterrupted) { if (dozing == mDozing) return; mDozing = dozing; - mSemiAwake = !EXPAND_ON_WAKE_UP && !mDozing && passiveInterrupted; + boolean doNotExpand = (!EXPAND_ON_PASSIVE_INTERRUPT && passivelyInterrupted) + || NEVER_EXPAND_WHEN_WAKING_UP; + mSemiAwake = doNotExpand && !mDozing; if (!mSemiAwake) { mNotificationStackScroller.setDark(mDozing, animate, wakeUpTouchLocation); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 913b2ae90cf6..4fa8321fa1e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -36,6 +36,7 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -63,6 +64,7 @@ public class SmartReplyView extends ViewGroup { private final SmartReplyConstants mConstants; private final KeyguardDismissUtil mKeyguardDismissUtil; + private final NotificationRemoteInputManager mRemoteInputManager; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); /** @@ -112,6 +114,7 @@ public class SmartReplyView extends ViewGroup { super(context, attrs); mConstants = Dependency.get(SmartReplyConstants.class); mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class); + mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext, R.dimen.smart_reply_button_max_height); @@ -242,12 +245,22 @@ public class SmartReplyView extends ViewGroup { b.setText(choice); OnDismissAction action = () -> { + // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags. + if (smartReplies.remoteInput.getEditChoicesBeforeSending() + == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) { + entry.remoteInputText = choice; + mRemoteInputManager.activateRemoteInput(b, + new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput, + smartReplies.pendingIntent); + return false; + } + smartReplyController.smartReplySent( entry, replyIndex, b.getText(), smartReplies.fromAssistant); Bundle results = new Bundle(); results.putString(smartReplies.remoteInput.getResultKey(), choice.toString()); Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - RemoteInput.addResultsToIntent(new RemoteInput[]{smartReplies.remoteInput}, intent, + RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent, results); RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE); entry.setHasSentReply(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d5deccef412d..36ca52a6e600 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2466,6 +2466,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** + * AIDL-exposed method. System only. + * Gets accessibility window id from window token. + * + * @param windowToken Window token to get accessibility window id. + * @return Accessibility window id for the window token. Returns -1 if no such token is + * registered. + */ + @Override + public int getAccessibilityWindowId(IBinder windowToken) { + synchronized (mLock) { + if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { + throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId"); + } + + return findWindowIdLocked(windowToken); + } + } + + /** * Get the recommended timeout of interactive controls and non-interactive controls. * * @return A long for pair of {@code int}s. First integer for interactive one, and second diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 854c03f128dd..08034f734bea 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -48,6 +48,7 @@ import android.content.pm.PermissionInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -1306,8 +1307,12 @@ class AlarmManagerService extends SystemService { // because kernel doesn't keep this after reboot setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY)); - // Also sure that we're booting with a halfway sensible current time - final long systemBuildTime = Environment.getRootDirectory().lastModified(); + // Ensure that we're booting with a halfway sensible current time. Use the + // most recent of Build.TIME, the root file system's timestamp, and the + // value of the ro.build.date.utc system property (which is in seconds). + final long systemBuildTime = Long.max( + 1000L * SystemProperties.getLong("ro.build.date.utc", -1L), + Long.max(Environment.getRootDirectory().lastModified(), Build.TIME)); if (mInjector.getCurrentTimeMillis() < systemBuildTime) { Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis() + ", advancing to build time " + systemBuildTime); diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java index d56492521276..8c5ee7f35027 100644 --- a/services/core/java/com/android/server/AnyMotionDetector.java +++ b/services/core/java/com/android/server/AnyMotionDetector.java @@ -26,8 +26,6 @@ import android.os.PowerManager; import android.os.SystemClock; import android.util.Slog; -import java.lang.Float; - /** * Determines if the device has been set upon a stationary object. */ @@ -140,6 +138,13 @@ public class AnyMotionDetector { } } + /** + * If we do not have an accelerometer, we are not going to collect much data. + */ + public boolean hasSensor() { + return mAccelSensor != null; + } + /* * Acquire accel data until we determine AnyMotion status. */ diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index af9d4c8c69b6..08cb7a2f5047 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -43,6 +43,7 @@ import android.net.ConnectivityManager; import android.net.INetworkPolicyManager; import android.net.NetworkInfo; import android.net.Uri; +import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; @@ -272,6 +273,7 @@ public class DeviceIdleController extends SystemService private PowerManager mPowerManager; private INetworkPolicyManager mNetworkPolicyManager; private SensorManager mSensorManager; + private final boolean mUseMotionSensor; private Sensor mMotionSensor; private LocationRequest mLocationRequest; private Intent mIdleIntent; @@ -520,9 +522,10 @@ public class DeviceIdleController extends SystemService updateConnectivityState(intent); } break; case Intent.ACTION_BATTERY_CHANGED: { + boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); + boolean plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; synchronized (DeviceIdleController.this) { - int plugged = intent.getIntExtra("plugged", 0); - updateChargingLocked(plugged != 0); + updateChargingLocked(present && plugged); } } break; case Intent.ACTION_PACKAGE_REMOVED: { @@ -1629,6 +1632,9 @@ public class DeviceIdleController extends SystemService mHandler = mInjector.getHandler(this); mAppStateTracker = mInjector.getAppStateTracker(context, FgThread.get().getLooper()); LocalServices.addService(AppStateTracker.class, mAppStateTracker); + + mUseMotionSensor = context.getResources().getBoolean( + com.android.internal.R.bool.config_autoPowerModeUseMotionSensor); } public DeviceIdleController(Context context) { @@ -1729,20 +1735,23 @@ public class DeviceIdleController extends SystemService ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class); mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE); - int sigMotionSensorId = getContext().getResources().getInteger( - com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor); - if (sigMotionSensorId > 0) { - mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true); - } - if (mMotionSensor == null && getContext().getResources().getBoolean( - com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) { - mMotionSensor = mSensorManager.getDefaultSensor( - Sensor.TYPE_WRIST_TILT_GESTURE, true); - } - if (mMotionSensor == null) { - // As a last ditch, fall back to SMD. - mMotionSensor = mSensorManager.getDefaultSensor( - Sensor.TYPE_SIGNIFICANT_MOTION, true); + + if (mUseMotionSensor) { + int sigMotionSensorId = getContext().getResources().getInteger( + com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor); + if (sigMotionSensorId > 0) { + mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true); + } + if (mMotionSensor == null && getContext().getResources().getBoolean( + com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) { + mMotionSensor = mSensorManager.getDefaultSensor( + Sensor.TYPE_WRIST_TILT_GESTURE, true); + } + if (mMotionSensor == null) { + // As a last ditch, fall back to SMD. + mMotionSensor = mSensorManager.getDefaultSensor( + Sensor.TYPE_SIGNIFICANT_MOTION, true); + } } if (getContext().getResources().getBoolean( @@ -2588,14 +2597,21 @@ public class DeviceIdleController extends SystemService mState = STATE_SENSING; if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING."); EventLogTags.writeDeviceIdle(mState, reason); - scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT); cancelLocatingLocked(); - mNotMoving = false; mLocated = false; mLastGenericLocation = null; mLastGpsLocation = null; - mAnyMotionDetector.checkForAnyMotion(); - break; + + // If we have an accelerometer, wait to find out whether we are moving. + if (mUseMotionSensor && mAnyMotionDetector.hasSensor()) { + scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT); + mNotMoving = false; + mAnyMotionDetector.checkForAnyMotion(); + break; + } + + mNotMoving = true; + // Otherwise, fall through and check this off the list of requirements. case STATE_SENSING: cancelSensingTimeoutAlarmLocked(); mState = STATE_LOCATING; @@ -2893,9 +2909,12 @@ public class DeviceIdleController extends SystemService void scheduleAlarmLocked(long delay, boolean idleUntil) { if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")"); - if (mMotionSensor == null && !(mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE - || mState == STATE_IDLE_MAINTENANCE)) { - // If there is no motion sensor on this device, then we won't schedule + + if (mUseMotionSensor && mMotionSensor == null + && mState != STATE_QUICK_DOZE_DELAY + && mState != STATE_IDLE + && mState != STATE_IDLE_MAINTENANCE) { + // If there is no motion sensor on this device, but we need one, then we won't schedule // alarms, because we can't determine if the device is not moving. This effectively // turns off normal execution of device idling, although it is still possible to // manually poke it by pretending like the alarm is going off. @@ -3765,13 +3784,20 @@ public class DeviceIdleController extends SystemService pw.print(" mLightEnabled="); pw.print(mLightEnabled); pw.print(" mDeepEnabled="); pw.println(mDeepEnabled); pw.print(" mForceIdle="); pw.println(mForceIdle); - pw.print(" mMotionSensor="); pw.println(mMotionSensor); + pw.print(" mUseMotionSensor="); pw.print(mUseMotionSensor); + if (mUseMotionSensor) { + pw.print(" mMotionSensor="); pw.println(mMotionSensor); + } else { + pw.println(); + } pw.print(" mScreenOn="); pw.println(mScreenOn); pw.print(" mScreenLocked="); pw.println(mScreenLocked); pw.print(" mNetworkConnected="); pw.println(mNetworkConnected); pw.print(" mCharging="); pw.println(mCharging); pw.print(" mMotionActive="); pw.println(mMotionListener.active); - pw.print(" mNotMoving="); pw.println(mNotMoving); + if (mUseMotionSensor) { + pw.print(" mNotMoving="); pw.println(mNotMoving); + } pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps="); pw.print(mHasGps); pw.print(" mHasNetwork="); pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated); diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index fe9f1b5e82d6..a9c38bcf2532 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -3,3 +3,4 @@ per-file ConnectivityService.java,NetworkManagementService.java,NsdService.java # Vibrator / Threads per-file VibratorService.java, DisplayThread.java = michaelwr@google.com +per-file VibratorService.java, DisplayThread.java = ogunwale@google.com diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 4b092b299029..5901ece7d89e 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -16,6 +16,11 @@ package com.android.server; +import static android.Manifest.permission.INSTALL_PACKAGES; +import static android.Manifest.permission.WRITE_MEDIA_STORAGE; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED; @@ -51,6 +56,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -116,6 +122,7 @@ import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.IAppOpsService; import com.android.internal.os.AppFuseMount; import com.android.internal.os.BackgroundThread; import com.android.internal.os.FuseUnavailableMountException; @@ -453,6 +460,9 @@ class StorageManagerService extends IStorageManager.Stub private UserManagerInternal mUmInternal; private ActivityManagerInternal mAmInternal; + private IPackageManager mIPackageManager; + private IAppOpsService mIAppOpsService; + private final Callbacks mCallbacks; private final LockPatternUtils mLockPatternUtils; @@ -1570,6 +1580,10 @@ class StorageManagerService extends IStorageManager.Stub .registerScreenObserver(this); mSystemReady = true; + mIPackageManager = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + mIAppOpsService = IAppOpsService.Stub.asInterface( + ServiceManager.getService(Context.APP_OPS_SERVICE)); mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); } @@ -3117,7 +3131,8 @@ class StorageManagerService extends IStorageManager.Stub throw new SecurityException("Shady looking path " + path); } - if (!mAmInternal.isAppStorageSandboxed(pid, uid)) { + final int mountMode = mAmInternal.getStorageMountMode(pid, uid); + if (mountMode == Zygote.MOUNT_EXTERNAL_FULL) { return path; } @@ -3126,6 +3141,11 @@ class StorageManagerService extends IStorageManager.Stub final String device = m.group(1); final String devicePath = m.group(2); + if (mountMode == Zygote.MOUNT_EXTERNAL_INSTALLER + && devicePath.startsWith("Android/obb/")) { + return path; + } + // Does path belong to any packages belonging to this UID? If so, // they get to go straight through to legacy paths. final String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid); @@ -3477,6 +3497,27 @@ class StorageManagerService extends IStorageManager.Stub } } + private int getMountMode(int uid, String packageName) { + try { + if (Process.isIsolated(uid)) { + return Zygote.MOUNT_EXTERNAL_NONE; + } + if (mIPackageManager.checkUidPermission(WRITE_MEDIA_STORAGE, uid) + == PERMISSION_GRANTED) { + return Zygote.MOUNT_EXTERNAL_FULL; + } else if (mIPackageManager.checkUidPermission(INSTALL_PACKAGES, uid) + == PERMISSION_GRANTED || mIAppOpsService.checkOperation( + OP_REQUEST_INSTALL_PACKAGES, uid, packageName) == MODE_ALLOWED) { + return Zygote.MOUNT_EXTERNAL_INSTALLER; + } else { + return Zygote.MOUNT_EXTERNAL_WRITE; + } + } catch (RemoteException e) { + // Should not happen + } + return Zygote.MOUNT_EXTERNAL_NONE; + } + private static class Callbacks extends Handler { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; @@ -3718,6 +3759,9 @@ class StorageManagerService extends IStorageManager.Stub @Override public int getExternalStorageMountMode(int uid, String packageName) { + if (ENABLE_ISOLATED_STORAGE) { + return getMountMode(uid, packageName); + } // No locking - CopyOnWriteArrayList int mountMode = Integer.MAX_VALUE; for (ExternalStorageMountPolicy policy : mPolicies) { @@ -3754,6 +3798,9 @@ class StorageManagerService extends IStorageManager.Stub if (uid == Process.SYSTEM_UID) { return true; } + if (ENABLE_ISOLATED_STORAGE) { + return getMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE; + } // No locking - CopyOnWriteArrayList for (ExternalStorageMountPolicy policy : mPolicies) { final boolean policyHasStorage = policy.hasExternalStorage(uid, packageName); diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index bbb1d13bdcdc..9f353a80a5be 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -16,7 +16,9 @@ package com.android.server; +import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.IUidObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -111,6 +113,7 @@ public class VibratorService extends IVibratorService.Stub private final boolean mSupportsAmplitudeControl; private final int mDefaultVibrationAmplitude; private final SparseArray<VibrationEffect> mFallbackEffects; + private final SparseArray<Integer> mProcStatesCache = new SparseArray(); private final WorkSource mTmpWorkSource = new WorkSource(); private final Handler mH = new Handler(); private final Object mLock = new Object(); @@ -147,6 +150,25 @@ public class VibratorService extends IVibratorService.Stub native static void vibratorSetAmplitude(int amplitude); native static long vibratorPerformEffect(long effect, long strength); + private final IUidObserver mUidObserver = new IUidObserver.Stub() { + @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) { + mProcStatesCache.put(uid, procState); + } + + @Override public void onUidGone(int uid, boolean disabled) { + mProcStatesCache.delete(uid); + } + + @Override public void onUidActive(int uid) { + } + + @Override public void onUidIdle(int uid, boolean disabled) { + } + + @Override public void onUidCachedChanged(int uid, boolean cached) { + } + }; + private class Vibration implements IBinder.DeathRecipient { public final IBinder token; // Start time in CLOCK_BOOTTIME base. @@ -411,6 +433,14 @@ public class VibratorService extends IVibratorService.Stub } }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH); + try { + ActivityManager.getService().registerUidObserver(mUidObserver, + ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + } catch (RemoteException e) { + // ignored; both services live in system_server + } + updateVibrators(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); @@ -502,6 +532,12 @@ public class VibratorService extends IVibratorService.Stub return; } verifyIncomingUid(uid); + if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) + > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + Slog.e(TAG, "Ignoring incoming vibration as process with uid = " + + uid + " is background"); + return; + } if (!verifyVibrationEffect(effect)) { return; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index f7acf7e83200..8751d24bcf67 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2019,6 +2019,13 @@ public final class ActiveServices { ComponentName className = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); ComponentName name = comp != null ? comp : className; + if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, + name.getPackageName(), sInfo.applicationInfo.uid)) { + String msg = "association not allowed between packages " + + callingPackage + " and " + r.packageName; + Slog.w(TAG, "Service lookup failed: " + msg); + return new ServiceLookupResult(null, msg); + } if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) { if (isBindExternal) { if (!sInfo.exported) { @@ -2099,6 +2106,17 @@ public final class ActiveServices { } } if (r != null) { + if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, r.packageName, + r.appInfo.uid)) { + String msg = "association not allowed between packages " + + callingPackage + " and " + r.packageName; + Slog.w(TAG, "Service lookup failed: " + msg); + return new ServiceLookupResult(null, msg); + } + if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid, + resolvedType, r.appInfo)) { + return new ServiceLookupResult(null, "blocked by firewall"); + } if (mAm.checkComponentPermission(r.permission, callingPid, callingUid, r.appInfo.uid, r.exported) != PERMISSION_GRANTED) { if (!r.exported) { @@ -2125,11 +2143,6 @@ public final class ActiveServices { return null; } } - - if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid, - resolvedType, r.appInfo)) { - return null; - } return new ServiceLookupResult(r, null); } return null; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6e9db88a25c3..6700a530edc0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -658,6 +658,12 @@ public class ActivityManagerService extends IActivityManager.Stub ArraySet<String> mBackgroundLaunchBroadcasts; /** + * When an app has restrictions on the other apps that can have associations with it, + * it appears here with a set of the allowed apps. + */ + ArrayMap<String, ArraySet<String>> mAllowedAssociations; + + /** * All of the processes we currently have running organized by pid. * The keys are the pid running the application. * @@ -2396,6 +2402,34 @@ public class ActivityManagerService extends IActivityManager.Stub return mBackgroundLaunchBroadcasts; } + boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) { + if (mAllowedAssociations == null) { + mAllowedAssociations = SystemConfig.getInstance().getAllowedAssociations(); + } + // Interactions with the system uid are always allowed, since that is the core system + // that everyone needs to be able to interact with. + if (UserHandle.getAppId(uid1) == SYSTEM_UID) { + return true; + } + if (UserHandle.getAppId(uid2) == SYSTEM_UID) { + return true; + } + // We won't allow this association if either pkg1 or pkg2 has a limit on the + // associations that are allowed with it, and the other package is not explicitly + // specified as one of those associations. + ArraySet<String> pkgs = mAllowedAssociations.get(pkg1); + if (pkgs != null) { + if (!pkgs.contains(pkg2)) { + return false; + } + } + pkgs = mAllowedAssociations.get(pkg2); + if (pkgs != null) { + return pkgs.contains(pkg1); + } + return true; + } + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -2724,47 +2758,86 @@ public class ActivityManagerService extends IActivityManager.Stub return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0; } - void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) { + /** + * Update battery stats on the activity' usage. + * @param activity + * @param uid + * @param userId + * @param resumed + */ + void updateBatteryStats(ComponentName activity, int uid, int userId, boolean resumed) { if (DEBUG_SWITCH) { Slog.d(TAG_SWITCH, - "updateUsageStats: comp=" + activity + "res=" + resumed); + "updateBatteryStats: comp=" + activity + "res=" + resumed); } final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED, - uid, activity.getPackageName(), - activity.getShortClassName(), resumed ? - StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND : + uid, activity.getPackageName(), activity.getShortClassName(), + resumed ? StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND : StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__BACKGROUND); - if (resumed) { - if (mUsageStatsService != null) { - mUsageStatsService.reportEvent(activity, userId, - UsageEvents.Event.MOVE_TO_FOREGROUND); - - } - synchronized (stats) { + synchronized (stats) { + if (resumed) { stats.noteActivityResumedLocked(uid); + } else { + stats.noteActivityPausedLocked(uid); } - } else { + } + } + + /** + * Update UsageStas on the activity's usage. + * @param activity + * @param userId + * @param event + * @param appToken ActivityRecord's appToken. + */ + public void updateActivityUsageStats(ComponentName activity, int userId, int event, + IBinder appToken) { + if (DEBUG_SWITCH) { + Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp=" + + activity + " hash=" + appToken.hashCode() + " event=" + event); + } + synchronized (this) { if (mUsageStatsService != null) { - mUsageStatsService.reportEvent(activity, userId, - UsageEvents.Event.MOVE_TO_BACKGROUND); + mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode()); } - synchronized (stats) { - stats.noteActivityPausedLocked(uid); + } + } + + /** + * Update UsageStats on this package's usage. + * @param packageName + * @param userId + * @param event + */ + public void updateActivityUsageStats(String packageName, int userId, int event) { + if (DEBUG_SWITCH) { + Slog.d(TAG_SWITCH, "updateActivityUsageStats: package=" + + packageName + " event=" + event); + } + synchronized (this) { + if (mUsageStatsService != null) { + mUsageStatsService.reportEvent(packageName, userId, event); } } } + /** + * Update Usages on this foreground service's usage. + * @param service + * @param userId + * @param started + */ void updateForegroundServiceUsageStats(ComponentName service, int userId, boolean started) { if (DEBUG_SWITCH) { Slog.d(TAG_SWITCH, "updateForegroundServiceUsageStats: comp=" - + service + "started=" + started); + + service + " started=" + started); } synchronized (this) { if (mUsageStatsService != null) { mUsageStatsService.reportEvent(service, userId, started ? UsageEvents.Event.FOREGROUND_SERVICE_START - : UsageEvents.Event.FOREGROUND_SERVICE_STOP); + : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0); } } } @@ -6225,6 +6298,21 @@ public class ActivityManagerService extends IActivityManager.Stub return state != 'Z' && state != 'X' && state != 'x' && state != 'K'; } + private String checkContentProviderAssociation(ProcessRecord callingApp, int callingUid, + ProviderInfo cpi) { + if (callingApp == null) { + return validateAssociationAllowedLocked(cpi.packageName, cpi.applicationInfo.uid, + null, callingUid) ? null : "<null>"; + } + for (int i = callingApp.pkgList.size() - 1; i >= 0; i--) { + if (!validateAssociationAllowedLocked(callingApp.pkgList.keyAt(i), callingApp.uid, + cpi.packageName, cpi.applicationInfo.uid)) { + return cpi.packageName; + } + } + return null; + } + private ContentProviderHolder getContentProviderImpl(IApplicationThread caller, String name, IBinder token, int callingUid, String callingTag, boolean stable, int userId) { @@ -6296,6 +6384,11 @@ public class ActivityManagerService extends IActivityManager.Stub String msg; if (r != null && cpr.canRunHere(r)) { + if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) { + throw new SecurityException("Content provider lookup " + + cpr.name.flattenToShortString() + + " failed: association not allowed with package " + msg); + } checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission"); if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser)) @@ -6325,6 +6418,11 @@ public class ActivityManagerService extends IActivityManager.Stub } catch (RemoteException e) { } + if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) { + throw new SecurityException("Content provider lookup " + + cpr.name.flattenToShortString() + + " failed: association not allowed with package " + msg); + } checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission"); if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser)) @@ -6422,6 +6520,11 @@ public class ActivityManagerService extends IActivityManager.Stub checkTime(startTime, "getContentProviderImpl: got app info for user"); String msg; + if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) { + throw new SecurityException("Content provider lookup " + + cpr.name.flattenToShortString() + + " failed: association not allowed with package " + msg); + } checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission"); if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton)) != null) { @@ -9168,6 +9271,12 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println("-------------------------------------------------------------------------------"); } + dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage); + pw.println(); + if (dumpAll) { + pw.println("-------------------------------------------------------------------------------"); + + } mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage); pw.println(); if (dumpAll) { @@ -9435,6 +9544,14 @@ public class ActivityManagerService extends IActivityManager.Stub System.gc(); pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid))); } + } else if ("allowed-associations".equals(cmd)) { + if (opti < args.length) { + dumpPackage = args[opti]; + opti++; + } + synchronized (this) { + dumpAllowedAssociationsLocked(fd, pw, args, opti, true, dumpPackage); + } } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { if (opti < args.length) { dumpPackage = args[opti]; @@ -10785,6 +10902,44 @@ public class ActivityManagerService extends IActivityManager.Stub proto.end(handlerToken); } + void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, String dumpPackage) { + boolean needSep = false; + boolean printedAnything = false; + + pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)"); + boolean printed = false; + if (mAllowedAssociations != null) { + for (int i = 0; i < mAllowedAssociations.size(); i++) { + final String pkg = mAllowedAssociations.keyAt(i); + final ArraySet<String> asc = mAllowedAssociations.valueAt(i); + boolean printedHeader = false; + for (int j = 0; j < asc.size(); j++) { + if (dumpPackage == null || pkg.equals(dumpPackage) + || asc.valueAt(j).equals(dumpPackage)) { + if (!printed) { + pw.println(" Allowed associations (by restricted package):"); + printed = true; + needSep = true; + printedAnything = true; + } + if (!printedHeader) { + pw.print(" * "); + pw.print(pkg); + pw.println(":"); + printedHeader = true; + } + pw.print(" Allow: "); + pw.println(asc.valueAt(j)); + } + } + } + } + if (!printed) { + pw.println(" (No association restrictions)"); + } + } + void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; @@ -19068,9 +19223,19 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) { + public void updateBatteryStats(ComponentName activity, int uid, int userId, + boolean resumed) { synchronized (ActivityManagerService.this) { - ActivityManagerService.this.updateUsageStats(activity, uid, userId, resumed); + ActivityManagerService.this.updateBatteryStats(activity, uid, userId, resumed); + } + } + + @Override + public void updateActivityUsageStats(ComponentName activity, int userId, int event, + IBinder appToken) { + synchronized (ActivityManagerService.this) { + ActivityManagerService.this.updateActivityUsageStats(activity, userId, event, + appToken); } } @@ -19392,16 +19557,13 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean isAppStorageSandboxed(int pid, int uid) { - if (!StorageManager.hasIsolatedStorage()) { - return false; - } + public int getStorageMountMode(int pid, int uid) { if (uid == SHELL_UID || uid == ROOT_UID) { - return false; + return Zygote.MOUNT_EXTERNAL_FULL; } synchronized (mPidsSelfLocked) { final ProcessRecord pr = mPidsSelfLocked.get(pid); - return pr == null || pr.mountMode != Zygote.MOUNT_EXTERNAL_FULL; + return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.mountMode; } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 67a4d14a6edb..98a82acd79a6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2852,6 +2852,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" prov[iders] [COMP_SPEC ...]: content provider state"); pw.println(" provider [COMP_SPEC]: provider client-side state"); pw.println(" s[ervices] [COMP_SPEC ...]: service state"); + pw.println(" allowed-associations: current package association restrictions"); pw.println(" as[sociations]: tracked app associations"); pw.println(" lmk: stats on low memory killer"); pw.println(" lru: raw LRU process list"); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 3a0899de75c3..c290fbe09864 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -527,6 +527,24 @@ public final class BroadcastQueue { private void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered, int index) { boolean skip = false; + if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid, + filter.packageName, filter.owningUid)) { + Slog.w(TAG, "Association not allowed: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ") to " + filter.packageName + " through " + + filter); + skip = true; + } + if (!skip && !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, + r.callingPid, r.resolvedType, filter.receiverList.uid)) { + Slog.w(TAG, "Firewall blocked: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ") to " + filter.packageName + " through " + + filter); + skip = true; + } if (filter.requiredPermission != null) { int perm = mService.checkComponentPermission(filter.requiredPermission, r.callingPid, r.callingUid, -1, true); @@ -619,11 +637,6 @@ public final class BroadcastQueue { skip = true; } - if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, - r.callingPid, r.resolvedType, filter.receiverList.uid)) { - skip = true; - } - if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed || filter.receiverList.app.isCrashing())) { Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r @@ -1082,6 +1095,24 @@ public final class BroadcastQueue { > brOptions.getMaxManifestReceiverApiLevel())) { skip = true; } + if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid, + component.getPackageName(), info.activityInfo.applicationInfo.uid)) { + Slog.w(TAG, "Association not allowed: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ") to " + component.flattenToShortString()); + skip = true; + } + if (!skip) { + skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, + r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid); + if (skip) { + Slog.w(TAG, "Firewall blocked: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ") to " + component.flattenToShortString()); + } + } int perm = mService.checkComponentPermission(info.activityInfo.permission, r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, info.activityInfo.exported); @@ -1170,10 +1201,6 @@ public final class BroadcastQueue { + " (uid " + r.callingUid + ")"); skip = true; } - if (!skip) { - skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, - r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid); - } boolean isSingleton = false; try { isSingleton = mService.isSingleton(info.activityInfo.processName, diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 9649ccd3c750..32219aa5955f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -282,9 +282,11 @@ public abstract class BiometricServiceBase extends SystemService public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int userId, int groupId, - byte[] cryptoToken, boolean restricted, String owner) { + byte[] cryptoToken, boolean restricted, String owner, + final int[] disabledFeatures) { super(context, getMetrics(), daemon, halDeviceId, token, listener, - userId, groupId, cryptoToken, restricted, owner, getBiometricUtils()); + userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(), + disabledFeatures); } @Override @@ -408,7 +410,8 @@ public abstract class BiometricServiceBase extends SystemService int cancel() throws RemoteException; int remove(int groupId, int biometricId) throws RemoteException; int enumerate() throws RemoteException; - int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException; + int enroll(byte[] cryptoToken, int groupId, int timeout, + ArrayList<Integer> disabledFeatures) throws RemoteException; } /** diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index f858ef5ec6f8..8a0f0858cedc 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -34,15 +34,18 @@ public abstract class EnrollClient extends ClientMonitor { private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute private final byte[] mCryptoToken; private final BiometricUtils mBiometricUtils; + private final int[] mDisabledFeatures; public EnrollClient(Context context, Metrics metrics, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, - byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) { + byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils, + final int[] disabledFeatures) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, owner, 0 /* cookie */); mBiometricUtils = utils; mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); + mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); } @Override @@ -74,7 +77,13 @@ public abstract class EnrollClient extends ClientMonitor { public int start() { final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); try { - final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout); + final ArrayList<Integer> disabledFeatures = new ArrayList<>(); + for (int i = 0; i < mDisabledFeatures.length; i++) { + disabledFeatures.add(mDisabledFeatures[i]); + } + + final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout, + disabledFeatures); if (result != 0) { Slog.w(getLogTag(), "startEnroll failed, result=" + result); mMetricsLogger.histogram(mMetrics.tagEnrollStartError(), result); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 557af0478b87..72f73f6aaf67 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -113,17 +113,17 @@ public class FaceService extends BiometricServiceBase { } @Override // Binder call - public void enroll(final IBinder token, final byte[] cryptoToken, final int userId, - final IFaceServiceReceiver receiver, final int flags, - final String opPackageName) { + public void enroll(final IBinder token, final byte[] cryptoToken, + final IFaceServiceReceiver receiver, final String opPackageName, + final int[] disabledFeatures) { checkPermission(MANAGE_BIOMETRIC); final boolean restricted = isRestricted(); final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, - 0 /* groupId */, cryptoToken, restricted, opPackageName); + 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures); - enrollInternal(client, userId); + enrollInternal(client, UserHandle.getCallingUserId()); } @Override // Binder call @@ -333,7 +333,7 @@ public class FaceService extends BiometricServiceBase { } @Override - public int setRequireAttention(boolean requireAttention, final byte[] token) { + public int setFeature(int feature, boolean enabled, final byte[] token) { checkPermission(MANAGE_BIOMETRIC); final ArrayList<Byte> byteToken = new ArrayList<>(); @@ -343,10 +343,11 @@ public class FaceService extends BiometricServiceBase { int result; try { - result = mDaemon != null ? mDaemon.setRequireAttention(requireAttention, byteToken) + result = mDaemon != null ? mDaemon.setFeature(feature, enabled, byteToken) : Status.INTERNAL_ERROR; } catch (RemoteException e) { - Slog.e(getTag(), "Unable to setRequireAttention to " + requireAttention, e); + Slog.e(getTag(), "Unable to set feature: " + feature + " to enabled:" + enabled, + e); result = Status.INTERNAL_ERROR; } @@ -354,17 +355,12 @@ public class FaceService extends BiometricServiceBase { } @Override - public boolean getRequireAttention(final byte[] token) { + public boolean getFeature(int feature) { checkPermission(MANAGE_BIOMETRIC); - final ArrayList<Byte> byteToken = new ArrayList<>(); - for (int i = 0; i < token.length; i++) { - byteToken.add(token[i]); - } - boolean result = true; try { - result = mDaemon != null ? mDaemon.getRequireAttention(byteToken).value : true; + result = mDaemon != null ? mDaemon.getFeature(feature) : true; } catch (RemoteException e) { Slog.e(getTag(), "Unable to getRequireAttention", e); } @@ -612,7 +608,8 @@ public class FaceService extends BiometricServiceBase { } @Override - public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException { + public int enroll(byte[] cryptoToken, int groupId, int timeout, + ArrayList<Integer> disabledFeatures) throws RemoteException { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no face HAL!"); @@ -623,7 +620,7 @@ public class FaceService extends BiometricServiceBase { token.add(cryptoToken[i]); } // TODO: plumb requireAttention down from framework - return daemon.enroll(token, timeout, true /* requireAttention */); + return daemon.enroll(token, timeout, disabledFeatures); } }; diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 6a5bc61f2eb6..3895ef78b357 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -144,7 +144,7 @@ public class FingerprintService extends BiometricServiceBase { final int groupId = userId; // default group for fingerprint enrollment final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, - cryptoToken, restricted, opPackageName); + cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */); enrollInternal(client, userId); } @@ -716,7 +716,8 @@ public class FingerprintService extends BiometricServiceBase { } @Override - public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException { + public int enroll(byte[] cryptoToken, int groupId, int timeout, + ArrayList<Integer> disabledFeatures) throws RemoteException { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no fingerprint HAL!"); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 360a7d105cce..cf8d21b66417 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -46,6 +46,8 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener; import android.hardware.display.DisplayViewport; +import android.hardware.display.DisplayedContentSample; +import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IDisplayManager; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; @@ -1241,6 +1243,29 @@ public final class DisplayManagerService extends SystemService { } } + @VisibleForTesting + DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributesInternal( + int displayId) { + IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId); + return SurfaceControl.getDisplayedContentSamplingAttributes(displayToken); + } + + @VisibleForTesting + boolean setDisplayedContentSamplingEnabledInternal( + int displayId, boolean enable, int componentMask, int maxFrames) { + IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId); + return SurfaceControl.setDisplayedContentSamplingEnabled( + displayToken, enable, componentMask, maxFrames); + } + + @VisibleForTesting + DisplayedContentSample getDisplayedContentSampleInternal(int displayId, + long maxFrames, long timestamp) { + IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId); + return SurfaceControl.getDisplayedContentSample( + displayToken, maxFrames, timestamp); + } + private void clearViewportsLocked() { mViewports.clear(); } @@ -2331,5 +2356,25 @@ public final class DisplayManagerService extends SystemService { } } } + + @Override + public DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes( + int displayId) { + return getDisplayedContentSamplingAttributesInternal(displayId); + } + + @Override + public boolean setDisplayedContentSamplingEnabled( + int displayId, boolean enable, int componentMask, int maxFrames) { + return setDisplayedContentSamplingEnabledInternal( + displayId, enable, componentMask, maxFrames); + } + + @Override + public DisplayedContentSample getDisplayedContentSample(int displayId, + long maxFrames, long timestamp) { + return getDisplayedContentSampleInternal(displayId, maxFrames, timestamp); + } + } } diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS index 98e32997e587..0d64dbd83a34 100644 --- a/services/core/java/com/android/server/display/OWNERS +++ b/services/core/java/com/android/server/display/OWNERS @@ -1,4 +1,5 @@ michaelwr@google.com +dangittik@google.com hackbod@google.com ogunwale@google.com diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 611c8b728d0c..78e18e91ae73 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -839,6 +839,15 @@ public class JobSchedulerService extends com.android.server.SystemService break; } } + if (DEBUG) { + Slog.d(TAG, "Something in " + pkgName + + " changed. Reevaluating controller states."); + } + synchronized (mLock) { + for (int c = mControllers.size() - 1; c >= 0; --c) { + mControllers.get(c).reevaluateStateLocked(pkgUid); + } + } } } else { Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid); @@ -1042,6 +1051,8 @@ public class JobSchedulerService extends com.android.server.SystemService mJobPackageTracker.notePending(jobStatus); addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator); maybeRunPendingJobsLocked(); + } else { + evaluateControllerStatesLocked(jobStatus); } } return JobScheduler.RESULT_SUCCESS; @@ -1884,6 +1895,8 @@ public class JobSchedulerService extends com.android.server.SystemService newReadyJobs = new ArrayList<JobStatus>(); } newReadyJobs.add(job); + } else { + evaluateControllerStatesLocked(job); } } @@ -1957,6 +1970,8 @@ public class JobSchedulerService extends com.android.server.SystemService runnableJobs = new ArrayList<>(); } runnableJobs.add(job); + } else { + evaluateControllerStatesLocked(job); } } @@ -2087,6 +2102,15 @@ public class JobSchedulerService extends com.android.server.SystemService HEARTBEAT_TAG, mHeartbeatAlarm, mHandler); } + /** Returns true if both the calling and source users for the job are started. */ + private boolean areUsersStartedLocked(final JobStatus job) { + boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId()); + if (job.getUserId() == job.getSourceUserId()) { + return sourceStarted; + } + return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId()); + } + /** * Criteria for moving a job into the pending queue: * - It's ready. @@ -2219,6 +2243,61 @@ public class JobSchedulerService extends com.android.server.SystemService return componentPresent; } + private void evaluateControllerStatesLocked(final JobStatus job) { + for (int c = mControllers.size() - 1; c >= 0; --c) { + final StateController sc = mControllers.get(c); + sc.evaluateStateLocked(job); + } + } + + /** + * Returns true if non-job constraint components are in place -- if job.isReady() returns true + * and this method returns true, then the job is ready to be executed. + */ + public boolean areComponentsInPlaceLocked(JobStatus job) { + // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same + // conditions. + + final boolean jobExists = mJobs.containsJob(job); + final boolean userStarted = areUsersStartedLocked(job); + + if (DEBUG) { + Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString() + + " exists=" + jobExists + " userStarted=" + userStarted); + } + + // These are also fairly cheap to check, though they typically will not + // be conditions we fail. + if (!jobExists || !userStarted) { + return false; + } + + // Job pending/active doesn't affect the readiness of a job. + + // Skipping the hearbeat check as this will only come into play when using the rolling + // window quota management system. + + // The expensive check last: validate that the defined package+service is + // still present & viable. + final boolean componentPresent; + try { + // TODO: cache result until we're notified that something in the package changed. + componentPresent = (AppGlobals.getPackageManager().getServiceInfo( + job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + job.getUserId()) != null); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + + if (DEBUG) { + Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString() + + " componentPresent=" + componentPresent); + } + + // Everything else checked out so far, so this is the final yes/no check + return componentPresent; + } + /** * Reconcile jobs in the pending queue against available execution contexts. * A controller can force a job into the pending queue even if it's already running, but diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 6989c334d876..8f104e4a1525 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -41,10 +41,12 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobSchedulerService.Constants; import com.android.server.job.JobServiceContext; import com.android.server.job.StateControllerProto; +import com.android.server.net.NetworkPolicyManagerInternal; import java.util.Objects; import java.util.function.Predicate; @@ -66,16 +68,29 @@ public final class ConnectivityController extends StateController implements private final ConnectivityManager mConnManager; private final NetworkPolicyManager mNetPolicyManager; + private final NetworkPolicyManagerInternal mNetPolicyManagerInternal; /** List of tracked jobs keyed by source UID. */ @GuardedBy("mLock") private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>(); + /** + * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager + * grant an exception to in the app standby chain. + */ + @GuardedBy("mLock") + private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>(); + + /** List of currently available networks. */ + @GuardedBy("mLock") + private final ArraySet<Network> mAvailableNetworks = new ArraySet<>(); + public ConnectivityController(JobSchedulerService service) { super(service); mConnManager = mContext.getSystemService(ConnectivityManager.class); mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class); + mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class); // We're interested in all network changes; internally we match these // network changes against the active network for each UID with jobs. @@ -109,7 +124,176 @@ public final class ConnectivityController extends StateController implements if (jobs != null) { jobs.remove(jobStatus); } + maybeRevokeStandbyExceptionLocked(jobStatus); + } + } + + @GuardedBy("mLock") + @Override + public void onConstantsUpdatedLocked() { + if (mConstants.USE_HEARTBEATS) { + // App idle exceptions are only requested for the rolling quota system. + if (DEBUG) Slog.i(TAG, "Revoking all standby exceptions"); + for (int i = 0; i < mRequestedWhitelistJobs.size(); ++i) { + int uid = mRequestedWhitelistJobs.keyAt(i); + mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false); + } + mRequestedWhitelistJobs.clear(); + } + } + + /** + * Returns true if the job's requested network is available. This DOES NOT necesarilly mean + * that the UID has been granted access to the network. + */ + public boolean isNetworkAvailable(JobStatus job) { + synchronized (mLock) { + for (int i = 0; i < mAvailableNetworks.size(); ++i) { + final Network network = mAvailableNetworks.valueAt(i); + final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities( + network); + final boolean satisfied = isSatisfied(job, network, capabilities, mConstants); + if (DEBUG) { + Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network + + " and capabilities " + capabilities + ". Satisfied=" + satisfied); + } + if (satisfied) { + return true; + } + } + return false; + } + } + + /** + * Request that NetworkPolicyManager grant an exception to the uid from its standby policy + * chain. + */ + @VisibleForTesting + @GuardedBy("mLock") + void requestStandbyExceptionLocked(JobStatus job) { + final int uid = job.getSourceUid(); + // Need to call this before adding the job. + final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid); + ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid); + if (jobs == null) { + jobs = new ArraySet<JobStatus>(); + mRequestedWhitelistJobs.put(uid, jobs); + } + if (!jobs.add(job) || isExceptionRequested) { + if (DEBUG) { + Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested."); + } + return; } + if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid); + mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true); + } + + /** Returns whether a standby exception has been requested for the UID. */ + @VisibleForTesting + @GuardedBy("mLock") + boolean isStandbyExceptionRequestedLocked(final int uid) { + ArraySet jobs = mRequestedWhitelistJobs.get(uid); + return jobs != null && jobs.size() > 0; + } + + @VisibleForTesting + @GuardedBy("mLock") + boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) { + final boolean networkAvailable = isNetworkAvailable(jobStatus); + if (DEBUG) { + Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString() + + " networkAvailable=" + networkAvailable); + } + // If the network isn't available, then requesting an exception won't help. + + return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus, + JobStatus.CONSTRAINT_CONNECTIVITY); + } + + /** + * Tell NetworkPolicyManager not to block a UID's network connection if that's the only + * thing stopping a job from running. + */ + @GuardedBy("mLock") + @Override + public void evaluateStateLocked(JobStatus jobStatus) { + if (mConstants.USE_HEARTBEATS) { + // This should only be used for the rolling quota system. + return; + } + + if (!jobStatus.hasConnectivityConstraint()) { + return; + } + + // Always check the full job readiness stat in case the component has been disabled. + if (wouldBeReadyWithConnectivityLocked(jobStatus)) { + if (DEBUG) { + Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready."); + } + requestStandbyExceptionLocked(jobStatus); + } else { + if (DEBUG) { + Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready."); + } + maybeRevokeStandbyExceptionLocked(jobStatus); + } + } + + @GuardedBy("mLock") + @Override + public void reevaluateStateLocked(final int uid) { + if (mConstants.USE_HEARTBEATS) { + return; + } + // Check if we still need a connectivity exception in case the JobService was disabled. + ArraySet<JobStatus> jobs = mTrackedJobs.get(uid); + if (jobs == null) { + return; + } + for (int i = jobs.size() - 1; i >= 0; i--) { + evaluateStateLocked(jobs.valueAt(i)); + } + } + + /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */ + @VisibleForTesting + @GuardedBy("mLock") + void maybeRevokeStandbyExceptionLocked(final JobStatus job) { + final int uid = job.getSourceUid(); + if (!isStandbyExceptionRequestedLocked(uid)) { + return; + } + ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid); + if (jobs == null) { + Slog.wtf(TAG, + "maybeRevokeStandbyExceptionLocked found null jobs array even though a " + + "standby exception has been requested."); + return; + } + if (!jobs.remove(job) || jobs.size() > 0) { + if (DEBUG) { + Slog.i(TAG, + "maybeRevokeStandbyExceptionLocked not revoking because there are still " + + jobs.size() + " jobs left."); + } + return; + } + // No more jobs that need an exception. + revokeStandbyExceptionLocked(uid); + } + + /** + * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain + * for the uid. + */ + @GuardedBy("mLock") + private void revokeStandbyExceptionLocked(final int uid) { + if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid); + mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false); + mRequestedWhitelistJobs.remove(uid); } /** @@ -326,6 +510,14 @@ public final class ConnectivityController extends StateController implements private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override + public void onAvailable(Network network) { + if (DEBUG) Slog.v(TAG, "onAvailable: " + network); + synchronized (mLock) { + mAvailableNetworks.add(network); + } + } + + @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) { if (DEBUG) { Slog.v(TAG, "onCapabilitiesChanged: " + network); @@ -338,6 +530,9 @@ public final class ConnectivityController extends StateController implements if (DEBUG) { Slog.v(TAG, "onLost: " + network); } + synchronized (mLock) { + mAvailableNetworks.remove(network); + } updateTrackedJobs(-1, network); } }; @@ -356,6 +551,27 @@ public final class ConnectivityController extends StateController implements @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { + if (mRequestedWhitelistJobs.size() > 0) { + pw.print("Requested standby exceptions:"); + for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) { + pw.print(" "); + pw.print(mRequestedWhitelistJobs.keyAt(i)); + pw.print(" ("); + pw.print(mRequestedWhitelistJobs.valueAt(i).size()); + pw.print(" jobs)"); + } + pw.println(); + } + if (mAvailableNetworks.size() > 0) { + pw.println("Available networks:"); + pw.increaseIndent(); + for (int i = 0; i < mAvailableNetworks.size(); i++) { + pw.println(mAvailableNetworks.valueAt(i)); + } + pw.decreaseIndent(); + } else { + pw.println("No available networks"); + } for (int i = 0; i < mTrackedJobs.size(); i++) { final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i); for (int j = 0; j < jobs.size(); j++) { diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 434158957c17..82bfa511507f 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -1007,6 +1007,18 @@ public final class JobStatus { * @return Whether or not this job is ready to run, based on its requirements. */ public boolean isReady() { + return isReady(mSatisfiedConstraintsOfInterest); + } + + /** + * @return Whether or not this job would be ready to run if it had the specified constraint + * granted, based on its requirements. + */ + public boolean wouldBeReadyWithConstraint(int constraint) { + return isReady(mSatisfiedConstraintsOfInterest | constraint); + } + + private boolean isReady(int satisfiedConstraints) { // Quota constraints trumps all other constraints. if (!mReadyWithinQuota) { return false; @@ -1017,7 +1029,7 @@ public final class JobStatus { // DeviceNotDozing implicit constraint must be satisfied // NotRestrictedInBackground implicit constraint must be satisfied return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied - || isConstraintsSatisfied()); + || isConstraintsSatisfied(satisfiedConstraints)); } static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW @@ -1033,12 +1045,16 @@ public final class JobStatus { * @return Whether the constraints set on this job are satisfied. */ public boolean isConstraintsSatisfied() { + return isConstraintsSatisfied(mSatisfiedConstraintsOfInterest); + } + + private boolean isConstraintsSatisfied(int satisfiedConstraints) { if (overrideState == OVERRIDE_FULL) { // force override: the job is always runnable return true; } - int sat = mSatisfiedConstraintsOfInterest; + int sat = satisfiedConstraints; if (overrideState == OVERRIDE_SOFT) { // override: pretend all 'soft' requirements are satisfied sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS); diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java index f73ffac96dfa..660c2383ea2f 100644 --- a/services/core/java/com/android/server/job/controllers/QuotaController.java +++ b/services/core/java/com/android/server/job/controllers/QuotaController.java @@ -151,8 +151,7 @@ public final class QuotaController extends StateController { return "<" + userId + ">" + packageName; } - @VisibleForTesting - static final class Package { + private static final class Package { public final String packageName; public final int userId; @@ -387,8 +386,9 @@ public final class QuotaController extends StateController { private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { final int standbyBucket = getEffectiveStandbyBucket(jobStatus); - return isWithinQuotaLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), - standbyBucket); + // Jobs for the active app should always be able to run. + return jobStatus.uidActive || isWithinQuotaLocked( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); } private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, @@ -579,7 +579,10 @@ public final class QuotaController extends StateController { boolean changed = false; for (int i = jobs.size() - 1; i >= 0; --i) { final JobStatus js = jobs.valueAt(i); - if (realStandbyBucket == getEffectiveStandbyBucket(js)) { + if (js.uidActive) { + // Jobs for the active app should always be able to run. + changed |= js.setQuotaConstraintSatisfied(true); + } else if (realStandbyBucket == getEffectiveStandbyBucket(js)) { changed |= js.setQuotaConstraintSatisfied(realInQuota); } else { // This job is somehow exempted. Need to determine its own quota status. @@ -765,18 +768,18 @@ public final class QuotaController extends StateController { public final long startTimeElapsed; // End timestamp in elapsed realtime timebase. public final long endTimeElapsed; - // How many jobs ran during this session. - public final int jobCount; + // How many background jobs ran during this session. + public final int bgJobCount; TimingSession(long startElapsed, long endElapsed, int jobCount) { this.startTimeElapsed = startElapsed; this.endTimeElapsed = endElapsed; - this.jobCount = jobCount; + this.bgJobCount = jobCount; } @Override public String toString() { - return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + jobCount + return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount + "}"; } @@ -786,7 +789,7 @@ public final class QuotaController extends StateController { TimingSession other = (TimingSession) obj; return startTimeElapsed == other.startTimeElapsed && endTimeElapsed == other.endTimeElapsed - && jobCount == other.jobCount; + && bgJobCount == other.bgJobCount; } else { return false; } @@ -794,7 +797,7 @@ public final class QuotaController extends StateController { @Override public int hashCode() { - return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, jobCount}); + return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount}); } public void dump(IndentingPrintWriter pw) { @@ -804,8 +807,8 @@ public final class QuotaController extends StateController { pw.print(" ("); pw.print(endTimeElapsed - startTimeElapsed); pw.print("), "); - pw.print(jobCount); - pw.print(" jobs."); + pw.print(bgJobCount); + pw.print(" bg jobs."); pw.println(); } @@ -816,7 +819,8 @@ public final class QuotaController extends StateController { startTimeElapsed); proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED, endTimeElapsed); - proto.write(StateControllerProto.QuotaController.TimingSession.JOB_COUNT, jobCount); + proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT, + bgJobCount); proto.end(token); } @@ -825,23 +829,32 @@ public final class QuotaController extends StateController { private final class Timer { private final Package mPkg; - // List of jobs currently running for this package. - private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); + // List of jobs currently running for this app that started when the app wasn't in the + // foreground. + private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); private long mStartTimeElapsed; - private int mJobCount; + private int mBgJobCount; Timer(int userId, String packageName) { mPkg = new Package(userId, packageName); } void startTrackingJob(@NonNull JobStatus jobStatus) { + if (jobStatus.uidActive) { + // We intentionally don't pay attention to fg state changes after a job has started. + if (DEBUG) { + Slog.v(TAG, + "Timer ignoring " + jobStatus.toShortString() + " because uidActive"); + } + return; + } if (DEBUG) Slog.v(TAG, "Starting to track " + jobStatus.toShortString()); synchronized (mLock) { // Always track jobs, even when charging. - mRunningJobs.add(jobStatus); + mRunningBgJobs.add(jobStatus); if (!mChargeTracker.isCharging()) { - mJobCount++; - if (mRunningJobs.size() == 1) { + mBgJobCount++; + if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); scheduleCutoff(); @@ -853,7 +866,7 @@ public final class QuotaController extends StateController { void stopTrackingJob(@NonNull JobStatus jobStatus) { if (DEBUG) Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString()); synchronized (mLock) { - if (mRunningJobs.size() == 0) { + if (mRunningBgJobs.size() == 0) { // maybeStopTrackingJobLocked can be called when an app cancels a job, so a // timer may not be running when it's asked to stop tracking a job. if (DEBUG) { @@ -861,8 +874,8 @@ public final class QuotaController extends StateController { } return; } - mRunningJobs.remove(jobStatus); - if (!mChargeTracker.isCharging() && mRunningJobs.size() == 0) { + if (mRunningBgJobs.remove(jobStatus) + && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) { emitSessionLocked(sElapsedRealtimeClock.millis()); cancelCutoff(); } @@ -870,13 +883,13 @@ public final class QuotaController extends StateController { } private void emitSessionLocked(long nowElapsed) { - if (mJobCount <= 0) { + if (mBgJobCount <= 0) { // Nothing to emit. return; } - TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mJobCount); + TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount); saveTimingSession(mPkg.userId, mPkg.packageName, ts); - mJobCount = 0; + mBgJobCount = 0; // Don't reset the tracked jobs list as we need to keep tracking the current number // of jobs. // However, cancel the currently scheduled cutoff since it's not currently useful. @@ -889,7 +902,7 @@ public final class QuotaController extends StateController { */ public boolean isActive() { synchronized (mLock) { - return mJobCount > 0; + return mBgJobCount > 0; } } @@ -905,12 +918,12 @@ public final class QuotaController extends StateController { emitSessionLocked(nowElapsed); } else { // Start timing from unplug. - if (mRunningJobs.size() > 0) { + if (mRunningBgJobs.size() > 0) { mStartTimeElapsed = nowElapsed; // NOTE: this does have the unfortunate consequence that if the device is // repeatedly plugged in and unplugged, the job count for a package may be // artificially high. - mJobCount = mRunningJobs.size(); + mBgJobCount = mRunningBgJobs.size(); // Schedule cutoff since we're now actively tracking for quotas again. scheduleCutoff(); } @@ -958,12 +971,12 @@ public final class QuotaController extends StateController { pw.print("NOT active"); } pw.print(", "); - pw.print(mJobCount); - pw.print(" running jobs"); + pw.print(mBgJobCount); + pw.print(" running bg jobs"); pw.println(); pw.increaseIndent(); - for (int i = 0; i < mRunningJobs.size(); i++) { - JobStatus js = mRunningJobs.valueAt(i); + for (int i = 0; i < mRunningBgJobs.size(); i++) { + JobStatus js = mRunningBgJobs.valueAt(i); if (predicate.test(js)) { pw.println(js.toShortString()); } @@ -979,9 +992,9 @@ public final class QuotaController extends StateController { proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive()); proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED, mStartTimeElapsed); - proto.write(StateControllerProto.QuotaController.Timer.JOB_COUNT, mJobCount); - for (int i = 0; i < mRunningJobs.size(); i++) { - JobStatus js = mRunningJobs.valueAt(i); + proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount); + for (int i = 0; i < mRunningBgJobs.size(); i++) { + JobStatus js = mRunningBgJobs.valueAt(i); if (predicate.test(js)) { js.writeToShortProto(proto, StateControllerProto.QuotaController.Timer.RUNNING_JOBS); diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java index b439c0ddd028..61dc4799f221 100644 --- a/services/core/java/com/android/server/job/controllers/StateController.java +++ b/services/core/java/com/android/server/job/controllers/StateController.java @@ -16,7 +16,10 @@ package com.android.server.job.controllers; +import static com.android.server.job.JobSchedulerService.DEBUG; + import android.content.Context; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.IndentingPrintWriter; @@ -32,6 +35,8 @@ import java.util.function.Predicate; * are ready to run, or whether they must be stopped. */ public abstract class StateController { + private static final String TAG = "JobScheduler.SC"; + protected final JobSchedulerService mService; protected final StateChangedListener mStateChangedListener; protected final Context mContext; @@ -78,6 +83,37 @@ public abstract class StateController { public void onConstantsUpdatedLocked() { } + protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) { + // This is very cheap to check (just a few conditions on data in JobStatus). + final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint); + if (DEBUG) { + Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString() + + " readyWithConstraint=" + jobWouldBeReady); + } + if (!jobWouldBeReady) { + // If the job wouldn't be ready, nothing to do here. + return false; + } + + // This is potentially more expensive since JSS may have to query component + // presence. + return mService.areComponentsInPlaceLocked(jobStatus); + } + + /** + * Called when JobSchedulerService has determined that the job is not ready to be run. The + * Controller can evaluate if it can or should do something to promote this job's readiness. + */ + public void evaluateStateLocked(JobStatus jobStatus) { + } + + /** + * Called when something with the UID has changed. The controller should re-evaluate any + * internal state tracking dependent on this UID. + */ + public void reevaluateStateLocked(int uid) { + } + public abstract void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate); public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS index 7e7335d68d3b..c7c6d5658d1d 100644 --- a/services/core/java/com/android/server/lights/OWNERS +++ b/services/core/java/com/android/server/lights/OWNERS @@ -1 +1,2 @@ michaelwr@google.com +dangittik@google.com diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java new file mode 100644 index 000000000000..2ae424dd4b1b --- /dev/null +++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java @@ -0,0 +1,115 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.server.pm.dex.DexLogger; + +import java.util.concurrent.TimeUnit; + +/** + * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and + * charging. The actual logging is performed by {@link DexLogger}. + * {@hide} + */ +public class DynamicCodeLoggingService extends JobService { + private static final String TAG = DynamicCodeLoggingService.class.getName(); + + private static final int JOB_ID = 2030028; + private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + + private volatile boolean mStopRequested = false; + + private static final boolean DEBUG = false; + + /** + * Schedule our job with the {@link JobScheduler}. + */ + public static void schedule(Context context) { + ComponentName serviceName = new ComponentName( + "android", DynamicCodeLoggingService.class.getName()); + + JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + js.schedule(new JobInfo.Builder(JOB_ID, serviceName) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(PERIOD_MILLIS) + .build()); + if (DEBUG) { + Log.d(TAG, "Job scheduled"); + } + } + + @Override + public boolean onStartJob(JobParameters params) { + if (DEBUG) { + Log.d(TAG, "onStartJob"); + } + mStopRequested = false; + new IdleLoggingThread(params).start(); + return true; // Job is running on another thread + } + + @Override + public boolean onStopJob(JobParameters params) { + if (DEBUG) { + Log.d(TAG, "onStopJob"); + } + mStopRequested = true; + return true; // Requests job be re-scheduled. + } + + private class IdleLoggingThread extends Thread { + private final JobParameters mParams; + + IdleLoggingThread(JobParameters params) { + super("DynamicCodeLoggingService_IdleLoggingJob"); + mParams = params; + } + + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "Starting logging run"); + } + + PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); + DexLogger dexLogger = pm.getDexManager().getDexLogger(); + for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) { + if (mStopRequested) { + Log.w(TAG, "Stopping logging run at scheduler request"); + return; + } + + dexLogger.logDynamicCodeLoading(packageName); + } + + jobFinished(mParams, /* reschedule */ false); + if (DEBUG) { + Log.d(TAG, "Finished logging run"); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 57922d052984..a95e73069568 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -27,7 +27,6 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; import android.app.PackageInstallObserver; -import android.app.admin.DeviceAdminInfo; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.Intent; @@ -486,10 +485,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if (!PackageHelper.fitsOnInternal(mContext, params)) { throw new IOException("No suitable internal storage available"); } - } else { + } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { // For now, installs to adopted media are treated as internal from // an install flag point-of-view. params.installFlags |= PackageManager.INSTALL_INTERNAL; + } else { + params.installFlags |= PackageManager.INSTALL_INTERNAL; // Resolve best location for install, based on combination of // requested install flags, delta size, and manifest settings. @@ -736,22 +737,19 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements // Check whether the caller is device owner or affiliated profile owner, in which case we do // it silently. - final int callingUserId = UserHandle.getUserId(callingUid); DevicePolicyManagerInternal dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); - final boolean isDeviceOwnerOrAffiliatedProfileOwner = - dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) - && dpmi.isUserAffiliatedWithDevice(callingUserId); + final boolean canSilentlyInstallPackage = + dpmi != null && dpmi.canSilentlyInstallPackage(callerPackageName, callingUid); final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, statusReceiver, versionedPackage.getPackageName(), - isDeviceOwnerOrAffiliatedProfileOwner, userId); + canSilentlyInstallPackage, userId); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES) == PackageManager.PERMISSION_GRANTED) { // Sweet, call straight through! mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); - } else if (isDeviceOwnerOrAffiliatedProfileOwner) { + } else if (canSilentlyInstallPackage) { // Allow the device owner and affiliated profile owner to silently delete packages // Need to clear the calling identity to get DELETE_PACKAGES permission long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 26a92a4cdde4..206a88bde616 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -44,7 +44,6 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.apex.IApexService; -import android.app.admin.DeviceAdminInfo; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.IIntentReceiver; @@ -194,7 +193,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** Package of the owner of the installer session */ @GuardedBy("mLock") - private String mInstallerPackageName; + private @Nullable String mInstallerPackageName; /** Uid of the owner of the installer session */ @GuardedBy("mLock") @@ -340,11 +339,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ @GuardedBy("mLock") private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked() { + if (userId != UserHandle.getUserId(mInstallerUid)) { + return false; + } DevicePolicyManagerInternal dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); - return dpmi != null && dpmi.isActiveAdminWithPolicy(mInstallerUid, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) && dpmi.isUserAffiliatedWithDevice( - userId); + return dpmi != null && dpmi.canSilentlyInstallPackage(mInstallerPackageName, mInstallerUid); } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index edab94c963f1..6cfb846e3ade 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24,7 +24,6 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; -import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; @@ -304,7 +303,6 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.ArtManagerService; -import com.android.server.pm.dex.DexLogger; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.PackageDexUsage; @@ -2168,10 +2166,7 @@ public class PackageManagerService extends IPackageManager.Stub mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); - DexManager.Listener dexManagerListener = DexLogger.getListener(this, - installer, mInstallLock); - mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock, - dexManagerListener); + mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock); mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); @@ -9215,7 +9210,7 @@ public class PackageManagerService extends IPackageManager.Stub /** * Reconcile the information we have about the secondary dex files belonging to - * {@code packagName} and the actual dex files. For all dex files that were + * {@code packageName} and the actual dex files. For all dex files that were * deleted, update the internal records and delete the generated oat files. */ @Override @@ -15269,9 +15264,12 @@ public class PackageManagerService extends IPackageManager.Stub final DeletePackageAction deletePackageAction; // we only want to try to delete for non system apps if (prepareResult.replace && !prepareResult.system) { + final boolean killApp = (scanResult.request.scanFlags & SCAN_DONT_KILL_APP) == 0; + final int deleteFlags = PackageManager.DELETE_KEEP_DATA + | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP); deletePackageAction = mayDeletePackageLocked(res.removedInfo, prepareResult.originalPs, prepareResult.disabledPs, - prepareResult.childPackageSettings); + prepareResult.childPackageSettings, deleteFlags, installArgs.user); if (deletePackageAction == null) { throw new ReconcileFailure( PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE, @@ -15353,12 +15351,9 @@ public class PackageManagerService extends IPackageManager.Stub } } } else { - final boolean killApp = (scanRequest.scanFlags & SCAN_DONT_KILL_APP) == 0; - final int deleteFlags = PackageManager.DELETE_KEEP_DATA - | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP); try { executeDeletePackageLIF(reconciledPkg.deletePackageAction, packageName, - null, true, request.mAllUsers, deleteFlags, true, pkg); + true, request.mAllUsers, true, pkg); } catch (SystemDeleteException e) { if (Build.IS_ENG) { throw new RuntimeException("Unexpected failure", e); @@ -17818,12 +17813,23 @@ public class PackageManagerService extends IPackageManager.Stub public final PackageSetting deletingPs; public final PackageSetting disabledPs; public final PackageRemovedInfo outInfo; + public final int flags; + public final UserHandle user; + /** + * True if this package is an unupdated system app that may be deleted by the system. + * When true, disabledPs will be null. + */ + public final boolean mayDeleteUnupdatedSystemApp; private DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs, - PackageRemovedInfo outInfo) { + PackageRemovedInfo outInfo, int flags, UserHandle user, + boolean mayDeleteUnupdatedSystemApp) { this.deletingPs = deletingPs; this.disabledPs = disabledPs; this.outInfo = outInfo; + this.flags = flags; + this.user = user; + this.mayDeleteUnupdatedSystemApp = mayDeleteUnupdatedSystemApp; } } @@ -17835,23 +17841,26 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mPackages") private static DeletePackageAction mayDeletePackageLocked( PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs, - @Nullable PackageSetting[] children) { + @Nullable PackageSetting[] children, int flags, UserHandle user) { if (ps == null) { return null; } + boolean mayDeleteUnupdatedSystemApp = false; if (isSystemApp(ps)) { if (ps.parentPackageName != null) { Slog.w(TAG, "Attempt to delete child system package " + ps.pkg.packageName); return null; } - // Confirm if the system package has been updated - // An updated system app can be deleted. This will also have to restore - // the system pkg from system partition - // reader - if (disabledPs == null) { - Slog.w(TAG, - "Attempt to delete unknown system package " + ps.pkg.packageName); + if (((flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null + && user.getIdentifier() != UserHandle.USER_ALL) { + mayDeleteUnupdatedSystemApp = true; + } else if (disabledPs == null) { + // Confirmed if the system package has been updated + // An updated system app can be deleted. This will also have to restore + // the system pkg from system partition + // reader + Slog.w(TAG, "Attempt to delete unknown system package " + ps.pkg.packageName); return null; } } @@ -17868,7 +17877,8 @@ public class PackageManagerService extends IPackageManager.Stub } } } - return new DeletePackageAction(ps, disabledPs, outInfo); + return new DeletePackageAction(ps, disabledPs, outInfo, flags, user, + mayDeleteUnupdatedSystemApp); } /* @@ -17883,7 +17893,7 @@ public class PackageManagerService extends IPackageManager.Stub final PackageSetting ps = mSettings.mPackages.get(packageName); final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps); PackageSetting[] children = mSettings.getChildSettingsLPr(ps); - action = mayDeletePackageLocked(outInfo, ps, disabledPs, children); + action = mayDeletePackageLocked(outInfo, ps, disabledPs, children, flags, user); } if (null == action) { return false; @@ -17892,8 +17902,8 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user); try { - executeDeletePackageLIF(action, packageName, user, deleteCodeAndResources, - allUserHandles, flags, writeSettings, replacingPackage); + executeDeletePackageLIF(action, packageName, deleteCodeAndResources, + allUserHandles, writeSettings, replacingPackage); } catch (SystemDeleteException e) { return false; } @@ -17910,11 +17920,13 @@ public class PackageManagerService extends IPackageManager.Stub /** Deletes a package. Only throws when install of a disabled package fails. */ private void executeDeletePackageLIF(DeletePackageAction action, - String packageName, UserHandle user, boolean deleteCodeAndResources, - int[] allUserHandles, int flags, boolean writeSettings, + String packageName, boolean deleteCodeAndResources, + int[] allUserHandles, boolean writeSettings, PackageParser.Package replacingPackage) throws SystemDeleteException { final PackageSetting ps = action.deletingPs; final PackageRemovedInfo outInfo = action.outInfo; + final UserHandle user = action.user; + final int flags = action.flags; final boolean systemApp = isSystemApp(ps); synchronized (mPackages) { @@ -17940,8 +17952,7 @@ public class PackageManagerService extends IPackageManager.Stub } - if (((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null - && user.getIdentifier() != UserHandle.USER_ALL)) { + if (!systemApp || action.mayDeleteUnupdatedSystemApp) { // The caller is asking that the package only be deleted for a single // user. To do this, we just mark its uninstalled state and delete // its data. If this is a system app, we only allow this to happen if @@ -20169,11 +20180,6 @@ public class PackageManagerService extends IPackageManager.Stub if (Process.isIsolated(uid)) { return Zygote.MOUNT_EXTERNAL_NONE; } - if (StorageManager.hasIsolatedStorage()) { - return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED - ? Zygote.MOUNT_EXTERNAL_FULL - : Zygote.MOUNT_EXTERNAL_WRITE; - } if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) { return Zygote.MOUNT_EXTERNAL_DEFAULT; } diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java index 88d9e52ccf51..68a755b382ca 100644 --- a/services/core/java/com/android/server/pm/dex/DexLogger.java +++ b/services/core/java/com/android/server/pm/dex/DexLogger.java @@ -18,29 +18,32 @@ package com.android.server.pm.dex; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.os.FileUtils; import android.os.RemoteException; - -import android.util.ArraySet; +import android.os.storage.StorageManager; import android.util.ByteStringUtils; import android.util.EventLog; import android.util.PackageUtils; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.Installer; import com.android.server.pm.Installer.InstallerException; +import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; +import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import java.io.File; +import java.util.Map; import java.util.Set; -import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; - /** * This class is responsible for logging data about secondary dex files. * The data logged includes hashes of the name and content of each file. */ -public class DexLogger implements DexManager.Listener { +public class DexLogger { private static final String TAG = "DexLogger"; // Event log tag & subtag used for SafetyNet logging of dynamic @@ -49,75 +52,172 @@ public class DexLogger implements DexManager.Listener { private static final String DCL_SUBTAG = "dcl"; private final IPackageManager mPackageManager; + private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; private final Object mInstallLock; @GuardedBy("mInstallLock") private final Installer mInstaller; - public static DexManager.Listener getListener(IPackageManager pms, - Installer installer, Object installLock) { - return new DexLogger(pms, installer, installLock); + public DexLogger(IPackageManager pms, Installer installer, Object installLock) { + this(pms, installer, installLock, new PackageDynamicCodeLoading()); } @VisibleForTesting - /*package*/ DexLogger(IPackageManager pms, Installer installer, Object installLock) { + DexLogger(IPackageManager pms, Installer installer, Object installLock, + PackageDynamicCodeLoading packageDynamicCodeLoading) { mPackageManager = pms; + mPackageDynamicCodeLoading = packageDynamicCodeLoading; mInstaller = installer; mInstallLock = installLock; } + public Set<String> getAllPackagesWithDynamicCodeLoading() { + return mPackageDynamicCodeLoading.getAllPackagesWithDynamicCodeLoading(); + } + /** - * Compute and log hashes of the name and content of a secondary dex file. + * Write information about code dynamically loaded by {@code packageName} to the event log. */ - @Override - public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo, - String dexPath, int storageFlags) { - int ownerUid = appInfo.uid; - - byte[] hash = null; - synchronized(mInstallLock) { - try { - hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName, - ownerUid, appInfo.volumeUuid, storageFlags); - } catch (InstallerException e) { - Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath + - " : " + e.getMessage()); - } - } - if (hash == null) { + public void logDynamicCodeLoading(String packageName) { + PackageDynamicCode info = getPackageDynamicCodeInfo(packageName); + if (info == null) { return; } - String dexFileName = new File(dexPath).getName(); - String message = PackageUtils.computeSha256Digest(dexFileName.getBytes()); - // Valid SHA256 will be 256 bits, 32 bytes. - if (hash.length == 32) { - message = message + ' ' + ByteStringUtils.toHexString(hash); - } + SparseArray<ApplicationInfo> appInfoByUser = new SparseArray<>(); + boolean needWrite = false; + + for (Map.Entry<String, DynamicCodeFile> fileEntry : info.mFileUsageMap.entrySet()) { + String filePath = fileEntry.getKey(); + DynamicCodeFile fileInfo = fileEntry.getValue(); + int userId = fileInfo.mUserId; - writeDclEvent(ownerUid, message); + int index = appInfoByUser.indexOfKey(userId); + ApplicationInfo appInfo; + if (index >= 0) { + appInfo = appInfoByUser.get(userId); + } else { + appInfo = null; - if (dexUseInfo.isUsedByOtherApps()) { - Set<String> otherPackages = dexUseInfo.getLoadingPackages(); - Set<Integer> otherUids = new ArraySet<>(otherPackages.size()); - for (String otherPackageName : otherPackages) { try { - int otherUid = mPackageManager.getPackageUid( - otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId()); - if (otherUid != -1 && otherUid != ownerUid) { - otherUids.add(otherUid); - } - } catch (RemoteException ignore) { + PackageInfo ownerInfo = + mPackageManager.getPackageInfo(packageName, /*flags*/ 0, userId); + appInfo = ownerInfo == null ? null : ownerInfo.applicationInfo; + } catch (RemoteException ignored) { // Can't happen, we're local. } + appInfoByUser.put(userId, appInfo); + if (appInfo == null) { + Slog.d(TAG, "Could not find package " + packageName + " for user " + userId); + // Package has probably been uninstalled for user. + needWrite |= mPackageDynamicCodeLoading.removeUserPackage(packageName, userId); + } + } + + if (appInfo == null) { + continue; } - for (int otherUid : otherUids) { - writeDclEvent(otherUid, message); + + int storageFlags; + if (appInfo.deviceProtectedDataDir != null + && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) { + storageFlags = StorageManager.FLAG_STORAGE_DE; + } else if (appInfo.credentialProtectedDataDir != null + && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) { + storageFlags = StorageManager.FLAG_STORAGE_CE; + } else { + Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath); + needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); + continue; + } + + byte[] hash = null; + synchronized (mInstallLock) { + try { + hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid, + appInfo.volumeUuid, storageFlags); + } catch (InstallerException e) { + Slog.e(TAG, "Got InstallerException when hashing file " + filePath + + ": " + e.getMessage()); + } + } + + String fileName = new File(filePath).getName(); + String message = PackageUtils.computeSha256Digest(fileName.getBytes()); + + // Valid SHA256 will be 256 bits, 32 bytes. + if (hash != null && hash.length == 32) { + message = message + ' ' + ByteStringUtils.toHexString(hash); + } else { + Slog.d(TAG, "Got no hash for " + filePath); + // File has probably been deleted. + needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); + } + + for (String loadingPackageName : fileInfo.mLoadingPackages) { + int loadingUid = -1; + if (loadingPackageName.equals(packageName)) { + loadingUid = appInfo.uid; + } else { + try { + loadingUid = mPackageManager.getPackageUid(loadingPackageName, /*flags*/ 0, + userId); + } catch (RemoteException ignored) { + // Can't happen, we're local. + } + } + + if (loadingUid != -1) { + writeDclEvent(loadingUid, message); + } } } + + if (needWrite) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } + + @VisibleForTesting + PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { + return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); } @VisibleForTesting - /*package*/ void writeDclEvent(int uid, String message) { + void writeDclEvent(int uid, String message) { EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message); } + + void record(int loaderUserId, String dexPath, + String owningPackageName, String loadingPackageName) { + if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath, + PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, + loadingPackageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } + + void clear() { + mPackageDynamicCodeLoading.clear(); + } + + void removePackage(String packageName) { + if (mPackageDynamicCodeLoading.removePackage(packageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } + + void removeUserPackage(String packageName, int userId) { + if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } + + void readAndSync(Map<String, Set<Integer>> packageToUsersMap) { + mPackageDynamicCodeLoading.read(); + mPackageDynamicCodeLoading.syncData(packageToUsersMap); + } + + void writeNow() { + mPackageDynamicCodeLoading.writeNow(); + } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 36b7269576b6..25ef7675e2b9 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -19,7 +19,6 @@ package com.android.server.pm.dex; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; -import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import android.content.ContentResolver; import android.content.Context; @@ -90,18 +89,17 @@ public class DexManager { // encode and save the dex usage data. private final PackageDexUsage mPackageDexUsage; - // PackageDynamicCodeLoading handles recording of dynamic code loading - - // which is similar to PackageDexUsage but records a different aspect of the data. + // DexLogger handles recording of dynamic code loading - which is similar to PackageDexUsage + // but records a different aspect of the data. // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't // record class loaders or ISAs.) - private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; + private final DexLogger mDexLogger; private final IPackageManager mPackageManager; private final PackageDexOptimizer mPackageDexOptimizer; private final Object mInstallLock; @GuardedBy("mInstallLock") private final Installer mInstaller; - private final Listener mListener; // Possible outcomes of a dex search. private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found @@ -122,26 +120,20 @@ public class DexManager { */ private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo(); - public interface Listener { - /** - * Invoked just before the secondary dex file {@code dexPath} for the specified application - * is reconciled. - */ - void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo, - String dexPath, int storageFlags); - } - public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, - Installer installer, Object installLock, Listener listener) { + Installer installer, Object installLock) { mContext = context; mPackageCodeLocationsCache = new HashMap<>(); mPackageDexUsage = new PackageDexUsage(); - mPackageDynamicCodeLoading = new PackageDynamicCodeLoading(); mPackageManager = pms; mPackageDexOptimizer = pdo; mInstaller = installer; mInstallLock = installLock; - mListener = listener; + mDexLogger = new DexLogger(pms, installer, installLock); + } + + public DexLogger getDexLogger() { + return mDexLogger; } public void systemReady() { @@ -243,11 +235,8 @@ public class DexManager { continue; } - if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath, - PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, - loadingAppInfo.packageName)) { - mPackageDynamicCodeLoading.maybeWriteAsync(); - } + mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName, + loadingAppInfo.packageName); if (classLoaderContexts != null) { @@ -284,7 +273,7 @@ public class DexManager { loadInternal(existingPackages); } catch (Exception e) { mPackageDexUsage.clear(); - mPackageDynamicCodeLoading.clear(); + mDexLogger.clear(); Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e); } } @@ -335,16 +324,12 @@ public class DexManager { if (mPackageDexUsage.removePackage(packageName)) { mPackageDexUsage.maybeWriteAsync(); } - if (mPackageDynamicCodeLoading.removePackage(packageName)) { - mPackageDynamicCodeLoading.maybeWriteAsync(); - } + mDexLogger.removePackage(packageName); } else { if (mPackageDexUsage.removeUserPackage(packageName, userId)) { mPackageDexUsage.maybeWriteAsync(); } - if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { - mPackageDynamicCodeLoading.maybeWriteAsync(); - } + mDexLogger.removeUserPackage(packageName, userId); } } @@ -423,10 +408,9 @@ public class DexManager { } try { - mPackageDynamicCodeLoading.read(); - mPackageDynamicCodeLoading.syncData(packageToUsersMap); + mDexLogger.readAndSync(packageToUsersMap); } catch (Exception e) { - mPackageDynamicCodeLoading.clear(); + mDexLogger.clear(); Slog.w(TAG, "Exception while loading package dynamic code usage. " + "Starting with a fresh state.", e); } @@ -460,11 +444,6 @@ public class DexManager { return mPackageDexUsage.getPackageUseInfo(packageName) != null; } - @VisibleForTesting - /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { - return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); - } - /** * Perform dexopt on with the given {@code options} on the secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped @@ -574,10 +553,6 @@ public class DexManager { continue; } - if (mListener != null) { - mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags); - } - boolean dexStillExists = true; synchronized(mInstallLock) { try { @@ -721,7 +696,7 @@ public class DexManager { */ public void writePackageDexUsageNow() { mPackageDexUsage.writeNow(); - mPackageDynamicCodeLoading.writeNow(); + mDexLogger.writeNow(); } private void registerSettingObserver() { diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index c5139b562065..cedb54801dc8 100644 --- a/services/core/java/com/android/server/power/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java @@ -111,7 +111,7 @@ public class BatterySaverPolicy extends ContentObserver { false, /* enableAdjustBrightness */ false, /* enableDataSaver */ true, /* enableFirewall */ - false, /* enableQuickDoze */ + true, /* enableQuickDoze */ new ArrayMap<>(), /* filesForInteractive */ new ArrayMap<>(), /* filesForNoninteractive */ true, /* forceAllAppsStandby */ diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS index 20e4985ddd19..244ccb69e958 100644 --- a/services/core/java/com/android/server/power/OWNERS +++ b/services/core/java/com/android/server/power/OWNERS @@ -1,4 +1,5 @@ michaelwr@google.com +santoscordon@google.com per-file BatterySaverPolicy.java=omakoto@google.com per-file ShutdownThread.java=fkupolov@google.com diff --git a/services/core/java/com/android/server/signedconfig/SignedConfig.java b/services/core/java/com/android/server/signedconfig/SignedConfig.java index e6bb800045c8..560a1e1cfe6c 100644 --- a/services/core/java/com/android/server/signedconfig/SignedConfig.java +++ b/services/core/java/com/android/server/signedconfig/SignedConfig.java @@ -33,19 +33,34 @@ import java.util.Set; * Represents signed configuration. * * <p>This configuration should only be used if the signature has already been verified. + * + * This class also parses signed config from JSON. The format expected is: + * <pre> + * { + * "version": 1 + * "config": [ + * { + * "min_sdk": 28, + * "max_sdk": 29, + * "values": { + * "key": "value", + * "key2": "value2" + * ... + * } + * }, + * ... + * ], + * } + * </pre> */ public class SignedConfig { private static final String KEY_VERSION = "version"; private static final String KEY_CONFIG = "config"; - private static final String CONFIG_KEY_MIN_SDK = "minSdk"; - private static final String CONFIG_KEY_MAX_SDK = "maxSdk"; + private static final String CONFIG_KEY_MIN_SDK = "min_sdk"; + private static final String CONFIG_KEY_MAX_SDK = "max_sdk"; private static final String CONFIG_KEY_VALUES = "values"; - // TODO it may be better to use regular key/value pairs in a JSON object, rather than an array - // of objects with the 2 keys below. - private static final String CONFIG_KEY_KEY = "key"; - private static final String CONFIG_KEY_VALUE = "value"; /** * Represents config values targeting an SDK range. @@ -141,14 +156,10 @@ public class SignedConfig { throws JSONException, InvalidConfigException { int minSdk = json.getInt(CONFIG_KEY_MIN_SDK); int maxSdk = json.getInt(CONFIG_KEY_MAX_SDK); - JSONArray valueArray = json.getJSONArray(CONFIG_KEY_VALUES); + JSONObject valuesJson = json.getJSONObject(CONFIG_KEY_VALUES); Map<String, String> values = new HashMap<>(); - for (int i = 0; i < valueArray.length(); ++i) { - JSONObject keyValuePair = valueArray.getJSONObject(i); - String key = keyValuePair.getString(CONFIG_KEY_KEY); - Object valueObject = keyValuePair.has(CONFIG_KEY_VALUE) - ? keyValuePair.get(CONFIG_KEY_VALUE) - : null; + for (String key : valuesJson.keySet()) { + Object valueObject = valuesJson.get(key); String value = valueObject == JSONObject.NULL || valueObject == null ? null : valueObject.toString(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 4e9c5ab39ea8..de8024fb9ae8 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -96,6 +96,7 @@ import static com.android.server.am.ActivityRecordProto.TRANSLUCENT; import static com.android.server.am.ActivityRecordProto.VISIBLE; import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY; import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY; +import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; @@ -122,8 +123,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; -import static com.android.server.wm.ActivityTaskManagerService - .RELAUNCH_REASON_WINDOWING_MODE_RESIZE; +import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; @@ -157,6 +157,7 @@ import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.PipModeChangeItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.WindowVisibilityItem; +import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -716,8 +717,12 @@ final class ActivityRecord extends ConfigurationContainer { // to forcing the update of the picture-in-picture mode as a part of the PiP animation. mLastReportedPictureInPictureMode = inPictureInPictureMode; mLastReportedMultiWindowMode = inPictureInPictureMode; - final Configuration newConfig = task.computeNewOverrideConfigurationForBounds( - targetStackBounds, null); + final Configuration newConfig = new Configuration(); + if (targetStackBounds != null && !targetStackBounds.isEmpty()) { + task.computeResolvedOverrideConfiguration(newConfig, + task.getParent().getConfiguration(), + task.getRequestedOverrideConfiguration()); + } schedulePictureInPictureModeChanged(newConfig); scheduleMultiWindowModeChanged(newConfig); } @@ -1035,8 +1040,6 @@ final class ActivityRecord extends ConfigurationContainer { inHistory = true; - final TaskWindowContainerController taskController = task.getWindowContainerController(); - // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration() task.updateOverrideConfigurationFromLaunchBounds(); // Make sure override configuration is up-to-date before using to create window controller. @@ -1048,10 +1051,9 @@ final class ActivityRecord extends ConfigurationContainer { // TODO: Should this throw an exception instead? Slog.w(TAG, "Attempted to add existing app token: " + appToken); } else { - final Task container = taskController.mContainer; + final Task container = task.getTask(); if (container == null) { - throw new IllegalArgumentException("AppWindowContainerController: invalid " - + " controller=" + taskController); + throw new IllegalArgumentException("createAppWindowToken: invalid task =" + task); } mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken, task.voiceSession != null, container.getDisplayContent(), @@ -1062,7 +1064,7 @@ final class ActivityRecord extends ConfigurationContainer { mLaunchTaskBehind, isAlwaysFocusable()); if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) { Slog.v(TAG, "addAppToken: " - + mAppWindowToken + " controller=" + taskController + " at " + + mAppWindowToken + " task=" + container + " at " + Integer.MAX_VALUE); } container.addChild(mAppWindowToken, Integer.MAX_VALUE /* add on top */); @@ -1091,6 +1093,12 @@ final class ActivityRecord extends ConfigurationContainer { Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + appToken); return false; } + if (mAppWindowToken.getTask() == null) { + // Can be removed after unification of Task and TaskRecord. + Slog.w(TAG_WM, "Attempted to start a window to an app token not having attached to any" + + " task: " + appToken); + return false; + } return mAppWindowToken.addStartingWindow(pkg, theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, transferFrom, newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated, fromRecents); @@ -1148,7 +1156,7 @@ final class ActivityRecord extends ConfigurationContainer { + " r=" + this + " (" + prevTask.getStackId() + ")"); } - mAppWindowToken.reparent(newTask.getWindowContainerController(), position); + mAppWindowToken.reparent(newTask.getTask(), position); // Reparenting prevents informing the parent stack of activity removal in the case that // the new stack has the same parent. we must manually signal here if this is not the case. @@ -1801,6 +1809,18 @@ final class ActivityRecord extends ConfigurationContainer { } mAppWindowToken.detachChildren(); } + + if (state == RESUMED) { + mAtmService.updateBatteryStats(this, true); + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_RESUMED); + } else if (state == PAUSED) { + mAtmService.updateBatteryStats(this, false); + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED); + } else if (state == STOPPED) { + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED); + } else if (state == DESTROYED) { + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED); + } } ActivityState getState() { @@ -1996,10 +2016,7 @@ final class ActivityRecord extends ConfigurationContainer { stopped = false; if (isActivityTypeHome()) { - WindowProcessController app = task.mActivities.get(0).app; - if (hasProcess() && app != mAtmService.mHomeProcess) { - mAtmService.mHomeProcess = app; - } + mStackSupervisor.updateHomeProcess(task.mActivities.get(0).app); } if (nowVisible) { @@ -2542,12 +2559,10 @@ final class ActivityRecord extends ConfigurationContainer { setBounds(mTmpBounds); - final Rect updatedBounds = getRequestedOverrideBounds(); - // Bounds changed...update configuration to match. if (!matchParentBounds()) { - task.computeOverrideConfiguration(mTmpConfig, updatedBounds, - false /* overrideWidth */, false /* overrideHeight */); + task.computeResolvedOverrideConfiguration(mTmpConfig, + task.getParent().getConfiguration(), getRequestedOverrideConfiguration()); } onRequestedOverrideConfigurationChanged(mTmpConfig); @@ -2774,7 +2789,7 @@ final class ActivityRecord extends ConfigurationContainer { final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged()); if (hasResizeChange) { final boolean isDragResizing = - getTaskRecord().getWindowContainerController().isDragResizing(); + getTaskRecord().getTask().isDragResizing(); mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE : RELAUNCH_REASON_WINDOWING_MODE_RESIZE; } else { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index aca9702a45c8..2663d997162c 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -294,13 +294,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // stack and the new stack will be on top of all stacks. static final int REMOVE_TASK_MODE_MOVING_TO_TOP = 2; - // The height/width divide used when fitting a task within a bounds with method - // {@link #fitWithinBounds}. - // We always want the task to to be visible in the bounds without affecting its size when - // fitting. To make sure this is the case, we don't adjust the task left or top side pass - // the input bounds right or bottom side minus the width or height divided by this value. - private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3; - final ActivityTaskManagerService mService; private final WindowManagerService mWindowManager; T mWindowContainerController; @@ -365,9 +358,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private boolean mUpdateBoundsDeferred; private boolean mUpdateBoundsDeferredCalled; + private boolean mUpdateDisplayedBoundsDeferredCalled; private final Rect mDeferredBounds = new Rect(); - private final Rect mDeferredTaskBounds = new Rect(); - private final Rect mDeferredTaskInsetBounds = new Rect(); + private final Rect mDeferredDisplayedBounds = new Rect(); int mCurrentUser; @@ -605,7 +598,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { getStackDockedModeBounds(null, null, mTmpRect2, mTmpRect3); // immediately resize so docked bounds are available in onSplitScreenModeActivated - resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */); + setTaskDisplayedBounds(null); + setTaskBounds(mTmpRect2); + setBounds(mTmpRect2); } else if ( getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { Rect dockedBounds = display.getSplitScreenPrimaryStack().getBounds(); @@ -911,7 +906,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } void positionChildWindowContainerAtTop(TaskRecord child) { - mWindowContainerController.positionChildAtTop(child.getWindowContainerController(), + mWindowContainerController.positionChildAtTop(child.getTask(), true /* includingParents */); } @@ -921,7 +916,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // task to bottom, the next focusable stack on the same display should be focused. final ActivityStack nextFocusableStack = getDisplay().getNextFocusableStack( child.getStack(), true /* ignoreCurrent */); - mWindowContainerController.positionChildAtBottom(child.getWindowContainerController(), + mWindowContainerController.positionChildAtBottom(child.getTask(), nextFocusableStack == null /* includingParents */); } @@ -949,17 +944,19 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * be resized to that bounds. */ void continueUpdateBounds() { - final boolean wasDeferred = mUpdateBoundsDeferred; - mUpdateBoundsDeferred = false; - if (wasDeferred && mUpdateBoundsDeferredCalled) { - resize(mDeferredBounds.isEmpty() ? null : mDeferredBounds, - mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds, - mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds); + if (mUpdateBoundsDeferred) { + mUpdateBoundsDeferred = false; + if (mUpdateBoundsDeferredCalled) { + setTaskBounds(mDeferredBounds); + setBounds(mDeferredBounds); + } + if (mUpdateDisplayedBoundsDeferredCalled) { + setTaskDisplayedBounds(mDeferredDisplayedBounds); + } } } - boolean updateBoundsAllowed(Rect bounds, Rect tempTaskBounds, - Rect tempTaskInsetBounds) { + boolean updateBoundsAllowed(Rect bounds) { if (!mUpdateBoundsDeferred) { return true; } @@ -968,17 +965,20 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } else { mDeferredBounds.setEmpty(); } - if (tempTaskBounds != null) { - mDeferredTaskBounds.set(tempTaskBounds); - } else { - mDeferredTaskBounds.setEmpty(); + mUpdateBoundsDeferredCalled = true; + return false; + } + + boolean updateDisplayedBoundsAllowed(Rect bounds) { + if (!mUpdateBoundsDeferred) { + return true; } - if (tempTaskInsetBounds != null) { - mDeferredTaskInsetBounds.set(tempTaskInsetBounds); + if (bounds != null) { + mDeferredDisplayedBounds.set(bounds); } else { - mDeferredTaskInsetBounds.setEmpty(); + mDeferredDisplayedBounds.setEmpty(); } - mUpdateBoundsDeferredCalled = true; + mUpdateDisplayedBoundsDeferredCalled = true; return false; } @@ -1617,7 +1617,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai try { EventLogTags.writeAmPauseActivity(prev.mUserId, System.identityHashCode(prev), prev.shortComponentName, "userLeaving=" + userLeaving); - mService.updateUsageStats(prev, false); mService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, @@ -2984,7 +2983,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai position = getAdjustedPositionForTask(task, position, null /* starting */); mTaskHistory.remove(task); mTaskHistory.add(position, task); - mWindowContainerController.positionChildAt(task.getWindowContainerController(), position); + mWindowContainerController.positionChildAt(task.getTask(), position); updateTaskMovement(task, true); } @@ -4649,9 +4648,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai r.mUserId, System.identityHashCode(r), r.getTaskRecord().taskId, r.shortComponentName, "proc died without state saved"); - if (r.getState() == RESUMED) { - mService.updateUsageStats(r, false); - } } } else { // We have the current state for this activity, so @@ -4912,7 +4908,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // TODO: Can only be called from special methods in ActivityStackSupervisor. // Need to consolidate those calls points into this resize method so anyone can call directly. void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) { - if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) { + if (!updateBoundsAllowed(bounds)) { return; } @@ -4926,20 +4922,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai for (int i = mTaskHistory.size() - 1; i >= 0; i--) { final TaskRecord task = mTaskHistory.get(i); if (task.isResizeable()) { - if (inFreeformWindowingMode()) { - // TODO(b/71028874): Can be removed since each freeform task is its own - // stack. - // For freeform stack we don't adjust the size of the tasks to match that - // of the stack, but we do try to make sure the tasks are still contained - // with the bounds of the stack. - if (task.getRequestedOverrideBounds() != null) { - mTmpRect2.set(task.getRequestedOverrideBounds()); - fitWithinBounds(mTmpRect2, bounds); - task.updateOverrideConfiguration(mTmpRect2); - } - } else { - task.updateOverrideConfiguration(taskBounds, insetBounds); - } + task.updateOverrideConfiguration(taskBounds, insetBounds); } if (task.hasDisplayedBounds()) { @@ -4951,7 +4934,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds); setBounds(bounds); } @@ -4961,41 +4943,37 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai /** - * Adjust bounds to stay within stack bounds. - * - * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way - * that keep them unchanged, but be contained within the stack bounds. - * - * @param bounds Bounds to be adjusted. - * @param stackBounds Bounds within which the other bounds should remain. + * Until we can break this "set task bounds to same as stack bounds" behavior, this + * basically resizes both stack and task bounds to the same bounds. */ - private static void fitWithinBounds(Rect bounds, Rect stackBounds) { - if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) { + void setTaskBounds(Rect bounds) { + if (!updateBoundsAllowed(bounds)) { return; } - if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) { - final int maxRight = stackBounds.right - - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER); - int horizontalDiff = stackBounds.left - bounds.left; - if ((horizontalDiff < 0 && bounds.left >= maxRight) - || (bounds.left + horizontalDiff >= maxRight)) { - horizontalDiff = maxRight - bounds.left; + for (int i = mTaskHistory.size() - 1; i >= 0; i--) { + final TaskRecord task = mTaskHistory.get(i); + if (task.isResizeable()) { + task.setBounds(bounds); + } else { + task.setBounds(null); } - bounds.left += horizontalDiff; - bounds.right += horizontalDiff; } + } - if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) { - final int maxBottom = stackBounds.bottom - - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER); - int verticalDiff = stackBounds.top - bounds.top; - if ((verticalDiff < 0 && bounds.top >= maxBottom) - || (bounds.top + verticalDiff >= maxBottom)) { - verticalDiff = maxBottom - bounds.top; + /** Helper to setDisplayedBounds on all child tasks */ + void setTaskDisplayedBounds(Rect bounds) { + if (!updateDisplayedBoundsAllowed(bounds)) { + return; + } + + for (int i = mTaskHistory.size() - 1; i >= 0; i--) { + final TaskRecord task = mTaskHistory.get(i); + if (bounds == null || bounds.isEmpty()) { + task.setDisplayedBounds(null); + } else if (task.isResizeable()) { + task.setDisplayedBounds(bounds); } - bounds.top += verticalDiff; - bounds.bottom += verticalDiff; } } @@ -5349,7 +5327,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) { task.updateOverrideConfiguration(getRequestedOverrideBounds()); } - task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); + task.createTask(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); return task; } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index e761ad86c770..c43fd2487470 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -179,6 +179,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12; static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14; static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15; + static final int REPORT_HOME_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 16; // Used to indicate that windows of activities should be preserved during the resize. static final boolean PRESERVE_WINDOWS = true; @@ -793,7 +794,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { System.identityHashCode(r), task.taskId, r.shortComponentName); if (r.isActivityTypeHome()) { // Home process is the root process of the task. - mService.mHomeProcess = task.mActivities.get(0).app; + updateHomeProcess(task.mActivities.get(0).app); } mService.getPackageManagerInternalLocked().notifyPackageUse( r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY); @@ -915,6 +916,15 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { return true; } + void updateHomeProcess(WindowProcessController app) { + if (app != null && mService.mHomeProcess != app) { + if (!mHandler.hasMessages(REPORT_HOME_CHANGED_MSG)) { + mHandler.sendEmptyMessage(REPORT_HOME_CHANGED_MSG); + } + mService.mHomeProcess = app; + } + } + private void logIfTransactionTooLarge(Intent intent, Bundle icicle) { int extrasSize = 0; if (intent != null) { @@ -1868,7 +1878,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { stack.addTask(task, onTop, "restoreRecentTask"); // TODO: move call for creation here and other place into Stack.addTask() - task.createWindowContainer(onTop, true /* showForAllUsers */); + task.createTask(onTop, true /* showForAllUsers */); if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Added restored task=" + task + " to stack=" + stack); final ArrayList<ActivityRecord> activities = task.mActivities; @@ -2040,9 +2050,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mStoppingActivities.remove(r); final ActivityStack stack = r.getActivityStack(); - if (mRootActivityContainer.isTopDisplayFocusedStack(stack)) { - mService.updateUsageStats(r, true); - } if (stack.getDisplay().allResumedActivitiesComplete()) { mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); // Make sure activity & window visibility should be identical @@ -2543,7 +2550,15 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } } break; + case REPORT_HOME_CHANGED_MSG: { + synchronized (mService.mGlobalLock) { + mHandler.removeMessages(REPORT_HOME_CHANGED_MSG); + // Start home activities on displays with no activities. + mRootActivityContainer.startHomeOnEmptyDisplays("homeChanged"); + } + } + break; } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 182d1a0f9c5d..986115726efb 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3799,7 +3799,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // changed, so we should reflect that check here as well. final PinnedActivityStack stack = r.getActivityStack(); final PinnedStackWindowController windowController = stack.getWindowContainerController(); - return !windowController.isAnimatingBoundsToFullscreen(); + return !windowController.mContainer.isAnimatingBoundsToFullscreen(); } @Override @@ -5234,13 +5234,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mH.post(mAmInternal::updateCpuStats); } - void updateUsageStats(ActivityRecord component, boolean resumed) { - final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateUsageStats, + void updateBatteryStats(ActivityRecord component, boolean resumed) { + final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateBatteryStats, mAmInternal, component.mActivityComponent, component.app.mUid, component.mUserId, resumed); mH.sendMessage(m); } + void updateActivityUsageStats(ActivityRecord activity, int event) { + final Message m = PooledLambda.obtainMessage( + ActivityManagerInternal::updateActivityUsageStats, mAmInternal, + activity.mActivityComponent, activity.mUserId, event, activity.appToken); + mH.sendMessage(m); + } + void setBooting(boolean booting) { mAmInternal.setBooting(booting); } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index c458c94b59e2..8624bff11a18 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -746,6 +746,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override void removeImmediately() { onRemovedFromDisplay(); + if (mActivityRecord != null) { + mActivityRecord.unregisterConfigurationChangeListener(this); + } super.removeImmediately(); } @@ -1175,21 +1178,14 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } - void reparent(TaskWindowContainerController taskController, int position) { + void reparent(Task task, int position) { if (DEBUG_ADD_REMOVE) { Slog.i(TAG_WM, "reparent: moving app token=" + this - + " to task=" + taskController + " at " + position); + + " to task=" + task.mTaskId + " at " + position); } - final Task task = taskController.mContainer; if (task == null) { - throw new IllegalArgumentException("reparent: could not find task=" - + taskController); + throw new IllegalArgumentException("reparent: could not find task"); } - reparent(task, position); - getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); - } - - void reparent(Task task, int position) { final Task currentTask = getTask(); if (task == currentTask) { throw new IllegalArgumentException( @@ -1220,6 +1216,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree onDisplayChanged(displayContent); prevDisplayContent.setLayoutNeeded(); } + getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } @Override diff --git a/services/core/java/com/android/server/wm/PinnedActivityStack.java b/services/core/java/com/android/server/wm/PinnedActivityStack.java index 1c7ebd63dfb3..2a05af4f473c 100644 --- a/services/core/java/com/android/server/wm/PinnedActivityStack.java +++ b/services/core/java/com/android/server/wm/PinnedActivityStack.java @@ -77,7 +77,7 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> } boolean isAnimatingBoundsToFullscreen() { - return getWindowContainerController().isAnimatingBoundsToFullscreen(); + return getWindowContainerController().mContainer.isAnimatingBoundsToFullscreen(); } /** diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java index bbdcc62ddbc0..518e39ba9d58 100644 --- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java +++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS; import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END; import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START; @@ -180,15 +181,6 @@ public class PinnedStackWindowController extends StackWindowController { } /** - * @return whether the bounds are currently animating to fullscreen. - */ - public boolean isAnimatingBoundsToFullscreen() { - synchronized (mGlobalLock) { - return mContainer.isAnimatingBoundsToFullscreen(); - } - } - - /** * @return whether the stack can be resized from the bounds animation. */ public boolean pinnedStackResizeDisallowed() { diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index c5b42f99fcda..84a32fc44c53 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -336,6 +336,15 @@ class RootActivityContainer extends ConfigurationContainer return homeStarted; } + void startHomeOnEmptyDisplays(String reason) { + for (int i = mActivityDisplays.size() - 1; i >= 0; i--) { + final ActivityDisplay display = mActivityDisplays.get(i); + if (display.topRunningActivity() == null) { + startHomeOnDisplay(mCurrentUser, reason, display.mDisplayId); + } + } + } + /** * This starts home activity on displays that can have system decorations and only if the * home activity can have multiple instances. diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java index 8f18aa56b001..ada807b1ff3c 100644 --- a/services/core/java/com/android/server/wm/StackWindowController.java +++ b/services/core/java/com/android/server/wm/StackWindowController.java @@ -21,7 +21,6 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import android.app.WindowConfiguration; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Handler; @@ -29,8 +28,6 @@ import android.os.Looper; import android.os.Message; import android.util.Slog; import android.util.SparseArray; -import android.view.DisplayCutout; -import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; @@ -49,11 +46,7 @@ public class StackWindowController private final H mHandler; - // Temp bounds only used in adjustConfigurationForBounds() - private final Rect mTmpRect = new Rect(); - private final Rect mTmpStableInsets = new Rect(); - private final Rect mTmpNonDecorInsets = new Rect(); - private final Rect mTmpDisplayBounds = new Rect(); + final Rect mTmpBounds = new Rect(); public StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds) { @@ -67,107 +60,87 @@ public class StackWindowController mStackId = stackId; mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); - synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - throw new IllegalArgumentException("Trying to add stackId=" + stackId - + " to unknown displayId=" + displayId); - } - - dc.createStack(stackId, onTop, this); - getRawBounds(outBounds); + final DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc == null) { + throw new IllegalArgumentException("Trying to add stackId=" + stackId + + " to unknown displayId=" + displayId); } + + dc.createStack(stackId, onTop, this); + getRawBounds(outBounds); } @Override public void removeContainer() { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.removeIfPossible(); - super.removeContainer(); - } + if (mContainer != null) { + mContainer.removeIfPossible(); + super.removeContainer(); } } - public void reparent(int displayId, Rect outStackBounds, boolean onTop) { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId - + " to displayId=" + displayId); - } - - final DisplayContent targetDc = mRoot.getDisplayContent(displayId); - if (targetDc == null) { - throw new IllegalArgumentException("Trying to move stackId=" + mStackId - + " to unknown displayId=" + displayId); - } + void reparent(int displayId, Rect outStackBounds, boolean onTop) { + if (mContainer == null) { + throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId + + " to displayId=" + displayId); + } - targetDc.moveStackToDisplay(mContainer, onTop); - getRawBounds(outStackBounds); + final DisplayContent targetDc = mRoot.getDisplayContent(displayId); + if (targetDc == null) { + throw new IllegalArgumentException("Trying to move stackId=" + mStackId + + " to unknown displayId=" + displayId); } + + targetDc.moveStackToDisplay(mContainer, onTop); + getRawBounds(outStackBounds); } - public void positionChildAt(TaskWindowContainerController child, int position) { - synchronized (mGlobalLock) { - if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child - + " at " + position); - if (child.mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionChildAt: could not find task=" + this); - return; + void positionChildAt(Task child, int position) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "positionChildAt: positioning task=" + child + " at " + position); + } + if (child == null) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "positionChildAt: could not find task=" + this); } - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionChildAt: could not find stack for task=" + mContainer); - return; + return; + } + if (mContainer == null) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "positionChildAt: could not find stack for task=" + mContainer); } - child.mContainer.positionAt(position); - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + return; } + child.positionAt(position); + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } - public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) { + void positionChildAtTop(Task child, boolean includingParents) { if (child == null) { // TODO: Fix the call-points that cause this to happen. return; } - synchronized (mGlobalLock) { - final Task childTask = child.mContainer; - if (childTask == null) { - Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found"); - return; - } - mContainer.positionChildAt(POSITION_TOP, childTask, includingParents); + mContainer.positionChildAt(POSITION_TOP, child, includingParents); - final DisplayContent displayContent = mContainer.getDisplayContent(); - if (displayContent.mAppTransition.isTransitionSet()) { - childTask.setSendingToBottom(false); - } - displayContent.layoutAndAssignWindowLayersIfNeeded(); + final DisplayContent displayContent = mContainer.getDisplayContent(); + if (displayContent.mAppTransition.isTransitionSet()) { + child.setSendingToBottom(false); } + displayContent.layoutAndAssignWindowLayersIfNeeded(); } - public void positionChildAtBottom(TaskWindowContainerController child, - boolean includingParents) { + void positionChildAtBottom(Task child, boolean includingParents) { if (child == null) { // TODO: Fix the call-points that cause this to happen. return; } - synchronized (mGlobalLock) { - final Task childTask = child.mContainer; - if (childTask == null) { - Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found"); - return; - } - mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents); + mContainer.positionChildAt(POSITION_BOTTOM, child, includingParents); - if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) { - childTask.setSendingToBottom(true); - } - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) { + child.setSendingToBottom(true); } + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } /** @@ -179,24 +152,20 @@ public class StackWindowController */ public void resize(Rect bounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); - } - // We might trigger a configuration change. Save the current task bounds for freezing. - mContainer.prepareFreezingTaskBounds(); - if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds) - && mContainer.isVisible()) { - mContainer.getDisplayContent().setLayoutNeeded(); - mService.mWindowPlacerLocked.performSurfacePlacement(); - } + if (mContainer == null) { + throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); + } + // We might trigger a configuration change. Save the current task bounds for freezing. + mContainer.prepareFreezingTaskBounds(); + if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds) + && mContainer.isVisible()) { + mContainer.getDisplayContent().setLayoutNeeded(); + mService.mWindowPlacerLocked.performSurfacePlacement(); } } public void onPipAnimationEndResize() { - synchronized (mService.mGlobalLock) { - mContainer.onPipAnimationEndResize(); - } + mContainer.onPipAnimationEndResize(); } /** @@ -205,167 +174,37 @@ public class StackWindowController public void getStackDockedModeBounds(Configuration parentConfig, Rect dockedBounds, Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds, - currentTempTaskBounds, outStackBounds, outTempTaskBounds); - return; - } - outStackBounds.setEmpty(); - outTempTaskBounds.setEmpty(); + if (mContainer != null) { + mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds, + currentTempTaskBounds, outStackBounds, outTempTaskBounds); + return; } + outStackBounds.setEmpty(); + outTempTaskBounds.setEmpty(); } public void prepareFreezingTaskBounds() { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this - + " not found."); - } - mContainer.prepareFreezingTaskBounds(); + if (mContainer == null) { + throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this + + " not found."); } + mContainer.prepareFreezingTaskBounds(); } public void getRawBounds(Rect outBounds) { - synchronized (mGlobalLock) { - if (mContainer.matchParentBounds()) { - outBounds.setEmpty(); - } else { - mContainer.getRawBounds(outBounds); - } - } - } - - public void getBounds(Rect outBounds) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.getBounds(outBounds); - return; - } + if (mContainer.matchParentBounds()) { outBounds.setEmpty(); + } else { + mContainer.getRawBounds(outBounds); } } - /** - * Adjusts the screen size in dp's for the {@param config} for the given params. The provided - * params represent the desired state of a configuration change. Since this utility is used - * before mContainer has been updated, any relevant properties (like {@param windowingMode}) - * need to be passed in. - */ - public void adjustConfigurationForBounds(Rect bounds, - Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, - boolean overrideHeight, float density, Configuration config, - Configuration parentConfig, int windowingMode) { - synchronized (mGlobalLock) { - final TaskStack stack = mContainer; - final DisplayContent displayContent = stack.getDisplayContent(); - final DisplayInfo di = displayContent.getDisplayInfo(); - final DisplayCutout displayCutout = di.displayCutout; - final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); - - // Get the insets and display bounds - displayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - displayCutout, mTmpStableInsets); - displayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - displayCutout, mTmpNonDecorInsets); - mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight); - - int width; - int height; - - final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); - - config.windowConfiguration.setBounds(bounds); - config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null); - boolean intersectParentBounds = false; - - if (WindowConfiguration.isFloating(windowingMode)) { - // Floating tasks should not be resized to the screen's bounds. - - if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED - && bounds.width() == mTmpDisplayBounds.width() - && bounds.height() == mTmpDisplayBounds.height()) { - // If the bounds we are animating is the same as the fullscreen stack - // dimensions, then apply the same inset calculations that we normally do for - // the fullscreen stack, without intersecting it with the display bounds - stableBounds.inset(mTmpStableInsets); - nonDecorBounds.inset(mTmpNonDecorInsets); - // Move app bounds to zero to apply intersection with parent correctly. They are - // used only for evaluating width and height, so it's OK to move them around. - config.windowConfiguration.getAppBounds().offsetTo(0, 0); - intersectParentBounds = true; - } - width = (int) (stableBounds.width() / density); - height = (int) (stableBounds.height() / density); - } else { - // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen - // area, i.e. the screen area without the system bars. - // Additionally task dimensions should not be bigger than its parents dimensions. - // The non decor inset are areas that could never be removed in Honeycomb. See - // {@link WindowManagerPolicy#getNonDecorInsetsLw}. - intersectDisplayBoundsExcludeInsets(nonDecorBounds, bounds, mTmpNonDecorInsets, - mTmpDisplayBounds, overrideWidth, overrideHeight); - intersectDisplayBoundsExcludeInsets(stableBounds, bounds, mTmpStableInsets, - mTmpDisplayBounds, overrideWidth, overrideHeight); - width = Math.min((int) (stableBounds.width() / density), - parentConfig.screenWidthDp); - height = Math.min((int) (stableBounds.height() / density), - parentConfig.screenHeightDp); - intersectParentBounds = true; - } - - if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) { - config.windowConfiguration.getAppBounds().intersect(parentAppBounds); - } - - config.screenWidthDp = width; - config.screenHeightDp = height; - config.smallestScreenWidthDp = getSmallestWidthForTaskBounds( - bounds, density, windowingMode); - } - } - - /** - * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable - * inset areas. - * - * @param inOutBounds The inOutBounds to subtract the stable inset areas from. - */ - private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds, - Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) { - mTmpRect.set(inInsetBounds); - mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect); - int leftInset = mTmpRect.left - inInsetBounds.left; - int topInset = mTmpRect.top - inInsetBounds.top; - int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right; - int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom; - inOutBounds.inset(leftInset, topInset, rightInset, bottomInset); - } - - /** - * Calculates the smallest width for a task given the target {@param bounds} and - * {@param windowingMode}. Avoid using values from mContainer since they can be out-of-date. - * - * @return the smallest width to be used in the Configuration, in dips - */ - private int getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode) { - final DisplayContent displayContent = mContainer.getDisplayContent(); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - - if (bounds == null || (bounds.width() == displayInfo.logicalWidth && - bounds.height() == displayInfo.logicalHeight)) { - // If the bounds are fullscreen, return the value of the fullscreen configuration - return displayContent.getConfiguration().smallestScreenWidthDp; - } else if (WindowConfiguration.isFloating(windowingMode)) { - // For floating tasks, calculate the smallest width from the bounds of the task - return (int) (Math.min(bounds.width(), bounds.height()) / density); - } else { - // Iterating across all screen orientations, and return the minimum of the task - // width taking into account that the bounds might change because the snap algorithm - // snaps to a different value - return displayContent.getDockedDividerController() - .getSmallestWidthDpForBounds(bounds); + public void getBounds(Rect outBounds) { + if (mContainer != null) { + mContainer.getBounds(outBounds); + return; } + outBounds.setEmpty(); } void requestResize(Rect bounds) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 67657d0427ba..b10fd31ba7ad 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.EMPTY; import static com.android.server.EventLogTags.WM_TASK_REMOVED; +import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS; import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.DEFER_REMOVAL; @@ -38,6 +39,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.CallSuper; +import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -54,7 +56,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.function.Consumer; -class Task extends WindowContainer<AppWindowToken> { +class Task extends WindowContainer<AppWindowToken> implements ConfigurationContainerListener{ static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM; // TODO: Track parent marks like this in WindowContainer. @@ -109,16 +111,24 @@ class Task extends WindowContainer<AppWindowToken> { /** @see #setCanAffectSystemUiFlags */ private boolean mCanAffectSystemUiFlags = true; + // TODO: remove after unification + TaskRecord mTaskRecord; + Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription, - TaskWindowContainerController controller) { + TaskRecord taskRecord) { super(service); mTaskId = taskId; mStack = stack; mUserId = userId; mResizeMode = resizeMode; mSupportsPictureInPicture = supportsPictureInPicture; - setController(controller); + mTaskRecord = taskRecord; + if (mTaskRecord != null) { + // This can be null when we call createTaskInStack in WindowTestUtils. Remove this after + // unification. + mTaskRecord.registerConfigurationChangeListener(this); + } setBounds(getRequestedOverrideBounds()); mTaskDescription = taskDescription; @@ -191,10 +201,28 @@ class Task extends WindowContainer<AppWindowToken> { if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId); EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask"); mDeferRemoval = false; + if (mTaskRecord != null) { + mTaskRecord.unregisterConfigurationChangeListener(this); + } super.removeImmediately(); } + void reparent(StackWindowController stackController, int position, boolean moveParents) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId + + " to stack=" + stackController + " at " + position); + } + final TaskStack stack = stackController.mContainer; + if (stack == null) { + throw new IllegalArgumentException("reparent: could not find stack=" + + stackController); + } + reparent(stack, position, moveParents); + getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + } + + void reparent(TaskStack stack, int position, boolean moveParents) { if (stack == mStack) { throw new IllegalArgumentException( @@ -300,6 +328,12 @@ class Task extends WindowContainer<AppWindowToken> { return boundsChange; } + void resize(boolean relayout, boolean forced) { + if (setBounds(getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE && relayout) { + getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + } + } + @Override void onDisplayChanged(DisplayContent dc) { adjustBoundsForDisplayChangeIfNeeded(dc); @@ -515,6 +549,15 @@ class Task extends WindowContainer<AppWindowToken> { return mDragResizeMode; } + /** + * Puts this task into docked drag resizing mode. See {@link DragResizeMode}. + * + * @param resizing Whether to put the task into drag resize mode. + */ + public void setTaskDockedResizing(boolean resizing) { + setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER); + } + private void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) { if (displayContent == null) { return; @@ -556,9 +599,8 @@ class Task extends WindowContainer<AppWindowToken> { displayContent.rotateBounds(mRotation, newRotation, mTmpRect2); if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) { - final TaskWindowContainerController controller = getController(); - if (controller != null) { - controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION); + if (mTaskRecord != null) { + mTaskRecord.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION); } } } @@ -631,6 +673,20 @@ class Task extends WindowContainer<AppWindowToken> { return null; } + void positionChildAtTop(AppWindowToken aToken) { + positionChildAt(aToken, POSITION_TOP); + } + + void positionChildAt(AppWindowToken aToken, int position) { + if (aToken == null) { + Slog.w(TAG_WM, + "Attempted to position of non-existing app"); + return; + } + + positionChildAt(position, aToken, false /* includeParents */); + } + boolean isFullscreen() { if (useCurrentBounds()) { return matchParentBounds(); @@ -656,6 +712,10 @@ class Task extends WindowContainer<AppWindowToken> { mTaskDescription = taskDescription; } + void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) { + mTaskRecord.onSnapshotChanged(snapshot); + } + TaskDescription getTaskDescription() { return mTaskDescription; } @@ -666,11 +726,6 @@ class Task extends WindowContainer<AppWindowToken> { } @Override - TaskWindowContainerController getController() { - return (TaskWindowContainerController) super.getController(); - } - - @Override void forAllTasks(Consumer<Task> callback) { callback.accept(this); } diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index b6a60090153c..5bb64407a28d 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -27,6 +27,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; @@ -47,6 +48,7 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.server.EventLogTags.WM_TASK_CREATED; import static com.android.server.am.TaskRecordProto.ACTIVITIES; import static com.android.server.am.TaskRecordProto.ACTIVITY_TYPE; import static com.android.server.am.TaskRecordProto.BOUNDS; @@ -76,10 +78,15 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECEN import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; +import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static java.lang.Integer.MAX_VALUE; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; @@ -106,8 +113,10 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.util.DisplayMetrics; +import android.util.EventLog; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; @@ -125,8 +134,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; -// TODO: Make package private again once move to WM package is complete. -public class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener { +class TaskRecord extends ConfigurationContainer { private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_ATM; private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; @@ -190,6 +198,13 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // Do not move the stack as a part of reparenting static final int REPARENT_LEAVE_STACK_IN_PLACE = 2; + // The height/width divide used when fitting a task within a bounds with method + // {@link #fitWithinBounds}. + // We always want the task to to be visible in the bounds without affecting its size when + // fitting. To make sure this is the case, we don't adjust the task left or top side pass + // the input bounds right or bottom side minus the width or height divided by this value. + private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3; + /** * The factory used to create {@link TaskRecord}. This allows OEM subclass {@link TaskRecord}. */ @@ -295,7 +310,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont private final Rect mTmpStableBounds = new Rect(); private final Rect mTmpNonDecorBounds = new Rect(); - private final Rect mTmpRect = new Rect(); + private final Rect mTmpBounds = new Rect(); + private final Rect mTmpInsets = new Rect(); // Last non-fullscreen bounds the task was launched in or resized to. // The information is persisted and used to determine the appropriate stack to launch the @@ -318,7 +334,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont /** Helper object used for updating override configuration. */ private Configuration mTmpConfig = new Configuration(); - private TaskWindowContainerController mWindowContainerController; + // TODO: remove after unification + Task mTask; /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, @@ -424,43 +441,54 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont mService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity); } - TaskWindowContainerController getWindowContainerController() { - return mWindowContainerController; + Task getTask() { + return mTask; } - void createWindowContainer(boolean onTop, boolean showForAllUsers) { - if (mWindowContainerController != null) { - throw new IllegalArgumentException("Window container=" + mWindowContainerController + void createTask(boolean onTop, boolean showForAllUsers) { + if (mTask != null) { + throw new IllegalArgumentException("mTask=" + mTask + " already created for task=" + this); } final Rect bounds = updateOverrideConfigurationFromLaunchBounds(); - setWindowContainerController(new TaskWindowContainerController(taskId, this, - getStack().getWindowContainerController(), userId, bounds, - mResizeMode, mSupportsPictureInPicture, onTop, - showForAllUsers, lastTaskDescription)); - } + final StackWindowController stackController = getStack().getWindowContainerController(); - /** - * Should only be invoked from {@link #createWindowContainer(boolean, boolean)}. - */ - @VisibleForTesting - protected void setWindowContainerController(TaskWindowContainerController controller) { - if (mWindowContainerController != null) { - throw new IllegalArgumentException("Window container=" + mWindowContainerController - + " already created for task=" + this); + if (DEBUG_STACK) { + Slog.i(TAG_WM, "TaskRecord: taskId=" + taskId + + " stack=" + stackController + " bounds=" + bounds); + } + + final TaskStack stack = stackController.mContainer; + if (stack == null) { + throw new IllegalArgumentException("TaskRecord: invalid stack=" + + stackController); } + EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId); + mTask = new Task(taskId, stack, userId, mService.mWindowManager, mResizeMode, + mSupportsPictureInPicture, lastTaskDescription, this); + final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; - mWindowContainerController = controller; - if (!mDisplayedBounds.isEmpty() && controller.mContainer != null) { - controller.mContainer.setOverrideDisplayedBounds(mDisplayedBounds); + if (!mDisplayedBounds.isEmpty()) { + mTask.setOverrideDisplayedBounds(mDisplayedBounds); } + // We only want to move the parents to the parents if we are creating this task at the + // top of its stack. + stack.addTask(mTask, position, showForAllUsers, onTop /* moveParents */); + } + + void setTask(Task task) { + mTask = task; } void removeWindowContainer() { mService.getLockTaskController().clearLockedTask(this); - mWindowContainerController.removeContainer(); - mWindowContainerController = null; + if (mTask == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + taskId); + return; + } + mTask.removeIfPossible(); + mTask = null; if (!getWindowConfiguration().persistTaskBounds()) { // Reset current bounds for task whose bounds shouldn't be persisted so it uses // default configuration the next time it launches. @@ -469,7 +497,6 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont mService.getTaskChangeNotificationController().notifyTaskRemoved(taskId); } - @Override public void onSnapshotChanged(TaskSnapshot snapshot) { mService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(taskId, snapshot); } @@ -479,17 +506,20 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont return; } mResizeMode = resizeMode; - mWindowContainerController.setResizeable(resizeMode); + mTask.setResizeable(resizeMode); mService.mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); mService.mRootActivityContainer.resumeFocusedStacksTopActivities(); } void setTaskDockedResizing(boolean resizing) { - mWindowContainerController.setTaskDockedResizing(resizing); + if (mTask == null) { + Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + taskId + " not found."); + return; + } + mTask.setTaskDockedResizing(resizing); } // TODO: Consolidate this with the resize() method below. - @Override public void requestResize(Rect bounds, int resizeMode) { mService.resizeTask(taskId, bounds, resizeMode); } @@ -511,7 +541,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont return true; } - if (mWindowContainerController == null) { + if (mTask == null) { // Task doesn't exist in window manager yet (e.g. was restored from recents). // All we can do for now is update the bounds so it can be used when the task is // added to window manager. @@ -558,7 +588,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } } } - mWindowContainerController.resize(kept, forced); + mTask.resize(kept, forced); saveLaunchingStateIfNeeded(); @@ -571,11 +601,15 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // TODO: Investigate combining with the resize() method above. void resizeWindowContainer() { - mWindowContainerController.resize(false /* relayout */, false /* forced */); + mTask.resize(false /* relayout */, false /* forced */); } void getWindowContainerBounds(Rect bounds) { - mWindowContainerController.getBounds(bounds); + if (mTask != null) { + mTask.getBounds(bounds); + } else { + bounds.setEmpty(); + } } /** @@ -679,7 +713,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // Must reparent first in window manager to avoid a situation where AM can delete the // we are coming from in WM before we reparent because it became empty. - mWindowContainerController.reparent(toStack.getWindowContainerController(), position, + mTask.reparent(toStack.getWindowContainerController(), position, moveStackMode == REPARENT_MOVE_STACK_TO_FRONT); final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT @@ -779,7 +813,11 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } void cancelWindowTransition() { - mWindowContainerController.cancelWindowTransition(); + if (mTask == null) { + Slog.w(TAG_WM, "cancelWindowTransition: taskId " + taskId + " not found."); + return; + } + mTask.cancelTaskWindowTransition(); } /** @@ -1190,7 +1228,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont mActivities.add(newTop); // Make sure window manager is aware of the position change. - mWindowContainerController.positionChildAtTop(newTop.mAppWindowToken); + mTask.positionChildAtTop(newTop.mAppWindowToken); updateEffectiveIntent(); setFrontOfTask(); @@ -1275,7 +1313,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont if (r.mAppWindowToken != null) { // Only attempt to move in WM if the child has a controller. It is possible we haven't // created controller for the activity we are starting yet. - mWindowContainerController.positionChildAt(r.mAppWindowToken, index); + mTask.positionChildAt(r.mAppWindowToken, index); } // Make sure the list of display UID whitelists is updated @@ -1643,8 +1681,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename, colorPrimary, colorBackground, statusBarColor, navigationBarColor); - if (mWindowContainerController != null) { - mWindowContainerController.setTaskDescription(lastTaskDescription); + if (mTask != null) { + mTask.setTaskDescription(lastTaskDescription); } // Update the task affiliation color if we are the parent of the group if (taskId == mAffiliatedTaskId) { @@ -1687,7 +1725,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // If the task has no requested minimal size, we'd like to enforce a minimal size // so that the user can not render the task too small to manipulate. We don't need // to do this for the pinned stack as the bounds are controlled by the system. - if (!inPinnedWindowingMode()) { + if (!inPinnedWindowingMode() && mStack != null) { final int defaultMinSizeDp = mService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp; final ActivityDisplay display = @@ -1731,31 +1769,6 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } /** - * @return a new Configuration for this Task, given the provided {@param bounds} and - * {@param insetBounds}. - */ - Configuration computeNewOverrideConfigurationForBounds(Rect bounds, Rect insetBounds) { - // Compute a new override configuration for the given bounds, if fullscreen bounds - // (bounds == null), then leave the override config unset - final Configuration newOverrideConfig = new Configuration(); - if (bounds != null) { - newOverrideConfig.setTo(getRequestedOverrideConfiguration()); - if (insetBounds != null && !insetBounds.isEmpty()) { - mTmpRect.set(insetBounds); - setDisplayedBounds(bounds); - } else { - mTmpRect.set(bounds); - setDisplayedBounds(null); - } - adjustForMinimalTaskDimensions(mTmpRect); - computeOverrideConfiguration(newOverrideConfig, mTmpRect, - mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom); - } - - return newOverrideConfig; - } - - /** * Update task's override configuration based on the bounds. * @param bounds The bounds of the task. * @return True if the override configuration was updated. @@ -1781,42 +1794,21 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont * @return True if the override configuration was updated. */ boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) { - if (equivalentRequestedOverrideBounds(bounds)) { + final boolean hasSetDisplayedBounds = (insetBounds != null && !insetBounds.isEmpty()); + if (hasSetDisplayedBounds) { + setDisplayedBounds(bounds); + } else { + setDisplayedBounds(null); + } + // "steady" bounds do not include any temporary offsets from animation or interaction. + Rect steadyBounds = hasSetDisplayedBounds ? insetBounds : bounds; + if (equivalentRequestedOverrideBounds(steadyBounds)) { return false; } - final Rect currentBounds = getRequestedOverrideBounds(); - - mTmpConfig.setTo(getRequestedOverrideConfiguration()); - final Configuration newConfig = getRequestedOverrideConfiguration(); - - final boolean matchParentBounds = bounds == null || bounds.isEmpty(); - final boolean persistBounds = getWindowConfiguration().persistTaskBounds(); - if (matchParentBounds) { - if (!currentBounds.isEmpty() && persistBounds) { - setLastNonFullscreenBounds(currentBounds); - } - setBounds(null); - setDisplayedBounds(null); - newConfig.unset(); - } else { - if (insetBounds != null && !insetBounds.isEmpty()) { - mTmpRect.set(insetBounds); - setDisplayedBounds(bounds); - } else { - mTmpRect.set(bounds); - setDisplayedBounds(null); - } - adjustForMinimalTaskDimensions(mTmpRect); - setBounds(mTmpRect); - if (mStack == null || persistBounds) { - setLastNonFullscreenBounds(getRequestedOverrideBounds()); - } - computeOverrideConfiguration(newConfig, mTmpRect, - mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom); - } - onRequestedOverrideConfigurationChanged(newConfig); - return !mTmpConfig.equals(newConfig); + mTmpConfig.setTo(getResolvedOverrideConfiguration()); + setBounds(steadyBounds); + return !mTmpConfig.equals(getResolvedOverrideConfiguration()); } /** @@ -1842,6 +1834,12 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont if (wasInMultiWindowMode != inMultiWindowMode()) { mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this); } + if (getWindowConfiguration().persistTaskBounds()) { + final Rect currentBounds = getRequestedOverrideBounds(); + if (!currentBounds.isEmpty()) { + setLastNonFullscreenBounds(currentBounds); + } + } // TODO: Should also take care of Pip mode changes here. saveLaunchingStateIfNeeded(); @@ -1869,6 +1867,45 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } /** + * Adjust bounds to stay within stack bounds. + * + * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way + * that keep them unchanged, but be contained within the stack bounds. + * + * @param bounds Bounds to be adjusted. + * @param stackBounds Bounds within which the other bounds should remain. + */ + private static void fitWithinBounds(Rect bounds, Rect stackBounds) { + if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) { + return; + } + + if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) { + final int maxRight = stackBounds.right + - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER); + int horizontalDiff = stackBounds.left - bounds.left; + if ((horizontalDiff < 0 && bounds.left >= maxRight) + || (bounds.left + horizontalDiff >= maxRight)) { + horizontalDiff = maxRight - bounds.left; + } + bounds.left += horizontalDiff; + bounds.right += horizontalDiff; + } + + if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) { + final int maxBottom = stackBounds.bottom + - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER); + int verticalDiff = stackBounds.top - bounds.top; + if ((verticalDiff < 0 && bounds.top >= maxBottom) + || (bounds.top + verticalDiff >= maxBottom)) { + verticalDiff = maxBottom - bounds.top; + } + bounds.top += verticalDiff; + bounds.bottom += verticalDiff; + } + } + + /** * Displayed bounds are used to set where the task is drawn at any given time. This is * separate from its actual bounds so that the app doesn't see any meaningful configuration * changes during transitionary periods. @@ -1879,9 +1916,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } else { mDisplayedBounds.set(bounds); } - final TaskWindowContainerController controller = getWindowContainerController(); - if (controller != null && controller.mContainer != null) { - controller.mContainer.setOverrideDisplayedBounds( + if (mTask != null) { + mTask.setOverrideDisplayedBounds( mDisplayedBounds.isEmpty() ? null : mDisplayedBounds); } } @@ -1901,46 +1937,205 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont return !mDisplayedBounds.isEmpty(); } - /** Clears passed config and fills it with new override values. */ - // TODO(b/36505427): TaskRecord.computeOverrideConfiguration() is a utility method that doesn't - // depend on task or stacks, but uses those object to get the display to base the calculation - // on. Probably best to centralize calculations like this in ConfigurationContainer. - void computeOverrideConfiguration(Configuration config, Rect bounds, - boolean overrideWidth, boolean overrideHeight) { - mTmpNonDecorBounds.set(bounds); - mTmpStableBounds.set(bounds); + /** + * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than + * intersectBounds on a side, then the respective side will not be intersected. + * + * The assumption is that if inOutBounds is initially larger than intersectBounds, then the + * inset on that side is no-longer applicable. This scenario happens when a task's minimal + * bounds are larger than the provided parent/display bounds. + * + * @param inOutBounds the bounds to intersect. + * @param intersectBounds the bounds to intersect with. + * @param intersectInsets insets to apply to intersectBounds before intersecting. + */ + private static void intersectWithInsetsIfFits( + Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) { + if (inOutBounds.right <= intersectBounds.right) { + inOutBounds.right = + Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right); + } + if (inOutBounds.bottom <= intersectBounds.bottom) { + inOutBounds.bottom = + Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom); + } + if (inOutBounds.left >= intersectBounds.left) { + inOutBounds.left = + Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left); + } + if (inOutBounds.top >= intersectBounds.top) { + inOutBounds.top = + Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top); + } + } + + /** + * Gets bounds with non-decor and stable insets applied respectively. + * + * If bounds overhangs the display, those edges will not get insets. See + * {@link #intersectWithInsetsIfFits} + * + * @param outNonDecorBounds where to place bounds with non-decor insets applied. + * @param outStableBounds where to place bounds with stable insets applied. + * @param bounds the bounds to inset. + */ + private void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds, + DisplayInfo displayInfo) { + outNonDecorBounds.set(bounds); + outStableBounds.set(bounds); + if (getStack() == null || getStack().getDisplay() == null) { + return; + } + DisplayPolicy policy = getStack().getDisplay().mDisplayContent.getDisplayPolicy(); + if (policy == null) { + return; + } + mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); + + policy.getStableInsetsLw(displayInfo.rotation, + displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout, + mTmpInsets); + intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); - config.unset(); - final Configuration parentConfig = getParent().getConfiguration(); + policy.getNonDecorInsetsLw(displayInfo.rotation, + displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout, + mTmpInsets); + intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); + } - final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + /** + * Asks docked-divider controller for the smallestwidthdp given bounds. + * @param bounds bounds to calculate smallestwidthdp for. + */ + private int getSmallestScreenWidthDpForDockedBounds(Rect bounds) { + DisplayContent dc = mStack.getDisplay().mDisplayContent; + if (dc != null) { + return dc.getDockedDividerController().getSmallestWidthDpForBounds(bounds); + } + return Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; + } - if (mStack != null) { - final StackWindowController stackController = mStack.getWindowContainerController(); - stackController.adjustConfigurationForBounds(bounds, - mTmpNonDecorBounds, mTmpStableBounds, overrideWidth, overrideHeight, density, - config, parentConfig, getWindowingMode()); - } else { - throw new IllegalArgumentException("Expected stack when calculating override config"); - } - - config.orientation = (config.screenWidthDp <= config.screenHeightDp) - ? Configuration.ORIENTATION_PORTRAIT - : Configuration.ORIENTATION_LANDSCAPE; - - // For calculating screen layout, we need to use the non-decor inset screen area for the - // calculation for compatibility reasons, i.e. screen area without system bars that could - // never go away in Honeycomb. - final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density); - final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density); - // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start override - // calculation with partial default. - // Reducing the screen layout starting from its parent config. - final int sl = parentConfig.screenLayout & - (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); - final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp); - final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp); - config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize); + /** + * Calculates configuration values used by the client to get resources. This should be run + * using app-facing bounds (bounds unmodified by animations or transient interactions). + * + * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely + * configuring an "inherit-bounds" window which means that all configuration settings would + * just be inherited from the parent configuration. + **/ + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Rect bounds, + @NonNull Configuration parentConfig) { + int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = parentConfig.windowConfiguration.getWindowingMode(); + } + + float density = inOutConfig.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = parentConfig.densityDpi; + } + density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; + + Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (outAppBounds == null || outAppBounds.isEmpty()) { + inOutConfig.windowConfiguration.setAppBounds(bounds); + outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + } + if (windowingMode != WINDOWING_MODE_FREEFORM) { + final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); + if (parentAppBounds != null && !parentAppBounds.isEmpty()) { + outAppBounds.intersect(parentAppBounds); + } + } + + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED + || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + if (mStack != null) { + final DisplayInfo di = new DisplayInfo(); + mStack.getDisplay().mDisplay.getDisplayInfo(di); + + // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen + // area, i.e. the screen area without the system bars. + // The non decor inset are areas that could never be removed in Honeycomb. See + // {@link WindowManagerPolicy#getNonDecorInsetsLw}. + calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, bounds, di); + } else { + mTmpNonDecorBounds.set(bounds); + mTmpStableBounds.set(bounds); + } + + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + inOutConfig.screenWidthDp = Math.min((int) (mTmpStableBounds.width() / density), + parentConfig.screenWidthDp); + } + if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + inOutConfig.screenHeightDp = Math.min((int) (mTmpStableBounds.height() / density), + parentConfig.screenHeightDp); + } + + if (inOutConfig.smallestScreenWidthDp + == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + if (WindowConfiguration.isFloating(windowingMode)) { + // For floating tasks, calculate the smallest width from the bounds of the task + inOutConfig.smallestScreenWidthDp = (int) ( + Math.min(bounds.width(), bounds.height()) / density); + } else if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) { + // Iterating across all screen orientations, and return the minimum of the task + // width taking into account that the bounds might change because the snap + // algorithm snaps to a different value + getSmallestScreenWidthDpForDockedBounds(bounds); + } + // otherwise, it will just inherit + } + } + + if (inOutConfig.orientation == Configuration.ORIENTATION_UNDEFINED) { + inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) + ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; + } + if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) { + // For calculating screen layout, we need to use the non-decor inset screen area for the + // calculation for compatibility reasons, i.e. screen area without system bars that + // could never go away in Honeycomb. + final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density); + final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density); + // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start + // override calculation with partial default. + // Reducing the screen layout starting from its parent config. + final int sl = parentConfig.screenLayout + & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); + final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp); + final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp); + inOutConfig.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize); + } + } + + // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore. + void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig, + Configuration overrideConfig) { + inOutConfig.setTo(overrideConfig); + + Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds(); + if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) { + adjustForMinimalTaskDimensions(outOverrideBounds); + + int windowingMode = overrideConfig.windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = parentConfig.windowConfiguration.getWindowingMode(); + } + if (windowingMode == WINDOWING_MODE_FREEFORM) { + // by policy, make sure the window remains within parent + fitWithinBounds(outOverrideBounds, parentConfig.windowConfiguration.getBounds()); + } + + computeConfigResourceOverrides(inOutConfig, outOverrideBounds, parentConfig); + } + } + + @Override + void resolveOverrideConfiguration(Configuration newParentConfig) { + computeResolvedOverrideConfiguration(getResolvedOverrideConfiguration(), newParentConfig, + getRequestedOverrideConfiguration()); } Rect updateOverrideConfigurationFromLaunchBounds() { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 7ab4d086b70a..01a5622c2b60 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -191,9 +191,7 @@ class TaskSnapshotController { } else { mCache.putSnapshot(task, snapshot); mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); - if (task.getController() != null) { - task.getController().reportSnapshotChanged(snapshot); - } + task.onSnapshotChanged(snapshot); } } } diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java deleted file mode 100644 index b87b65e432d3..000000000000 --- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static com.android.server.EventLogTags.WM_TASK_CREATED; -import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE; -import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; -import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; -import static com.android.server.wm.WindowContainer.POSITION_TOP; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; - -import android.app.ActivityManager.TaskDescription; -import android.app.ActivityManager.TaskSnapshot; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.EventLog; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.ref.WeakReference; - -/** - * Controller for the task container. This is created by activity manager to link task records to - * the task container they use in window manager. - * - * Test class: {@link TaskWindowContainerControllerTests} - */ -public class TaskWindowContainerController - extends WindowContainerController<Task, TaskWindowContainerListener> { - - private final int mTaskId; - private final H mHandler; - - public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, - StackWindowController stackController, int userId, Rect bounds, int resizeMode, - boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers, - TaskDescription taskDescription) { - this(taskId, listener, stackController, userId, bounds, resizeMode, - supportsPictureInPicture, toTop, showForAllUsers, taskDescription, - WindowManagerService.getInstance()); - } - - public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, - StackWindowController stackController, int userId, Rect bounds, int resizeMode, - boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers, - TaskDescription taskDescription, WindowManagerService service) { - super(listener, service); - mTaskId = taskId; - mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); - - synchronized (mGlobalLock) { - if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId - + " stack=" + stackController + " bounds=" + bounds); - - final TaskStack stack = stackController.mContainer; - if (stack == null) { - throw new IllegalArgumentException("TaskWindowContainerController: invalid stack=" - + stackController); - } - EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId); - final Task task = createTask(taskId, stack, userId, resizeMode, - supportsPictureInPicture, taskDescription); - final int position = toTop ? POSITION_TOP : POSITION_BOTTOM; - // We only want to move the parents to the parents if we are creating this task at the - // top of its stack. - stack.addTask(task, position, showForAllUsers, toTop /* moveParents */); - } - } - - @VisibleForTesting - Task createTask(int taskId, TaskStack stack, int userId, int resizeMode, - boolean supportsPictureInPicture, TaskDescription taskDescription) { - return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture, - taskDescription, this); - } - - @Override - public void removeContainer() { - synchronized (mGlobalLock) { - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + mTaskId); - return; - } - mContainer.removeIfPossible(); - super.removeContainer(); - } - } - - void positionChildAtTop(AppWindowToken aToken) { - positionChildAt(aToken, POSITION_TOP); - } - - void positionChildAt(AppWindowToken aToken, int position) { - synchronized (mService.mGlobalLock) { - if (aToken == null) { - Slog.w(TAG_WM, - "Attempted to position of non-existing app"); - return; - } - - final Task task = mContainer; - if (task == null) { - throw new IllegalArgumentException("positionChildAt: invalid task=" + this); - } - task.positionChildAt(position, aToken, false /* includeParents */); - } - } - - public void reparent(StackWindowController stackController, int position, boolean moveParents) { - synchronized (mGlobalLock) { - if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId - + " to stack=" + stackController + " at " + position); - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "reparent: could not find taskId=" + mTaskId); - return; - } - final TaskStack stack = stackController.mContainer; - if (stack == null) { - throw new IllegalArgumentException("reparent: could not find stack=" - + stackController); - } - mContainer.reparent(stack, position, moveParents); - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); - } - } - - public void setResizeable(int resizeMode) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.setResizeable(resizeMode); - } - } - } - - public void resize(boolean relayout, boolean forced) { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found."); - } - - if (mContainer.setBounds( - mContainer.getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE - && relayout) { - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); - } - } - } - - public void getBounds(Rect bounds) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.getBounds(bounds); - return; - } - bounds.setEmpty(); - } - } - - /** - * Puts this task into docked drag resizing mode. See {@link DragResizeMode}. - * - * @param resizing Whether to put the task into drag resize mode. - */ - public void setTaskDockedResizing(boolean resizing) { - synchronized (mGlobalLock) { - if (mContainer == null) { - Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + mTaskId + " not found."); - return; - } - mContainer.setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER); - } - } - - public void cancelWindowTransition() { - synchronized (mGlobalLock) { - if (mContainer == null) { - Slog.w(TAG_WM, "cancelWindowTransition: taskId " + mTaskId + " not found."); - return; - } - mContainer.cancelTaskWindowTransition(); - } - } - - public void setTaskDescription(TaskDescription taskDescription) { - synchronized (mGlobalLock) { - if (mContainer == null) { - Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found."); - return; - } - mContainer.setTaskDescription(taskDescription); - } - } - - public boolean isDragResizing() { - synchronized (mGlobalLock) { - return mContainer.isDragResizing(); - } - } - - void reportSnapshotChanged(TaskSnapshot snapshot) { - mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget(); - } - - void requestResize(Rect bounds, int resizeMode) { - mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget(); - } - - @Override - public String toString() { - return "{TaskWindowContainerController taskId=" + mTaskId + "}"; - } - - private static final class H extends Handler { - - static final int REPORT_SNAPSHOT_CHANGED = 0; - static final int REQUEST_RESIZE = 1; - - private final WeakReference<TaskWindowContainerController> mController; - - H(WeakReference<TaskWindowContainerController> controller, Looper looper) { - super(looper); - mController = controller; - } - - @Override - public void handleMessage(Message msg) { - final TaskWindowContainerController controller = mController.get(); - final TaskWindowContainerListener listener = (controller != null) - ? controller.mListener : null; - if (listener == null) { - return; - } - switch (msg.what) { - case REPORT_SNAPSHOT_CHANGED: - listener.onSnapshotChanged((TaskSnapshot) msg.obj); - break; - case REQUEST_RESIZE: - listener.requestResize((Rect) msg.obj, msg.arg1); - break; - } - } - } -} diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java deleted file mode 100644 index af67de38e5b3..000000000000 --- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java +++ /dev/null @@ -1,33 +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.wm; - -import android.app.ActivityManager.TaskSnapshot; -import android.graphics.Rect; - -/** - * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with - * the task container. - */ -public interface TaskWindowContainerListener extends WindowContainerListener { - - /** Called when the snapshot of this task has changed. */ - void onSnapshotChanged(TaskSnapshot snapshot); - - /** Called when the task container would like its controller to resize. */ - void requestResize(Rect bounds, int resizeMode); -} diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp index 649f1a56f011..4d4a7b41643c 100644 --- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp +++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp @@ -34,7 +34,6 @@ #include "netdbpf/BpfNetworkStats.h" using android::bpf::Stats; -using android::bpf::hasBpfSupport; using android::bpf::bpfGetUidStats; using android::bpf::bpfGetIfaceStats; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 240b8206baf6..d8225b38487c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -99,6 +99,11 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { } @Override + public int getPasswordComplexity() { + return DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + } + + @Override public void installUpdateFromFile(ComponentName admin, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {} @@ -136,4 +141,10 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public boolean isUnattendedManagedKiosk() { return false; } + + @Override + public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, + long start, long end, boolean allDay, int flags) { + 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 bc550dc8bd12..7186cdf96dee 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; +import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; @@ -40,10 +41,13 @@ import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; +import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION; import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP; import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE; import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES; +import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING; import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; +import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_INSTALLATION; 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; @@ -53,6 +57,7 @@ import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLE import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF; @@ -113,6 +118,7 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; @@ -124,6 +130,7 @@ import android.app.admin.SystemUpdatePolicy; import android.app.backup.IBackupManager; import android.app.trust.TrustManager; import android.app.usage.UsageStatsManagerInternal; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentValues; @@ -184,6 +191,7 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.os.UserManagerInternal.UserRestrictionsListener; import android.os.storage.StorageManager; +import android.provider.CalendarContract; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; import android.provider.Settings; @@ -361,9 +369,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DELEGATION_PACKAGE_ACCESS, DELEGATION_PERMISSION_GRANT, DELEGATION_INSTALL_EXISTING_PACKAGE, - DELEGATION_KEEP_UNINSTALLED_PACKAGES + DELEGATION_KEEP_UNINSTALLED_PACKAGES, + DELEGATION_NETWORK_LOGGING, + DELEGATION_CERT_SELECTION, + DELEGATION_PACKAGE_INSTALLATION }; + // Subset of delegations that can only be delegated by Device Owner. + private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] { + DELEGATION_NETWORK_LOGGING, + DELEGATION_PACKAGE_INSTALLATION + }); + + // Subset of delegations that only one single package within a given user can hold + private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] { + DELEGATION_NETWORK_LOGGING, + DELEGATION_CERT_SELECTION, + }); + /** * System property whose value is either "true" or "false", indicating whether * device owner is present. @@ -4714,6 +4737,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + @PasswordComplexity + public int getPasswordComplexity() { + final int callingUserId = mInjector.userHandleGetCallingUserId(); + enforceUserUnlocked(callingUserId); + mContext.enforceCallingOrSelfPermission( + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY, + "Must have " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY + " permission."); + + synchronized (getLockObject()) { + int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false); + PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId); + return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity(); + } + } + + @Override public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) { enforceFullCrossUsersPermission(userHandle); synchronized (getLockObject()) { @@ -5346,7 +5385,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void enforceCanManageCaCerts(ComponentName who, String callerPackage) { if (who == null) { - if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) { + if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), + DELEGATION_CERT_INSTALL)) { mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null); } } else { @@ -5765,13 +5805,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS); - intent.setComponent(aliasChooser); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, uid); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI, uri); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final ComponentName delegateReceiver; + delegateReceiver = resolveDelegateReceiver(DELEGATION_CERT_SELECTION, + DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS, caller.getIdentifier()); + + if (delegateReceiver != null) { + intent.setComponent(delegateReceiver); + } else { + intent.setComponent(aliasChooser); + } + final long id = mInjector.binderClearCallingIdentity(); try { mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() { @@ -5831,22 +5880,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public void setDelegatedScopes(ComponentName who, String delegatePackage, - List<String> scopes) throws SecurityException { + List<String> scopeList) throws SecurityException { Preconditions.checkNotNull(who, "ComponentName is null"); Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty"); - Preconditions.checkCollectionElementsNotNull(scopes, "Scopes"); + Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes"); // Remove possible duplicates. - scopes = new ArrayList(new ArraySet(scopes)); + final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList)); // Ensure given scopes are valid. if (scopes.retainAll(Arrays.asList(DELEGATIONS))) { throw new IllegalArgumentException("Unexpected delegation scopes"); } - + final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS); // Retrieve the user ID of the calling process. final int userId = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { // Ensure calling process is device/profile owner. - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (hasDoDelegation) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } else { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + } // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N). if (shouldCheckIfDelegatePackageIsInstalled(delegatePackage, getTargetSdk(who.getPackageName(), userId), scopes)) { @@ -5859,31 +5912,57 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Set the new delegate in user policies. final DevicePolicyData policy = getUserData(userId); + List<String> exclusiveScopes = null; if (!scopes.isEmpty()) { policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes)); + exclusiveScopes = new ArrayList<>(scopes); + exclusiveScopes.retainAll(EXCLUSIVE_DELEGATIONS); } else { // Remove any delegation info if the given scopes list is empty. policy.mDelegationMap.remove(delegatePackage); } - - // Notify delegate package of updates. - final Intent intent = new Intent( - DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED); - // Only call receivers registered with Context#registerReceiver (don’t wake delegate). - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - // Limit components this intent resolves to to the delegate package. - intent.setPackage(delegatePackage); - // Include the list of delegated scopes as an extra. - intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, - (ArrayList<String>) scopes); - // Send the broadcast. - mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - + sendDelegationChangedBroadcast(delegatePackage, scopes, userId); + + // If set, remove exclusive scopes from all other delegates + if (exclusiveScopes != null && !exclusiveScopes.isEmpty()) { + for (Map.Entry<String, List<String>> entry : policy.mDelegationMap.entrySet()) { + final String currentPackage = entry.getKey(); + final List<String> currentScopes = entry.getValue(); + + if (!currentPackage.equals(delegatePackage)) { + // Iterate through all other delegates + if (currentScopes.removeAll(exclusiveScopes)) { + // And if this delegate had some exclusive scopes which are now moved + // to the new delegate, notify about its delegation changes. + if (currentScopes.isEmpty()) { + policy.mDelegationMap.remove(currentPackage); + } + sendDelegationChangedBroadcast(currentPackage, + new ArrayList<>(currentScopes), userId); + } + } + } + } // Persist updates. saveSettingsLocked(userId); } } + private void sendDelegationChangedBroadcast(String delegatePackage, ArrayList<String> scopes, + int userId) { + // Notify delegate package of updates. + final Intent intent = new Intent( + DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED); + // Only call receivers registered with Context#registerReceiver (don’t wake delegate). + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + // Limit components this intent resolves to to the delegate package. + intent.setPackage(delegatePackage); + // Include the list of delegated scopes as an extra. + intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes); + // Send the broadcast. + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + } + /** * Get the delegation scopes given to a delegate package by a device owner or profile owner. * @@ -5951,17 +6030,59 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { // Ensure calling process is device/profile owner. getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - final DevicePolicyData policy = getUserData(userId); + return getDelegatePackagesInternalLocked(scope, userId); + } + } - // Create a list to hold the resulting delegate packages. - final List<String> delegatePackagesWithScope = new ArrayList<>(); - // Add all delegations containing scope to the result list. - for (int i = 0; i < policy.mDelegationMap.size(); i++) { - if (policy.mDelegationMap.valueAt(i).contains(scope)) { - delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i)); - } + private List<String> getDelegatePackagesInternalLocked(String scope, int userId) { + final DevicePolicyData policy = getUserData(userId); + + // Create a list to hold the resulting delegate packages. + final List<String> delegatePackagesWithScope = new ArrayList<>(); + // Add all delegations containing scope to the result list. + for (int i = 0; i < policy.mDelegationMap.size(); i++) { + if (policy.mDelegationMap.valueAt(i).contains(scope)) { + delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i)); } - return delegatePackagesWithScope; + } + return delegatePackagesWithScope; + } + + /** + * Return the ComponentName of the receiver that handles the given broadcast action, from + * the app that holds the given delegation capability. If the app defines multiple receivers + * with the same intent action filter, will return any one of them nondeterministically. + * + * @return ComponentName of the receiver or {@null} if none exists. + */ + private ComponentName resolveDelegateReceiver(String scope, String action, int userId) { + + final List<String> delegates; + synchronized (getLockObject()) { + delegates = getDelegatePackagesInternalLocked(scope, userId); + } + if (delegates.size() != 1) { + Slog.wtf(LOG_TAG, "More than one delegate holds " + scope); + return null; + } + final String pkg = delegates.get(0); + Intent intent = new Intent(action); + intent.setPackage(pkg); + final List<ResolveInfo> receivers; + try { + receivers = mIPackageManager.queryIntentReceivers( + intent, null, 0, userId).getList(); + } catch (RemoteException e) { + return null; + } + final int count = receivers.size(); + if (count >= 1) { + if (count > 1) { + Slog.w(LOG_TAG, pkg + " defines more than one delegate receiver for " + action); + } + return receivers.get(0).activityInfo.getComponentName(); + } else { + return null; } } @@ -5978,15 +6099,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * @param scope the delegation scope to be checked. * @return {@code true} if the calling process is a delegate of {@code scope}. */ - private boolean isCallerDelegate(String callerPackage, String scope) { + private boolean isCallerDelegate(String callerPackage, int callerUid, String scope) { Preconditions.checkNotNull(callerPackage, "callerPackage is null"); if (!Arrays.asList(DELEGATIONS).contains(scope)) { throw new IllegalArgumentException("Unexpected delegation scope: " + scope); } // Retrieve the UID and user ID of the calling process. - final int callingUid = mInjector.binderGetCallingUid(); - final int userId = UserHandle.getUserId(callingUid); + final int userId = UserHandle.getUserId(callerUid); synchronized (getLockObject()) { // Retrieve user policy data. final DevicePolicyData policy = getUserData(userId); @@ -5999,7 +6119,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int uid = mInjector.getPackageManager() .getPackageUidAsUser(callerPackage, userId); // Return true if the caller is actually callerPackage. - return uid == callingUid; + return uid == callerUid; } catch (NameNotFoundException e) { // Ignore. } @@ -6024,15 +6144,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy, String scope) { + enforceCanManageScopeOrCheckPermission(who, callerPackage, reqPolicy, scope, null); + } + + /** + * Throw a security exception if a ComponentName is given and it is not a device/profile owner + * OR if the calling process is not a delegate of the given scope and does not hold the + * required permission. + */ + private void enforceCanManageScopeOrCheckPermission(@Nullable ComponentName who, + @NonNull String callerPackage, int reqPolicy, @NonNull String scope, + @Nullable String permission) { // If a ComponentName is given ensure it is a device or profile owner according to policy. if (who != null) { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, reqPolicy); } - // If no ComponentName is given ensure calling process has scope delegation. - } else if (!isCallerDelegate(callerPackage, scope)) { - throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() - + " is not a delegate of scope " + scope + "."); + } else { + // If no ComponentName is given ensure calling process has scope delegation or required + // permission + if (isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), scope)) { + return; + } + if (permission == null) { + throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() + + " is not a delegate of scope " + scope + "."); + } else { + mContext.enforceCallingOrSelfPermission(permission, null); + } } } @@ -6971,9 +7110,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException { + private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) + throws SecurityException { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + ensureAllUsersAffiliated(); + } + + private void ensureAllUsersAffiliated() throws SecurityException { + synchronized (getLockObject()) { if (!areAllUsersAffiliatedWithDeviceLocked()) { throw new SecurityException("Not all users are affiliated."); } @@ -7032,14 +7178,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } void sendDeviceOwnerCommand(String action, Bundle extras) { - int deviceOwnerUserId; - ComponentName deviceOwnerComponent; + final int deviceOwnerUserId; synchronized (getLockObject()) { deviceOwnerUserId = mOwners.getDeviceOwnerUserId(); - deviceOwnerComponent = mOwners.getDeviceOwnerComponent(); } - sendActiveAdminCommand(action, extras, deviceOwnerUserId, - deviceOwnerComponent); + + ComponentName receiverComponent = null; + if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) { + receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, + deviceOwnerUserId); + } + if (receiverComponent == null) { + synchronized (getLockObject()) { + receiverComponent = mOwners.getDeviceOwnerComponent(); + } + } + sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent); } private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) { @@ -8496,7 +8650,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) { - return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS); + return isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), + DELEGATION_APP_RESTRICTIONS); } @Override @@ -10715,6 +10870,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public boolean canSilentlyInstallPackage(String callerPackage, int callerUid) { + if (callerPackage == null) { + return false; + } + if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid)) + && isActiveAdminWithPolicy(callerUid, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)) { + // device owner or a profile owner affiliated with the device owner + return true; + } + if (DevicePolicyManagerService.this.isCallerDelegate(callerPackage, callerUid, + DELEGATION_PACKAGE_INSTALLATION)) { + return true; + } + return false; + } + + @Override public void reportSeparateProfileChallengeChanged(@UserIdInt int userId) { synchronized (getLockObject()) { updateMaximumTimeToLockLocked(userId); @@ -12507,13 +12680,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void setNetworkLoggingEnabled(ComponentName admin, boolean enabled) { + public void setNetworkLoggingEnabled(@Nullable ComponentName admin, + @NonNull String packageName, boolean enabled) { if (!mHasFeature) { return; } synchronized (getLockObject()) { - Preconditions.checkNotNull(admin); - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + DELEGATION_NETWORK_LOGGING); if (enabled == isNetworkLoggingEnabledInternalLocked()) { // already in the requested state @@ -12614,12 +12788,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean isNetworkLoggingEnabled(ComponentName admin) { + public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin, + @NonNull String packageName) { if (!mHasFeature) { return false; } synchronized (getLockObject()) { - enforceDeviceOwnerOrManageUsers(); + enforceCanManageScopeOrCheckPermission(admin, packageName, + DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, DELEGATION_NETWORK_LOGGING, + android.Manifest.permission.MANAGE_USERS); return isNetworkLoggingEnabledInternalLocked(); } } @@ -12637,12 +12814,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH */ @Override - public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) { + public List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin, + @NonNull String packageName, long batchToken) { if (!mHasFeature) { return null; } - Preconditions.checkNotNull(admin); - ensureDeviceOwnerAndAllUsersAffiliated(admin); + enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + DELEGATION_NETWORK_LOGGING); + ensureAllUsersAffiliated(); synchronized (getLockObject()) { if (mNetworkLogger == null @@ -13658,4 +13837,59 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private PowerManagerInternal getPowerManagerInternal() { return mInjector.getPowerManagerInternal(); } + + @Override + public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, + long start, long end, boolean allDay, int flags) { + if (!mHasFeature) { + return false; + } + Preconditions.checkStringNotEmpty(packageName, "Package name is empty"); + + final int callingUid = mInjector.binderGetCallingUid(); + final int callingUserId = mInjector.userHandleGetCallingUserId(); + if (!isCallingFromPackage(packageName, callingUid)) { + throw new SecurityException("Input package name doesn't align with actual " + + "calling package."); + } + final long identity = mInjector.binderClearCallingIdentity(); + try { + final int workProfileUserId = getManagedUserId(callingUserId); + if (workProfileUserId < 0) { + return false; + } + if (!isPackageAllowedToAccessCalendarForUser(packageName, workProfileUserId)) { + Log.d(LOG_TAG, String.format("Package %s is not allowed to access cross-profile" + + "calendar APIs", packageName)); + return false; + } + final Intent intent = new Intent(CalendarContract.ACTION_VIEW_WORK_CALENDAR_EVENT); + intent.setPackage(packageName); + intent.putExtra(CalendarContract.EXTRA_EVENT_ID, eventId); + intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start); + intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end); + intent.putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, allDay); + intent.setFlags(flags); + try { + mContext.startActivityAsUser(intent, UserHandle.of(workProfileUserId)); + } catch (ActivityNotFoundException e) { + Log.e(LOG_TAG, "View event activity not found", e); + return false; + } + } finally { + mInjector.binderRestoreCallingIdentity(identity); + } + return true; + } + + private boolean isCallingFromPackage(String packageName, int callingUid) { + try { + final int packageUid = mInjector.getPackageManager().getPackageUidAsUser( + packageName, UserHandle.getUserId(callingUid)); + return packageUid == callingUid; + } catch (NameNotFoundException e) { + Log.d(LOG_TAG, "Calling package not found", e); + return false; + } + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java index 7910598d8429..d8a875d7747b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java @@ -16,6 +16,7 @@ package com.android.server.devicepolicy; +import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.StartInstallingUpdateCallback; import android.content.Context; @@ -26,6 +27,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.stats.devicepolicy.DevicePolicyEnums; import android.util.Log; import java.io.File; @@ -132,6 +134,10 @@ abstract class UpdateInstaller { protected void notifyCallbackOnError(int errorCode, String errorMessage) { cleanupUpdateFile(); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE_ERROR) + .setInt(errorCode) + .write(); try { mCallback.onStartInstallingUpdateError(errorCode, errorMessage); } catch (RemoteException e) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 88f645defa6d..e1b83fc99897 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -109,6 +109,7 @@ import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.SchedulingPolicyService; import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.CrossProfileAppsService; +import com.android.server.pm.DynamicCodeLoggingService; import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; import com.android.server.pm.OtaDexoptService; @@ -1667,6 +1668,18 @@ public final class SystemServer { traceEnd(); if (!isWatch) { + // We don't run this on watches as there are no plans to use the data logged + // on watch devices. + traceBeginAndSlog("StartDynamicCodeLoggingService"); + try { + DynamicCodeLoggingService.schedule(context); + } catch (Throwable e) { + reportWtf("starting DynamicCodeLoggingService", e); + } + traceEnd(); + } + + if (!isWatch) { traceBeginAndSlog("StartPruneInstantAppsJobService"); try { PruneInstantAppsJobService.schedule(context); diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index a7209a076461..b9cc372c5138 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -111,7 +111,7 @@ public class ApfFilter { * the last writable 32bit word. */ @VisibleForTesting - private static enum Counter { + public static enum Counter { RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds) TOTAL_PACKETS, PASSED_ARP, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java new file mode 100644 index 000000000000..8e78a5686b85 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.job.controllers; + +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.job.JobInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkPolicyManager; +import android.os.Build; +import android.os.SystemClock; +import android.util.DataUnit; + +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.JobSchedulerService.Constants; +import com.android.server.net.NetworkPolicyManagerInternal; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.time.Clock; +import java.time.ZoneOffset; + +@RunWith(MockitoJUnitRunner.class) +public class ConnectivityControllerTest { + + @Mock + private Context mContext; + @Mock + private ConnectivityManager mConnManager; + @Mock + private NetworkPolicyManager mNetPolicyManager; + @Mock + private NetworkPolicyManagerInternal mNetPolicyManagerInternal; + @Mock + private JobSchedulerService mService; + + private Constants mConstants; + + private static final int UID_RED = 10001; + private static final int UID_BLUE = 10002; + + @Before + public void setUp() throws Exception { + // Assume all packages are current SDK + final PackageManagerInternal pm = mock(PackageManagerInternal.class); + when(pm.getPackageTargetSdkVersion(anyString())) + .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, pm); + + LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); + LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal); + + // Freeze the clocks at this moment in time + JobSchedulerService.sSystemClock = + Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); + JobSchedulerService.sUptimeMillisClock = + Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC); + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC); + + // Assume default constants for now + mConstants = new Constants(); + + // Get our mocks ready + when(mContext.getSystemServiceName(ConnectivityManager.class)) + .thenReturn(Context.CONNECTIVITY_SERVICE); + when(mContext.getSystemService(ConnectivityManager.class)) + .thenReturn(mConnManager); + when(mContext.getSystemServiceName(NetworkPolicyManager.class)) + .thenReturn(Context.NETWORK_POLICY_SERVICE); + when(mContext.getSystemService(NetworkPolicyManager.class)) + .thenReturn(mNetPolicyManager); + when(mService.getTestableContext()).thenReturn(mContext); + when(mService.getLock()).thenReturn(mService); + when(mService.getConstants()).thenReturn(mConstants); + } + + @Test + public void testInsane() throws Exception { + final Network net = new Network(101); + final JobInfo.Builder job = createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + + // Slow network is too slow + assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net, + createCapabilities().setLinkUpstreamBandwidthKbps(1) + .setLinkDownstreamBandwidthKbps(1), mConstants)); + // Fast network looks great + assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net, + createCapabilities().setLinkUpstreamBandwidthKbps(1024) + .setLinkDownstreamBandwidthKbps(1024), mConstants)); + } + + @Test + public void testCongestion() throws Exception { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final JobInfo.Builder job = createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + final JobStatus early = createJobStatus(job, now - 1000, now + 2000); + final JobStatus late = createJobStatus(job, now - 2000, now + 1000); + + // Uncongested network is whenever + { + final Network net = new Network(101); + final NetworkCapabilities caps = createCapabilities() + .addCapability(NET_CAPABILITY_NOT_CONGESTED); + assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants)); + assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); + } + + // Congested network is more selective + { + final Network net = new Network(101); + final NetworkCapabilities caps = createCapabilities(); + assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants)); + assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); + } + } + + @Test + public void testRelaxed() throws Exception { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final JobInfo.Builder job = createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); + final JobStatus early = createJobStatus(job, now - 1000, now + 2000); + final JobStatus late = createJobStatus(job, now - 2000, now + 1000); + + job.setIsPrefetch(true); + final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000); + final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000); + + // Unmetered network is whenever + { + final Network net = new Network(101); + final NetworkCapabilities caps = createCapabilities() + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .addCapability(NET_CAPABILITY_NOT_METERED); + assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants)); + assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); + assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants)); + assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants)); + } + + // Metered network is only when prefetching and late + { + final Network net = new Network(101); + final NetworkCapabilities caps = createCapabilities() + .addCapability(NET_CAPABILITY_NOT_CONGESTED); + assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants)); + assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants)); + assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants)); + assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants)); + } + } + + @Test + public void testUpdates() throws Exception { + final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor + .forClass(NetworkCallback.class); + doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture()); + + final ConnectivityController controller = new ConnectivityController(mService); + + final Network meteredNet = new Network(101); + final NetworkCapabilities meteredCaps = createCapabilities(); + final Network unmeteredNet = new Network(202); + final NetworkCapabilities unmeteredCaps = createCapabilities() + .addCapability(NET_CAPABILITY_NOT_METERED); + + final JobStatus red = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + final JobStatus blue = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + + // Pretend we're offline when job is added + { + reset(mConnManager); + answerNetwork(UID_RED, null, null); + answerNetwork(UID_BLUE, null, null); + + controller.maybeStartTrackingJobLocked(red, null); + controller.maybeStartTrackingJobLocked(blue, null); + + assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + } + + // Metered network + { + reset(mConnManager); + answerNetwork(UID_RED, meteredNet, meteredCaps); + answerNetwork(UID_BLUE, meteredNet, meteredCaps); + + callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps); + + assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + } + + // Unmetered network background + { + reset(mConnManager); + answerNetwork(UID_RED, meteredNet, meteredCaps); + answerNetwork(UID_BLUE, meteredNet, meteredCaps); + + callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps); + + assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + } + + // Lost metered network + { + reset(mConnManager); + answerNetwork(UID_RED, unmeteredNet, unmeteredCaps); + answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps); + + callback.getValue().onLost(meteredNet); + + assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + } + + // Specific UID was blocked + { + reset(mConnManager); + answerNetwork(UID_RED, null, null); + answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps); + + callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps); + + assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + } + } + + @Test + public void testRequestStandbyExceptionLocked() { + final ConnectivityController controller = new ConnectivityController(mService); + final JobStatus red = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + final JobStatus blue = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + + InOrder inOrder = inOrder(mNetPolicyManagerInternal); + + controller.requestStandbyExceptionLocked(red); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_RED), eq(true)); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean()); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + // Whitelisting doesn't need to be requested again. + controller.requestStandbyExceptionLocked(red); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean()); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + + controller.requestStandbyExceptionLocked(blue); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_BLUE), eq(true)); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + } + + @Test + public void testWouldBeReadyWithConnectivityLocked() { + final ConnectivityController controller = spy(new ConnectivityController(mService)); + final JobStatus red = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + + doReturn(false).when(controller).isNetworkAvailable(any()); + assertFalse(controller.wouldBeReadyWithConnectivityLocked(red)); + + doReturn(true).when(controller).isNetworkAvailable(any()); + doReturn(false).when(controller).wouldBeReadyWithConstraintLocked(any(), + eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(controller.wouldBeReadyWithConnectivityLocked(red)); + + doReturn(true).when(controller).isNetworkAvailable(any()); + doReturn(true).when(controller).wouldBeReadyWithConstraintLocked(any(), + eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(controller.wouldBeReadyWithConnectivityLocked(red)); + } + + @Test + public void testEvaluateStateLocked_HeartbeatsOn() { + mConstants.USE_HEARTBEATS = true; + final ConnectivityController controller = new ConnectivityController(mService); + final JobStatus red = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + + controller.evaluateStateLocked(red); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED)); + verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + } + + @Test + public void testEvaluateStateLocked_JobWithoutConnectivity() { + mConstants.USE_HEARTBEATS = false; + final ConnectivityController controller = new ConnectivityController(mService); + final JobStatus red = createJobStatus(createJob().setMinimumLatency(1)); + + controller.evaluateStateLocked(red); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED)); + verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + } + + @Test + public void testEvaluateStateLocked_JobWouldBeReady() { + mConstants.USE_HEARTBEATS = false; + final ConnectivityController controller = spy(new ConnectivityController(mService)); + doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any()); + final JobStatus red = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + final JobStatus blue = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + + InOrder inOrder = inOrder(mNetPolicyManagerInternal); + + controller.evaluateStateLocked(red); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_RED), eq(true)); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean()); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + // Whitelisting doesn't need to be requested again. + controller.evaluateStateLocked(red); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean()); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + + controller.evaluateStateLocked(blue); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_BLUE), eq(true)); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + } + + @Test + public void testEvaluateStateLocked_JobWouldNotBeReady() { + mConstants.USE_HEARTBEATS = false; + final ConnectivityController controller = spy(new ConnectivityController(mService)); + doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any()); + final JobStatus red = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + final JobStatus blue = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + + InOrder inOrder = inOrder(mNetPolicyManagerInternal); + + controller.evaluateStateLocked(red); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean()); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + + // Test that a currently whitelisted uid is now removed. + controller.requestStandbyExceptionLocked(blue); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_BLUE), eq(true)); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + controller.evaluateStateLocked(blue); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_BLUE), eq(false)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + } + + @Test + public void testReevaluateStateLocked() { + mConstants.USE_HEARTBEATS = false; + final ConnectivityController controller = spy(new ConnectivityController(mService)); + final JobStatus redOne = createJobStatus(createJob(1) + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + final JobStatus redTwo = createJobStatus(createJob(2) + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + final JobStatus blue = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + controller.maybeStartTrackingJobLocked(redOne, null); + controller.maybeStartTrackingJobLocked(redTwo, null); + controller.maybeStartTrackingJobLocked(blue, null); + + InOrder inOrder = inOrder(mNetPolicyManagerInternal); + controller.requestStandbyExceptionLocked(redOne); + controller.requestStandbyExceptionLocked(redTwo); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_RED), eq(true)); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + + // Make sure nothing happens if an exception hasn't been requested. + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE)); + controller.reevaluateStateLocked(UID_BLUE); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean()); + + // Make sure a job that isn't being tracked doesn't cause issues. + assertFalse(controller.isStandbyExceptionRequestedLocked(12345)); + controller.reevaluateStateLocked(12345); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(12345), anyBoolean()); + + // Both jobs would still be ready. Exception should not be revoked. + doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any()); + controller.reevaluateStateLocked(UID_RED); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + + // One job is still ready. Exception should not be revoked. + doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(eq(redOne)); + doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(eq(redTwo)); + controller.reevaluateStateLocked(UID_RED); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + + // Both jobs are not ready. Exception should be revoked. + doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any()); + controller.reevaluateStateLocked(UID_RED); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_RED), eq(false)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED)); + } + + @Test + public void testMaybeRevokeStandbyExceptionLocked() { + final ConnectivityController controller = new ConnectivityController(mService); + final JobStatus red = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); + final JobStatus blue = createJobStatus(createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + + InOrder inOrder = inOrder(mNetPolicyManagerInternal); + controller.requestStandbyExceptionLocked(red); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_RED), eq(true)); + + // Try revoking for blue instead of red. Red should still have an exception requested. + controller.maybeRevokeStandbyExceptionLocked(blue); + inOrder.verify(mNetPolicyManagerInternal, never()) + .setAppIdleWhitelist(anyInt(), anyBoolean()); + assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED)); + + // Now revoke for red. + controller.maybeRevokeStandbyExceptionLocked(red); + inOrder.verify(mNetPolicyManagerInternal, times(1)) + .setAppIdleWhitelist(eq(UID_RED), eq(false)); + assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED)); + } + + private void answerNetwork(int uid, Network net, NetworkCapabilities caps) { + when(mConnManager.getActiveNetworkForUid(eq(uid))).thenReturn(net); + when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps); + if (net != null) { + final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null); + ni.setDetailedState(DetailedState.CONNECTED, null, null); + when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni); + } + } + + private static NetworkCapabilities createCapabilities() { + return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_VALIDATED); + } + + private static JobInfo.Builder createJob() { + return createJob(101); + } + + private static JobInfo.Builder createJob(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName("foo", "bar")); + } + + private static JobStatus createJobStatus(JobInfo.Builder job) { + return createJobStatus(job, android.os.Process.NOBODY_UID, 0, Long.MAX_VALUE); + } + + private static JobStatus createJobStatus(JobInfo.Builder job, int uid) { + return createJobStatus(job, uid, 0, Long.MAX_VALUE); + } + + private static JobStatus createJobStatus(JobInfo.Builder job, + long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { + return createJobStatus(job, android.os.Process.NOBODY_UID, + earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); + } + + private static JobStatus createJobStatus(JobInfo.Builder job, int uid, + long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { + return new JobStatus(job.build(), uid, null, -1, 0, 0, null, + earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index b2ec83583eba..71aec235640b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -87,7 +87,6 @@ public class QuotaControllerTest { private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; private static final String TAG_CLEANUP = "*job.cleanup*"; private static final String TAG_QUOTA_CHECK = "*job.quota_check*"; - private static final long IN_QUOTA_BUFFER_MILLIS = 30 * SECOND_IN_MILLIS; private static final int CALLING_UID = 1000; private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final int SOURCE_USER_ID = 0; @@ -365,7 +364,8 @@ public class QuotaControllerTest { final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); // Counting backwards, the quota will come back one minute before the end. final long expectedAlarmTime = - end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS; + end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; mQuotaController.saveTimingSession(0, "com.android.test", new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1)); mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); @@ -415,7 +415,8 @@ public class QuotaControllerTest { // Test with timing sessions in window but still in quota. final long start = now - (6 * HOUR_IN_MILLIS); - final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS; + final long expectedAlarmTime = + start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1)); mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); @@ -468,7 +469,8 @@ public class QuotaControllerTest { // Counting backwards, the first minute in the session is over the allowed time, so it // needs to be excluded. final long expectedAlarmTime = - start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS; + start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1)); mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); @@ -529,19 +531,22 @@ public class QuotaControllerTest { // And down from there. final long expectedWorkingAlarmTime = - outOfQuotaTime + (2 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS; + outOfQuotaTime + (2 * HOUR_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX); inOrder.verify(mAlarmManager, times(1)) .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); final long expectedFrequentAlarmTime = - outOfQuotaTime + (8 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS; + outOfQuotaTime + (8 * HOUR_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX); inOrder.verify(mAlarmManager, times(1)) .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); final long expectedRareAlarmTime = - outOfQuotaTime + (24 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS; + outOfQuotaTime + (24 * HOUR_IN_MILLIS) + + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX); inOrder.verify(mAlarmManager, times(1)) .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); @@ -775,6 +780,139 @@ public class QuotaControllerTest { assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } + /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */ + @Test + public void testTimerTracking_AllBackground() { + setDischarging(); + + JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + List<TimingSession> expected = new ArrayList<>(); + + // Test single job. + long start = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.prepareForExecutionLocked(jobStatus); + advanceElapsedClock(5 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); + assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // Test overlapping jobs. + JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2); + mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); + + JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3); + mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); + + advanceElapsedClock(SECOND_IN_MILLIS); + + start = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + mQuotaController.prepareForExecutionLocked(jobStatus); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobStatus2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobStatus3); + advanceElapsedClock(20 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); + assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + /** Tests that Timers don't count foreground jobs. */ + @Test + public void testTimerTracking_AllForeground() { + setDischarging(); + + JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1); + jobStatus.uidActive = true; + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + mQuotaController.prepareForExecutionLocked(jobStatus); + advanceElapsedClock(5 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + /** + * Tests that Timers properly track overlapping foreground and background jobs. + */ + @Test + public void testTimerTracking_ForegroundAndBackground() { + setDischarging(); + + JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1); + JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2); + JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3); + jobFg3.uidActive = true; + mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); + mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + List<TimingSession> expected = new ArrayList<>(); + + // UID starts out inactive. + long start = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.prepareForExecutionLocked(jobBg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); + assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(SECOND_IN_MILLIS); + + // Bg job starts while inactive, spans an entire active session, and ends after the + // active session. + // Fg job starts after the bg job and ends before the bg job. + // Entire bg job duration should be counted since it started before active session. However, + // count should only be 1 since Timer shouldn't count fg jobs. + start = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); + mQuotaController.prepareForExecutionLocked(jobBg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobFg3); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1)); + assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + advanceElapsedClock(SECOND_IN_MILLIS); + + // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes + // "inactive" and then bg job 2 starts. Then fg job ends. + // This should result in two TimingSessions with a count of one each. + start = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); + mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); + mQuotaController.prepareForExecutionLocked(jobBg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobFg3); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); + advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now + start = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.prepareForExecutionLocked(jobBg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); + assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + /** * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches * its quota. diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java new file mode 100644 index 000000000000..db69242538be --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.job.controllers; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; + +import android.app.AlarmManager; +import android.app.job.JobInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.os.SystemClock; +import android.util.proto.ProtoOutputStream; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.JobSchedulerService.Constants; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.time.Clock; +import java.time.ZoneOffset; +import java.util.function.Predicate; + +@RunWith(AndroidJUnit4.class) +public class StateControllerTest { + private static final long SECOND_IN_MILLIS = 1000L; + private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; + private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; + private static final int CALLING_UID = 1000; + private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; + private static final int SOURCE_USER_ID = 0; + + private Constants mConstants; + private StateController mStateController; + + private MockitoSession mMockingSession; + @Mock + private AlarmManager mAlarmManager; + @Mock + private Context mContext; + @Mock + private JobSchedulerService mJobSchedulerService; + + private class TestStateController extends StateController { + TestStateController(JobSchedulerService service) { + super(service); + } + + public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { + } + + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, + boolean forUpdate) { + } + + public void dumpControllerStateLocked(IndentingPrintWriter pw, + Predicate<JobStatus> predicate) { + } + + public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, + Predicate<JobStatus> predicate) { + } + } + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(LocalServices.class) + .startMocking(); + // Use default constants for now. + mConstants = new Constants(); + + // Called in StateController constructor. + when(mJobSchedulerService.getTestableContext()).thenReturn(mContext); + when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService); + when(mJobSchedulerService.getConstants()).thenReturn(mConstants); + // Called in QuotaController constructor. + // Used in JobStatus. + doReturn(mock(PackageManagerInternal.class)) + .when(() -> LocalServices.getService(PackageManagerInternal.class)); + + // Freeze the clocks at this moment in time + JobSchedulerService.sSystemClock = + Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); + JobSchedulerService.sUptimeMillisClock = + Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC); + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC); + + // Initialize real objects. + mStateController = new TestStateController(mJobSchedulerService); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private JobStatus createJobStatus(String testTag, int jobId) { + JobInfo jobInfo = new JobInfo.Builder(jobId, + new ComponentName(mContext, "TestQuotaJobService")) + .setMinimumLatency(Math.abs(jobId) + 1) + .build(); + return JobStatus.createFromJobInfo( + jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag); + } + + @Test + public void testWouldBeReadyWithConstraintLocked() { + JobStatus job = spy(createJobStatus("testWouldBeReadyWithConstraintLocked", 1)); + + when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(false); + assertFalse(mStateController.wouldBeReadyWithConstraintLocked(job, 1)); + + when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(true); + when(mJobSchedulerService.areComponentsInPlaceLocked(job)).thenReturn(false); + assertFalse(mStateController.wouldBeReadyWithConstraintLocked(job, 1)); + + when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(true); + when(mJobSchedulerService.areComponentsInPlaceLocked(job)).thenReturn(true); + assertTrue(mStateController.wouldBeReadyWithConstraintLocked(job, 1)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java index e6b328a128b7..ec5d93edc126 100644 --- a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java @@ -29,8 +29,7 @@ import android.content.pm.PackageManagerInternal; import android.os.UserManagerInternal; import android.os.storage.StorageManagerInternal; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import com.android.internal.os.Zygote; import org.junit.Before; import org.junit.Test; @@ -38,6 +37,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + @SmallTest @RunWith(AndroidJUnit4.class) public class StorageManagerServiceTest { @@ -97,15 +99,15 @@ public class StorageManagerServiceTest { when(mPm.getPackagesForUid(eq(UID_GREY))).thenReturn(new String[] { PKG_GREY }); when(mPm.getPackagesForUid(eq(UID_COLORS))).thenReturn(new String[] { PKG_RED, PKG_BLUE }); - setIsAppStorageSandboxed(PID_BLUE, UID_COLORS, true); - setIsAppStorageSandboxed(PID_GREY, UID_GREY, true); - setIsAppStorageSandboxed(PID_RED, UID_COLORS, true); + setStorageMountMode(PID_BLUE, UID_COLORS, Zygote.MOUNT_EXTERNAL_WRITE); + setStorageMountMode(PID_GREY, UID_GREY, Zygote.MOUNT_EXTERNAL_WRITE); + setStorageMountMode(PID_RED, UID_COLORS, Zygote.MOUNT_EXTERNAL_WRITE); mService = new StorageManagerService(mContext); } - private void setIsAppStorageSandboxed(int pid, int uid, boolean sandboxed) { - when(mAmi.isAppStorageSandboxed(pid, uid)).thenReturn(sandboxed); + private void setStorageMountMode(int pid, int uid, int mountMode) { + when(mAmi.getStorageMountMode(pid, uid)).thenReturn(mountMode); } @Test @@ -210,7 +212,7 @@ public class StorageManagerServiceTest { @Test public void testPackageNotSandboxed() throws Exception { - setIsAppStorageSandboxed(PID_RED, UID_COLORS, false); + setStorageMountMode(PID_RED, UID_COLORS, Zygote.MOUNT_EXTERNAL_FULL); // Both app and system have the same view assertTranslation( @@ -224,6 +226,29 @@ public class StorageManagerServiceTest { PID_RED, UID_COLORS); } + @Test + public void testInstallerPackage() throws Exception { + setStorageMountMode(PID_GREY, UID_GREY, Zygote.MOUNT_EXTERNAL_INSTALLER); + + assertTranslation( + "/storage/emulated/0/Android/obb/com.grey/foo.jpg", + "/storage/emulated/0/Android/obb/com.grey/foo.jpg", + PID_GREY, UID_GREY); + assertTranslation( + "/storage/emulated/0/Android/obb/com.blue/bar.jpg", + "/storage/emulated/0/Android/obb/com.blue/bar.jpg", + PID_GREY, UID_GREY); + + assertTranslation( + "/storage/emulated/0/Android/data/com.grey/foo.jpg", + "/storage/emulated/0/Android/data/com.grey/foo.jpg", + PID_GREY, UID_GREY); + assertTranslation( + "/storage/emulated/0/Android/sandbox/com.grey/Android/data/com.blue/bar.jpg", + "/storage/emulated/0/Android/data/com.blue/bar.jpg", + PID_GREY, UID_GREY); + } + private void assertTranslation(String system, String sandbox, int pid, int uid) throws Exception { assertEquals(system, 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 c3a0ddaff85f..729fac5b1dff 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -21,6 +21,9 @@ 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.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY; import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY; @@ -48,6 +51,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; +import static org.testng.Assert.assertThrows; import android.Manifest.permission; import android.annotation.RawRes; @@ -5133,6 +5137,71 @@ public class DevicePolicyManagerTest extends DpmTestBase { }); } + public void testGetPasswordComplexity_securityExceptionIfParentInstance() { + assertThrows(SecurityException.class, + () -> new DevicePolicyManagerTestable( + mServiceContext, + dpms, + /* parentInstance= */ true) + .getPasswordComplexity()); + } + + public void testGetPasswordComplexity_illegalStateExceptionIfLocked() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(false); + assertThrows(IllegalStateException.class, () -> dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_securityExceptionWithoutPermissions() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + assertThrows(SecurityException.class, () -> dpm.getPasswordComplexity()); + } + + + public void testGetPasswordComplexity_currentUserNoPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(DpmMockContext.CALLER_USER_HANDLE); + + assertEquals(PASSWORD_COMPLEXITY_NONE, dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_currentUserHasPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(DpmMockContext.CALLER_USER_HANDLE); + dpms.mUserPasswordMetrics.put( + DpmMockContext.CALLER_USER_HANDLE, + PasswordMetrics.computeForPassword("asdf")); + + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_unifiedChallengeReturnsParentUserPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + + UserInfo parentUser = new UserInfo(); + parentUser.id = DpmMockContext.CALLER_USER_HANDLE + 10; + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(parentUser.id); + + dpms.mUserPasswordMetrics.put( + DpmMockContext.CALLER_USER_HANDLE, + PasswordMetrics.computeForPassword("asdf")); + dpms.mUserPasswordMetrics.put( + parentUser.id, + PasswordMetrics.computeForPassword("parentUser")); + + assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity()); + } + private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) { final long ident = mServiceContext.binder.clearCallingIdentity(); mServiceContext.binder.callingUid = diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java index 3da61d69c742..4982d6e8817f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java @@ -26,7 +26,12 @@ public class DevicePolicyManagerTestable extends DevicePolicyManager { public DevicePolicyManagerTestable(DpmMockContext context, DevicePolicyManagerServiceTestable dpms) { - super(context, dpms, /* parentInstance = */ false); + this(context, dpms, /* parentInstance= */ false); + } + + public DevicePolicyManagerTestable(DpmMockContext context, + DevicePolicyManagerServiceTestable dpms, boolean parentInstance) { + super(context, dpms, parentInstance); this.dpms = dpms; } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index b4212808d585..e9bfa8f4e0c8 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -18,11 +18,21 @@ package com.android.server.display; import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.content.Context; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.Curve; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayViewport; +import android.hardware.display.DisplayedContentSample; +import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.input.InputManagerInternal; import android.os.Handler; @@ -31,9 +41,9 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; -import androidx.test.runner.AndroidJUnit4; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -49,17 +59,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Arrays; import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.mock; +import java.util.stream.LongStream; @SmallTest @RunWith(AndroidJUnit4.class) @@ -397,6 +398,43 @@ public class DisplayManagerServiceTest { displayManager.validateBrightnessConfiguration(null); } + /** + * Tests that collection of display color sampling results are sensible. + */ + @Test + public void testDisplayedContentSampling() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + registerDefaultDisplays(displayManager); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0); + assertNotNull(ddi); + + DisplayedContentSamplingAttributes attr = + displayManager.getDisplayedContentSamplingAttributesInternal(0); + if (attr == null) return; //sampling not supported on device, skip remainder of test. + + boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0); + assertTrue(!enabled); + + displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0); + DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0); + assertNotNull(sample); + + long numPixels = ddi.width * ddi.height * sample.getNumFrames(); + long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + + samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + + samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + + samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + } + private void registerDefaultDisplays(DisplayManagerService displayManager) { Handler handler = displayManager.getDisplayHandler(); // Would prefer to call displayManager.onStart() directly here but it performs binderService diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java deleted file mode 100644 index 5b59e607cba7..000000000000 --- a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.job.controllers; - -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.when; - -import android.app.job.JobInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManagerInternal; -import android.net.ConnectivityManager; -import android.net.ConnectivityManager.NetworkCallback; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; -import android.net.NetworkPolicyManager; -import android.os.Build; -import android.os.SystemClock; -import android.util.DataUnit; - -import com.android.server.LocalServices; -import com.android.server.job.JobSchedulerService; -import com.android.server.job.JobSchedulerService.Constants; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import java.time.Clock; -import java.time.ZoneOffset; - -@RunWith(MockitoJUnitRunner.class) -public class ConnectivityControllerTest { - - @Mock private Context mContext; - @Mock private ConnectivityManager mConnManager; - @Mock private NetworkPolicyManager mNetPolicyManager; - @Mock private JobSchedulerService mService; - - private Constants mConstants; - - private static final int UID_RED = 10001; - private static final int UID_BLUE = 10002; - - @Before - public void setUp() throws Exception { - // Assume all packages are current SDK - final PackageManagerInternal pm = mock(PackageManagerInternal.class); - when(pm.getPackageTargetSdkVersion(anyString())) - .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.addService(PackageManagerInternal.class, pm); - - // Freeze the clocks at this moment in time - JobSchedulerService.sSystemClock = - Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); - JobSchedulerService.sUptimeMillisClock = - Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC); - JobSchedulerService.sElapsedRealtimeClock = - Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC); - - // Assume default constants for now - mConstants = new Constants(); - - // Get our mocks ready - when(mContext.getSystemServiceName(ConnectivityManager.class)) - .thenReturn(Context.CONNECTIVITY_SERVICE); - when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) - .thenReturn(mConnManager); - when(mContext.getSystemServiceName(NetworkPolicyManager.class)) - .thenReturn(Context.NETWORK_POLICY_SERVICE); - when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE)) - .thenReturn(mNetPolicyManager); - when(mService.getTestableContext()).thenReturn(mContext); - when(mService.getLock()).thenReturn(mService); - when(mService.getConstants()).thenReturn(mConstants); - } - - @Test - public void testInsane() throws Exception { - final Network net = new Network(101); - final JobInfo.Builder job = createJob() - .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); - - // Slow network is too slow - assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net, - createCapabilities().setLinkUpstreamBandwidthKbps(1) - .setLinkDownstreamBandwidthKbps(1), mConstants)); - // Fast network looks great - assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net, - createCapabilities().setLinkUpstreamBandwidthKbps(1024) - .setLinkDownstreamBandwidthKbps(1024), mConstants)); - } - - @Test - public void testCongestion() throws Exception { - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - final JobInfo.Builder job = createJob() - .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); - final JobStatus early = createJobStatus(job, now - 1000, now + 2000); - final JobStatus late = createJobStatus(job, now - 2000, now + 1000); - - // Uncongested network is whenever - { - final Network net = new Network(101); - final NetworkCapabilities caps = createCapabilities() - .addCapability(NET_CAPABILITY_NOT_CONGESTED); - assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants)); - assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); - } - - // Congested network is more selective - { - final Network net = new Network(101); - final NetworkCapabilities caps = createCapabilities(); - assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants)); - assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); - } - } - - @Test - public void testRelaxed() throws Exception { - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - final JobInfo.Builder job = createJob() - .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); - final JobStatus early = createJobStatus(job, now - 1000, now + 2000); - final JobStatus late = createJobStatus(job, now - 2000, now + 1000); - - job.setIsPrefetch(true); - final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000); - final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000); - - // Unmetered network is whenever - { - final Network net = new Network(101); - final NetworkCapabilities caps = createCapabilities() - .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .addCapability(NET_CAPABILITY_NOT_METERED); - assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants)); - assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); - assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants)); - assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants)); - } - - // Metered network is only when prefetching and late - { - final Network net = new Network(101); - final NetworkCapabilities caps = createCapabilities() - .addCapability(NET_CAPABILITY_NOT_CONGESTED); - assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants)); - assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants)); - assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants)); - assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants)); - } - } - - @Test - public void testUpdates() throws Exception { - final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor - .forClass(NetworkCallback.class); - doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture()); - - final ConnectivityController controller = new ConnectivityController(mService); - - final Network meteredNet = new Network(101); - final NetworkCapabilities meteredCaps = createCapabilities(); - final Network unmeteredNet = new Network(202); - final NetworkCapabilities unmeteredCaps = createCapabilities() - .addCapability(NET_CAPABILITY_NOT_METERED); - - final JobStatus red = createJobStatus(createJob() - .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); - final JobStatus blue = createJobStatus(createJob() - .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); - - // Pretend we're offline when job is added - { - reset(mConnManager); - answerNetwork(UID_RED, null, null); - answerNetwork(UID_BLUE, null, null); - - controller.maybeStartTrackingJobLocked(red, null); - controller.maybeStartTrackingJobLocked(blue, null); - - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - } - - // Metered network - { - reset(mConnManager); - answerNetwork(UID_RED, meteredNet, meteredCaps); - answerNetwork(UID_BLUE, meteredNet, meteredCaps); - - callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps); - - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - } - - // Unmetered network background - { - reset(mConnManager); - answerNetwork(UID_RED, meteredNet, meteredCaps); - answerNetwork(UID_BLUE, meteredNet, meteredCaps); - - callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps); - - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - } - - // Lost metered network - { - reset(mConnManager); - answerNetwork(UID_RED, unmeteredNet, unmeteredCaps); - answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps); - - callback.getValue().onLost(meteredNet); - - assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - } - - // Specific UID was blocked - { - reset(mConnManager); - answerNetwork(UID_RED, null, null); - answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps); - - callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps); - - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - } - } - - private void answerNetwork(int uid, Network net, NetworkCapabilities caps) { - when(mConnManager.getActiveNetworkForUid(eq(uid))).thenReturn(net); - when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps); - if (net != null) { - final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null); - ni.setDetailedState(DetailedState.CONNECTED, null, null); - when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni); - } - } - - private static NetworkCapabilities createCapabilities() { - return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_VALIDATED); - } - - private static JobInfo.Builder createJob() { - return new JobInfo.Builder(101, new ComponentName("foo", "bar")); - } - - private static JobStatus createJobStatus(JobInfo.Builder job) { - return createJobStatus(job, android.os.Process.NOBODY_UID, 0, Long.MAX_VALUE); - } - - private static JobStatus createJobStatus(JobInfo.Builder job, int uid) { - return createJobStatus(job, uid, 0, Long.MAX_VALUE); - } - - private static JobStatus createJobStatus(JobInfo.Builder job, - long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { - return createJobStatus(job, android.os.Process.NOBODY_UID, - earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); - } - - private static JobStatus createJobStatus(JobInfo.Builder job, int uid, - long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { - return new JobStatus(job.build(), uid, null, -1, 0, 0, null, - earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0); - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java index 87c3cd2dad06..3b6b48b6aa3f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java @@ -16,14 +16,20 @@ package com.android.server.pm.dex; -import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; import android.os.storage.StorageManager; import androidx.test.filters.SmallTest; @@ -43,13 +49,12 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; - -import java.util.Arrays; +import org.mockito.stubbing.Stubber; @RunWith(AndroidJUnit4.class) @SmallTest public class DexLoggerTests { - private static final String PACKAGE_NAME = "package.name"; + private static final String OWNING_PACKAGE_NAME = "package.name"; private static final String VOLUME_UUID = "volUuid"; private static final String DEX_PATH = "/bar/foo.jar"; private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE; @@ -66,6 +71,7 @@ public class DexLoggerTests { }; private static final String CONTENT_HASH = "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"; + private static final byte[] EMPTY_BYTES = {}; @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); @@ -73,92 +79,191 @@ public class DexLoggerTests { @Mock Installer mInstaller; private final Object mInstallLock = new Object(); - private DexManager.Listener mListener; + private PackageDynamicCodeLoading mPackageDynamicCodeLoading; + private DexLogger mDexLogger; private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create(); + private boolean mWriteTriggered = false; + private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH = + DEX_FILENAME_HASH + " " + CONTENT_HASH; @Before - public void setup() { + public void setup() throws Exception { + // Disable actually attempting to do file writes. + mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() { + @Override + void maybeWriteAsync() { + mWriteTriggered = true; + } + + @Override + protected void writeNow(Void data) { + throw new AssertionError("These tests should never call this method."); + } + }; + // For test purposes capture log messages as well as sending to the event log. - mListener = new DexLogger(mPM, mInstaller, mInstallLock) { - @Override + mDexLogger = new DexLogger(mPM, mInstaller, mInstallLock, mPackageDynamicCodeLoading) { + @Override void writeDclEvent(int uid, String message) { super.writeDclEvent(uid, message); mMessagesForUid.put(uid, message); } }; + + // Make the owning package exist in our mock PackageManager. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.deviceProtectedDataDir = "/bar"; + appInfo.uid = OWNER_UID; + appInfo.volumeUuid = VOLUME_UUID; + PackageInfo packageInfo = new PackageInfo(); + packageInfo.applicationInfo = appInfo; + + doReturn(packageInfo).when(mPM) + .getPackageInfo(OWNING_PACKAGE_NAME, /*flags*/ 0, OWNER_USER_ID); } @Test - public void testSingleAppWithFileHash() throws Exception { - doReturn(CONTENT_HASH_BYTES).when(mInstaller).hashSecondaryDexFile( - DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS); + public void testOneLoader_ownFile_withFileHash() throws Exception { + whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); - runOnReconcile(); + recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); - assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID); - String expectedMessage = DEX_FILENAME_HASH + " " + CONTENT_HASH; - assertThat(mMessagesForUid).containsEntry(OWNER_UID, expectedMessage); + assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH); + + assertThat(mWriteTriggered).isFalse(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()) + .containsExactly(OWNING_PACKAGE_NAME); } @Test - public void testSingleAppNoFileHash() throws Exception { - doReturn(new byte[] { }).when(mInstaller).hashSecondaryDexFile( - DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS); + public void testOneLoader_ownFile_noFileHash() throws Exception { + whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES)); - runOnReconcile(); + recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); - assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID); + assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + + // File should be removed from the DCL list, since we can't hash it. + assertThat(mWriteTriggered).isTrue(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty(); } @Test - public void testSingleAppHashFails() throws Exception { - doThrow(new InstallerException("Testing failure")).when(mInstaller).hashSecondaryDexFile( - DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS); + public void testOneLoader_ownFile_hashingFails() throws Exception { + whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test"))); - runOnReconcile(); + recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + + // File should be removed from the DCL list, since we can't hash it. + assertThat(mWriteTriggered).isTrue(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty(); + } + + @Test + public void testOneLoader_ownFile_unknownPath() { + recordLoad(OWNING_PACKAGE_NAME, "other/path"); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid).isEmpty(); + assertThat(mWriteTriggered).isTrue(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty(); } @Test - public void testOtherApps() throws Exception { - doReturn(CONTENT_HASH_BYTES).when(mInstaller).hashSecondaryDexFile( - DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS); + public void testOneLoader_differentOwner() throws Exception { + whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + setPackageUid("other.package.name", 1001); - // Simulate three packages from two different UIDs - String packageName1 = "other1.package.name"; - String packageName2 = "other2.package.name"; - String packageName3 = "other3.package.name"; - int uid1 = 1001; - int uid2 = 1002; + recordLoad("other.package.name", DEX_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); - doReturn(uid1).when(mPM).getPackageUid(packageName1, 0, OWNER_USER_ID); - doReturn(uid2).when(mPM).getPackageUid(packageName2, 0, OWNER_USER_ID); - doReturn(uid1).when(mPM).getPackageUid(packageName3, 0, OWNER_USER_ID); + assertThat(mMessagesForUid.keys()).containsExactly(1001); + assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH); + assertThat(mWriteTriggered).isFalse(); + } - runOnReconcile(packageName1, packageName2, packageName3); + @Test + public void testOneLoader_differentOwner_uninstalled() throws Exception { + whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + setPackageUid("other.package.name", -1); - assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID, uid1, uid2); + recordLoad("other.package.name", DEX_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); - String expectedMessage = DEX_FILENAME_HASH + " " + CONTENT_HASH; - assertThat(mMessagesForUid).containsEntry(OWNER_UID, expectedMessage); - assertThat(mMessagesForUid).containsEntry(uid1, expectedMessage); - assertThat(mMessagesForUid).containsEntry(uid2, expectedMessage); + assertThat(mMessagesForUid).isEmpty(); + assertThat(mWriteTriggered).isFalse(); } - private void runOnReconcile(String... otherPackageNames) { - ApplicationInfo appInfo = new ApplicationInfo(); - appInfo.packageName = PACKAGE_NAME; - appInfo.volumeUuid = VOLUME_UUID; - appInfo.uid = OWNER_UID; + @Test + public void testMultipleLoadersAndFiles() throws Exception { + String otherDexPath = "/bar/nosuchdir/foo.jar"; + whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES)); + setPackageUid("other.package.name1", 1001); + setPackageUid("other.package.name2", 1002); + + recordLoad("other.package.name1", DEX_PATH); + recordLoad("other.package.name1", otherDexPath); + recordLoad("other.package.name2", DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID); + assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH); + assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH); + + assertThat(mWriteTriggered).isTrue(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()) + .containsExactly(OWNING_PACKAGE_NAME); + + // Check the DexLogger caching is working + verify(mPM, atMost(1)).getPackageInfo(OWNING_PACKAGE_NAME, /*flags*/ 0, OWNER_USER_ID); + } + + @Test + public void testUnknownOwner() { + reset(mPM); + recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + mDexLogger.logDynamicCodeLoading("other.package.name"); + + assertThat(mMessagesForUid).isEmpty(); + assertThat(mWriteTriggered).isFalse(); + verifyZeroInteractions(mPM); + } + + @Test + public void testUninstalledPackage() { + reset(mPM); + recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); - boolean isUsedByOtherApps = otherPackageNames.length > 0; - DexUseInfo dexUseInfo = new DexUseInfo( - isUsedByOtherApps, OWNER_USER_ID, /* classLoaderContext */ null, /* loaderIsa */ null); - dexUseInfo.getLoadingPackages().addAll(Arrays.asList(otherPackageNames)); + assertThat(mMessagesForUid).isEmpty(); + assertThat(mWriteTriggered).isTrue(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty(); + } + + private void setPackageUid(String packageName, int uid) throws Exception { + doReturn(uid).when(mPM).getPackageUid(packageName, /*flags*/ 0, OWNER_USER_ID); + } + + private void whenFileIsHashed(String dexPath, Stubber stubber) throws Exception { + stubber.when(mInstaller).hashSecondaryDexFile( + dexPath, OWNING_PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS); + } - mListener.onReconcileSecondaryDexFile(appInfo, dexUseInfo, DEX_PATH, STORAGE_FLAGS); + private void recordLoad(String loadingPackageName, String dexPath) { + mPackageDynamicCodeLoading.record( + OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index fd07cb046fb5..7cd8ceddfd23 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -27,12 +27,6 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -78,7 +72,6 @@ public class DexManagerTests { @Mock Installer mInstaller; @Mock IPackageManager mPM; private final Object mInstallLock = new Object(); - @Mock DexManager.Listener mListener; private DexManager mDexManager; @@ -114,9 +107,8 @@ public class DexManagerTests { mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0, DELEGATE_LAST_CLASS_LOADER_NAME); - mDexManager = new DexManager( - /*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock, - mListener); + mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, + mInstaller, mInstallLock); // Foo and Bar are available to user0. // Only Bar is available to user1; @@ -415,9 +407,10 @@ public class DexManagerTests { String frameworkDex = "/system/framework/com.android.location.provider.jar"; // Load a dex file from framework. notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); - // The dex file should not be recognized as a package. - assertFalse(mDexManager.hasInfoOnPackage(frameworkDex)); - assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex)); + // The dex file should not be recognized as owned by the package. + assertFalse(mDexManager.hasInfoOnPackage(mFooUser0.getPackageName())); + + assertNull(getPackageDynamicCodeInfo(mFooUser0)); } @Test @@ -510,21 +503,6 @@ public class DexManagerTests { assertHasDclInfo(mBarUser0, mBarUser0, secondaries); } - @Test - public void testReconcileSecondaryDexFiles_invokesListener() throws Exception { - List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs(); - notifyDexLoad(mFooUser0, fooSecondaries, mUser0); - - when(mPM.getPackageInfo(mFooUser0.getPackageName(), 0, 0)) - .thenReturn(mFooUser0.mPackageInfo); - - mDexManager.reconcileSecondaryDexFiles(mFooUser0.getPackageName()); - - verify(mListener, times(fooSecondaries.size())) - .onReconcileSecondaryDexFile(any(ApplicationInfo.class), - any(DexUseInfo.class), anyString(), anyInt()); - } - private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, String[] expectedContexts) { @@ -585,6 +563,10 @@ public class DexManagerTests { return pui; } + private PackageDynamicCode getPackageDynamicCodeInfo(TestData testData) { + return mDexManager.getDexLogger().getPackageDynamicCodeInfo(testData.getPackageName()); + } + private void assertNoUseInfo(TestData testData) { assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName())); } @@ -600,11 +582,11 @@ public class DexManagerTests { } private void assertNoDclInfo(TestData testData) { - assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName())); + assertNull(getPackageDynamicCodeInfo(testData)); } private void assertNoDclInfo(TestData testData, int userId) { - PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()); + PackageDynamicCode info = getPackageDynamicCodeInfo(testData); if (info == null) { return; } @@ -615,7 +597,7 @@ public class DexManagerTests { } private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) { - PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName()); + PackageDynamicCode info = getPackageDynamicCodeInfo(owner); assertNotNull("No DCL data for owner " + owner.getPackageName(), info); for (String path : paths) { DynamicCodeFile fileInfo = info.mFileUsageMap.get(path); diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java index 77f6a23c26b1..acf511e38738 100644 --- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import android.content.Context; -import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerSaveState; @@ -175,7 +174,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase { @SmallTest public void testGetBatterySaverPolicy_PolicyQuickDoze_DefaultValueCorrect() { - testServiceDefaultValue_Off(ServiceType.QUICK_DOZE); + testServiceDefaultValue_On(ServiceType.QUICK_DOZE); } @SmallTest diff --git a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java index 70fadd101a91..00a62a300487 100644 --- a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java @@ -66,7 +66,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigSdkMinMax() throws JSONException, InvalidConfigException { - JSONObject json = new JSONObject("{\"minSdk\":2, \"maxSdk\": 3, \"values\": []}"); + JSONObject json = new JSONObject("{\"min_sdk\":2, \"max_sdk\": 3, \"values\": {}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); assertThat(config.minSdk).isEqualTo(2); @@ -75,7 +75,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigNoMinSdk() throws JSONException { - JSONObject json = new JSONObject("{\"maxSdk\": 3, \"values\": []}"); + JSONObject json = new JSONObject("{\"max_sdk\": 3, \"values\": {}}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -86,7 +86,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigNoMaxSdk() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\": 1, \"values\": []}"); + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"values\": {}}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -97,7 +97,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigNoValues() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3}"); + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -108,7 +108,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\":null, \"maxSdk\": 3, \"values\": []}"); + JSONObject json = new JSONObject("{\"min_sdk\":null, \"max_sdk\": 3, \"values\": {}}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -119,7 +119,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\":1, \"maxSdk\": null, \"values\": []}"); + JSONObject json = new JSONObject("{\"min_sdk\":1, \"max_sdk\": null, \"values\": {}}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -130,7 +130,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigNullValues() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": null}"); + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": null}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -142,7 +142,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigZeroValues() throws JSONException, InvalidConfigException { - JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": []}"); + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": {}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), emptyMap()); assertThat(config.values).hasSize(0); @@ -152,7 +152,7 @@ public class SignedConfigTest { public void testParsePerSdkConfigSingleKey() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), emptyMap()); assertThat(config.values).containsExactly("a", "1"); @@ -162,7 +162,7 @@ public class SignedConfigTest { public void testParsePerSdkConfigSingleKeyNullValue() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), emptyMap()); assertThat(config.values.keySet()).containsExactly("a"); @@ -173,8 +173,7 @@ public class SignedConfigTest { public void testParsePerSdkConfigMultiKeys() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}, " - + "{\"key\":\"c\", \"value\": \"2\"}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"c\": \"2\"}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig( json, setOf("a", "b", "c"), emptyMap()); assertThat(config.values).containsExactly("a", "1", "c", "2"); @@ -183,7 +182,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigSingleKeyNotAllowed() throws JSONException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); try { SignedConfig.parsePerSdkConfig(json, setOf("b"), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -196,7 +195,7 @@ public class SignedConfigTest { public void testParsePerSdkConfigSingleKeyWithMap() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"), mapOf("a", mapOf("1", "one"))); assertThat(config.values).containsExactly("a", "one"); @@ -205,7 +204,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigSingleKeyWithMapInvalidValue() throws JSONException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"2\"}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"2\"}}"); try { SignedConfig.parsePerSdkConfig(json, setOf("b"), mapOf("a", mapOf("1", "one"))); fail("Expected InvalidConfigException"); @@ -218,8 +217,7 @@ public class SignedConfigTest { public void testParsePerSdkConfigMultiKeysWithMap() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}," - + "{\"key\":\"b\", \"value\": \"1\"}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"b\": \"1\"}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), mapOf("a", mapOf("1", "one"))); assertThat(config.values).containsExactly("a", "one", "b", "1"); @@ -229,7 +227,7 @@ public class SignedConfigTest { public void testParsePerSdkConfigSingleKeyWithMapToNull() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"), mapOf("a", mapOf("1", null))); assertThat(config.values).containsExactly("a", null); @@ -241,25 +239,15 @@ public class SignedConfigTest { assertThat(mapOf(null, "allitnil")).containsExactly(null, "allitnil"); assertThat(mapOf(null, "allitnil").containsKey(null)).isTrue(); JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}"); + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"), mapOf("a", mapOf(null, "allitnil"))); assertThat(config.values).containsExactly("a", "allitnil"); } @Test - public void testParsePerSdkConfigSingleKeyNoValue() - throws JSONException, InvalidConfigException { - JSONObject json = new JSONObject( - "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\"}]}"); - SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), - emptyMap()); - assertThat(config.values).containsExactly("a", null); - } - - @Test public void testParsePerSdkConfigValuesInvalid() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": \"foo\"}"); + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": \"foo\"}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -270,18 +258,7 @@ public class SignedConfigTest { @Test public void testParsePerSdkConfigConfigEntryInvalid() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [1, 2]}"); - try { - SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); - fail("Expected InvalidConfigException or JSONException"); - } catch (JSONException | InvalidConfigException e) { - // expected - } - } - - @Test - public void testParsePerSdkConfigConfigEntryNull() throws JSONException { - JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [null]}"); + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": [1, 2]}"); try { SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); fail("Expected InvalidConfigException or JSONException"); @@ -361,7 +338,7 @@ public class SignedConfigTest { @Test public void testParseSdkConfigSingle() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( - "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}]}", + "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}]}", emptySet(), emptyMap()); assertThat(config.perSdkConfig).hasSize(1); } @@ -369,8 +346,8 @@ public class SignedConfigTest { @Test public void testParseSdkConfigMultiple() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( - "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}, " - + "{\"minSdk\": 2, \"maxSdk\": 2, \"values\": []}]}", emptySet(), + "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}, " + + "{\"min_sdk\": 2, \"max_sdk\": 2, \"values\": {}}]}", emptySet(), emptyMap()); assertThat(config.perSdkConfig).hasSize(2); } diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index bf7f53dd7d8f..860656bf47f4 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -16,6 +16,8 @@ package com.android.server.usage; +import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE; + import static junit.framework.TestCase.fail; import static org.testng.Assert.assertEquals; @@ -136,9 +138,11 @@ public class UsageStatsDatabaseTest { event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP; } - event.mClass = ".fake.class.name" + i % 11; + final int instanceId = i % 11; + event.mClass = ".fake.class.name" + instanceId; event.mTimeStamp = time; - event.mEventType = i % 19; //"random" event type + event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type + event.mInstanceId = instanceId; switch (event.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -160,7 +164,8 @@ public class UsageStatsDatabaseTest { } mIntervalStats.events.insert(event); - mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType); + mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType, + event.mInstanceId); time += timeProgression; // Arbitrary progression of time } @@ -219,7 +224,9 @@ public class UsageStatsDatabaseTest { // mBeginTimeStamp is based on the enclosing IntervalStats, don't bother checking // mEndTimeStamp is based on the enclosing IntervalStats, don't bother checking assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed); + assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible); assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground); + assertEquals(us1.mTotalTimeVisible, us2.mTotalTimeVisible); assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed); assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed); // mLaunchCount not persisted, so skipped diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index f6871b3182ca..85410f5e14df 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -82,17 +82,17 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testEmptyTaskCleanupOnRemove() { - assertNotNull(mTask.getWindowContainerController()); + assertNotNull(mTask.getTask()); mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); - assertNull(mTask.getWindowContainerController()); + assertNull(mTask.getTask()); } @Test public void testOccupiedTaskCleanupOnRemove() { final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); - assertNotNull(mTask.getWindowContainerController()); + assertNotNull(mTask.getTask()); mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); - assertNotNull(mTask.getWindowContainerController()); + assertNotNull(mTask.getTask()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index f553c35071a4..569c6d4af37d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -345,7 +345,7 @@ class ActivityTestsBase { mStack.moveToFront("test"); mStack.addTask(task, true, "creating test task"); task.setStack(mStack); - task.setWindowContainerController(); + task.setTask(); } task.touchActiveTime(); @@ -361,12 +361,12 @@ class ActivityTestsBase { } @Override - void createWindowContainer(boolean onTop, boolean showForAllUsers) { - setWindowContainerController(); + void createTask(boolean onTop, boolean showForAllUsers) { + setTask(); } - private void setWindowContainerController() { - setWindowContainerController(mock(TaskWindowContainerController.class)); + private void setTask() { + setTask(mock(Task.class)); } } } @@ -447,7 +447,11 @@ class ActivityTestsBase { } @Override - void updateUsageStats(ActivityRecord component, boolean resumed) { + void updateBatteryStats(ActivityRecord component, boolean resumed) { + } + + @Override + void updateActivityUsageStats(ActivityRecord activity, int event) { } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java index ce5b13cab1a7..5690b58737ab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java @@ -41,17 +41,14 @@ public class StackWindowControllerTests extends WindowTestsBase { public void testRemoveContainer() { final StackWindowController stackController = createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController); final TaskStack stack = stackController.mContainer; - final Task task = taskController.mContainer; assertNotNull(stack); assertNotNull(task); stackController.removeContainer(); // Assert that the container was removed. assertNull(stackController.mContainer); - assertNull(taskController.mContainer); assertNull(stack.getDisplayContent()); assertNull(task.getDisplayContent()); assertNull(task.mStack); @@ -61,11 +58,9 @@ public class StackWindowControllerTests extends WindowTestsBase { public void testRemoveContainer_deferRemoval() { final StackWindowController stackController = createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController); final TaskStack stack = stackController.mContainer; - final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; // Stack removal is deferred if one of its child is animating. task.setLocalIsAnimating(true); @@ -75,11 +70,12 @@ public class StackWindowControllerTests extends WindowTestsBase { // the stack window container is removed. assertNull(stackController.mContainer); assertNull(stack.getController()); - assertNotNull(taskController.mContainer); - assertNotNull(task.getController()); + assertNotNull(task); stack.removeImmediately(); - assertNull(taskController.mContainer); + // After removing, the task will be isolated. + assertNull(task.getParent()); + assertEquals(task.getChildCount(), 0); assertNull(task.getController()); } @@ -89,9 +85,7 @@ public class StackWindowControllerTests extends WindowTestsBase { final StackWindowController stack1Controller = createStackControllerOnDisplay(mDisplayContent); final TaskStack stack1 = stack1Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); - final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; + final WindowTestUtils.TestTask task1 = WindowTestUtils.createTestTask(stack1Controller); task1.mOnDisplayChangedCalled = false; // Create second display and put second stack on it. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index 630a8bffdb30..6b6b33c52d91 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; @@ -31,10 +34,12 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.util.Xml; +import android.view.DisplayInfo; import androidx.test.filters.MediumTest; @@ -66,10 +71,13 @@ public class TaskRecordTests extends ActivityTestsBase { private static final String TASK_TAG = "task"; + private Rect mParentBounds; + @Before public void setUp() throws Exception { TaskRecord.setTaskRecordFactory(null); setupActivityTaskManagerService(); + mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/); } @Test @@ -124,6 +132,72 @@ public class TaskRecordTests extends ActivityTestsBase { assertTrue(task.returnsToHomeStack()); } + /** Ensures that bounds are clipped to their parent. */ + @Test + public void testAppBounds_BoundsClipping() { + final Rect shiftedBounds = new Rect(mParentBounds); + shiftedBounds.offset(10, 10); + final Rect expectedBounds = new Rect(mParentBounds); + expectedBounds.intersect(shiftedBounds); + testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds, + expectedBounds); + } + + /** Ensures that empty bounds are not propagated to the configuration. */ + @Test + public void testAppBounds_EmptyBounds() { + final Rect emptyBounds = new Rect(); + testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds, + null /*ExpectedBounds*/); + } + + /** Ensures that bounds on freeform stacks are not clipped. */ + @Test + public void testAppBounds_FreeFormBounds() { + final Rect freeFormBounds = new Rect(mParentBounds); + freeFormBounds.offset(10, 10); + testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds, + freeFormBounds); + } + + /** Ensures that fully contained bounds are not clipped. */ + @Test + public void testAppBounds_ContainedBounds() { + final Rect insetBounds = new Rect(mParentBounds); + insetBounds.inset(5, 5, 5, 5); + testStackBoundsConfiguration( + WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds); + } + + /** Ensures that full screen free form bounds are clipped */ + @Test + public void testAppBounds_FullScreenFreeFormBounds() { + ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); + DisplayInfo info = new DisplayInfo(); + display.mDisplay.getDisplayInfo(info); + final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight); + testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds, + mParentBounds); + } + + private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, + Rect expectedConfigBounds) { + + ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); + ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build(); + + final Configuration parentConfig = stack.getConfiguration(); + parentConfig.windowConfiguration.setAppBounds(parentBounds); + task.setBounds(bounds); + + task.resolveOverrideConfiguration(parentConfig); + // Assert that both expected and actual are null or are equal to each other + assertEquals(expectedConfigBounds, + task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()); + } + private byte[] serializeToBytes(TaskRecord r) throws IOException, XmlPullParserException { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { final XmlSerializer serializer = Xml.newSerializer(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java new file mode 100644 index 000000000000..3e32ed37a6f3 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -0,0 +1,136 @@ +/* + * 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Test class for {@link Task}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:TaskTests + */ +@SmallTest +@Presubmit +public class TaskTests extends WindowTestsBase { + + @Test + public void testRemoveContainer() { + final StackWindowController stackController1 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1); + final WindowTestUtils.TestAppWindowToken appToken = + WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task); + + task.removeIfPossible(); + // Assert that the container was removed. + assertNull(task.getParent()); + assertEquals(task.getChildCount(), 0); + assertNull(appToken.getParent()); + } + + @Test + public void testRemoveContainer_deferRemoval() { + final StackWindowController stackController1 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1); + final WindowTestUtils.TestAppWindowToken appToken = + WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task); + + task.mShouldDeferRemoval = true; + + task.removeIfPossible(); + // For the case of deferred removal the task will still be connected to the its app token + // until the task window container is removed. + assertNotNull(task.getParent()); + assertNotEquals(task.getChildCount(), 0); + assertNotNull(appToken.getParent()); + + task.removeImmediately(); + assertNull(task.getParent()); + assertEquals(task.getChildCount(), 0); + assertNull(appToken.getParent()); + } + + @Test + public void testReparent() { + final StackWindowController stackController1 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1); + final StackWindowController stackController2 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stackController2); + + boolean gotException = false; + try { + task.reparent(stackController1, 0, false/* moveParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue("Should not be able to reparent to the same parent", gotException); + + final StackWindowController stackController3 = + createStackControllerOnDisplay(mDisplayContent); + stackController3.setContainer(null); + gotException = false; + try { + task.reparent(stackController3, 0, false/* moveParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue("Should not be able to reparent to a stack that doesn't have a container", + gotException); + + task.reparent(stackController2, 0, false/* moveParents */); + assertEquals(stackController2.mContainer, task.getParent()); + assertEquals(0, task.positionInParent()); + assertEquals(1, task2.positionInParent()); + } + + @Test + public void testReparent_BetweenDisplays() { + // Create first stack on primary display. + final StackWindowController stack1Controller = + createStackControllerOnDisplay(mDisplayContent); + final TaskStack stack1 = stack1Controller.mContainer; + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1Controller); + task.mOnDisplayChangedCalled = false; + assertEquals(mDisplayContent, stack1.getDisplayContent()); + + // Create second display and put second stack on it. + final DisplayContent dc = createNewDisplay(); + final StackWindowController stack2Controller = createStackControllerOnDisplay(dc); + final TaskStack stack2 = stack2Controller.mContainer; + final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stack2Controller); + // Reparent and check state + task.reparent(stack2Controller, 0, false /* moveParents */); + assertEquals(stack2, task.getParent()); + assertEquals(0, task.positionInParent()); + assertEquals(1, task2.positionInParent()); + assertTrue(task.mOnDisplayChangedCalled); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java deleted file mode 100644 index bbf508dd1630..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java +++ /dev/null @@ -1,145 +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.wm; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Test; - -/** - * Test class for {@link TaskWindowContainerController}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:TaskWindowContainerControllerTests - */ -@SmallTest -@Presubmit -public class TaskWindowContainerControllerTests extends WindowTestsBase { - - /* Comment out due to removal of AppWindowContainerController - @Test - public void testRemoveContainer() { - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(this); - final WindowTestUtils.TestAppWindowContainerController appController = - new WindowTestUtils.TestAppWindowContainerController(taskController); - - taskController.removeContainer(); - // Assert that the container was removed. - assertNull(taskController.mContainer); - assertNull(appController.mContainer); - } - */ - - /* Comment out due to removal of AppWindowContainerController - @Test - public void testRemoveContainer_deferRemoval() { - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(this); - final WindowTestUtils.TestAppWindowContainerController appController = - new WindowTestUtils.TestAppWindowContainerController(taskController); - - final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; - final AppWindowToken app = appController.mContainer; - task.mShouldDeferRemoval = true; - - taskController.removeContainer(); - // For the case of deferred removal the task controller will no longer be connected to the - // container, but the app controller will still be connected to the its container until - // the task window container is removed. - assertNull(taskController.mContainer); - assertNull(task.getController()); - assertNotNull(appController.mContainer); - assertNotNull(app.getController()); - - task.removeImmediately(); - assertNull(appController.mContainer); - assertNull(app.getController()); - } - */ - - @Test - public void testReparent() { - final StackWindowController stackController1 = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController1); - final StackWindowController stackController2 = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController2 = - new WindowTestUtils.TestTaskWindowContainerController(stackController2); - - boolean gotException = false; - try { - taskController.reparent(stackController1, 0, false/* moveParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue("Should not be able to reparent to the same parent", gotException); - - final StackWindowController stackController3 = - createStackControllerOnDisplay(mDisplayContent); - stackController3.setContainer(null); - gotException = false; - try { - taskController.reparent(stackController3, 0, false/* moveParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue("Should not be able to reparent to a stack that doesn't have a container", - gotException); - - taskController.reparent(stackController2, 0, false/* moveParents */); - assertEquals(stackController2.mContainer, taskController.mContainer.getParent()); - assertEquals(0, ((WindowTestUtils.TestTask) taskController.mContainer).positionInParent()); - assertEquals(1, ((WindowTestUtils.TestTask) taskController2.mContainer).positionInParent()); - } - - @Test - public void testReparent_BetweenDisplays() { - // Create first stack on primary display. - final StackWindowController stack1Controller = - createStackControllerOnDisplay(mDisplayContent); - final TaskStack stack1 = stack1Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); - final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; - task1.mOnDisplayChangedCalled = false; - assertEquals(mDisplayContent, stack1.getDisplayContent()); - - // Create second display and put second stack on it. - final DisplayContent dc = createNewDisplay(); - final StackWindowController stack2Controller = createStackControllerOnDisplay(dc); - final TaskStack stack2 = stack2Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController2 = - new WindowTestUtils.TestTaskWindowContainerController(stack2Controller); - final WindowTestUtils.TestTask task2 = - (WindowTestUtils.TestTask) taskController2.mContainer; - - // Reparent and check state - taskController.reparent(stack2Controller, 0, false /* moveParents */); - assertEquals(stack2, task1.getParent()); - assertEquals(0, task1.positionInParent()); - assertEquals(1, task2.positionInParent()); - assertTrue(task1.mOnDisplayChangedCalled); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java index 885a7e02199b..c653762fb96f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOW_CONFIG_ALWAYS_ON_TOP; import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; import static android.app.WindowConfiguration.WINDOW_CONFIG_ROTATION; @@ -174,70 +173,4 @@ public class WindowConfigurationTests extends WindowTestsBase { assertEquals(appBounds.width(), info.appWidth); assertEquals(appBounds.height(), info.appHeight); } - - /** Ensures that bounds are clipped to their parent. */ - @Test - public void testAppBounds_BoundsClipping() { - final Rect shiftedBounds = new Rect(mParentBounds); - shiftedBounds.offset(10, 10); - final Rect expectedBounds = new Rect(mParentBounds); - expectedBounds.intersect(shiftedBounds); - testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds, - expectedBounds); - } - - /** Ensures that empty bounds are not propagated to the configuration. */ - @Test - public void testAppBounds_EmptyBounds() { - final Rect emptyBounds = new Rect(); - testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds, - null /*ExpectedBounds*/); - } - - /** Ensures that bounds on freeform stacks are not clipped. */ - @Test - public void testAppBounds_FreeFormBounds() { - final Rect freeFormBounds = new Rect(mParentBounds); - freeFormBounds.offset(10, 10); - testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds, - freeFormBounds); - } - - /** Ensures that fully contained bounds are not clipped. */ - @Test - public void testAppBounds_ContainedBounds() { - final Rect insetBounds = new Rect(mParentBounds); - insetBounds.inset(5, 5, 5, 5); - testStackBoundsConfiguration( - WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds); - } - - /** Ensures that full screen free form bounds are clipped */ - @Test - public void testAppBounds_FullScreenFreeFormBounds() { - final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth, - mDisplayInfo.logicalHeight); - testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds, - mParentBounds); - } - - private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, - Rect expectedConfigBounds) { - final StackWindowController stackController = createStackControllerOnStackOnDisplay( - windowingMode, ACTIVITY_TYPE_STANDARD, mDisplayContent); - - final Configuration parentConfig = mDisplayContent.getConfiguration(); - parentConfig.windowConfiguration.setAppBounds(parentBounds); - - final Configuration config = new Configuration(); - final WindowConfiguration winConfig = config.windowConfiguration; - stackController.adjustConfigurationForBounds(bounds, - new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/, - false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig, - windowingMode); - // Assert that both expected and actual are null or are equal to each other - - assertEquals(expectedConfigBounds, winConfig.getAppBounds()); - } - } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index 65e18354a5ae..65cde77e7c03 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -19,11 +19,6 @@ package com.android.server.wm; import static android.app.AppOpsManager.OP_NONE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -34,8 +29,6 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.view.Display; @@ -45,8 +38,6 @@ import android.view.Surface; import android.view.SurfaceControl.Transaction; import android.view.WindowManager; -import org.mockito.invocation.InvocationOnMock; - /** * A collection of static functions that can be referenced by other test packages to provide access * to WindowManager related test functionality. @@ -116,17 +107,6 @@ public class WindowTestUtils { public static StackWindowController createMockStackWindowContainerController() { StackWindowController controller = mock(StackWindowController.class); controller.mContainer = mock(TestTaskStack.class); - - // many components rely on the {@link StackWindowController#adjustConfigurationForBounds} - // to properly set bounds values in the configuration. We must mimick those actions here. - doAnswer((InvocationOnMock invocationOnMock) -> { - final Configuration config = invocationOnMock.<Configuration>getArgument(6); - final Rect bounds = invocationOnMock.<Rect>getArgument(0); - config.windowConfiguration.setBounds(bounds); - return null; - }).when(controller).adjustConfigurationForBounds(any(), any(), any(), - anyBoolean(), anyBoolean(), anyFloat(), any(), any(), anyInt()); - return controller; } @@ -141,6 +121,13 @@ public class WindowTestUtils { } } + /** Creates an {@link AppWindowToken} and adds it to the specified {@link Task}. */ + public static TestAppWindowToken createAppWindowTokenInTask(DisplayContent dc, Task task) { + final TestAppWindowToken newToken = createTestAppWindowToken(dc); + task.addChild(newToken, POSITION_TOP); + return newToken; + } + /** * An extension of {@link TestTaskStack}, which overrides package scoped methods that would not * normally be mocked out. @@ -264,9 +251,10 @@ public class WindowTestUtils { TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode, boolean supportsPictureInPicture, - TaskWindowContainerController controller) { + TaskRecord taskRecord) { super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture, - new ActivityManager.TaskDescription(), controller); + new ActivityManager.TaskDescription(), taskRecord); + stack.addTask(this, POSITION_TOP); } boolean shouldDeferRemoval() { @@ -293,49 +281,10 @@ public class WindowTestUtils { } } - /** - * Used so we can gain access to some protected members of {@link TaskWindowContainerController} - * class. - */ - public static class TestTaskWindowContainerController extends TaskWindowContainerController { - - static final TaskWindowContainerListener NOP_LISTENER = new TaskWindowContainerListener() { - @Override - public void registerConfigurationChangeListener( - ConfigurationContainerListener listener) { - } - - @Override - public void unregisterConfigurationChangeListener( - ConfigurationContainerListener listener) { - } - - @Override - public void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) { - } - - @Override - public void requestResize(Rect bounds, int resizeMode) { - } - }; - - TestTaskWindowContainerController(WindowTestsBase testsBase) { - this(testsBase.createStackControllerOnDisplay(testsBase.mDisplayContent)); - } - - TestTaskWindowContainerController(StackWindowController stackController) { - super(sNextTaskId++, NOP_LISTENER, stackController, 0 /* userId */, null /* bounds */, - RESIZE_MODE_UNRESIZEABLE, false /* supportsPictureInPicture */, true /* toTop*/, - true /* showForAllUsers */, new ActivityManager.TaskDescription(), - stackController.mService); - } - - @Override - TestTask createTask(int taskId, TaskStack stack, int userId, int resizeMode, - boolean supportsPictureInPicture, ActivityManager.TaskDescription taskDescription) { - return new TestTask(taskId, stack, userId, mService, resizeMode, - supportsPictureInPicture, this); - } + public static TestTask createTestTask(StackWindowController stackWindowController) { + return new TestTask(sNextTaskId++, stackWindowController.mContainer, 0, + stackWindowController.mService, RESIZE_MODE_UNRESIZEABLE, false, + mock(TaskRecord.class)); } public static class TestIApplicationToken implements IApplicationToken { diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 4f573a475ae7..65ed85db17bd 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -842,8 +842,8 @@ public class AppStandbyController { final boolean previouslyIdle = mAppIdleHistory.isIdle( event.mPackage, userId, elapsedRealtime); // Inform listeners if necessary - if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND - || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND + if ((event.mEventType == UsageEvents.Event.ACTIVITY_RESUMED + || event.mEventType == UsageEvents.Event.ACTIVITY_PAUSED || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION || event.mEventType == UsageEvents.Event.USER_INTERACTION || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN @@ -894,8 +894,8 @@ public class AppStandbyController { private int usageEventToSubReason(int eventType) { switch (eventType) { - case UsageEvents.Event.MOVE_TO_FOREGROUND: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND; - case UsageEvents.Event.MOVE_TO_BACKGROUND: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND; + case UsageEvents.Event.ACTIVITY_RESUMED: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND; + case UsageEvents.Event.ACTIVITY_PAUSED: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND; case UsageEvents.Event.SYSTEM_INTERACTION: return REASON_SUB_USAGE_SYSTEM_INTERACTION; case UsageEvents.Event.USER_INTERACTION: return REASON_SUB_USAGE_USER_INTERACTION; case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN; diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 84052672f6d3..94cc6502a12d 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -15,10 +15,31 @@ */ package com.android.server.usage; +import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; +import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE; +import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; +import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; +import static android.app.usage.UsageEvents.Event.END_OF_DAY; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; +import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; +import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; +import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN; +import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; +import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; +import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; +import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; +import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE; +import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; +import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED; +import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; + import android.app.usage.ConfigurationStats; import android.app.usage.EventList; import android.app.usage.EventStats; -import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.content.res.Configuration; import android.util.ArrayMap; @@ -125,8 +146,8 @@ public class IntervalStats { /** * Builds a UsageEvents.Event, but does not add it internally. */ - UsageEvents.Event buildEvent(String packageName, String className) { - UsageEvents.Event event = new UsageEvents.Event(); + Event buildEvent(String packageName, String className) { + Event event = new Event(); event.mPackage = getCachedStringRef(packageName); if (className != null) { event.mClass = getCachedStringRef(className); @@ -138,9 +159,9 @@ public class IntervalStats { * Builds a UsageEvents.Event from a proto, but does not add it internally. * Built here to take advantage of the cached String Refs */ - UsageEvents.Event buildEvent(ProtoInputStream parser, List<String> stringPool) + Event buildEvent(ProtoInputStream parser, List<String> stringPool) throws IOException { - final UsageEvents.Event event = new UsageEvents.Event(); + final Event event = new Event(); while (true) { switch (parser.nextField()) { case (int) IntervalStatsProto.Event.PACKAGE: @@ -190,20 +211,23 @@ public class IntervalStats { parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX) - 1)); break; + case (int) IntervalStatsProto.Event.INSTANCE_ID: + event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID); + break; case ProtoInputStream.NO_MORE_FIELDS: // Handle default values for certain events types switch (event.mEventType) { - case UsageEvents.Event.CONFIGURATION_CHANGE: + case CONFIGURATION_CHANGE: if (event.mConfiguration == null) { event.mConfiguration = new Configuration(); } break; - case UsageEvents.Event.SHORTCUT_INVOCATION: + case SHORTCUT_INVOCATION: if (event.mShortcutId == null) { event.mShortcutId = ""; } break; - case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case NOTIFICATION_INTERRUPTION: if (event.mNotificationChannelId == null) { event.mNotificationChannelId = ""; } @@ -220,14 +244,15 @@ public class IntervalStats { private boolean isStatefulEvent(int eventType) { switch (eventType) { - case UsageEvents.Event.MOVE_TO_FOREGROUND: - case UsageEvents.Event.MOVE_TO_BACKGROUND: - case UsageEvents.Event.FOREGROUND_SERVICE_START: - case UsageEvents.Event.FOREGROUND_SERVICE_STOP: - case UsageEvents.Event.END_OF_DAY: - case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE: - case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: - case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE: + case ACTIVITY_RESUMED: + case ACTIVITY_PAUSED: + case ACTIVITY_STOPPED: + case FOREGROUND_SERVICE_START: + case FOREGROUND_SERVICE_STOP: + case END_OF_DAY: + case ROLLOVER_FOREGROUND_SERVICE: + case CONTINUE_PREVIOUS_DAY: + case CONTINUING_FOREGROUND_SERVICE: return true; } return false; @@ -238,17 +263,56 @@ public class IntervalStats { * interaction. Excludes those that are internally generated. */ private boolean isUserVisibleEvent(int eventType) { - return eventType != UsageEvents.Event.SYSTEM_INTERACTION - && eventType != UsageEvents.Event.STANDBY_BUCKET_CHANGED; + return eventType != SYSTEM_INTERACTION + && eventType != STANDBY_BUCKET_CHANGED; } /** + * Update the IntervalStats by a activity or foreground service event. + * @param packageName package name of this event. Is null if event targets to all packages. + * @param className class name of a activity or foreground service, could be null to if this + * is sent to all activities/services in this package. + * @param timeStamp Epoch timestamp in milliseconds. + * @param eventType event type as in {@link Event} + * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken. + * if className is not an activity, instanceId is not used. * @hide */ @VisibleForTesting - public void update(String packageName, String className, long timeStamp, int eventType) { - UsageStats usageStats = getOrCreateUsageStats(packageName); - usageStats.update(className, timeStamp, eventType); + public void update(String packageName, String className, long timeStamp, int eventType, + int instanceId) { + if (eventType == FLUSH_TO_DISK) { + // FLUSH_TO_DISK are sent to all packages. + final int size = packageStats.size(); + for (int i = 0; i < size; i++) { + UsageStats usageStats = packageStats.valueAt(i); + usageStats.update(null, timeStamp, eventType, instanceId); + } + } else if (eventType == ACTIVITY_DESTROYED) { + UsageStats usageStats = packageStats.get(packageName); + if (usageStats != null) { + // If previous event is not ACTIVITY_STOPPED, convert ACTIVITY_DESTROYED + // to ACTIVITY_STOPPED and add to event list. + // Otherwise do not add anything to event list. (Because we want to save space + // and we do not want a ACTIVITY_STOPPED followed by + // ACTIVITY_DESTROYED in event list). + final int index = usageStats.mActivities.indexOfKey(instanceId); + if (index >= 0) { + final int type = usageStats.mActivities.valueAt(index); + if (type != ACTIVITY_STOPPED) { + Event event = new Event(ACTIVITY_STOPPED, timeStamp); + event.mPackage = packageName; + event.mClass = className; + event.mInstanceId = instanceId; + addEvent(event); + } + } + usageStats.update(className, timeStamp, ACTIVITY_DESTROYED, instanceId); + } + } else { + UsageStats usageStats = getOrCreateUsageStats(packageName); + usageStats.update(className, timeStamp, eventType, instanceId); + } endTime = timeStamp; } @@ -256,7 +320,7 @@ public class IntervalStats { * @hide */ @VisibleForTesting - public void addEvent(UsageEvents.Event event) { + public void addEvent(Event event) { if (events == null) { events = new EventList(); } @@ -265,7 +329,7 @@ public class IntervalStats { if (event.mClass != null) { event.mClass = getCachedStringRef(event.mClass); } - if (event.mEventType == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { + if (event.mEventType == NOTIFICATION_INTERRUPTION) { event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId); } events.insert(event); @@ -338,13 +402,13 @@ public class IntervalStats { } void addEventStatsTo(List<EventStats> out) { - interactiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_INTERACTIVE, + interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE, beginTime, endTime); - nonInteractiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_NON_INTERACTIVE, + nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE, beginTime, endTime); - keyguardShownTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_SHOWN, + keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN, beginTime, endTime); - keyguardHiddenTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_HIDDEN, + keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN, beginTime, endTime); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java index 8e1c06091605..d70653781eff 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProto.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java @@ -135,6 +135,15 @@ final class UsageStatsProto { stats.mTotalTimeForegroundServiceUsed = proto.readLong( IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS); break; + case (int) IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS: + // Time attributes stored is an offset of the beginTime. + stats.mLastTimeVisible = statsOut.beginTime + proto.readLong( + IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS); + break; + case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS: + stats.mTotalTimeVisible = proto.readLong( + IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS); + break; } } if (stats.mLastTimeUsed == 0) { @@ -337,6 +346,11 @@ final class UsageStatsProto { usageStats.mLastTimeForegroundServiceUsed - stats.beginTime); proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS, usageStats.mTotalTimeForegroundServiceUsed); + // Time attributes stored as an offset of the beginTime. + proto.write(IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS, + usageStats.mLastTimeVisible - stats.beginTime); + proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS, + usageStats.mTotalTimeVisible); proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount); writeChooserCounts(proto, usageStats); proto.end(token); @@ -427,6 +441,7 @@ final class UsageStatsProto { proto.write(IntervalStatsProto.Event.TIME_MS, event.mTimeStamp - stats.beginTime); proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags); proto.write(IntervalStatsProto.Event.TYPE, event.mEventType); + proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId); switch (event.mEventType) { case UsageEvents.Event.CONFIGURATION_CHANGE: if (event.mConfiguration != null) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 262125212c14..57dc08fcd253 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -16,6 +16,12 @@ package com.android.server.usage; +import static android.app.usage.UsageEvents.Event.CHOOSER_ACTION; +import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; +import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; +import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; + import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; @@ -28,10 +34,10 @@ import android.app.usage.ConfigurationStats; import android.app.usage.EventStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManager; -import android.app.usage.UsageStatsManager.StandbyBuckets; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.app.usage.UsageStatsManager.StandbyBuckets; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -75,9 +81,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; /** * A service that collects, aggregates, and persists application usage data. @@ -106,6 +110,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_FLUSH_TO_DISK = 1; static final int MSG_REMOVE_USER = 2; static final int MSG_UID_STATE_CHANGED = 3; + static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4; private final Object mLock = new Object(); Handler mHandler; @@ -135,12 +140,10 @@ public class UsageStatsService extends SystemService implements @Override public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket, int reason) { - Event event = new Event(); - event.mEventType = Event.STANDBY_BUCKET_CHANGED; + Event event = new Event(Event.STANDBY_BUCKET_CHANGED, + SystemClock.elapsedRealtime()); event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF); event.mPackage = packageName; - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -397,7 +400,7 @@ public class UsageStatsService extends SystemService implements * Assuming the event's timestamp is measured in milliseconds since boot, * convert it to a system wall time. */ - private void convertToSystemTimeLocked(UsageEvents.Event event) { + private void convertToSystemTimeLocked(Event event) { event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot; } @@ -406,7 +409,6 @@ public class UsageStatsService extends SystemService implements */ void shutdown() { synchronized (mLock) { - mHandler.removeMessages(MSG_REPORT_EVENT); flushToDiskLocked(); } } @@ -414,7 +416,7 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - void reportEvent(UsageEvents.Event event, int userId) { + void reportEvent(Event event, int userId) { synchronized (mLock) { final long timeNow = checkAndGetTimeLocked(); final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -431,14 +433,14 @@ public class UsageStatsService extends SystemService implements mAppStandby.reportEvent(event, elapsedRealtime, userId); switch (event.mEventType) { - case Event.MOVE_TO_FOREGROUND: + case Event.ACTIVITY_RESUMED: try { mAppTimeLimit.noteUsageStart(event.getPackageName(), userId); } catch (IllegalArgumentException iae) { Slog.e(TAG, "Failed to note usage start", iae); } break; - case Event.MOVE_TO_BACKGROUND: + case Event.ACTIVITY_PAUSED: try { mAppTimeLimit.noteUsageStop(event.getPackageName(), userId); } catch (IllegalArgumentException iae) { @@ -450,10 +452,31 @@ public class UsageStatsService extends SystemService implements } /** - * Called by the Binder stub. + * Some events like FLUSH_TO_DISK need to be sent to all userId. + * @param event + */ + void reportEventToAllUserId(Event event) { + synchronized (mLock) { + final int userCount = mUserState.size(); + for (int i = 0; i < userCount; i++) { + Event copy = new Event(event); + reportEvent(copy, mUserState.keyAt(i)); + } + } + } + + /** + * Called by the Handler for message MSG_FLUSH_TO_DISK. */ void flushToDisk() { synchronized (mLock) { + // Before flush to disk, report FLUSH_TO_DISK event to signal UsageStats to update app + // usage. In case of abrupt power shutdown like battery drain or cold temperature, + // all UsageStats has correct data up to last flush to disk. + // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats' + // EventList. + Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime()); + reportEventToAllUserId(event); flushToDiskLocked(); } } @@ -656,9 +679,11 @@ public class UsageStatsService extends SystemService implements public void handleMessage(Message msg) { switch (msg.what) { case MSG_REPORT_EVENT: - reportEvent((UsageEvents.Event) msg.obj, msg.arg1); + reportEvent((Event) msg.obj, msg.arg1); + break; + case MSG_REPORT_EVENT_TO_ALL_USERID: + reportEventToAllUserId((Event) msg.obj); break; - case MSG_FLUSH_TO_DISK: flushToDisk(); break; @@ -1120,20 +1145,11 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(CHOOSER_ACTION, SystemClock.elapsedRealtime()); event.mPackage = packageName; - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = Event.CHOOSER_ACTION; - event.mAction = action; - event.mContentType = contentType; - event.mContentAnnotations = annotations; - mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1251,37 +1267,29 @@ public class UsageStatsService extends SystemService implements private final class LocalService extends UsageStatsManagerInternal { @Override - public void reportEvent(ComponentName component, int userId, int eventType) { + public void reportEvent(ComponentName component, int userId, int eventType, + int instanceId) { if (component == null) { Slog.w(TAG, "Event reported without a component name"); return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(eventType, SystemClock.elapsedRealtime()); event.mPackage = component.getPackageName(); event.mClass = component.getClassName(); - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = eventType; + event.mInstanceId = instanceId; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @Override public void reportEvent(String packageName, int userId, int eventType) { if (packageName == null) { - Slog.w(TAG, "Event reported without a package name"); + Slog.w(TAG, "Event reported without a package name, eventType:" + eventType); return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(eventType, SystemClock.elapsedRealtime()); event.mPackage = packageName; - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = eventType; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1292,13 +1300,8 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(CONFIGURATION_CHANGE, SystemClock.elapsedRealtime()); event.mPackage = "android"; - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE; event.mConfiguration = new Configuration(config); mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1311,14 +1314,9 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(NOTIFICATION_INTERRUPTION, SystemClock.elapsedRealtime()); event.mPackage = packageName.intern(); event.mNotificationChannelId = channelId.intern(); - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = Event.NOTIFICATION_INTERRUPTION; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1329,14 +1327,9 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(SHORTCUT_INVOCATION, SystemClock.elapsedRealtime()); event.mPackage = packageName.intern(); event.mShortcutId = shortcutId.intern(); - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = Event.SHORTCUT_INVOCATION; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1372,7 +1365,7 @@ public class UsageStatsService extends SystemService implements // This method *WILL* do IO work, but we must block until it is finished or else // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because // we are shutting down. - shutdown(); + UsageStatsService.this.shutdown(); } @Override diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 01e566cdd85f..ec475bf26bb3 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -61,11 +61,13 @@ final class UsageStatsXmlV1 { private static final String FLAGS_ATTR = "flags"; private static final String CLASS_ATTR = "class"; private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive"; + private static final String TOTAL_TIME_VISIBLE_ATTR = "timeVisible"; private static final String TOTAL_TIME_SERVICE_USED_ATTR = "timeServiceUsed"; private static final String COUNT_ATTR = "count"; private static final String ACTIVE_ATTR = "active"; private static final String LAST_EVENT_ATTR = "lastEvent"; private static final String TYPE_ATTR = "type"; + private static final String INSTANCE_ID_ATTR = "instanceId"; private static final String SHORTCUT_ID_ATTR = "shortcutId"; private static final String STANDBY_BUCKET_ATTR = "standbyBucket"; private static final String APP_LAUNCH_COUNT_ATTR = "appLaunchCount"; @@ -75,6 +77,7 @@ final class UsageStatsXmlV1 { // Time attributes stored as an offset of the beginTime. private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive"; + private static final String LAST_TIME_VISIBLE_ATTR = "lastTimeVisible"; private static final String LAST_TIME_SERVICE_USED_ATTR = "lastTimeServiceUsed"; private static final String END_TIME_ATTR = "endTime"; private static final String TIME_ATTR = "time"; @@ -92,6 +95,13 @@ final class UsageStatsXmlV1 { parser, LAST_TIME_ACTIVE_ATTR); try { + stats.mLastTimeVisible = statsOut.beginTime + XmlUtils.readLongAttribute( + parser, LAST_TIME_VISIBLE_ATTR); + } catch (IOException e) { + Log.e(TAG, "Failed to parse mLastTimeVisible", e); + } + + try { stats.mLastTimeForegroundServiceUsed = statsOut.beginTime + XmlUtils.readLongAttribute( parser, LAST_TIME_SERVICE_USED_ATTR); } catch (IOException e) { @@ -101,8 +111,14 @@ final class UsageStatsXmlV1 { stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); try { + stats.mTotalTimeVisible = XmlUtils.readLongAttribute(parser, TOTAL_TIME_VISIBLE_ATTR); + } catch (IOException e) { + Log.e(TAG, "Failed to parse mTotalTimeVisible", e); + } + + try { stats.mTotalTimeForegroundServiceUsed = XmlUtils.readLongAttribute(parser, - TOTAL_TIME_SERVICE_USED_ATTR); + TOTAL_TIME_SERVICE_USED_ATTR); } catch (IOException e) { Log.e(TAG, "Failed to parse mTotalTimeForegroundServiceUsed", e); } @@ -199,6 +215,13 @@ final class UsageStatsXmlV1 { event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR); event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR); + + try { + event.mInstanceId = XmlUtils.readIntAttribute(parser, INSTANCE_ID_ATTR); + } catch (IOException e) { + Log.e(TAG, "Failed to parse mInstanceId", e); + } + switch (event.mEventType) { case UsageEvents.Event.CONFIGURATION_CHANGE: event.mConfiguration = new Configuration(); @@ -227,10 +250,13 @@ final class UsageStatsXmlV1 { // Write the time offset. XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, usageStats.mLastTimeUsed - stats.beginTime); + XmlUtils.writeLongAttribute(xml, LAST_TIME_VISIBLE_ATTR, + usageStats.mLastTimeVisible - stats.beginTime); XmlUtils.writeLongAttribute(xml, LAST_TIME_SERVICE_USED_ATTR, usageStats.mLastTimeForegroundServiceUsed - stats.beginTime); XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName); XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground); + XmlUtils.writeLongAttribute(xml, TOTAL_TIME_VISIBLE_ATTR, usageStats.mTotalTimeVisible); XmlUtils.writeLongAttribute(xml, TOTAL_TIME_SERVICE_USED_ATTR, usageStats.mTotalTimeForegroundServiceUsed); XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent); @@ -317,6 +343,7 @@ final class UsageStatsXmlV1 { } XmlUtils.writeIntAttribute(xml, FLAGS_ATTR, event.mFlags); XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType); + XmlUtils.writeIntAttribute(xml, INSTANCE_ID_ATTR, event.mInstanceId); switch (event.mEventType) { case UsageEvents.Event.CONFIGURATION_CHANGE: diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 94d7dbb49b74..5128ae1f9130 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -16,10 +16,18 @@ package com.android.server.usage; +import static android.app.usage.UsageStatsManager.INTERVAL_BEST; +import static android.app.usage.UsageStatsManager.INTERVAL_COUNT; +import static android.app.usage.UsageStatsManager.INTERVAL_DAILY; +import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY; +import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY; +import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY; + import android.app.usage.ConfigurationStats; import android.app.usage.EventList; import android.app.usage.EventStats; import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.content.Context; @@ -29,6 +37,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.util.IndentingPrintWriter; import com.android.server.usage.UsageStatsDatabase.StatCombiner; @@ -65,7 +74,7 @@ class UserUsageStatsService { private final int mUserId; // STOPSHIP: Temporary member variable for debugging b/110930764. - private UsageEvents.Event mLastEvent; + private Event mLastEvent; private static final long[] INTERVAL_LENGTH = new long[] { UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS, @@ -87,7 +96,7 @@ class UserUsageStatsService { mContext = context; mDailyExpiryDate = new UnixCalendar(0); mDatabase = new UsageStatsDatabase(usageStatsDir); - mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; + mCurrentStats = new IntervalStats[INTERVAL_COUNT]; mListener = listener; mLogPrefix = "User[" + Integer.toString(userId) + "] "; mUserId = userId; @@ -125,28 +134,6 @@ class UserUsageStatsService { updateRolloverDeadline(); } - // Now close off any events that were open at the time this was saved. - for (IntervalStats stat : mCurrentStats) { - final int pkgCount = stat.packageStats.size(); - for (int i = 0; i < pkgCount; i++) { - final UsageStats pkgStats = stat.packageStats.valueAt(i); - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty() - || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) { - stat.update(pkgStats.mPackageName, null, stat.lastTimeSaved, - UsageEvents.Event.END_OF_DAY); - } - if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - stat.update(pkgStats.mPackageName, null , stat.lastTimeSaved, - UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE); - } - notifyStatsChanged(); - } - } - - stat.updateConfigurationStats(null, stat.lastTimeSaved); - } - if (mDatabase.isNewUpdate()) { notifyNewUpdate(); } @@ -158,40 +145,46 @@ class UserUsageStatsService { loadActiveStats(newTime); } - void reportEvent(UsageEvents.Event event) { + void reportEvent(Event event) { if (DEBUG) { Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage + "[" + event.mTimeStamp + "]: " + eventToString(event.mEventType)); } - mLastEvent = new UsageEvents.Event(event); + mLastEvent = new Event(event); if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { // Need to rollover rolloverStats(event.mTimeStamp); } - final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY]; + final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY]; final Configuration newFullConfig = event.mConfiguration; - if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE && - currentDailyStats.activeConfiguration != null) { + if (event.mEventType == Event.CONFIGURATION_CHANGE + && currentDailyStats.activeConfiguration != null) { // Make the event configuration a delta. event.mConfiguration = Configuration.generateDelta( currentDailyStats.activeConfiguration, newFullConfig); } - if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) { + if (event.mEventType != Event.SYSTEM_INTERACTION + // ACTIVITY_DESTROYED is a private event. If there is preceding ACTIVITY_STOPPED + // ACTIVITY_DESTROYED will be dropped. Otherwise it will be converted to + // ACTIVITY_STOPPED. + && event.mEventType != Event.ACTIVITY_DESTROYED + // FLUSH_TO_DISK is a private event. + && event.mEventType != Event.FLUSH_TO_DISK) { currentDailyStats.addEvent(event); } boolean incrementAppLaunch = false; - if (event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND) { + if (event.mEventType == Event.ACTIVITY_RESUMED) { if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) { incrementAppLaunch = true; } - } else if (event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND) { + } else if (event.mEventType == Event.ACTIVITY_PAUSED) { if (event.mPackage != null) { mLastBackgroundedPackage = event.mPackage; } @@ -199,10 +192,10 @@ class UserUsageStatsService { for (IntervalStats stats : mCurrentStats) { switch (event.mEventType) { - case UsageEvents.Event.CONFIGURATION_CHANGE: { + case Event.CONFIGURATION_CHANGE: { stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); } break; - case UsageEvents.Event.CHOOSER_ACTION: { + case Event.CHOOSER_ACTION: { stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); String[] annotations = event.mContentAnnotations; if (annotations != null) { @@ -211,21 +204,21 @@ class UserUsageStatsService { } } } break; - case UsageEvents.Event.SCREEN_INTERACTIVE: { + case Event.SCREEN_INTERACTIVE: { stats.updateScreenInteractive(event.mTimeStamp); } break; - case UsageEvents.Event.SCREEN_NON_INTERACTIVE: { + case Event.SCREEN_NON_INTERACTIVE: { stats.updateScreenNonInteractive(event.mTimeStamp); } break; - case UsageEvents.Event.KEYGUARD_SHOWN: { + case Event.KEYGUARD_SHOWN: { stats.updateKeyguardShown(event.mTimeStamp); } break; - case UsageEvents.Event.KEYGUARD_HIDDEN: { + case Event.KEYGUARD_HIDDEN: { stats.updateKeyguardHidden(event.mTimeStamp); } break; default: { stats.update(event.mPackage, event.getClassName(), - event.mTimeStamp, event.mEventType); + event.mTimeStamp, event.mEventType, event.mInstanceId); if (incrementAppLaunch) { stats.incrementAppLaunchCount(event.mPackage); } @@ -286,12 +279,12 @@ class UserUsageStatsService { */ private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, StatCombiner<T> combiner) { - if (intervalType == UsageStatsManager.INTERVAL_BEST) { + if (intervalType == INTERVAL_BEST) { intervalType = mDatabase.findBestFitBucket(beginTime, endTime); if (intervalType < 0) { // Nothing saved to disk yet, so every stat is just as equal (no rollover has // occurred. - intervalType = UsageStatsManager.INTERVAL_DAILY; + intervalType = INTERVAL_DAILY; } } @@ -316,10 +309,10 @@ class UserUsageStatsService { } // STOPSHIP: Temporary logging for b/110930764. - if (intervalType == UsageStatsManager.INTERVAL_DAILY + if (intervalType == INTERVAL_DAILY && mLastEvent != null && mLastEvent.mTimeStamp >= beginTime) { final IntervalStats diskStats = mDatabase.getLatestUsageStats( - UsageStatsManager.INTERVAL_DAILY); + INTERVAL_DAILY); StringBuilder sb = new StringBuilder(256); sb.append("Last 24 hours of UsageStats missing! timeRange : "); sb.append(beginTime); @@ -395,11 +388,11 @@ class UserUsageStatsService { UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps) { final ArraySet<String> names = new ArraySet<>(); - List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, - beginTime, endTime, new StatCombiner<UsageEvents.Event>() { + List<Event> results = queryStats(INTERVAL_DAILY, + beginTime, endTime, new StatCombiner<Event>() { @Override public void combine(IntervalStats stats, boolean mutable, - List<UsageEvents.Event> accumulatedResult) { + List<Event> accumulatedResult) { if (stats.events == null) { return; } @@ -411,11 +404,13 @@ class UserUsageStatsService { return; } - UsageEvents.Event event = stats.events.get(i); + Event event = stats.events.get(i); if (obfuscateInstantApps) { event = event.getObfuscatedIfInstantApp(); } - names.add(event.mPackage); + if (event.mPackage != null) { + names.add(event.mPackage); + } if (event.mClass != null) { names.add(event.mClass); } @@ -437,7 +432,7 @@ class UserUsageStatsService { final String packageName) { final ArraySet<String> names = new ArraySet<>(); names.add(packageName); - final List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, + final List<Event> results = queryStats(INTERVAL_DAILY, beginTime, endTime, (stats, mutable, accumulatedResult) -> { if (stats.events == null) { return; @@ -450,7 +445,7 @@ class UserUsageStatsService { return; } - final UsageEvents.Event event = stats.events.get(i); + final Event event = stats.events.get(i); if (!packageName.equals(event.mPackage)) { continue; } @@ -492,33 +487,33 @@ class UserUsageStatsService { // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or // CONTINUING_FOREGROUND_SERVICE entry. final Configuration previousConfig = - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration; - ArraySet<String> continuePreviousDay = new ArraySet<>(); - ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundActivity = + mCurrentStats[INTERVAL_DAILY].activeConfiguration; + ArraySet<String> continuePkgs = new ArraySet<>(); + ArrayMap<String, SparseIntArray> continueActivity = new ArrayMap<>(); - ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundService = + ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService = new ArrayMap<>(); for (IntervalStats stat : mCurrentStats) { final int pkgCount = stat.packageStats.size(); for (int i = 0; i < pkgCount; i++) { final UsageStats pkgStats = stat.packageStats.valueAt(i); - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty() - || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) { - continuePreviousDayForegroundActivity.put(pkgStats.mPackageName, - pkgStats.mLastForegroundActivityEventMap); + if (pkgStats.mActivities.size() > 0 + || !pkgStats.mForegroundServices.isEmpty()) { + if (pkgStats.mActivities.size() > 0) { + continueActivity.put(pkgStats.mPackageName, + pkgStats.mActivities); stat.update(pkgStats.mPackageName, null, mDailyExpiryDate.getTimeInMillis() - 1, - UsageEvents.Event.END_OF_DAY); + Event.END_OF_DAY, 0); } - if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - continuePreviousDayForegroundService.put(pkgStats.mPackageName, - pkgStats.mLastForegroundServiceEventMap); + if (!pkgStats.mForegroundServices.isEmpty()) { + continueForegroundService.put(pkgStats.mPackageName, + pkgStats.mForegroundServices); stat.update(pkgStats.mPackageName, null, mDailyExpiryDate.getTimeInMillis() - 1, - UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE); + Event.ROLLOVER_FOREGROUND_SERVICE, 0); } - continuePreviousDay.add(pkgStats.mPackageName); + continuePkgs.add(pkgStats.mPackageName); notifyStatsChanged(); } } @@ -532,27 +527,27 @@ class UserUsageStatsService { mDatabase.prune(currentTimeMillis); loadActiveStats(currentTimeMillis); - final int continueCount = continuePreviousDay.size(); + final int continueCount = continuePkgs.size(); for (int i = 0; i < continueCount; i++) { - String pkgName = continuePreviousDay.valueAt(i); - final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime; + String pkgName = continuePkgs.valueAt(i); + final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime; for (IntervalStats stat : mCurrentStats) { - if (continuePreviousDayForegroundActivity.containsKey(pkgName)) { - final ArrayMap<String, Integer> foregroundActivityEventMap = - continuePreviousDayForegroundActivity.get(pkgName); - final int size = foregroundActivityEventMap.size(); + if (continueActivity.containsKey(pkgName)) { + final SparseIntArray eventMap = + continueActivity.get(pkgName); + final int size = eventMap.size(); for (int j = 0; j < size; j++) { - stat.update(pkgName, foregroundActivityEventMap.keyAt(j), beginTime, - UsageEvents.Event.CONTINUE_PREVIOUS_DAY); + stat.update(pkgName, null, beginTime, + eventMap.valueAt(j), eventMap.keyAt(j)); } } - if (continuePreviousDayForegroundService.containsKey(pkgName)) { - final ArrayMap<String, Integer> foregroundServiceEventMap = - continuePreviousDayForegroundService.get(pkgName); - final int size = foregroundServiceEventMap.size(); + if (continueForegroundService.containsKey(pkgName)) { + final ArrayMap<String, Integer> eventMap = + continueForegroundService.get(pkgName); + final int size = eventMap.size(); for (int j = 0; j < size; j++) { - stat.update(pkgName, foregroundServiceEventMap.keyAt(j), beginTime, - UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE); + stat.update(pkgName, eventMap.keyAt(j), beginTime, + eventMap.valueAt(j), 0); } } stat.updateConfigurationStats(previousConfig, beginTime); @@ -611,7 +606,7 @@ class UserUsageStatsService { private void updateRolloverDeadline() { mDailyExpiryDate.setTimeInMillis( - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); + mCurrentStats[INTERVAL_DAILY].beginTime); mDailyExpiryDate.addDays(1); Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + @@ -660,7 +655,7 @@ class UserUsageStatsService { } - void printEvent(IndentingPrintWriter pw, UsageEvents.Event event, boolean prettyDates) { + void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) { pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); pw.printPair("type", eventToString(event.mEventType)); pw.printPair("package", event.mPackage); @@ -673,10 +668,15 @@ class UserUsageStatsService { if (event.mShortcutId != null) { pw.printPair("shortcutId", event.mShortcutId); } - if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) { + if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) { pw.printPair("standbyBucket", event.getStandbyBucket()); pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason())); + } else if (event.mEventType == Event.ACTIVITY_RESUMED + || event.mEventType == Event.ACTIVITY_PAUSED + || event.mEventType == Event.ACTIVITY_STOPPED) { + pw.printPair("instanceId", event.getInstanceId()); } + if (event.mNotificationChannelId != null) { pw.printPair("channelId", event.mNotificationChannelId); } @@ -691,11 +691,11 @@ class UserUsageStatsService { final long beginTime = yesterday.getTimeInMillis(); - List<UsageEvents.Event> events = queryStats(UsageStatsManager.INTERVAL_DAILY, - beginTime, endTime, new StatCombiner<UsageEvents.Event>() { + List<Event> events = queryStats(INTERVAL_DAILY, + beginTime, endTime, new StatCombiner<Event>() { @Override public void combine(IntervalStats stats, boolean mutable, - List<UsageEvents.Event> accumulatedResult) { + List<Event> accumulatedResult) { if (stats.events == null) { return; } @@ -707,7 +707,7 @@ class UserUsageStatsService { return; } - UsageEvents.Event event = stats.events.get(i); + Event event = stats.events.get(i); if (pkg != null && !pkg.equals(event.mPackage)) { continue; } @@ -727,7 +727,7 @@ class UserUsageStatsService { pw.println(")"); if (events != null) { pw.increaseIndent(); - for (UsageEvents.Event event : events) { + for (Event event : events) { printEvent(pw, event, prettyDates); } pw.decreaseIndent(); @@ -772,9 +772,17 @@ class UserUsageStatsService { continue; } pw.printPair("package", usageStats.mPackageName); - pw.printPair("totalTime", + pw.printPair("totalTimeUsed", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); - pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); + pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); + pw.printPair("totalTimeVisible", + formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates)); + pw.printPair("lastTimeVisible", + formatDateTime(usageStats.mLastTimeVisible, prettyDates)); + pw.printPair("totalTimeFS", + formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates)); + pw.printPair("lastTimeFS", + formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates)); pw.printPair("appLaunchCount", usageStats.mAppLaunchCount); pw.println(); } @@ -845,7 +853,7 @@ class UserUsageStatsService { final EventList events = stats.events; final int eventCount = events != null ? events.size() : 0; for (int i = 0; i < eventCount; i++) { - final UsageEvents.Event event = events.get(i); + final Event event = events.get(i); if (pkg != null && !pkg.equals(event.mPackage)) { continue; } @@ -858,13 +866,13 @@ class UserUsageStatsService { private static String intervalToString(int interval) { switch (interval) { - case UsageStatsManager.INTERVAL_DAILY: + case INTERVAL_DAILY: return "daily"; - case UsageStatsManager.INTERVAL_WEEKLY: + case INTERVAL_WEEKLY: return "weekly"; - case UsageStatsManager.INTERVAL_MONTHLY: + case INTERVAL_MONTHLY: return "monthly"; - case UsageStatsManager.INTERVAL_YEARLY: + case INTERVAL_YEARLY: return "yearly"; default: return "?"; @@ -873,47 +881,49 @@ class UserUsageStatsService { private static String eventToString(int eventType) { switch (eventType) { - case UsageEvents.Event.NONE: + case Event.NONE: return "NONE"; - case UsageEvents.Event.MOVE_TO_BACKGROUND: - return "MOVE_TO_BACKGROUND"; - case UsageEvents.Event.MOVE_TO_FOREGROUND: - return "MOVE_TO_FOREGROUND"; - case UsageEvents.Event.FOREGROUND_SERVICE_START: + case Event.ACTIVITY_PAUSED: + return "ACTIVITY_PAUSED"; + case Event.ACTIVITY_RESUMED: + return "ACTIVITY_RESUMED"; + case Event.FOREGROUND_SERVICE_START: return "FOREGROUND_SERVICE_START"; - case UsageEvents.Event.FOREGROUND_SERVICE_STOP: + case Event.FOREGROUND_SERVICE_STOP: return "FOREGROUND_SERVICE_STOP"; - case UsageEvents.Event.END_OF_DAY: + case Event.ACTIVITY_STOPPED: + return "ACTIVITY_STOPPED"; + case Event.END_OF_DAY: return "END_OF_DAY"; - case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE: + case Event.ROLLOVER_FOREGROUND_SERVICE: return "ROLLOVER_FOREGROUND_SERVICE"; - case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: + case Event.CONTINUE_PREVIOUS_DAY: return "CONTINUE_PREVIOUS_DAY"; - case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE: + case Event.CONTINUING_FOREGROUND_SERVICE: return "CONTINUING_FOREGROUND_SERVICE"; - case UsageEvents.Event.CONFIGURATION_CHANGE: + case Event.CONFIGURATION_CHANGE: return "CONFIGURATION_CHANGE"; - case UsageEvents.Event.SYSTEM_INTERACTION: + case Event.SYSTEM_INTERACTION: return "SYSTEM_INTERACTION"; - case UsageEvents.Event.USER_INTERACTION: + case Event.USER_INTERACTION: return "USER_INTERACTION"; - case UsageEvents.Event.SHORTCUT_INVOCATION: + case Event.SHORTCUT_INVOCATION: return "SHORTCUT_INVOCATION"; - case UsageEvents.Event.CHOOSER_ACTION: + case Event.CHOOSER_ACTION: return "CHOOSER_ACTION"; - case UsageEvents.Event.NOTIFICATION_SEEN: + case Event.NOTIFICATION_SEEN: return "NOTIFICATION_SEEN"; - case UsageEvents.Event.STANDBY_BUCKET_CHANGED: + case Event.STANDBY_BUCKET_CHANGED: return "STANDBY_BUCKET_CHANGED"; - case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case Event.NOTIFICATION_INTERRUPTION: return "NOTIFICATION_INTERRUPTION"; - case UsageEvents.Event.SLICE_PINNED: + case Event.SLICE_PINNED: return "SLICE_PINNED"; - case UsageEvents.Event.SLICE_PINNED_PRIV: + case Event.SLICE_PINNED_PRIV: return "SLICE_PINNED_PRIV"; - case UsageEvents.Event.SCREEN_INTERACTIVE: + case Event.SCREEN_INTERACTIVE: return "SCREEN_INTERACTIVE"; - case UsageEvents.Event.SCREEN_NON_INTERACTIVE: + case Event.SCREEN_NON_INTERACTIVE: return "SCREEN_NON_INTERACTIVE"; case UsageEvents.Event.KEYGUARD_SHOWN: return "KEYGUARD_SHOWN"; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index e7ce78a7380c..fb371c176352 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -460,6 +460,12 @@ public class TelecomManager { "android.telecom.extra.START_CALL_WITH_RTT"; /** + * A boolean extra set to indicate whether an app is eligible to be bound to when there are + * ongoing calls on the device. + */ + public static final String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED"; + + /** * A boolean meta-data value indicating whether an {@link InCallService} implements an * in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which * would also like to replace the in-call interface should set this meta-data to {@code true} in @@ -470,9 +476,7 @@ public class TelecomManager { /** * A boolean meta-data value indicating whether an {@link InCallService} implements an * in-call user interface to be used while the device is in car-mode (see - * {@link android.content.res.Configuration.UI_MODE_TYPE_CAR}). - * - * @hide + * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}). */ public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"; @@ -2038,7 +2042,6 @@ public class TelecomManager { } catch (RemoteException e) { Log.e(TAG, "RemoteException handleCallIntent: " + e); } - } private ITelecomService getTelecomService() { diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 6a396ce33093..1cbe5a26caed 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -3640,8 +3640,9 @@ public final class Telephony { /** * Generates a content {@link Uri} used to receive updates on precise carrier identity - * change on the given subscriptionId - * {@link TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED}. + * change on the given subscriptionId returned by + * {@link TelephonyManager#getSimPreciseCarrierId()}. + * @see TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED * <p> * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the * precise carrier identity {@link TelephonyManager#getSimPreciseCarrierId()} @@ -3652,7 +3653,6 @@ public final class Telephony { * * @param subscriptionId the subscriptionId to receive updates on * @return the Uri used to observe precise carrier identity changes - * @hide */ public static Uri getPreciseCarrierIdUriForSubscriptionId(int subscriptionId) { return Uri.withAppendedPath(Uri.withAppendedPath(CONTENT_URI, "precise"), @@ -3674,22 +3674,20 @@ public final class Telephony { public static final String CARRIER_ID = "carrier_id"; /** - * A user facing carrier name for precise carrier id. - * @see TelephonyManager#getSimPreciseCarrierIdName() + * A fine-grained carrier id. + * @see TelephonyManager#getSimPreciseCarrierId() * This is not a database column, only used to notify content observers for * {@link #getPreciseCarrierIdUriForSubscriptionId(int)} - * @hide */ - public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name"; + public static final String PRECISE_CARRIER_ID = "precise_carrier_id"; /** - * A fine-grained carrier id. - * @see TelephonyManager#getSimPreciseCarrierId() + * A user facing carrier name for precise carrier id {@link #PRECISE_CARRIER_ID}. + * @see TelephonyManager#getSimPreciseCarrierIdName() * This is not a database column, only used to notify content observers for * {@link #getPreciseCarrierIdUriForSubscriptionId(int)} - * @hide */ - public static final String PRECISE_CARRIER_ID = "precise_carrier_id"; + public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name"; /** * A unique parent carrier id. The parent-child @@ -3703,18 +3701,6 @@ public final class Telephony { public static final String PARENT_CARRIER_ID = "parent_carrier_id"; /** - * A unique mno carrier id. mno carrier shares the same {@link All#MCCMNC} as carrier id - * and can be solely identified by {@link All#MCCMNC} only. If there is no such mno - * carrier, then mno carrier id equals to {@link #CARRIER_ID carrier id}. - * - * <p>mno carrier id can be used as fallback id. When the exact carrier id configurations - * are not found, usually fall back to its mno carrier id. - * <P>Type: INTEGER </P> - * @hide - */ - public static final String MNO_CARRIER_ID = "mno_carrier_id"; - - /** * Contains mappings between matching rules with carrier id for all carriers. * @hide */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 17f32616027f..388b5fb43096 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -68,7 +68,13 @@ public class CarrierConfigManager { * This intent is broadcast by the system when carrier config changes. An int is specified in * {@link #EXTRA_SLOT_INDEX} to indicate the slot index that this is for. An optional int extra * {@link #EXTRA_SUBSCRIPTION_INDEX} is included to indicate the subscription index if a valid - * one is available for the slot index. + * one is available for the slot index. An optional int extra + * {@link TelephonyManager#EXTRA_CARRIER_ID} is included to indicate the carrier id for the + * changed carrier configuration. An optional int extra + * {@link TelephonyManager#EXTRA_PRECISE_CARRIER_ID} is included to indicate the precise + * carrier id for the changed carrier configuration. + * @see TelephonyManager#getSimCarrierId() + * @see TelephonyManager#getSimPreciseCarrierId() */ public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; @@ -81,7 +87,6 @@ public class CarrierConfigManager { * Specifies a value that identifies the version of the carrier configuration that is * currently in use. This string is displayed on the UI. * The format of the string is not specified. - * @hide */ public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string"; @@ -1393,9 +1398,9 @@ public class CarrierConfigManager { * Example: "default" * * {@code ERROR_CODE_1} is an integer defined in - * {@link com.android.internal.telephony.dataconnection.DcFailCause DcFailure} + * {@link DataFailCause DcFailure} * Example: - * {@link com.android.internal.telephony.dataconnection.DcFailCause#MISSING_UNKNOWN_APN} + * {@link DataFailCause#MISSING_UNKNOWN_APN} * * {@code CARRIER_ACTION_IDX_1} is an integer defined in * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java new file mode 100644 index 000000000000..c6f7d0e458db --- /dev/null +++ b/telephony/java/android/telephony/DataFailCause.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony; + +import android.content.Context; +import android.os.PersistableBundle; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Returned as the reason for a connection failure as defined + * by RIL_DataCallFailCause in ril.h and some local errors. + * @hide + */ +public enum DataFailCause { + NONE(0), + + // This series of errors as specified by the standards + // specified in ril.h + OPERATOR_BARRED(0x08), /* no retry */ + NAS_SIGNALLING(0x0E), + LLC_SNDCP(0x19), + INSUFFICIENT_RESOURCES(0x1A), + MISSING_UNKNOWN_APN(0x1B), /* no retry */ + UNKNOWN_PDP_ADDRESS_TYPE(0x1C), /* no retry */ + USER_AUTHENTICATION(0x1D), /* no retry */ + ACTIVATION_REJECT_GGSN(0x1E), /* no retry */ + ACTIVATION_REJECT_UNSPECIFIED(0x1F), + SERVICE_OPTION_NOT_SUPPORTED(0x20), /* no retry */ + SERVICE_OPTION_NOT_SUBSCRIBED(0x21), /* no retry */ + SERVICE_OPTION_OUT_OF_ORDER(0x22), + NSAPI_IN_USE(0x23), /* no retry */ + REGULAR_DEACTIVATION(0x24), /* possibly restart radio, based on config */ + QOS_NOT_ACCEPTED(0x25), + NETWORK_FAILURE(0x26), + UMTS_REACTIVATION_REQ(0x27), + FEATURE_NOT_SUPP(0x28), + TFT_SEMANTIC_ERROR(0x29), + TFT_SYTAX_ERROR(0x2A), + UNKNOWN_PDP_CONTEXT(0x2B), + FILTER_SEMANTIC_ERROR(0x2C), + FILTER_SYTAX_ERROR(0x2D), + PDP_WITHOUT_ACTIVE_TFT(0x2E), + ONLY_IPV4_ALLOWED(0x32), /* no retry */ + ONLY_IPV6_ALLOWED(0x33), /* no retry */ + ONLY_SINGLE_BEARER_ALLOWED(0x34), + ESM_INFO_NOT_RECEIVED(0x35), + PDN_CONN_DOES_NOT_EXIST(0x36), + MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED(0x37), + MAX_ACTIVE_PDP_CONTEXT_REACHED(0x41), + UNSUPPORTED_APN_IN_CURRENT_PLMN(0x42), + INVALID_TRANSACTION_ID(0x51), + MESSAGE_INCORRECT_SEMANTIC(0x5F), + INVALID_MANDATORY_INFO(0x60), + MESSAGE_TYPE_UNSUPPORTED(0x61), + MSG_TYPE_NONCOMPATIBLE_STATE(0x62), + UNKNOWN_INFO_ELEMENT(0x63), + CONDITIONAL_IE_ERROR(0x64), + MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE(0x65), + PROTOCOL_ERRORS(0x6F), /* no retry */ + APN_TYPE_CONFLICT(0x70), + INVALID_PCSCF_ADDR(0x71), + INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN(0x72), + EMM_ACCESS_BARRED(0x73), + EMERGENCY_IFACE_ONLY(0x74), + IFACE_MISMATCH(0x75), + COMPANION_IFACE_IN_USE(0x76), + IP_ADDRESS_MISMATCH(0x77), + IFACE_AND_POL_FAMILY_MISMATCH(0x78), + EMM_ACCESS_BARRED_INFINITE_RETRY(0x79), + AUTH_FAILURE_ON_EMERGENCY_CALL(0x7A), + + // OEM sepecific error codes. To be used by OEMs when they don't + // want to reveal error code which would be replaced by ERROR_UNSPECIFIED + OEM_DCFAILCAUSE_1(0x1001), + OEM_DCFAILCAUSE_2(0x1002), + OEM_DCFAILCAUSE_3(0x1003), + OEM_DCFAILCAUSE_4(0x1004), + OEM_DCFAILCAUSE_5(0x1005), + OEM_DCFAILCAUSE_6(0x1006), + OEM_DCFAILCAUSE_7(0x1007), + OEM_DCFAILCAUSE_8(0x1008), + OEM_DCFAILCAUSE_9(0x1009), + OEM_DCFAILCAUSE_10(0x100A), + OEM_DCFAILCAUSE_11(0x100B), + OEM_DCFAILCAUSE_12(0x100C), + OEM_DCFAILCAUSE_13(0x100D), + OEM_DCFAILCAUSE_14(0x100E), + OEM_DCFAILCAUSE_15(0x100F), + + // Local errors generated by Vendor RIL + // specified in ril.h + REGISTRATION_FAIL(-1), + GPRS_REGISTRATION_FAIL(-2), + SIGNAL_LOST(-3), /* no retry */ + PREF_RADIO_TECH_CHANGED(-4), + RADIO_POWER_OFF(-5), /* no retry */ + TETHERED_CALL_ACTIVE(-6), /* no retry */ + ERROR_UNSPECIFIED(0xFFFF), + + // Errors generated by the Framework + // specified here + UNKNOWN(0x10000), + RADIO_NOT_AVAILABLE(0x10001), /* no retry */ + UNACCEPTABLE_NETWORK_PARAMETER(0x10002), /* no retry */ + CONNECTION_TO_DATACONNECTIONAC_BROKEN(0x10003), + LOST_CONNECTION(0x10004), + RESET_BY_FRAMEWORK(0x10005); + + private final int mErrorCode; + private static final HashMap<Integer, DataFailCause> sErrorCodeToFailCauseMap; + static { + sErrorCodeToFailCauseMap = new HashMap<Integer, DataFailCause>(); + for (DataFailCause fc : values()) { + sErrorCodeToFailCauseMap.put(fc.getErrorCode(), fc); + } + } + + /** + * Map of subId -> set of data call setup permanent failure for the carrier. + */ + private static final HashMap<Integer, HashSet<DataFailCause>> sPermanentFailureCache = + new HashMap<>(); + + DataFailCause(int errorCode) { + mErrorCode = errorCode; + } + + public int getErrorCode() { + return mErrorCode; + } + + /** + * Returns whether or not the fail cause is a failure that requires a modem restart + * + * @param context device context + * @param subId subscription index + * @return true if the fail cause code needs platform to trigger a modem restart. + */ + public boolean isRadioRestartFailure(Context context, int subId) { + CarrierConfigManager configManager = (CarrierConfigManager) + context.getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager != null) { + PersistableBundle b = configManager.getConfigForSubId(subId); + + if (b != null) { + if (this == REGULAR_DEACTIVATION + && b.getBoolean(CarrierConfigManager + .KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL)) { + // This is for backward compatibility support. We need to continue support this + // old configuration until it gets removed in the future. + return true; + } + // Check the current configurations. + int[] causeCodes = b.getIntArray(CarrierConfigManager + .KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY); + if (causeCodes != null) { + return Arrays.stream(causeCodes).anyMatch(i -> i == getErrorCode()); + } + } + } + + return false; + } + + public boolean isPermanentFailure(Context context, int subId) { + + synchronized (sPermanentFailureCache) { + + HashSet<DataFailCause> permanentFailureSet = sPermanentFailureCache.get(subId); + + // In case of cache miss, we need to look up the settings from carrier config. + if (permanentFailureSet == null) { + // Retrieve the permanent failure from carrier config + CarrierConfigManager configManager = (CarrierConfigManager) + context.getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager != null) { + PersistableBundle b = configManager.getConfigForSubId(subId); + if (b != null) { + String[] permanentFailureStrings = b.getStringArray(CarrierConfigManager. + KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS); + + if (permanentFailureStrings != null) { + permanentFailureSet = new HashSet<>(); + for (String failure : permanentFailureStrings) { + permanentFailureSet.add(DataFailCause.valueOf(failure)); + } + } + } + } + + // If we are not able to find the configuration from carrier config, use the default + // ones. + if (permanentFailureSet == null) { + permanentFailureSet = new HashSet<DataFailCause>() { + { + add(OPERATOR_BARRED); + add(MISSING_UNKNOWN_APN); + add(UNKNOWN_PDP_ADDRESS_TYPE); + add(USER_AUTHENTICATION); + add(ACTIVATION_REJECT_GGSN); + add(SERVICE_OPTION_NOT_SUPPORTED); + add(SERVICE_OPTION_NOT_SUBSCRIBED); + add(NSAPI_IN_USE); + add(ONLY_IPV4_ALLOWED); + add(ONLY_IPV6_ALLOWED); + add(PROTOCOL_ERRORS); + add(RADIO_POWER_OFF); + add(TETHERED_CALL_ACTIVE); + add(RADIO_NOT_AVAILABLE); + add(UNACCEPTABLE_NETWORK_PARAMETER); + add(SIGNAL_LOST); + } + }; + } + + sPermanentFailureCache.put(subId, permanentFailureSet); + } + + return permanentFailureSet.contains(this); + } + } + + public boolean isEventLoggable() { + return (this == OPERATOR_BARRED) || (this == INSUFFICIENT_RESOURCES) || + (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) || + (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) || + (this == SERVICE_OPTION_NOT_SUBSCRIBED) || + (this == SERVICE_OPTION_NOT_SUPPORTED) || + (this == SERVICE_OPTION_OUT_OF_ORDER) || (this == NSAPI_IN_USE) || + (this == ONLY_IPV4_ALLOWED) || (this == ONLY_IPV6_ALLOWED) || + (this == PROTOCOL_ERRORS) || (this == SIGNAL_LOST) || + (this == RADIO_POWER_OFF) || (this == TETHERED_CALL_ACTIVE) || + (this == UNACCEPTABLE_NETWORK_PARAMETER); + } + + public static DataFailCause fromInt(int errorCode) { + DataFailCause fc = sErrorCodeToFailCauseMap.get(errorCode); + if (fc == null) { + fc = UNKNOWN; + } + return fc; + } +} diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index c40eb9ac91e3..387453fa3985 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2403,7 +2403,7 @@ public class SubscriptionManager { * * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} * permission or had carrier privilege permission on the subscriptions: - * {@link TelephonyManager#hasCarrierPrivileges(int)} or + * {@link TelephonyManager#hasCarrierPrivileges()} or * {@link #canManageSubscription(SubscriptionInfo)} * * @throws SecurityException if the caller doesn't meet the requirements @@ -2441,7 +2441,7 @@ public class SubscriptionManager { * * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} * permission or had carrier privilege permission on the subscriptions: - * {@link TelephonyManager#hasCarrierPrivileges(int)} or + * {@link TelephonyManager#hasCarrierPrivileges()} or * {@link #canManageSubscription(SubscriptionInfo)} * * @throws SecurityException if the caller doesn't meet the requirements @@ -2477,7 +2477,7 @@ public class SubscriptionManager { * * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE} * permission or had carrier privilege permission on the subscription. - * {@link TelephonyManager#hasCarrierPrivileges(int)} + * {@link TelephonyManager#hasCarrierPrivileges()} * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 33f856726791..348ab2a38716 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -225,6 +225,13 @@ public class TelephonyManager { @SystemApi public static final int SRVCC_STATE_HANDOVER_CANCELED = 3; + /** + * An invalid card identifier. + * @hide + */ + @SystemApi + public static final int INVALID_CARD_ID = -1; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"SRVCC_STATE_"}, @@ -1217,81 +1224,79 @@ public class TelephonyManager { "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED"; /** + * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates + * the updated carrier id returned by {@link TelephonyManager#getSimCarrierId()}. + * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or + * the carrier cannot be identified. + */ + public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID"; + + /** + * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which + * indicates the updated carrier name of the current subscription. + * @see TelephonyManager#getSimCarrierIdName() + * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID}, + * usually the brand name of the subsidiary (e.g. T-Mobile). + */ + public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME"; + + /** * Broadcast Action: The subscription precise carrier identity has changed. - * Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be sent - * on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}. However, its possible - * that precise carrier identity changes while - * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, the same - * subscription switches to different IMSI could potentially change its precise carrier id. + * The precise carrier id can be used to further differentiate a carrier by different + * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique + * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id. + * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, + * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based + * on the current subscription IMSI. For carriers without any fine-grained ids, precise carrier + * id is same as carrier id. + * + * <p>Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be + * sent on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} while its also + * possible to be sent without {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} when + * precise carrier id changes with the same carrier id. + * e.g, the same subscription switches to different IMSI could potentially change its + * precise carrier id while carrier id remains the same. + * @see #getSimPreciseCarrierId() + * @see #getSimCarrierId() * * The intent will have the following extra values: * <ul> * <li>{@link #EXTRA_PRECISE_CARRIER_ID} The up-to-date precise carrier id of the * current subscription. * </li> - * <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date carrier name of the current - * subscription. + * <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date name of the precise carrier id. * </li> * <li>{@link #EXTRA_SUBSCRIPTION_ID} The subscription id associated with the changed carrier * identity. * </li> * </ul> * <p class="note">This is a protected intent that can only be sent by the system. - * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED"; /** - * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates - * the updated carrier id {@link TelephonyManager#getSimCarrierId()} of - * the current subscription. - * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or - * the carrier cannot be identified. - */ - public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID"; - - /** - * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates - * the updated mno carrier id of the current subscription. - * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or - * the carrier cannot be identified. - * - *@hide - */ - public static final String EXTRA_MNO_CARRIER_ID = "android.telephony.extra.MNO_CARRIER_ID"; - - /** - * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which - * indicates the updated carrier name of the current subscription. - * {@see TelephonyManager#getSimCarrierIdName()} - * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID}, - * usually the brand name of the subsidiary (e.g. T-Mobile). - */ - public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME"; - - /** * An int extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which - * indicates the updated precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()} of - * the current subscription. Note, its possible precise carrier id changes while - * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, when - * subscription switch to different IMSI. + * indicates the updated precise carrier id returned by + * {@link TelephonyManager#getSimPreciseCarrierId()}. Note, its possible precise carrier id + * changes while {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same + * e.g, when subscription switch to different IMSIs. * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or * the carrier cannot be identified. - * @hide */ public static final String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID"; /** * An string extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which - * indicates the updated precise carrier name of the current subscription. - * {@see TelephonyManager#getSimPreciseCarrierIdName()} - * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID}, - * @hide + * indicates the updated precise carrier name returned by + * {@link TelephonyManager#getSimPreciseCarrierIdName()}. + * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID}, e.g, + * Tracfone-AT&T. */ - public static final String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME"; + public static final String EXTRA_PRECISE_CARRIER_NAME = + "android.telephony.extra.PRECISE_CARRIER_NAME"; /** * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} to indicate the @@ -3143,6 +3148,34 @@ public class TelephonyManager { } /** + * Get the card ID of the default eUICC card. If there is no eUICC, returns + * {@link #INVALID_CARD_ID}. + * + * <p>The card ID is a unique identifier associated with a UICC or eUICC card. Card IDs are + * unique to a device, and always refer to the same UICC or eUICC card unless the device goes + * through a factory reset. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * + * @return card ID of the default eUICC card. + * @hide + */ + @SystemApi + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getCardIdForDefaultEuicc() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return INVALID_CARD_ID; + } + return telephony.getCardIdForDefaultEuicc(mSubId, mContext.getOpPackageName()); + } catch (RemoteException e) { + return INVALID_CARD_ID; + } + } + + /** * Gets all the UICC slots. The objects in the array can be null if the slot info is not * available, which is possible between phone process starting and getting slot info from modem. * @@ -8518,7 +8551,7 @@ public class TelephonyManager { /** * Returns carrier id name of the current subscription. - * <p>Carrier id name is a user-facing name of carrier id + * <p>Carrier id name is a user-facing name of carrier id returned by * {@link #getSimCarrierId()}, usually the brand name of the subsidiary * (e.g. T-Mobile). Each carrier could configure multiple {@link #getSimOperatorName() SPN} but * should have a single carrier name. Carrier name is not a canonical identity, @@ -8528,7 +8561,7 @@ public class TelephonyManager { * @return Carrier name of the current subscription. Return {@code null} if the subscription is * unavailable or the carrier cannot be identified. */ - public CharSequence getSimCarrierIdName() { + public @Nullable CharSequence getSimCarrierIdName() { try { ITelephony service = getITelephony(); if (service != null) { @@ -8545,10 +8578,10 @@ public class TelephonyManager { * * <p>The precise carrier id can be used to further differentiate a carrier by different * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique - * carrier id {@link #getSimCarrierId()} but can have multiple precise carrier id. e.g, - * {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, while - * {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based on the - * current subscription IMSI. + * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id. + * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, + * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based + * on the current subscription IMSI. * * <p>For carriers without any fine-grained carrier ids, return {@link #getSimCarrierId()} * <p>Precise carrier ids are defined in the same way as carrier id @@ -8558,8 +8591,6 @@ public class TelephonyManager { * @return Returns fine-grained carrier id of the current subscription. * Return {@link #UNKNOWN_CARRIER_ID} if the subscription is unavailable or the carrier cannot * be identified. - * - * @hide */ public int getSimPreciseCarrierId() { try { @@ -8575,16 +8606,14 @@ public class TelephonyManager { /** * Similar like {@link #getSimCarrierIdName()}, returns user-facing name of the - * precise carrier id {@link #getSimPreciseCarrierId()} + * precise carrier id returned by {@link #getSimPreciseCarrierId()}. * * <p>The returned name is unlocalized. * * @return user-facing name of the subscription precise carrier id. Return {@code null} if the * subscription is unavailable or the carrier cannot be identified. - * - * @hide */ - public CharSequence getSimPreciseCarrierIdName() { + public @Nullable CharSequence getSimPreciseCarrierIdName() { try { ITelephony service = getITelephony(); if (service != null) { @@ -8597,43 +8626,54 @@ public class TelephonyManager { } /** - * Return a list of certs in hex string from loaded carrier privileges access rules. + * Returns carrier id based on sim MCCMNC (returned by {@link #getSimOperator()}) only. + * This is used for fallback when configurations/logic for exact carrier id + * {@link #getSimCarrierId()} are not found. * - * @return a list of certificate in hex string. return {@code null} if there is no certs - * or privilege rules are not loaded yet. + * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a> + * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier + * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id + * by default. After carrier id table update, a new carrier id was assigned. If apps don't + * take the update with the new id, it might be helpful to always fallback by using carrier + * id based on MCCMNC if there is no match. * - * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} - * @hide + * @return matching carrier id from sim MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the + * subscription is unavailable or the carrier cannot be identified. */ - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public List<String> getCertsFromCarrierPrivilegeAccessRules() { + public int getCarrierIdFromSimMccMnc() { try { ITelephony service = getITelephony(); if (service != null) { - return service.getCertsFromCarrierPrivilegeAccessRules(getSubId()); + return service.getCarrierIdFromMccMnc(getSlotIndex(), getSimOperator(), true); } } catch (RemoteException ex) { // This could happen if binder process crashes. } - return null; + return UNKNOWN_CARRIER_ID; } - /** - * Returns MNO carrier id of the current subscription’s MCCMNC. - * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used - * for MNO fallback when exact carrier id {@link #getSimCarrierId()} - * configurations are not found. - * - * @return MNO carrier id of the current subscription. Return the value same as carrier id - * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified. - * @hide - */ - public int getSimMNOCarrierId() { + /** + * Returns carrier id based on MCCMNC (returned by {@link #getSimOperator()}) only. This is + * used for fallback when configurations/logic for exact carrier id {@link #getSimCarrierId()} + * are not found. + * + * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a> + * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier + * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id + * by default. After carrier id table update, a new carrier id was assigned. If apps don't + * take the update with the new id, it might be helpful to always fallback by using carrier + * id based on MCCMNC if there is no match. + * + * @return matching carrier id from passing MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the + * subscription is unavailable or the carrier cannot be identified. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public int getCarrierIdFromMccMnc(String mccmnc) { try { ITelephony service = getITelephony(); if (service != null) { - return service.getSubscriptionMNOCarrierId(getSubId()); + return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc, false); } } catch (RemoteException ex) { // This could happen if binder process crashes. @@ -8641,24 +8681,27 @@ public class TelephonyManager { return UNKNOWN_CARRIER_ID; } - /** - * Returns carrier id based on MCCMNC only. This is for fallback when exact carrier id - * {@link #getSimCarrierId()} configurations are not found - * - * @return matching carrier id from passing mccmnc. - * @hide - */ - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public int getCarrierIdFromMccMnc(String mccmnc) { + /** + * Return a list of certs in hex string from loaded carrier privileges access rules. + * + * @return a list of certificate in hex string. return {@code null} if there is no certs + * or privilege rules are not loaded yet. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public List<String> getCertsFromCarrierPrivilegeAccessRules() { try { ITelephony service = getITelephony(); if (service != null) { - return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc); + return service.getCertsFromCarrierPrivilegeAccessRules(getSubId()); } } catch (RemoteException ex) { // This could happen if binder process crashes. } - return UNKNOWN_CARRIER_ID; + return null; } /** diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java new file mode 100644 index 000000000000..d50b516b8754 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsManager.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.SystemService; +import android.content.Context; + +/** + * The manager class for RCS related utilities. + * @hide + */ +@SystemService(Context.TELEPHONY_RCS_SERVICE) +public class RcsManager { + + private static final RcsMessageStore sRcsMessageStoreInstance = new RcsMessageStore(); + + /** + * Returns an instance of RcsMessageStore. + */ + public RcsMessageStore getRcsMessageStore() { + return sRcsMessageStoreInstance; + } +} diff --git a/telephony/java/android/telephony/rcs/RcsManager.java b/telephony/java/android/telephony/ims/RcsMessageStore.java index 0ef4e1552085..c89c0bebb1a1 100644 --- a/telephony/java/android/telephony/rcs/RcsManager.java +++ b/telephony/java/android/telephony/ims/RcsMessageStore.java @@ -14,24 +14,20 @@ * limitations under the License. */ -package android.telephony.rcs; +package android.telephony.ims; -import android.annotation.SystemService; -import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.Rlog; - -import com.android.internal.telephony.rcs.IRcs; +import android.telephony.ims.aidl.IRcs; /** - * RcsManager is the application interface to RcsProvider and provides access methods to + * RcsMessageStore is the application interface to RcsProvider and provides access methods to * RCS related database tables. * @hide - TODO make this public */ -@SystemService(Context.TELEPHONY_RCS_SERVICE) -public class RcsManager { - private static final String TAG = "RcsManager"; +public class RcsMessageStore { + private static final String TAG = "RcsMessageStore"; private static final boolean VDBG = false; /** diff --git a/telephony/java/android/telephony/ims/RcsThread.aidl b/telephony/java/android/telephony/ims/RcsThread.aidl new file mode 100644 index 000000000000..79d473266272 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsThread.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable RcsThread;
\ No newline at end of file diff --git a/telephony/java/android/telephony/rcs/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java index 83eb973ec12b..b7f440d94583 100644 --- a/telephony/java/android/telephony/rcs/RcsThread.java +++ b/telephony/java/android/telephony/ims/RcsThread.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package android.telephony.rcs; +package android.telephony.ims; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; - -import com.android.internal.telephony.rcs.IRcs; +import android.telephony.ims.aidl.IRcs; /** * RcsThread represents a single RCS conversation thread. It holds messages that were sent and diff --git a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl index 4c289acd15ef..b2e2fadca138 100644 --- a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl +++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl @@ -14,10 +14,14 @@ * limitations under the License. */ -package com.android.internal.telephony.rcs; +package android.telephony.ims.aidl; +/** + * RPC definition between RCS storage APIs and phone process. + * {@hide} + */ interface IRcs { - // RcsManager APIs + // RcsMessageStore APIs void deleteThread(int threadId); // RcsThread APIs diff --git a/telephony/java/android/telephony/rcs/RcsThread.aidl b/telephony/java/android/telephony/rcs/RcsThread.aidl deleted file mode 100644 index e2e0da5da347..000000000000 --- a/telephony/java/android/telephony/rcs/RcsThread.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* -** -** Copyright 2018, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package android.telephony; - -parcelable RcsThread;
\ No newline at end of file diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 88b9302afacb..46366d66a6eb 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1337,18 +1337,6 @@ interface ITelephony { String getSubscriptionCarrierName(int subId); /** - * Returns MNO carrier id of the current subscription’s MCCMNC. - * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used - * for MNO fallback when exact carrier id {@link #getSimCarrierId()} - * configurations are not found. - * - * @return MNO carrier id of the current subscription. Return the value same as carrier id - * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified. - * @hide - */ - int getSubscriptionMNOCarrierId(int subId); - - /** * Returns fine-grained carrier id of the current subscription. * * <p>The precise carrier id can be used to further differentiate a carrier by different @@ -1383,10 +1371,13 @@ interface ITelephony { * Returns carrier id based on MCCMNC only. This will return a MNO carrier id used for fallback * check when exact carrier id {@link #getSimCarrierId()} configurations are not found * + * @param isSubscriptionMccMnc. If {@true} it means this is a query for subscription mccmnc + * {@false} otherwise. + * * @return carrier id from passing mccmnc. * @hide */ - int getCarrierIdFromMccMnc(int slotIndex, String mccmnc); + int getCarrierIdFromMccMnc(int slotIndex, String mccmnc, boolean isSubscriptionMccMnc); /** * Action set from carrier signalling broadcast receivers to enable/disable metered apns @@ -1482,6 +1473,19 @@ interface ITelephony { SignalStrength getSignalStrength(int subId); /** + * Get the card ID of the default eUICC card. If there is no eUICC, returns + * {@link #INVALID_CARD_ID}. + * + * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * + * @param subId subscription ID used for authentication + * @param callingPackage package making the call + * @return card ID of the default eUICC card. + * @hide + */ + int getCardIdForDefaultEuicc(int subId, String callingPackage); + + /** * Get slot info for all the UICC slots. * @return UiccSlotInfo array. * @hide diff --git a/test-base/Android.bp b/test-base/Android.bp index 4d765d3e5f3f..157609cec09c 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -37,8 +37,6 @@ java_sdk_library { "junit.framework", ], - droiddoc_options: ["-stubsourceonly"], - metalava_enabled: false, compile_dex: true, } diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 0a0d50cc330c..db5053eeb903 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -40,8 +40,6 @@ java_sdk_library { "junit.textui", ], - droiddoc_options: ["-stubsourceonly"], - metalava_enabled: false, compile_dex: true } diff --git a/test-runner/api/current.txt b/test-runner/api/current.txt index 1170eb53ab7f..4ba1b8f2fdc1 100644 --- a/test-runner/api/current.txt +++ b/test-runner/api/current.txt @@ -125,8 +125,8 @@ package android.test { method public static void assertEquals(double[], double[]); method public static void assertEquals(java.lang.String, java.lang.Object[], java.lang.Object[]); method public static void assertEquals(java.lang.Object[], java.lang.Object[]); - method public static void assertEquals(java.lang.String, java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>); - method public static void assertEquals(java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>); + method public static void assertEquals(java.lang.String, java.util.Set<?>, java.util.Set<?>); + method public static void assertEquals(java.util.Set<?>, java.util.Set<?>); method public static java.util.regex.MatchResult assertMatchesRegex(java.lang.String, java.lang.String, java.lang.String); method public static java.util.regex.MatchResult assertMatchesRegex(java.lang.String, java.lang.String); method public static void assertNotContainsRegex(java.lang.String, java.lang.String, java.lang.String); diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java index d8b3b2086335..75ee0896c23a 100644 --- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java +++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java @@ -18,20 +18,23 @@ package com.android.server.pm.dex; import static com.google.common.truth.Truth.assertThat; +import android.app.UiAutomation; import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; 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.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; @@ -40,6 +43,7 @@ import java.security.MessageDigest; import java.util.ArrayList; import java.util.Formatter; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Integration tests for {@link com.android.server.pm.dex.DexLogger}. @@ -47,10 +51,10 @@ import java.util.List; * 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. + * We then use shell commands to trigger dynamic code logging (and wait + * for it to complete). This causes DexLogger to log the hash of the + * file's name and content. We verify that this message appears in + * the event log. * * Run with "atest DexLoggerIntegrationTests". */ @@ -58,29 +62,89 @@ import java.util.List; @RunWith(JUnit4.class) public final class DexLoggerIntegrationTests { - private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest"; - // Event log tag used for SNET related events private static final int SNET_TAG = 0x534e4554; + // Subtag used to distinguish dynamic code loading events private static final String DCL_SUBTAG = "dcl"; - // Obtained via "echo -n copied.jar | sha256sum" - private static final String EXPECTED_NAME_HASH = - "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; + // All the tags we care about + private static final int[] TAG_LIST = new int[] { SNET_TAG }; + + // This is {@code DynamicCodeLoggingService#JOB_ID} + private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028; - private static String expectedContentHash; + private static Context sContext; + private static int sMyUid; @BeforeClass - public static void setUpAll() throws Exception { - Context context = InstrumentationRegistry.getTargetContext(); + public static void setUpAll() { + sContext = InstrumentationRegistry.getTargetContext(); + sMyUid = android.os.Process.myUid(); + } + + @Before + public void primeEventLog() { + // Force a round trip to logd to make sure everything is up to date. + // Without this the first test passes and others don't - we don't see new events in the + // log. The exact reason is unclear. + EventLog.writeEvent(SNET_TAG, "Dummy event"); + } + + @Test + public void testDexLoggerGeneratesEvents() throws Exception { + File privateCopyFile = fileForJar("copied.jar"); + // Obtained via "echo -n copied.jar | sha256sum" + String expectedNameHash = + "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; + String expectedContentHash = copyAndHashJar(privateCopyFile); + + // Feed the jar to a class loader and make sure it contains what we expect. + ClassLoader parentClassLoader = sContext.getClass().getClassLoader(); + ClassLoader loader = + new DexClassLoader(privateCopyFile.toString(), null, null, parentClassLoader); + loader.loadClass("com.android.dcl.Simple"); + + // And make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDexLogger(); + + assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + } + + @Test + + public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception { + File privateCopyFile = fileForJar("copied2.jar"); + String expectedNameHash = + "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93"; + String expectedContentHash = copyAndHashJar(privateCopyFile); + + // This time make sure an unknown class loader is an ancestor of the class loader we use. + ClassLoader knownClassLoader = sContext.getClass().getClassLoader(); + ClassLoader unknownClassLoader = new UnknownClassLoader(knownClassLoader); + ClassLoader loader = + new DexClassLoader(privateCopyFile.toString(), null, null, unknownClassLoader); + loader.loadClass("com.android.dcl.Simple"); + + // And make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDexLogger(); + + assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + } + + private static File fileForJar(String name) { + return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name); + } + + private static String copyAndHashJar(File copyTo) throws Exception { 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"); Class<?> thisClass = DexLoggerIntegrationTests.class; try (InputStream input = thisClass.getResourceAsStream("/javalib.jar"); - OutputStream output = new FileOutputStream(privateCopy)) { + OutputStream output = new FileOutputStream(copyTo)) { byte[] buffer = new byte[1024]; while (true) { int numRead = input.read(buffer); @@ -92,42 +156,63 @@ public final class DexLoggerIntegrationTests { } } - // Remember the SHA-256 of the file content to check that it is the same as - // the value we see logged. + // Compute the SHA-256 of the file content so we can 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"); + return formatter.toString(); } - @Test - public void testDexLoggerReconcileGeneratesEvents() throws Exception { - int[] tagList = new int[] { SNET_TAG }; + private static long mostRecentEventTimeNanos() throws Exception { 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(); + EventLog.readEvents(TAG_LIST, events); + return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); + } - Process process = Runtime.getRuntime().exec( - "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME); - int exitCode = process.waitFor(); - assertThat(exitCode).isEqualTo(0); + private static void runDexLogger() throws Exception { + // This forces {@code DynamicCodeLoggingService} to start now. + runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID); + // Wait for the job to have run. + long startTime = SystemClock.elapsedRealtime(); + while (true) { + String response = runCommand( + "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID); + if (!response.contains("pending") && !response.contains("active")) { + break; + } + if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) { + throw new AssertionError("Job has not completed: " + response); + } + SystemClock.sleep(100); + } + } - int myUid = android.os.Process.myUid(); - String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash; + private static String runCommand(String command) throws Exception { + ByteArrayOutputStream response = new ByteArrayOutputStream(); + byte[] buffer = new byte[1000]; + UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + ParcelFileDescriptor fd = ui.executeShellCommand(command); + try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd)) { + while (true) { + int count = input.read(buffer); + if (count == -1) { + break; + } + response.write(buffer, 0, count); + } + } + return response.toString("UTF-8"); + } - EventLog.readEvents(tagList, events); - boolean found = false; + private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash, + String expectedContentHash) throws Exception { + List<EventLog.Event> events = new ArrayList<>(); + EventLog.readEvents(TAG_LIST, events); + int found = 0; for (EventLog.Event event : events) { if (event.getTimeNanos() <= previousEventNanos) { continue; @@ -140,15 +225,28 @@ public final class DexLoggerIntegrationTests { continue; } int uid = (int) data[1]; - if (uid != myUid) { + if (uid != sMyUid) { continue; } String message = (String) data[2]; - assertThat(message).isEqualTo(expectedMessage); - found = true; + if (!message.startsWith(expectedNameHash)) { + continue; + } + + assertThat(message).endsWith(expectedContentHash); + ++found; } - assertThat(found).isTrue(); + assertThat(found).isEqualTo(1); + } + + /** + * A class loader that does nothing useful, but importantly doesn't extend BaseDexClassLoader. + */ + private static class UnknownClassLoader extends ClassLoader { + UnknownClassLoader(ClassLoader parent) { + super(parent); + } } } diff --git a/tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java b/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java index 7f5f03e0d5a4..290e04ce8abb 100644 --- a/tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java +++ b/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java @@ -16,17 +16,17 @@ package com.android.tests.rcs; import android.support.test.runner.AndroidJUnit4; -import android.telephony.rcs.RcsManager; +import android.telephony.ims.RcsMessageStore; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) -public class RcsManagerTest { +public class RcsMessageStoreTest { //TODO(sahinc): Add meaningful tests once we have more of the implementation in place @Test public void testDeleteThreadDoesntCrash() { - RcsManager mRcsManager = new RcsManager(); - mRcsManager.deleteThread(0); + RcsMessageStore mRcsMessageStore = new RcsMessageStore(); + mRcsMessageStore.deleteThread(0); } } diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java index be74a6d162ae..7a5e7325ad22 100644 --- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java +++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java @@ -92,11 +92,11 @@ public class UsageStatsDatabasePerfTest { event.mPackage = "fake.package.name" + pkg; event.mClass = event.mPackage + ".class1"; event.mTimeStamp = 1; - event.mEventType = UsageEvents.Event.MOVE_TO_FOREGROUND; + event.mEventType = UsageEvents.Event.ACTIVITY_RESUMED; for (int evt = 0; evt < eventsPerPackage; evt++) { intervalStats.events.insert(event); intervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, - event.mEventType); + event.mEventType, 1); } } } diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java index 3480e96b3547..53afa26796ea 100644 --- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java +++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java @@ -21,13 +21,14 @@ import android.app.usage.UsageStatsManager; import android.content.Context; import android.os.Bundle; import android.os.Handler; -import androidx.collection.CircularArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; +import androidx.collection.CircularArray; + public class UsageLogActivity extends ListActivity implements Runnable { private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; @@ -155,10 +156,10 @@ public class UsageLogActivity extends ListActivity implements Runnable { private String eventToString(int eventType) { switch (eventType) { - case UsageEvents.Event.MOVE_TO_FOREGROUND: + case UsageEvents.Event.ACTIVITY_RESUMED: return "Foreground"; - case UsageEvents.Event.MOVE_TO_BACKGROUND: + case UsageEvents.Event.ACTIVITY_PAUSED: return "Background"; case UsageEvents.Event.CONFIGURATION_CHANGE: diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java index 983802035bfb..436dd859beca 100644 --- a/tests/net/java/android/net/apf/ApfTest.java +++ b/tests/net/java/android/net/apf/ApfTest.java @@ -46,6 +46,7 @@ import android.support.test.runner.AndroidJUnit4; import android.system.ErrnoException; import android.system.Os; import android.text.format.DateUtils; +import android.util.Log; import com.android.frameworks.tests.net.R; import com.android.internal.util.HexDump; import java.io.File; @@ -89,6 +90,7 @@ public class ApfTest { System.loadLibrary("frameworksnettestsjni"); } + private static final String TAG = "ApfTest"; // Expected return codes from APF interpreter. private static final int PASS = 1; private static final int DROP = 0; @@ -869,6 +871,37 @@ public class ApfTest { } } + /** + * Generate APF program, run pcap file though APF filter, then check all the packets in the file + * should be dropped. + */ + @Test + public void testApfFilterPcapFile() throws Exception { + final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151}; + String pcapFilename = stageFile(R.raw.apfPcap); + MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16); + LinkProperties lp = new LinkProperties(); + lp.addLinkAddress(link); + + ApfConfiguration config = getDefaultConfig(); + ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER); + config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES; + config.multicastFilter = DROP_MULTICAST; + config.ieee802_3Filter = DROP_802_3_FRAMES; + TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog); + apfFilter.setLinkProperties(lp); + byte[] program = ipClientCallback.getApfProgram(); + byte[] data = new byte[ApfFilter.Counter.totalSize()]; + final boolean result; + + result = dropsAllPackets(program, data, pcapFilename); + Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false)); + + assertTrue("Failed to drop all packets by filter. \nAPF counters:" + + HexDump.toHexString(data, false), result); + } + private class MockIpClientCallback extends IpClient.Callback { private final ConditionVariable mGotApfProgram = new ConditionVariable(); private byte[] mLastApfProgram; @@ -1706,6 +1739,14 @@ public class ApfTest { private native static boolean compareBpfApf(String filter, String pcap_filename, byte[] apf_program); + + /** + * Open packet capture file {@code pcapFilename} and run it through APF filter. Then + * checks whether all the packets are dropped and populates data[] {@code data} with + * the APF counters. + */ + private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename); + @Test public void testBroadcastAddress() throws Exception { assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0)); diff --git a/tests/net/jni/apf_jni.cpp b/tests/net/jni/apf_jni.cpp index 1ea9e274ab9e..4222adf9e06b 100644 --- a/tests/net/jni/apf_jni.cpp +++ b/tests/net/jni/apf_jni.cpp @@ -21,37 +21,40 @@ #include <stdlib.h> #include <string> #include <utils/Log.h> +#include <vector> #include "apf_interpreter.h" +#include "nativehelper/scoped_primitive_array.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) // JNI function acting as simply call-through to native APF interpreter. static jint com_android_server_ApfTest_apfSimulate( - JNIEnv* env, jclass, jbyteArray program, jbyteArray packet, - jbyteArray data, jint filter_age) { - uint8_t* program_raw = (uint8_t*)env->GetByteArrayElements(program, nullptr); - uint8_t* packet_raw = (uint8_t*)env->GetByteArrayElements(packet, nullptr); - uint8_t* data_raw = (uint8_t*)(data ? env->GetByteArrayElements(data, nullptr) : nullptr); - uint32_t program_len = env->GetArrayLength(program); - uint32_t packet_len = env->GetArrayLength(packet); - uint32_t data_len = data ? env->GetArrayLength(data) : 0; - - // Merge program and data into a single buffer. - uint8_t* program_and_data = (uint8_t*)malloc(program_len + data_len); - memcpy(program_and_data, program_raw, program_len); - memcpy(program_and_data + program_len, data_raw, data_len); + JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jpacket, + jbyteArray jdata, jint filter_age) { + + ScopedByteArrayRO packet(env, jpacket); + uint32_t packet_len = (uint32_t)packet.size(); + uint32_t program_len = env->GetArrayLength(jprogram); + uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0; + std::vector<uint8_t> buf(program_len + data_len, 0); + + env->GetByteArrayRegion(jprogram, 0, program_len, reinterpret_cast<jbyte*>(buf.data())); + if (jdata) { + // Merge program and data into a single buffer. + env->GetByteArrayRegion(jdata, 0, data_len, + reinterpret_cast<jbyte*>(buf.data() + program_len)); + } jint result = - accept_packet(program_and_data, program_len, program_len + data_len, - packet_raw, packet_len, filter_age); - if (data) { - memcpy(data_raw, program_and_data + program_len, data_len); - env->ReleaseByteArrayElements(data, (jbyte*)data_raw, 0 /* copy back */); - } - free(program_and_data); - env->ReleaseByteArrayElements(packet, (jbyte*)packet_raw, JNI_ABORT); - env->ReleaseByteArrayElements(program, (jbyte*)program_raw, JNI_ABORT); + accept_packet(buf.data(), program_len, program_len + data_len, + reinterpret_cast<const uint8_t*>(packet.get()), packet_len, filter_age); + + if (jdata) { + env->SetByteArrayRegion(jdata, 0, data_len, + reinterpret_cast<jbyte*>(buf.data() + program_len)); + } + return result; } @@ -118,8 +121,7 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js jstring jpcap_filename, jbyteArray japf_program) { ScopedUtfChars filter(env, jfilter); ScopedUtfChars pcap_filename(env, jpcap_filename); - uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL); - uint32_t apf_program_len = env->GetArrayLength(japf_program); + ScopedByteArrayRO apf_program(env, japf_program); // Open pcap file for BPF filtering ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb")); @@ -161,14 +163,15 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js do { apf_packet = pcap_next(apf_pcap.get(), &apf_header); } while (apf_packet != NULL && !accept_packet( - apf_program, apf_program_len, 0 /* data_len */, + reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())), + apf_program.size(), 0 /* data_len */, apf_packet, apf_header.len, 0 /* filter_age */)); // Make sure both filters matched the same packet. if (apf_packet == NULL && bpf_packet == NULL) - break; + break; if (apf_packet == NULL || bpf_packet == NULL) - return false; + return false; if (apf_header.len != bpf_header.len || apf_header.ts.tv_sec != bpf_header.ts.tv_sec || apf_header.ts.tv_usec != bpf_header.ts.tv_usec || @@ -178,6 +181,48 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js return true; } +static jboolean com_android_server_ApfTest_dropsAllPackets(JNIEnv* env, jclass, jbyteArray jprogram, + jbyteArray jdata, jstring jpcap_filename) { + ScopedUtfChars pcap_filename(env, jpcap_filename); + ScopedByteArrayRO apf_program(env, jprogram); + uint32_t apf_program_len = (uint32_t)apf_program.size(); + uint32_t data_len = env->GetArrayLength(jdata); + pcap_pkthdr apf_header; + const uint8_t* apf_packet; + char pcap_error[PCAP_ERRBUF_SIZE]; + std::vector<uint8_t> buf(apf_program_len + data_len, 0); + + // Merge program and data into a single buffer. + env->GetByteArrayRegion(jprogram, 0, apf_program_len, reinterpret_cast<jbyte*>(buf.data())); + env->GetByteArrayRegion(jdata, 0, data_len, + reinterpret_cast<jbyte*>(buf.data() + apf_program_len)); + + // Open pcap file + ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb")); + ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error)); + + if (apf_pcap.get() == NULL) { + throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error)); + return false; + } + + while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) { + int result = accept_packet(buf.data(), apf_program_len, + apf_program_len + data_len, apf_packet, apf_header.len, 0); + + // Return false once packet passes the filter + if (result) { + env->SetByteArrayRegion(jdata, 0, data_len, + reinterpret_cast<jbyte*>(buf.data() + apf_program_len)); + return false; + } + } + + env->SetByteArrayRegion(jdata, 0, data_len, + reinterpret_cast<jbyte*>(buf.data() + apf_program_len)); + return true; +} + extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { @@ -192,6 +237,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { (void*)com_android_server_ApfTest_compileToBpf }, { "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z", (void*)com_android_server_ApfTest_compareBpfApf }, + { "dropsAllPackets", "([B[BLjava/lang/String;)Z", + (void*)com_android_server_ApfTest_dropsAllPackets }, }; jniRegisterNativeMethods(env, "android/net/apf/ApfTest", diff --git a/tests/net/res/raw/apfPcap.pcap b/tests/net/res/raw/apfPcap.pcap Binary files differnew file mode 100644 index 000000000000..6f69c4add0f8 --- /dev/null +++ b/tests/net/res/raw/apfPcap.pcap diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 583f14ac0cbd..9460c9e596e9 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -306,31 +306,6 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& break; } - for (size_t i = 0; i < entry->overlayable_declarations.size(); i++) { - printer->Print((i == 0) ? " " : "|"); - printer->Print("OVERLAYABLE"); - - if (entry->overlayable_declarations[i].policy) { - switch (entry->overlayable_declarations[i].policy.value()) { - case Overlayable::Policy::kProduct: - printer->Print("_PRODUCT"); - break; - case Overlayable::Policy::kProductServices: - printer->Print("_PRODUCT_SERVICES"); - break; - case Overlayable::Policy::kSystem: - printer->Print("_SYSTEM"); - break; - case Overlayable::Policy::kVendor: - printer->Print("_VENDOR"); - break; - case Overlayable::Policy::kPublic: - printer->Print("_PUBLIC"); - break; - } - } - } - printer->Println(); if (options.show_values) { diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 4f25e0968c0e..95877045072b 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -99,7 +99,7 @@ struct ParsedResource { ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; bool allow_new = false; - std::vector<Overlayable> overlayable_declarations; + Maybe<Overlayable> overlayable; std::string comment; std::unique_ptr<Value> value; @@ -133,8 +133,8 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed } } - for (auto& overlayable : res->overlayable_declarations) { - if (!table->AddOverlayable(res->name, overlayable, diag)) { + if (res->overlayable) { + if (!table->SetOverlayable(res->name, res->overlayable.value(), diag)) { return false; } } @@ -1063,20 +1063,19 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource << "' for <overlayable> tag"); } - std::string comment; - std::vector<Overlayable::Policy> policies; - bool error = false; + std::string comment; + Overlayable::PolicyFlags current_policies = Overlayable::Policy::kNone; const size_t start_depth = parser->depth(); while (xml::XmlPullParser::IsGoodEvent(parser->Next())) { xml::XmlPullParser::Event event = parser->event(); if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) { - // Break the loop when exiting the overyabale element + // Break the loop when exiting the overlayable element break; } else if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth + 1) { // Clear the current policies when exiting the policy element - policies.clear(); + current_policies = Overlayable::Policy::kNone; continue; } else if (event == xml::XmlPullParser::Event::kComment) { // Get the comment of individual item elements @@ -1090,43 +1089,71 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource const Source item_source = source_.WithLine(parser->line_number()); const std::string& element_name = parser->element_name(); const std::string& element_namespace = parser->element_namespace(); - if (element_namespace.empty() && element_name == "item") { - if (!ParseOverlayableItem(parser, policies, comment, out_resource)) { + // Items specify the name and type of resource that should be overlayable + Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!maybe_name) { + diag_->Error(DiagMessage(item_source) + << "<item> within an <overlayable> tag must have a 'name' attribute"); + error = true; + continue; + } + + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!maybe_type) { + diag_->Error(DiagMessage(item_source) + << "<item> within an <overlayable> tag must have a 'type' attribute"); error = true; + continue; + } + + const ResourceType* type = ParseResourceType(maybe_type.value()); + if (type == nullptr) { + diag_->Error(DiagMessage(item_source) + << "invalid resource type '" << maybe_type.value() + << "' in <item> within an <overlayable>"); + error = true; + continue; } + + ParsedResource child_resource; + child_resource.name.type = *type; + child_resource.name.entry = maybe_name.value().to_string(); + child_resource.overlayable = Overlayable{current_policies, item_source, comment}; + out_resource->child_resources.push_back(std::move(child_resource)); + } else if (element_namespace.empty() && element_name == "policy") { - if (!policies.empty()) { + if (current_policies != Overlayable::Policy::kNone) { // If the policy list is not empty, then we are currently inside a policy element diag_->Error(DiagMessage(item_source) << "<policy> blocks cannot be recursively nested"); error = true; break; } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { // Parse the polices separated by vertical bar characters to allow for specifying multiple - // policies at once + // policies for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); if (trimmed_part == "public") { - policies.push_back(Overlayable::Policy::kPublic); + current_policies |= Overlayable::Policy::kPublic; } else if (trimmed_part == "product") { - policies.push_back(Overlayable::Policy::kProduct); + current_policies |= Overlayable::Policy::kProduct; } else if (trimmed_part == "product_services") { - policies.push_back(Overlayable::Policy::kProductServices); + current_policies |= Overlayable::Policy::kProductServices; } else if (trimmed_part == "system") { - policies.push_back(Overlayable::Policy::kSystem); + current_policies |= Overlayable::Policy::kSystem; } else if (trimmed_part == "vendor") { - policies.push_back(Overlayable::Policy::kVendor); + current_policies |= Overlayable::Policy::kVendor; } else { - diag_->Error(DiagMessage(out_resource->source) - << "<policy> has unsupported type '" << trimmed_part << "'"); + diag_->Error(DiagMessage(item_source) + << "<policy> has unsupported type '" << trimmed_part << "'"); error = true; continue; } } } } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> in " - << " <overlayable>"); + diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> " + << " in <overlayable>"); error = true; break; } @@ -1135,61 +1162,6 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource return !error; } -bool ResourceParser::ParseOverlayableItem(xml::XmlPullParser* parser, - const std::vector<Overlayable::Policy>& policies, - const std::string& comment, - ParsedResource* out_resource) { - const Source item_source = source_.WithLine(parser->line_number()); - - Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); - if (!maybe_name) { - diag_->Error(DiagMessage(item_source) - << "<item> within an <overlayable> tag must have a 'name' attribute"); - return false; - } - - Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); - if (!maybe_type) { - diag_->Error(DiagMessage(item_source) - << "<item> within an <overlayable> tag must have a 'type' attribute"); - return false; - } - - const ResourceType* type = ParseResourceType(maybe_type.value()); - if (type == nullptr) { - diag_->Error(DiagMessage(out_resource->source) - << "invalid resource type '" << maybe_type.value() - << "' in <item> within an <overlayable>"); - return false; - } - - ParsedResource child_resource; - child_resource.name.type = *type; - child_resource.name.entry = maybe_name.value().to_string(); - child_resource.source = item_source; - - if (policies.empty()) { - Overlayable overlayable; - overlayable.source = item_source; - overlayable.comment = comment; - child_resource.overlayable_declarations.push_back(overlayable); - } else { - for (Overlayable::Policy policy : policies) { - Overlayable overlayable; - overlayable.policy = policy; - overlayable.source = item_source; - overlayable.comment = comment; - child_resource.overlayable_declarations.push_back(overlayable); - } - } - - if (options_.visibility) { - child_resource.visibility_level = options_.visibility.value(); - } - out_resource->child_resources.push_back(std::move(child_resource)); - return true; -} - bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (ParseSymbolImpl(parser, out_resource)) { out_resource->visibility_level = Visibility::Level::kUndefined; diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index ebacd6f1280e..06bb0c9cf264 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -96,10 +96,6 @@ class ResourceParser { bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource); - bool ParseOverlayableItem(xml::XmlPullParser* parser, - const std::vector<Overlayable::Policy>& policies, - const std::string& comment, - ParsedResource* out_resource); bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index c6f29ac53ca6..03e6197027cb 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -905,16 +905,16 @@ TEST_F(ResourceParserTest, ParseOverlayable) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy); + ASSERT_TRUE(search_result.value().entry->overlayable); + EXPECT_THAT(search_result.value().entry->overlayable.value().policies, + Eq(Overlayable::Policy::kNone)); search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy); + ASSERT_TRUE(search_result.value().entry->overlayable); + EXPECT_THAT(search_result.value().entry->overlayable.value().policies, + Eq(Overlayable::Policy::kNone)); } TEST_F(ResourceParserTest, ParseOverlayablePolicy) { @@ -945,49 +945,44 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy); + ASSERT_TRUE(search_result.value().entry->overlayable); + Overlayable& overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kNone)); search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kProduct)); + ASSERT_TRUE(search_result.value().entry->overlayable); + overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct)); search_result = table_.FindResource(test::ParseNameOrDie("string/baz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable); + overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProductServices)); search_result = table_.FindResource(test::ParseNameOrDie("string/fiz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kSystem)); + ASSERT_TRUE(search_result.value().entry->overlayable); + overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kSystem)); search_result = table_.FindResource(test::ParseNameOrDie("string/fuz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kVendor)); + ASSERT_TRUE(search_result.value().entry->overlayable); + overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor)); search_result = table_.FindResource(test::ParseNameOrDie("string/faz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kPublic)); + ASSERT_TRUE(search_result.value().entry->overlayable); + overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kPublic)); } TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { @@ -1031,22 +1026,18 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kVendor)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy, - Eq(Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable); + Overlayable& overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor + | Overlayable::Policy::kProductServices)); search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kProduct)); - EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy, - Eq(Overlayable::Policy::kSystem)); + ASSERT_TRUE(search_result.value().entry->overlayable); + overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct + | Overlayable::Policy::kSystem)); } TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { @@ -1067,7 +1058,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable"> + <overlayable> <policy type="product"> <item type="string" name="foo" /> <item type="string" name="foo" /> @@ -1080,45 +1071,30 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { <policy type="product"> <item type="string" name="foo" /> </policy> - </overlayable> + <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + input = R"( <overlayable> <policy type="product"> <item type="string" name="foo" /> </policy> - </overlayable>)"; - EXPECT_FALSE(TestParse(input)); -} - -TEST_F(ResourceParserTest, PolicyAndNonPolicyOverlayableError) { - std::string input = R"( - <overlayable policy="product"> - <item type="string" name="foo" /> - </overlayable> - <overlayable policy=""> + <policy type="vendor"> <item type="string" name="foo" /> - </overlayable>)"; + </policy> + </overlayable>)"; EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable policy=""> - <item type="string" name="foo" /> - </overlayable> - <overlayable policy="product"> - <item type="string" name="foo" /> - </overlayable>)"; - EXPECT_FALSE(TestParse(input)); -} - -TEST_F(ResourceParserTest, DuplicateOverlayableMultiplePolicyError) { - std::string input = R"( <overlayable> - <policy type="vendor|product"> + <policy type="product"> <item type="string" name="foo" /> </policy> </overlayable> + <overlayable> - <policy type="product_services|vendor"> + <policy type="product"> <item type="string" name="foo" /> </policy> </overlayable>)"; diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index bc8a4d1f85b8..54633ad5c5e3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -625,18 +625,18 @@ bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& return true; } -bool ResourceTable::AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, +bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, IDiagnostics* diag) { - return AddOverlayableImpl(name, overlayable, ResourceNameValidator, diag); + return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag); } -bool ResourceTable::AddOverlayableMangled(const ResourceNameRef& name, +bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable, IDiagnostics* diag) { - return AddOverlayableImpl(name, overlayable, SkipNameValidator, diag); + return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag); } -bool ResourceTable::AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable, - NameValidator name_validator, IDiagnostics* diag) { +bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable, + NameValidator name_validator, IDiagnostics *diag) { CHECK(diag != nullptr); if (!ValidateName(name_validator, name, overlayable.source, diag)) { @@ -647,27 +647,14 @@ bool ResourceTable::AddOverlayableImpl(const ResourceNameRef& name, const Overla ResourceTableType* type = package->FindOrCreateType(name.type); ResourceEntry* entry = type->FindOrCreateEntry(name.entry); - for (auto& overlayable_declaration : entry->overlayable_declarations) { - // An overlayable resource cannot be declared twice with the same policy - if (overlayable.policy == overlayable_declaration.policy) { - diag->Error(DiagMessage(overlayable.source) + if (entry->overlayable) { + diag->Error(DiagMessage(overlayable.source) << "duplicate overlayable declaration for resource '" << name << "'"); - diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here"); - return false; - } - - // An overlayable resource cannot be declared once with a policy and without a policy because - // the policy becomes unused - if (!overlayable.policy || !overlayable_declaration.policy) { - diag->Error(DiagMessage(overlayable.source) - << "overlayable resource '" << name << "'" - << " declared once with a policy and once with no policy"); - diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here"); - return false; - } + diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here"); + return false; } - entry->overlayable_declarations.push_back(overlayable); + entry->overlayable = overlayable; return true; } @@ -703,7 +690,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const { new_entry->id = entry->id; new_entry->visibility = entry->visibility; new_entry->allow_new = entry->allow_new; - new_entry->overlayable_declarations = entry->overlayable_declarations; + new_entry->overlayable = entry->overlayable; for (const auto& config_value : entry->values) { ResourceConfigValue* new_value = diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 3dd0a769d944..e646f5be43c7 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -57,27 +57,32 @@ struct AllowNew { std::string comment; }; -// Represents a declaration that a resource is overayable at runtime. +// Represents a declaration that a resource is overlayable at runtime. struct Overlayable { + // Represents the types overlays that are allowed to overlay the resource. - enum class Policy { + enum Policy : uint32_t { + kNone = 0x00, + // The resource can be overlaid by any overlay. - kPublic, + kPublic = 0x01, // The resource can be overlaid by any overlay on the system partition. - kSystem, + kSystem = 0x02, // The resource can be overlaid by any overlay on the vendor partition. - kVendor, + kVendor = 0x04, // The resource can be overlaid by any overlay on the product partition. - kProduct, + kProduct = 0x08, // The resource can be overlaid by any overlay on the product services partition. - kProductServices, + kProductServices = 0x10 }; - Maybe<Policy> policy; + typedef uint32_t PolicyFlags; + PolicyFlags policies = Policy::kNone; + Source source; std::string comment; }; @@ -116,7 +121,7 @@ class ResourceEntry { Maybe<AllowNew> allow_new; // The declarations of this resource as overlayable for RROs - std::vector<Overlayable> overlayable_declarations; + Maybe<Overlayable> overlayable; // The resource's values for each configuration. std::vector<std::unique_ptr<ResourceConfigValue>> values; @@ -246,9 +251,9 @@ class ResourceTable { bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility, const ResourceId& res_id, IDiagnostics* diag); - bool AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, - IDiagnostics* diag); - bool AddOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable, + bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, + IDiagnostics *diag); + bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable, IDiagnostics* diag); bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag); @@ -323,8 +328,8 @@ class ResourceTable { bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new, NameValidator name_validator, IDiagnostics* diag); - bool AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable, - NameValidator name_validator, IDiagnostics* diag); + bool SetOverlayableImpl(const ResourceNameRef &name, const Overlayable &overlayable, + NameValidator name_validator, IDiagnostics *diag); bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id, const Visibility& symbol, NameValidator name_validator, diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 7c28f07d0f66..31095c4d88c8 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -242,69 +242,50 @@ TEST(ResourceTableTest, SetAllowNew) { ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second")); } -TEST(ResourceTableTest, AddOverlayable) { +TEST(ResourceTableTest, SetOverlayable) { ResourceTable table; - const ResourceName name = test::ParseNameOrDie("android:string/foo"); - - Overlayable overlayable; - overlayable.policy = Overlayable::Policy::kProduct; - overlayable.comment = "first"; - ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics())); - Maybe<ResourceTable::SearchResult> result = table.FindResource(name); - ASSERT_TRUE(result); - ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1)); - ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first")); - ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kProduct)); - - Overlayable overlayable2; - overlayable2.comment = "second"; - overlayable2.policy = Overlayable::Policy::kProductServices; - ASSERT_TRUE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics())); - result = table.FindResource(name); - ASSERT_TRUE(result); - ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2)); - ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first")); - ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kProduct)); - ASSERT_THAT(result.value().entry->overlayable_declarations[1].comment, StrEq("second")); - ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy, - Eq(Overlayable::Policy::kProductServices)); -} + Overlayable overlayable{}; + overlayable.policies |= Overlayable::Policy::kProduct; + overlayable.policies |= Overlayable::Policy::kProductServices; + overlayable.comment = "comment"; -TEST(ResourceTableTest, AddDuplicateOverlayableFail) { - ResourceTable table; const ResourceName name = test::ParseNameOrDie("android:string/foo"); + ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); + Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name); - Overlayable overlayable; - overlayable.policy = Overlayable::Policy::kProduct; - ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics())); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable overlayable2; - overlayable2.policy = Overlayable::Policy::kProduct; - ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics())); + Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); + ASSERT_THAT(result_overlayable.comment, StrEq("comment")); + EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct + | Overlayable::Policy::kProductServices)); } -TEST(ResourceTableTest, AddOverlayablePolicyAndNoneFirstFail) { +TEST(ResourceTableTest, AddDuplicateOverlayableSamePolicyFail) { ResourceTable table; const ResourceName name = test::ParseNameOrDie("android:string/foo"); - ASSERT_TRUE(table.AddOverlayable(name, {}, test::GetDiagnostics())); + Overlayable overlayable{}; + overlayable.policies = Overlayable::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); - Overlayable overlayable2; - overlayable2.policy = Overlayable::Policy::kProduct; - ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics())); + Overlayable overlayable2{}; + overlayable2.policies = Overlayable::Policy::kProduct; + ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics())); } -TEST(ResourceTableTest, AddOverlayablePolicyAndNoneLastFail) { +TEST(ResourceTableTest, AddDuplicateOverlayableDifferentPolicyFail) { ResourceTable table; const ResourceName name = test::ParseNameOrDie("android:string/foo"); - Overlayable overlayable; - overlayable.policy = Overlayable::Policy::kProduct; - ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics())); + Overlayable overlayable{}; + overlayable.policies = Overlayable::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); - ASSERT_FALSE(table.AddOverlayable(name, {}, test::GetDiagnostics())); + Overlayable overlayable2{}; + overlayable2.policies = Overlayable::Policy::kVendor; + ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics())); } TEST(ResourceTableTest, AllowDuplictaeResourcesNames) { diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index bf9fe49da2d6..81a2c2e5cc02 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -136,12 +136,11 @@ message AllowNew { // Represents a declaration that a resource is overayable at runtime. message Overlayable { enum Policy { - NONE = 0; - PUBLIC = 1; - SYSTEM = 2; - VENDOR = 3; - PRODUCT = 4; - PRODUCT_SERVICES = 5; + PUBLIC = 0; + SYSTEM = 1; + VENDOR = 2; + PRODUCT = 3; + PRODUCT_SERVICES = 4; } // Where this declaration was defined in source. @@ -150,8 +149,8 @@ message Overlayable { // Any comment associated with the declaration. string comment = 2; - // The policy of the overlayable declaration - Policy policy = 3; + // The policy defined in the overlayable declaration. + repeated Policy policy = 3; } // An entry ID in the range [0x0000, 0xffff]. @@ -181,7 +180,7 @@ message Entry { AllowNew allow_new = 4; // Whether this resource can be overlaid by a runtime resource overlay (RRO). - repeated Overlayable overlayable = 5; + Overlayable overlayable = 5; // The set of values defined for this entry, each corresponding to a different // configuration/variant. diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h index 89d19cf4ba08..5cf056e60640 100644 --- a/tools/aapt2/cmd/Dump.h +++ b/tools/aapt2/cmd/Dump.h @@ -255,6 +255,7 @@ class DumpCommand : public Command { AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true); + // TODO(b/120609160): Add aapt2 overlayable dump command } int Action(const std::vector<std::string>& args) override { diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index df0daebe8453..61ebd4ee26ca 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -441,25 +441,25 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { const ResTable_overlayable_policy_header* policy_header = ConvertTo<ResTable_overlayable_policy_header>(parser.chunk()); - std::vector<Overlayable::Policy> policies; + Overlayable::PolicyFlags policies = Overlayable::Policy::kNone; if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) { - policies.push_back(Overlayable::Policy::kPublic); + policies |= Overlayable::Policy::kPublic; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) { - policies.push_back(Overlayable::Policy::kSystem); + policies |= Overlayable::Policy::kSystem; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) { - policies.push_back(Overlayable::Policy::kVendor); + policies |= Overlayable::Policy::kVendor; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { - policies.push_back(Overlayable::Policy::kProduct); + policies |= Overlayable::Policy::kProduct; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION) { - policies.push_back(Overlayable::Policy::kProductServices); + policies |= Overlayable::Policy::kProductServices; } const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>( @@ -478,13 +478,11 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { return false; } - for (Overlayable::Policy policy : policies) { - Overlayable overlayable; - overlayable.source = source_.WithLine(0); - overlayable.policy = policy; - if (!table_->AddOverlayable(iter->second, overlayable, diag_)) { - return false; - } + Overlayable overlayable{}; + overlayable.source = source_.WithLine(0); + overlayable.policies = policies; + if (!table_->SetOverlayable(iter->second, overlayable, diag_)) { + return false; } } } diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 976c3288bfca..200e2d468500 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -429,56 +429,52 @@ class PackageFlattener { CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>"; for (auto& entry : type->entries) { CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>"; + if (!entry->overlayable) { + continue; + } - // TODO(b/120298168): Convert the policies vector to a policy set or bitmask - if (!entry->overlayable_declarations.empty()) { - uint16_t policy_flags = 0; - for (Overlayable overlayable : entry->overlayable_declarations) { - if (overlayable.policy) { - switch (overlayable.policy.value()) { - case Overlayable::Policy::kPublic: - policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; - break; - case Overlayable::Policy::kSystem: - policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; - break; - case Overlayable::Policy::kVendor: - policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; - break; - case Overlayable::Policy::kProduct: - policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; - break; - case Overlayable::Policy::kProductServices: - policy_flags |= - ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION; - break; - } - } else { - // Encode overlayable entries defined without a policy as publicly overlayable - policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; - } - } + Overlayable overlayable = entry->overlayable.value(); + uint32_t policy_flags = Overlayable::Policy::kNone; + if (overlayable.policies & Overlayable::Policy::kPublic) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; + } + if (overlayable.policies & Overlayable::Policy::kSystem) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; + } + if (overlayable.policies & Overlayable::Policy::kVendor) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; + } + if (overlayable.policies & Overlayable::Policy::kProduct) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; + } + if (overlayable.policies & Overlayable::Policy::kProductServices) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION; + } - // Find the overlayable policy chunk with the same policies as the entry - PolicyChunk* policy_chunk = nullptr; - for (PolicyChunk& policy : policies) { - if (policy.policy_flags == policy_flags) { - policy_chunk = &policy; - break; - } - } + if (overlayable.policies == Overlayable::Policy::kNone) { + // Encode overlayable entries defined without a policy as publicly overlayable + policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; + } - // Create a new policy chunk if an existing one with the same policy cannot be found - if (policy_chunk == nullptr) { - PolicyChunk p; - p.policy_flags = policy_flags; - policies.push_back(p); - policy_chunk = &policies.back(); + // Find the overlayable policy chunk with the same policies as the entry + PolicyChunk* policy_chunk = nullptr; + for (PolicyChunk& policy : policies) { + if (policy.policy_flags == policy_flags) { + policy_chunk = &policy; + break; } + } - policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(), - entry->id.value())); + // Create a new policy chunk if an existing one with the same policy cannot be found + if (policy_chunk == nullptr) { + PolicyChunk p; + p.policy_flags = policy_flags; + policies.push_back(p); + policy_chunk = &policies.back(); } + + policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(), + entry->id.value())); } } diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index 410efbe83b1b..e99ab1f37761 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -628,14 +628,17 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { } TEST_F(TableFlattenerTest, FlattenOverlayable) { + Overlayable overlayable{}; + overlayable.policies |= Overlayable::Policy::kProduct; + overlayable.policies |= Overlayable::Policy::kSystem; + overlayable.policies |= Overlayable::Policy::kVendor; + std::string name = "com.app.test:integer/overlayable"; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) .AddSimple(name, ResourceId(0x7f020000)) - .AddOverlayable(name, Overlayable::Policy::kProduct) - .AddOverlayable(name, Overlayable::Policy::kSystem) - .AddOverlayable(name, Overlayable::Policy::kVendor) + .SetOverlayable(name, overlayable) .Build(); ResourceTable output_table; @@ -644,39 +647,45 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) { auto search_result = output_table.FindResource(test::ParseNameOrDie(name)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, - Overlayable::Policy::kSystem); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, - Overlayable::Policy::kVendor); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy, - Overlayable::Policy::kProduct); + ASSERT_TRUE(search_result.value().entry->overlayable); + Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem + | Overlayable::Policy::kVendor + | Overlayable::Policy::kProduct); } TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { std::string name_zero = "com.app.test:integer/overlayable_zero"; + Overlayable overlayable_zero{}; + overlayable_zero.policies |= Overlayable::Policy::kProduct; + overlayable_zero.policies |= Overlayable::Policy::kSystem; + overlayable_zero.policies |= Overlayable::Policy::kProductServices; + std::string name_one = "com.app.test:integer/overlayable_one"; + Overlayable overlayable_one{}; + overlayable_one.policies |= Overlayable::Policy::kPublic; + overlayable_one.policies |= Overlayable::Policy::kProductServices; + std::string name_two = "com.app.test:integer/overlayable_two"; + Overlayable overlayable_two{}; + overlayable_two.policies |= Overlayable::Policy::kProduct; + overlayable_two.policies |= Overlayable::Policy::kSystem; + overlayable_two.policies |= Overlayable::Policy::kVendor; + std::string name_three = "com.app.test:integer/overlayable_three"; + Overlayable overlayable_three{}; + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) .AddSimple(name_zero, ResourceId(0x7f020000)) - .AddOverlayable(name_zero, Overlayable::Policy::kProduct) - .AddOverlayable(name_zero, Overlayable::Policy::kSystem) - .AddOverlayable(name_zero, Overlayable::Policy::kProductServices) + .SetOverlayable(name_zero, overlayable_zero) .AddSimple(name_one, ResourceId(0x7f020001)) - .AddOverlayable(name_one, Overlayable::Policy::kPublic) - .AddOverlayable(name_one, Overlayable::Policy::kSystem) + .SetOverlayable(name_one, overlayable_one) .AddSimple(name_two, ResourceId(0x7f020002)) - .AddOverlayable(name_two, Overlayable::Policy::kProduct) - .AddOverlayable(name_two, Overlayable::Policy::kSystem) - .AddOverlayable(name_two, Overlayable::Policy::kProductServices) + .SetOverlayable(name_two, overlayable_two) .AddSimple(name_three, ResourceId(0x7f020003)) - .AddOverlayable(name_three, {}) + .SetOverlayable(name_three, overlayable_three) .Build(); ResourceTable output_table; @@ -685,51 +694,35 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, - Overlayable::Policy::kSystem); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, - Overlayable::Policy::kProduct); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy, - Overlayable::Policy::kProductServices); + ASSERT_TRUE(search_result.value().entry->overlayable); + Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem + | Overlayable::Policy::kProduct + | Overlayable::Policy::kProductServices); search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 2); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, - Overlayable::Policy::kPublic); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, - Overlayable::Policy::kSystem); + ASSERT_TRUE(search_result.value().entry->overlayable); + result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic + | Overlayable::Policy::kProductServices); search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, - Overlayable::Policy::kSystem); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, - Overlayable::Policy::kProduct); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy, - Overlayable::Policy::kProductServices); + ASSERT_TRUE(search_result.value().entry->overlayable); + result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem + | Overlayable::Policy::kProduct + | Overlayable::Policy::kVendor); search_result = output_table.FindResource(test::ParseNameOrDie(name_three)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 1); - EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); - EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, - Overlayable::Policy::kPublic); - + ASSERT_TRUE(search_result.value().entry->overlayable); + result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic); } - } // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index f612914269de..cf2ab0f45ad6 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -437,37 +437,39 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr entry->allow_new = std::move(allow_new); } - for (const pb::Overlayable& pb_overlayable : pb_entry.overlayable()) { - Overlayable overlayable; - switch (pb_overlayable.policy()) { - case pb::Overlayable::NONE: - overlayable.policy = {}; - break; - case pb::Overlayable::PUBLIC: - overlayable.policy = Overlayable::Policy::kPublic; - break; - case pb::Overlayable::PRODUCT: - overlayable.policy = Overlayable::Policy::kProduct; - break; - case pb::Overlayable::PRODUCT_SERVICES: - overlayable.policy = Overlayable::Policy::kProductServices; - break; - case pb::Overlayable::SYSTEM: - overlayable.policy = Overlayable::Policy::kSystem; - break; - case pb::Overlayable::VENDOR: - overlayable.policy = Overlayable::Policy::kVendor; - break; - default: - *out_error = "unknown overlayable policy"; - return false; + if (pb_entry.has_overlayable()) { + Overlayable overlayable{}; + + const pb::Overlayable& pb_overlayable = pb_entry.overlayable(); + for (const int policy : pb_overlayable.policy()) { + switch (policy) { + case pb::Overlayable::PUBLIC: + overlayable.policies |= Overlayable::Policy::kPublic; + break; + case pb::Overlayable::SYSTEM: + overlayable.policies |= Overlayable::Policy::kSystem; + break; + case pb::Overlayable::VENDOR: + overlayable.policies |= Overlayable::Policy::kVendor; + break; + case pb::Overlayable::PRODUCT: + overlayable.policies |= Overlayable::Policy::kProduct; + break; + case pb::Overlayable::PRODUCT_SERVICES: + overlayable.policies |= Overlayable::Policy::kProductServices; + break; + default: + *out_error = "unknown overlayable policy"; + return false; + } } if (pb_overlayable.has_source()) { DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source); } + overlayable.comment = pb_overlayable.comment(); - entry->overlayable_declarations.push_back(overlayable); + entry->overlayable = overlayable; } ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(), diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index ecf34d13e1b3..70bf8684f8a8 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -310,26 +310,24 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table pb_allow_new->set_comment(entry->allow_new.value().comment); } - for (const Overlayable& overlayable : entry->overlayable_declarations) { - pb::Overlayable* pb_overlayable = pb_entry->add_overlayable(); - if (overlayable.policy) { - switch (overlayable.policy.value()) { - case Overlayable::Policy::kPublic: - pb_overlayable->set_policy(pb::Overlayable::PUBLIC); - break; - case Overlayable::Policy::kProduct: - pb_overlayable->set_policy(pb::Overlayable::PRODUCT); - break; - case Overlayable::Policy::kProductServices: - pb_overlayable->set_policy(pb::Overlayable::PRODUCT_SERVICES); - break; - case Overlayable::Policy::kSystem: - pb_overlayable->set_policy(pb::Overlayable::SYSTEM); - break; - case Overlayable::Policy::kVendor: - pb_overlayable->set_policy(pb::Overlayable::VENDOR); - break; - } + if (entry->overlayable) { + pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable(); + + Overlayable overlayable = entry->overlayable.value(); + if (overlayable.policies & Overlayable::Policy::kPublic) { + pb_overlayable->add_policy(pb::Overlayable::PUBLIC); + } + if (overlayable.policies & Overlayable::Policy::kProduct) { + pb_overlayable->add_policy(pb::Overlayable::PRODUCT); + } + if (overlayable.policies & Overlayable::Policy::kProductServices) { + pb_overlayable->add_policy(pb::Overlayable::PRODUCT_SERVICES); + } + if (overlayable.policies & Overlayable::Policy::kSystem) { + pb_overlayable->add_policy(pb::Overlayable::SYSTEM); + } + if (overlayable.policies & Overlayable::Policy::kVendor) { + pb_overlayable->add_policy(pb::Overlayable::VENDOR); } SerializeSourceToPb(overlayable.source, &source_pool, diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 1cd2f0b9a961..fb913f409f52 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -93,7 +93,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { util::make_unique<Reference>(expected_ref), context->GetDiagnostics())); // Make an overlayable resource. - ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"), + ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"), Overlayable{}, test::GetDiagnostics())); pb::ResourceTable pb_table; @@ -160,8 +160,9 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy); + ASSERT_TRUE(search_result.value().entry->overlayable); + EXPECT_THAT(search_result.value().entry->overlayable.value().policies, + Eq(Overlayable::Policy::kNone)); } TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { @@ -502,15 +503,26 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) { } TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { + Overlayable overlayable_foo{}; + overlayable_foo.policies |= Overlayable::Policy::kSystem; + overlayable_foo.policies |= Overlayable::Policy::kProduct; + + Overlayable overlayable_bar{}; + overlayable_bar.policies |= Overlayable::Policy::kProductServices; + overlayable_bar.policies |= Overlayable::Policy::kVendor; + + Overlayable overlayable_baz{}; + overlayable_baz.policies |= Overlayable::Policy::kPublic; + + Overlayable overlayable_biz{}; + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kSystem) - .AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kProduct) - .AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kProductServices) - .AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kVendor) - .AddOverlayable("com.app.a:bool/baz", Overlayable::Policy::kPublic) - .AddOverlayable("com.app.a:bool/biz", {}) + .SetOverlayable("com.app.a:bool/foo", overlayable_foo) + .SetOverlayable("com.app.a:bool/bar", overlayable_bar) + .SetOverlayable("com.app.a:bool/baz", overlayable_baz) + .SetOverlayable("com.app.a:bool/biz", overlayable_biz) .AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true")) .Build(); @@ -523,37 +535,36 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)); EXPECT_THAT(error, IsEmpty()); - Maybe<ResourceTable::SearchResult> result = + Maybe<ResourceTable::SearchResult> search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); - ASSERT_TRUE(result); - ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2)); - EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kSystem)); - EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy, - Eq(Overlayable::Policy::kProduct)); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable); + Overlayable result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kSystem + | Overlayable::Policy::kProduct)); - result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar")); - ASSERT_TRUE(result); - ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2)); - EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kProductServices)); - EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy, - Eq(Overlayable::Policy::kVendor)); + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar")); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable); + result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProductServices + | Overlayable::Policy::kVendor)); - result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz")); - ASSERT_TRUE(result); - ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kPublic)); + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz")); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable); + result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kPublic); - result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz")); - ASSERT_TRUE(result); - ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1)); - EXPECT_FALSE(result.value().entry->overlayable_declarations[0].policy); + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz")); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable); + result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kNone); - result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz")); - ASSERT_TRUE(result); - EXPECT_THAT(result.value().entry->overlayable_declarations.size(), Eq(0)); + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz")); + ASSERT_TRUE(search_result); + ASSERT_FALSE(search_result.value().entry->overlayable); } } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 1b6626a8dfe9..8cbc03738677 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -374,8 +374,8 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { } // Ensure that definitions for values declared as overlayable exist - if (!entry->overlayable_declarations.empty() && entry->values.empty()) { - context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_declarations[0].source) + if (entry->overlayable && entry->values.empty()) { + context->GetDiagnostics()->Error(DiagMessage(entry->overlayable.value().source) << "no definition for overlayable symbol '" << name << "'"); error = true; diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index d777e22fa4b7..22e1723591a8 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -134,35 +134,21 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->allow_new = std::move(src_entry->allow_new); } - for (auto& src_overlayable : src_entry->overlayable_declarations) { - for (auto& dst_overlayable : dst_entry->overlayable_declarations) { - // An overlayable resource cannot be declared twice with the same policy - if (src_overlayable.policy == dst_overlayable.policy) { - context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source) - << "duplicate overlayable declaration for resource '" - << src_entry->name << "'"); - context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source) - << "previous declaration here"); - return false; - } - - // An overlayable resource cannot be declared once with a policy and without a policy because - // the policy becomes unused - if (!src_overlayable.policy || !dst_overlayable.policy) { - context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source) - << "overlayable resource '" << src_entry->name - << "' declared once with a policy and once with no " - << "policy"); - context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source) - << "previous declaration here"); - return false; - } + if (src_entry->overlayable) { + if (dst_entry->overlayable) { + // Do not allow a resource with an overlayable declaration to have that overlayable + // declaration redefined + context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source) + << "duplicate overlayable declaration for resource '" + << src_entry->name << "'"); + context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source) + << "previous declaration here"); + return false; + } else { + dst_entry->overlayable = std::move(src_entry->overlayable); } } - dst_entry->overlayable_declarations.insert(dst_entry->overlayable_declarations.end(), - src_entry->overlayable_declarations.begin(), - src_entry->overlayable_declarations.end()); return true; } diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index d6579d37b452..17b2a83bad04 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -436,17 +436,21 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent"))))); } -TEST_F(TableMergerTest, AddOverlayable) { +TEST_F(TableMergerTest, SetOverlayable) { + Overlayable overlayable{}; + overlayable.policies |= Overlayable::Policy::kProduct; + overlayable.policies |= Overlayable::Policy::kVendor; + std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", Overlayable::Policy::kProduct) + .SetOverlayable("bool/foo", overlayable) .Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", Overlayable::Policy::kProductServices) + .AddSimple("bool/foo") .Build(); ResourceTable final_table; @@ -457,26 +461,28 @@ TEST_F(TableMergerTest, AddOverlayable) { ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); - Maybe<ResourceTable::SearchResult> result = final_table.FindResource(name); - ASSERT_TRUE(result); - ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2)); - ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy, - Eq(Overlayable::Policy::kProduct)); - ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy, - Eq(Overlayable::Policy::kProductServices)); + Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable); + Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct + | Overlayable::Policy::kVendor)); } -TEST_F(TableMergerTest, AddDuplicateOverlayableFail) { +TEST_F(TableMergerTest, SetOverlayableLater) { std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", Overlayable::Policy::kProduct) + .AddSimple("bool/foo") .Build(); + Overlayable overlayable{}; + overlayable.policies |= Overlayable::Policy::kPublic; + overlayable.policies |= Overlayable::Policy::kProductServices; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", Overlayable::Policy::kProduct) + .SetOverlayable("bool/foo", overlayable) .Build(); ResourceTable final_table; @@ -484,20 +490,32 @@ TEST_F(TableMergerTest, AddDuplicateOverlayableFail) { options.auto_add_overlay = true; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); - ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); + + const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); + Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable); + Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); + EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kPublic + | Overlayable::Policy::kProductServices)); } -TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneFirstFail) { +TEST_F(TableMergerTest, SetOverlayableSamePolicesFail) { + Overlayable overlayable_first{}; + overlayable_first.policies |= Overlayable::Policy::kProduct; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", {}) + .SetOverlayable("bool/foo", overlayable_first) .Build(); + Overlayable overlayable_second{}; + overlayable_second.policies |= Overlayable::Policy::kProduct; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", Overlayable::Policy::kProduct) + .SetOverlayable("bool/foo", overlayable_second) .Build(); ResourceTable final_table; @@ -508,17 +526,21 @@ TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneFirstFail) { ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/)); } -TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneLastFail) { +TEST_F(TableMergerTest, SetOverlayableDifferentPolicesFail) { + Overlayable overlayable_first{}; + overlayable_first.policies |= Overlayable::Policy::kVendor; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", Overlayable::Policy::kProduct) + .SetOverlayable("bool/foo",overlayable_first) .Build(); + Overlayable overlayable_second{}; + overlayable_second.policies |= Overlayable::Policy::kProduct; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddOverlayable("bool/foo", {}) + .SetOverlayable("bool/foo", overlayable_second) .Build(); ResourceTable final_table; diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 2e717ff2bc3b..9c5b5d36b798 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -248,7 +248,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { if (!split_entry->id) { split_entry->id = entry->id; split_entry->visibility = entry->visibility; - split_entry->overlayable_declarations = entry->overlayable_declarations; + split_entry->overlayable = entry->overlayable; } // Copy the selected values into the new Split Entry. diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 03b59e033402..884ec38290f8 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -135,12 +135,11 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na return *this; } -ResourceTableBuilder& ResourceTableBuilder::AddOverlayable(const StringPiece& name, - const Maybe<Overlayable::Policy> p) { +ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name, + const Overlayable& overlayable) { + ResourceName res_name = ParseNameOrDie(name); - Overlayable overlayable; - overlayable.policy = p; - CHECK(table_->AddOverlayable(res_name, overlayable, GetDiagnostics())); + CHECK(table_->SetOverlayable(res_name, overlayable, GetDiagnostics())); return *this; } diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index d68c24ddc665..a12048436e38 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -73,8 +73,8 @@ class ResourceTableBuilder { const ResourceId& id, std::unique_ptr<Value> value); ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, Visibility::Level level, bool allow_new = false); - ResourceTableBuilder& AddOverlayable(const android::StringPiece& name, - Maybe<Overlayable::Policy> policy); + ResourceTableBuilder& SetOverlayable(const android::StringPiece& name, + const Overlayable& overlayable); StringPool* string_pool(); std::unique_ptr<ResourceTable> Build(); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index c6acd026bd39..21d6b94fba24 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -193,5 +193,7 @@ interface IWifiManager int addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); + + String[] getFactoryMacAddresses(); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 46ecc497da31..57c97eaf1f10 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -4434,4 +4434,19 @@ public class WifiManager { public boolean isOweSupported() { return isFeatureSupported(WIFI_FEATURE_OWE); } + + /** + * Gets the factory Wi-Fi MAC addresses. + * @return Array of String representing Wi-Fi MAC addresses sorted lexically or an empty Array + * if failed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public String[] getFactoryMacAddresses() { + try { + return mService.getFactoryMacAddresses(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java index 0f4e3a8ba20f..36f66aa81661 100644 --- a/wifi/java/com/android/server/wifi/AbstractWifiService.java +++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java @@ -452,4 +452,9 @@ public abstract class AbstractWifiService extends IWifiManager.Stub { List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { throw new UnsupportedOperationException(); } + + @Override + public String[] getFactoryMacAddresses() { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 13c8c9ea7ead..1001b100cb3b 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -29,6 +29,7 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -88,6 +89,7 @@ public class WifiManagerTest { private static final int TEST_UID = 14553; private static final String TEST_PACKAGE_NAME = "TestPackage"; private static final String TEST_COUNTRY_CODE = "US"; + private static final String[] TEST_MAC_ADDRESSES = {"da:a1:19:0:0:0"}; @Mock Context mContext; @Mock @@ -1320,4 +1322,15 @@ i * Verify that a call to cancel WPS immediately returns a failure. assertEquals(WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP, mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp()); } + + /** + * Verify getting the factory MAC address. + * @throws Exception + */ + @Test + public void testGetFactoryMacAddress() throws Exception { + when(mWifiService.getFactoryMacAddresses()).thenReturn(TEST_MAC_ADDRESSES); + assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses()); + verify(mWifiService).getFactoryMacAddresses(); + } } |