diff options
155 files changed, 5820 insertions, 1714 deletions
diff --git a/api/current.txt b/api/current.txt index f8b9f41e634f..ec13b5560d37 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2777,6 +2777,7 @@ package android.accessibilityservice { field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe field public static final int GLOBAL_ACTION_BACK = 1; // 0x1 field public static final int GLOBAL_ACTION_HOME = 2; // 0x2 + field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8 field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4 field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6 field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5 @@ -5616,6 +5617,7 @@ package android.app { method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); @@ -5628,8 +5630,12 @@ package android.app { method public void setNotificationPolicy(android.app.NotificationManager.Policy); method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule); field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID"; field public static final int IMPORTANCE_DEFAULT = 3; // 0x3 field public static final int IMPORTANCE_HIGH = 4; // 0x4 field public static final int IMPORTANCE_LOW = 2; // 0x2 @@ -6885,6 +6891,7 @@ package android.app.job { method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long); method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle); + method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean); method public android.app.job.JobInfo.Builder setMinimumLatency(long); method public android.app.job.JobInfo.Builder setOverrideDeadline(long); method public android.app.job.JobInfo.Builder setPeriodic(long); @@ -6988,31 +6995,33 @@ package android.app.slice { field public static final java.lang.String HINT_LARGE = "large"; field public static final java.lang.String HINT_LIST = "list"; field public static final java.lang.String HINT_LIST_ITEM = "list_item"; - field public static final java.lang.String HINT_MESSAGE = "message"; field public static final java.lang.String HINT_NO_TINT = "no_tint"; field public static final java.lang.String HINT_PARTIAL = "partial"; field public static final java.lang.String HINT_SELECTED = "selected"; - field public static final java.lang.String HINT_SOURCE = "source"; field public static final java.lang.String HINT_TITLE = "title"; + field public static final java.lang.String SUBTYPE_MESSAGE = "message"; + field public static final java.lang.String SUBTYPE_SOURCE = "source"; } public static class Slice.Builder { ctor public Slice.Builder(android.net.Uri); ctor public Slice.Builder(android.app.slice.Slice.Builder); method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice); - method public android.app.slice.Slice.Builder addColor(int, java.lang.String...); - method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String); + method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>); method public android.app.slice.Slice.Builder addHints(java.lang.String...); method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...); - method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...); + method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...); method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice); - method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...); - method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...); - method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String); + method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>); method public android.app.slice.Slice build(); method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec); } @@ -7021,23 +7030,24 @@ package android.app.slice { method public int describeContents(); method public android.app.PendingIntent getAction(); method public int getColor(); + method public java.lang.String getFormat(); method public java.util.List<java.lang.String> getHints(); method public android.graphics.drawable.Icon getIcon(); method public android.app.RemoteInput getRemoteInput(); method public android.app.slice.Slice getSlice(); + method public java.lang.String getSubType(); method public java.lang.CharSequence getText(); method public long getTimestamp(); - method public int getType(); method public boolean hasHint(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR; - field public static final int TYPE_ACTION = 4; // 0x4 - field public static final int TYPE_COLOR = 6; // 0x6 - field public static final int TYPE_IMAGE = 3; // 0x3 - field public static final int TYPE_REMOTE_INPUT = 9; // 0x9 - field public static final int TYPE_SLICE = 1; // 0x1 - field public static final int TYPE_TEXT = 2; // 0x2 - field public static final int TYPE_TIMESTAMP = 8; // 0x8 + field public static final java.lang.String FORMAT_ACTION = "action"; + field public static final java.lang.String FORMAT_COLOR = "color"; + field public static final java.lang.String FORMAT_IMAGE = "image"; + field public static final java.lang.String FORMAT_REMOTE_INPUT = "input"; + field public static final java.lang.String FORMAT_SLICE = "slice"; + field public static final java.lang.String FORMAT_TEXT = "text"; + field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp"; } public abstract class SliceProvider extends android.content.ContentProvider { @@ -23043,6 +23053,7 @@ package android.media { public static final class MediaMuxer.OutputFormat { 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_WEBM = 1; // 0x1 } @@ -25852,7 +25863,6 @@ package android.net { method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { diff --git a/api/system-current.txt b/api/system-current.txt index 00681a09e7d8..ce3d27795d22 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2919,6 +2919,7 @@ package android.accessibilityservice { field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe field public static final int GLOBAL_ACTION_BACK = 1; // 0x1 field public static final int GLOBAL_ACTION_HOME = 2; // 0x2 + field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8 field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4 field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6 field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5 @@ -5825,6 +5826,7 @@ package android.app { method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); @@ -5837,8 +5839,12 @@ package android.app { method public void setNotificationPolicy(android.app.NotificationManager.Policy); method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule); field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID"; field public static final int IMPORTANCE_DEFAULT = 3; // 0x3 field public static final int IMPORTANCE_HIGH = 4; // 0x4 field public static final int IMPORTANCE_LOW = 2; // 0x2 @@ -7328,6 +7334,7 @@ package android.app.job { method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long); method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle); + method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean); method public android.app.job.JobInfo.Builder setMinimumLatency(long); method public android.app.job.JobInfo.Builder setOverrideDeadline(long); method public android.app.job.JobInfo.Builder setPeriodic(long); @@ -7432,31 +7439,33 @@ package android.app.slice { field public static final java.lang.String HINT_LARGE = "large"; field public static final java.lang.String HINT_LIST = "list"; field public static final java.lang.String HINT_LIST_ITEM = "list_item"; - field public static final java.lang.String HINT_MESSAGE = "message"; field public static final java.lang.String HINT_NO_TINT = "no_tint"; field public static final java.lang.String HINT_PARTIAL = "partial"; field public static final java.lang.String HINT_SELECTED = "selected"; - field public static final java.lang.String HINT_SOURCE = "source"; field public static final java.lang.String HINT_TITLE = "title"; + field public static final java.lang.String SUBTYPE_MESSAGE = "message"; + field public static final java.lang.String SUBTYPE_SOURCE = "source"; } public static class Slice.Builder { ctor public Slice.Builder(android.net.Uri); ctor public Slice.Builder(android.app.slice.Slice.Builder); method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice); - method public android.app.slice.Slice.Builder addColor(int, java.lang.String...); - method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String); + method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>); method public android.app.slice.Slice.Builder addHints(java.lang.String...); method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...); - method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...); + method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...); method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice); - method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...); - method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...); - method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String); + method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>); method public android.app.slice.Slice build(); method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec); } @@ -7465,23 +7474,24 @@ package android.app.slice { method public int describeContents(); method public android.app.PendingIntent getAction(); method public int getColor(); + method public java.lang.String getFormat(); method public java.util.List<java.lang.String> getHints(); method public android.graphics.drawable.Icon getIcon(); method public android.app.RemoteInput getRemoteInput(); method public android.app.slice.Slice getSlice(); + method public java.lang.String getSubType(); method public java.lang.CharSequence getText(); method public long getTimestamp(); - method public int getType(); method public boolean hasHint(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR; - field public static final int TYPE_ACTION = 4; // 0x4 - field public static final int TYPE_COLOR = 6; // 0x6 - field public static final int TYPE_IMAGE = 3; // 0x3 - field public static final int TYPE_REMOTE_INPUT = 9; // 0x9 - field public static final int TYPE_SLICE = 1; // 0x1 - field public static final int TYPE_TEXT = 2; // 0x2 - field public static final int TYPE_TIMESTAMP = 8; // 0x8 + field public static final java.lang.String FORMAT_ACTION = "action"; + field public static final java.lang.String FORMAT_COLOR = "color"; + field public static final java.lang.String FORMAT_IMAGE = "image"; + field public static final java.lang.String FORMAT_REMOTE_INPUT = "input"; + field public static final java.lang.String FORMAT_SLICE = "slice"; + field public static final java.lang.String FORMAT_TEXT = "text"; + field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp"; } public abstract class SliceProvider extends android.content.ContentProvider { @@ -24937,6 +24947,7 @@ package android.media { public static final class MediaMuxer.OutputFormat { 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_WEBM = 1; // 0x1 } @@ -28098,7 +28109,6 @@ package android.net { method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { diff --git a/api/test-current.txt b/api/test-current.txt index bc82fbe8cdc9..8a718f588d70 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2777,6 +2777,7 @@ package android.accessibilityservice { field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe field public static final int GLOBAL_ACTION_BACK = 1; // 0x1 field public static final int GLOBAL_ACTION_HOME = 2; // 0x2 + field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8 field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4 field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6 field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5 @@ -5647,6 +5648,7 @@ package android.app { method public android.content.ComponentName getEffectsSuppressor(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); @@ -5659,8 +5661,12 @@ package android.app { method public void setNotificationPolicy(android.app.NotificationManager.Policy); method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule); field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID"; field public static final int IMPORTANCE_DEFAULT = 3; // 0x3 field public static final int IMPORTANCE_HIGH = 4; // 0x4 field public static final int IMPORTANCE_LOW = 2; // 0x2 @@ -6959,6 +6965,7 @@ package android.app.job { method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long); method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle); + method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean); method public android.app.job.JobInfo.Builder setMinimumLatency(long); method public android.app.job.JobInfo.Builder setOverrideDeadline(long); method public android.app.job.JobInfo.Builder setPeriodic(long); @@ -7062,31 +7069,33 @@ package android.app.slice { field public static final java.lang.String HINT_LARGE = "large"; field public static final java.lang.String HINT_LIST = "list"; field public static final java.lang.String HINT_LIST_ITEM = "list_item"; - field public static final java.lang.String HINT_MESSAGE = "message"; field public static final java.lang.String HINT_NO_TINT = "no_tint"; field public static final java.lang.String HINT_PARTIAL = "partial"; field public static final java.lang.String HINT_SELECTED = "selected"; - field public static final java.lang.String HINT_SOURCE = "source"; field public static final java.lang.String HINT_TITLE = "title"; + field public static final java.lang.String SUBTYPE_MESSAGE = "message"; + field public static final java.lang.String SUBTYPE_SOURCE = "source"; } public static class Slice.Builder { ctor public Slice.Builder(android.net.Uri); ctor public Slice.Builder(android.app.slice.Slice.Builder); method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice); - method public android.app.slice.Slice.Builder addColor(int, java.lang.String...); - method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String); + method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>); method public android.app.slice.Slice.Builder addHints(java.lang.String...); method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...); - method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...); + method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...); method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice); - method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...); - method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>); - method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...); - method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String); + method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>); + method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>); method public android.app.slice.Slice build(); method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec); } @@ -7095,23 +7104,24 @@ package android.app.slice { method public int describeContents(); method public android.app.PendingIntent getAction(); method public int getColor(); + method public java.lang.String getFormat(); method public java.util.List<java.lang.String> getHints(); method public android.graphics.drawable.Icon getIcon(); method public android.app.RemoteInput getRemoteInput(); method public android.app.slice.Slice getSlice(); + method public java.lang.String getSubType(); method public java.lang.CharSequence getText(); method public long getTimestamp(); - method public int getType(); method public boolean hasHint(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR; - field public static final int TYPE_ACTION = 4; // 0x4 - field public static final int TYPE_COLOR = 6; // 0x6 - field public static final int TYPE_IMAGE = 3; // 0x3 - field public static final int TYPE_REMOTE_INPUT = 9; // 0x9 - field public static final int TYPE_SLICE = 1; // 0x1 - field public static final int TYPE_TEXT = 2; // 0x2 - field public static final int TYPE_TIMESTAMP = 8; // 0x8 + field public static final java.lang.String FORMAT_ACTION = "action"; + field public static final java.lang.String FORMAT_COLOR = "color"; + field public static final java.lang.String FORMAT_IMAGE = "image"; + field public static final java.lang.String FORMAT_REMOTE_INPUT = "input"; + field public static final java.lang.String FORMAT_SLICE = "slice"; + field public static final java.lang.String FORMAT_TEXT = "text"; + field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp"; } public abstract class SliceProvider extends android.content.ContentProvider { @@ -23246,6 +23256,7 @@ package android.media { public static final class MediaMuxer.OutputFormat { 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_WEBM = 1; // 0x1 } diff --git a/cmds/pm/Android.mk b/cmds/pm/Android.mk index 6a03defc9e2c..960c8052f935 100644 --- a/cmds/pm/Android.mk +++ b/cmds/pm/Android.mk @@ -3,14 +3,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE := pmlib -LOCAL_MODULE_STEM := pm -include $(BUILD_JAVA_LIBRARY) - -include $(CLEAR_VARS) LOCAL_MODULE := pm -LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_SRC_FILES := pm -LOCAL_REQUIRED_MODULES := pmlib +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional include $(BUILD_PREBUILT) diff --git a/cmds/pm/pm b/cmds/pm/pm index 53f85b2f9c51..4d1f94554a78 100755 --- a/cmds/pm/pm +++ b/cmds/pm/pm @@ -1,8 +1,2 @@ #!/system/bin/sh -# Script to start "pm" on the device, which has a very rudimentary -# shell. -# -base=/system -export CLASSPATH=$base/framework/pm.jar -exec app_process $base/bin com.android.commands.pm.Pm "$@" - +cmd package "$@" diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java deleted file mode 100644 index 9490880afe0f..000000000000 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ /dev/null @@ -1,822 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.commands.pm; - -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; - -import android.accounts.IAccountManager; -import android.app.ActivityManager; -import android.app.PackageInstallObserver; -import android.content.ComponentName; -import android.content.Context; -import android.content.IIntentReceiver; -import android.content.IIntentSender; -import android.content.Intent; -import android.content.IntentSender; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageDataObserver; -import android.content.pm.IPackageInstaller; -import android.content.pm.IPackageManager; -import android.content.pm.PackageInfo; -import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionParams; -import android.content.pm.PackageManager; -import android.content.pm.PackageParser; -import android.content.pm.PackageParser.ApkLite; -import android.content.pm.PackageParser.PackageLite; -import android.content.pm.PackageParser.PackageParserException; -import android.content.pm.UserInfo; -import android.net.Uri; -import android.os.Binder; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.IUserManager; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.SELinux; -import android.os.ServiceManager; -import android.os.ShellCallback; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.storage.StorageManager; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.content.PackageHelper; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.SizedInputStream; - -import libcore.io.IoUtils; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.TimeUnit; - -public final class Pm { - private static final String TAG = "Pm"; - private static final String STDIN_PATH = "-"; - - IPackageManager mPm; - IPackageInstaller mInstaller; - IUserManager mUm; - IAccountManager mAm; - - private String[] mArgs; - private int mNextArg; - private String mCurArgData; - - private static final String PM_NOT_RUNNING_ERR = - "Error: Could not access the Package Manager. Is the system running?"; - - public static void main(String[] args) { - int exitCode = 1; - try { - exitCode = new Pm().run(args); - } catch (Exception e) { - Log.e(TAG, "Error", e); - System.err.println("Error: " + e); - if (e instanceof RemoteException) { - System.err.println(PM_NOT_RUNNING_ERR); - } - } - System.exit(exitCode); - } - - public int run(String[] args) throws RemoteException { - if (args.length < 1) { - return runShellCommand("package", mArgs); - } - mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE)); - mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE)); - mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); - - if (mPm == null) { - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - mInstaller = mPm.getPackageInstaller(); - - mArgs = args; - String op = args[0]; - mNextArg = 1; - - if ("install".equals(op)) { - return runInstall(); - } - - if ("install-create".equals(op)) { - return runInstallCreate(); - } - - if ("install-write".equals(op)) { - return runInstallWrite(); - } - - if ("install-commit".equals(op)) { - return runInstallCommit(); - } - - if ("install-abandon".equals(op) || "install-destroy".equals(op)) { - return runInstallAbandon(); - } - - return runShellCommand("package", mArgs); - } - - static final class MyShellCallback extends ShellCallback { - @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, - String mode) { - File file = new File(path); - final ParcelFileDescriptor fd; - try { - fd = ParcelFileDescriptor.open(file, - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE | - ParcelFileDescriptor.MODE_WRITE_ONLY); - } catch (FileNotFoundException e) { - String msg = "Unable to open file " + path + ": " + e; - System.err.println(msg); - throw new IllegalArgumentException(msg); - } - if (seLinuxContext != null) { - final String tcon = SELinux.getFileContext(file.getAbsolutePath()); - if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) { - try { - fd.close(); - } catch (IOException e) { - } - String msg = "System server has no access to file context " + tcon; - System.err.println(msg + " (from path " + file.getAbsolutePath() - + ", context " + seLinuxContext + ")"); - throw new IllegalArgumentException(msg); - } - } - return fd; - } - } - - private int runShellCommand(String serviceName, String[] args) { - final HandlerThread handlerThread = new HandlerThread("results"); - handlerThread.start(); - try { - ServiceManager.getService(serviceName).shellCommand( - FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, - args, new MyShellCallback(), - new ResultReceiver(new Handler(handlerThread.getLooper()))); - return 0; - } catch (RemoteException e) { - e.printStackTrace(); - } finally { - handlerThread.quitSafely(); - } - return -1; - } - - private static class LocalIntentReceiver { - private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>(); - - private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { - @Override - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, - IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - try { - mResult.offer(intent, 5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - }; - - public IntentSender getIntentSender() { - return new IntentSender((IIntentSender) mLocalSender); - } - - public Intent getResult() { - try { - return mResult.take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - private int translateUserId(int userId, String logContext) { - return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), - userId, true, true, logContext, "pm command"); - } - - private static String checkAbiArgument(String abi) { - if (TextUtils.isEmpty(abi)) { - throw new IllegalArgumentException("Missing ABI argument"); - } - if ("-".equals(abi)) { - return abi; - } - final String[] supportedAbis = Build.SUPPORTED_ABIS; - for (String supportedAbi : supportedAbis) { - if (supportedAbi.equals(abi)) { - return abi; - } - } - throw new IllegalArgumentException("ABI " + abi + " not supported on this device"); - } - - /* - * Keep this around to support existing users of the "pm install" command that may not be - * able to be updated [or, at least informed the API has changed] such as ddmlib. - * - * Moving the implementation of "pm install" to "cmd package install" changes the executing - * context. Instead of being a stand alone process, "cmd package install" runs in the - * system_server process. Due to SELinux rules, system_server cannot access many directories; - * one of which being the package install staging directory [/data/local/tmp]. - * - * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged. - */ - private int runInstall() throws RemoteException { - long startedTime = SystemClock.elapsedRealtime(); - final InstallParams params = makeInstallParams(); - final String inPath = nextArg(); - if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) { - File file = new File(inPath); - if (file.isFile()) { - try { - ApkLite baseApk = PackageParser.parseApkLite(file, 0); - PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null, - null, null); - params.sessionParams.setSize( - PackageHelper.calculateInstalledSize(pkgLite, - params.sessionParams.abiOverride)); - } catch (PackageParserException | IOException e) { - System.err.println("Error: Failed to parse APK file: " + e); - return 1; - } - } else { - System.err.println("Error: Can't open non-file: " + inPath); - return 1; - } - } - - final int sessionId = doCreateSession(params.sessionParams, - params.installerPackageName, params.userId); - - try { - if (inPath == null && params.sessionParams.sizeBytes == -1) { - System.err.println("Error: must either specify a package size or an APK file"); - return 1; - } - if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk", - false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/); - if (status.second != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime() - - startedTime) + " ms"); - System.out.println("Success"); - return 0; - } finally { - try { - mInstaller.abandonSession(sessionId); - } catch (Exception ignore) { - } - } - } - - private int runInstallAbandon() throws RemoteException { - final int sessionId = Integer.parseInt(nextArg()); - return doAbandonSession(sessionId, true /*logSuccess*/); - } - - private int runInstallCommit() throws RemoteException { - final int sessionId = Integer.parseInt(nextArg()); - return doCommitSession(sessionId, true /*logSuccess*/).second; - } - - private int runInstallCreate() throws RemoteException { - final InstallParams installParams = makeInstallParams(); - final int sessionId = doCreateSession(installParams.sessionParams, - installParams.installerPackageName, installParams.userId); - - // NOTE: adb depends on parsing this string - System.out.println("Success: created install session [" + sessionId + "]"); - return PackageInstaller.STATUS_SUCCESS; - } - - private int runInstallWrite() throws RemoteException { - long sizeBytes = -1; - - String opt; - while ((opt = nextOption()) != null) { - if (opt.equals("-S")) { - sizeBytes = Long.parseLong(nextArg()); - } else { - throw new IllegalArgumentException("Unknown option: " + opt); - } - } - - final int sessionId = Integer.parseInt(nextArg()); - final String splitName = nextArg(); - final String path = nextArg(); - return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/); - } - - private static class InstallParams { - SessionParams sessionParams; - String installerPackageName; - int userId = UserHandle.USER_ALL; - } - - private InstallParams makeInstallParams() { - final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL); - final InstallParams params = new InstallParams(); - params.sessionParams = sessionParams; - String opt; - while ((opt = nextOption()) != null) { - switch (opt) { - case "-l": - sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK; - break; - case "-r": - sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; - break; - case "-i": - params.installerPackageName = nextArg(); - if (params.installerPackageName == null) { - throw new IllegalArgumentException("Missing installer package"); - } - break; - case "-t": - sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST; - break; - case "-s": - sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL; - break; - case "-f": - sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL; - break; - case "-d": - sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; - break; - case "-g": - sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS; - break; - case "--dont-kill": - sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP; - break; - case "--originating-uri": - sessionParams.originatingUri = Uri.parse(nextOptionData()); - break; - case "--referrer": - sessionParams.referrerUri = Uri.parse(nextOptionData()); - break; - case "-p": - sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING; - sessionParams.appPackageName = nextOptionData(); - if (sessionParams.appPackageName == null) { - throw new IllegalArgumentException("Missing inherit package name"); - } - break; - case "--pkg": - sessionParams.appPackageName = nextOptionData(); - if (sessionParams.appPackageName == null) { - throw new IllegalArgumentException("Missing package name"); - } - break; - case "-S": - final long sizeBytes = Long.parseLong(nextOptionData()); - if (sizeBytes <= 0) { - throw new IllegalArgumentException("Size must be positive"); - } - sessionParams.setSize(sizeBytes); - break; - case "--abi": - sessionParams.abiOverride = checkAbiArgument(nextOptionData()); - break; - case "--ephemeral": - case "--instant": - sessionParams.setInstallAsInstantApp(true /*isInstantApp*/); - break; - case "--full": - sessionParams.setInstallAsInstantApp(false /*isInstantApp*/); - break; - case "--user": - params.userId = UserHandle.parseUserArg(nextOptionData()); - break; - case "--install-location": - sessionParams.installLocation = Integer.parseInt(nextOptionData()); - break; - case "--force-uuid": - sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID; - sessionParams.volumeUuid = nextOptionData(); - if ("internal".equals(sessionParams.volumeUuid)) { - sessionParams.volumeUuid = null; - } - break; - case "--force-sdk": - sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK; - break; - default: - throw new IllegalArgumentException("Unknown option " + opt); - } - } - return params; - } - - private int doCreateSession(SessionParams params, String installerPackageName, int userId) - throws RemoteException { - userId = translateUserId(userId, "runInstallCreate"); - if (userId == UserHandle.USER_ALL) { - userId = UserHandle.USER_SYSTEM; - params.installFlags |= PackageManager.INSTALL_ALL_USERS; - } - - final int sessionId = mInstaller.createSession(params, installerPackageName, userId); - return sessionId; - } - - private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName, - boolean logSuccess) throws RemoteException { - if (STDIN_PATH.equals(inPath)) { - inPath = null; - } else if (inPath != null) { - final File file = new File(inPath); - if (file.isFile()) { - sizeBytes = file.length(); - } - } - - final SessionInfo info = mInstaller.getSessionInfo(sessionId); - - PackageInstaller.Session session = null; - InputStream in = null; - OutputStream out = null; - try { - session = new PackageInstaller.Session( - mInstaller.openSession(sessionId)); - - if (inPath != null) { - in = new FileInputStream(inPath); - } else { - in = new SizedInputStream(System.in, sizeBytes); - } - out = session.openWrite(splitName, 0, sizeBytes); - - int total = 0; - byte[] buffer = new byte[1024 * 1024]; - int c; - while ((c = in.read(buffer)) != -1) { - total += c; - out.write(buffer, 0, c); - - if (info.sizeBytes > 0) { - final float fraction = ((float) c / (float) info.sizeBytes); - session.addProgress(fraction); - } - } - session.fsync(out); - - if (logSuccess) { - System.out.println("Success: streamed " + total + " bytes"); - } - return PackageInstaller.STATUS_SUCCESS; - } catch (IOException e) { - System.err.println("Error: failed to write; " + e.getMessage()); - return PackageInstaller.STATUS_FAILURE; - } finally { - IoUtils.closeQuietly(out); - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(session); - } - } - - private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess) - throws RemoteException { - PackageInstaller.Session session = null; - try { - session = new PackageInstaller.Session( - mInstaller.openSession(sessionId)); - - final LocalIntentReceiver receiver = new LocalIntentReceiver(); - session.commit(receiver.getIntentSender()); - - final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status == PackageInstaller.STATUS_SUCCESS) { - if (logSuccess) { - System.out.println("Success"); - } - } else { - System.err.println("Failure [" - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); - } - return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status); - } finally { - IoUtils.closeQuietly(session); - } - } - - private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException { - PackageInstaller.Session session = null; - try { - session = new PackageInstaller.Session(mInstaller.openSession(sessionId)); - session.abandon(); - if (logSuccess) { - System.out.println("Success"); - } - return PackageInstaller.STATUS_SUCCESS; - } finally { - IoUtils.closeQuietly(session); - } - } - - class LocalPackageInstallObserver extends PackageInstallObserver { - boolean finished; - int result; - String extraPermission; - String extraPackage; - - @Override - public void onPackageInstalled(String name, int status, String msg, Bundle extras) { - synchronized (this) { - finished = true; - result = status; - if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) { - extraPermission = extras.getString( - PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION); - extraPackage = extras.getString( - PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE); - } - notifyAll(); - } - } - } - - private static boolean isNumber(String s) { - try { - Integer.parseInt(s); - } catch (NumberFormatException nfe) { - return false; - } - return true; - } - - static class ClearCacheObserver extends IPackageDataObserver.Stub { - boolean finished; - boolean result; - - @Override - public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { - synchronized (this) { - finished = true; - result = succeeded; - notifyAll(); - } - } - - } - - static class ClearDataObserver extends IPackageDataObserver.Stub { - boolean finished; - boolean result; - - @Override - public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { - synchronized (this) { - finished = true; - result = succeeded; - notifyAll(); - } - } - } - - /** - * Displays the package file for a package. - * @param pckg - */ - private int displayPackageFilePath(String pckg, int userId) { - try { - PackageInfo info = mPm.getPackageInfo(pckg, 0, userId); - if (info != null && info.applicationInfo != null) { - System.out.print("package:"); - System.out.println(info.applicationInfo.sourceDir); - if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { - for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { - System.out.print("package:"); - System.out.println(splitSourceDir); - } - } - return 0; - } - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - } - return 1; - } - - private String nextOption() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - if (!arg.startsWith("-")) { - return null; - } - mNextArg++; - if (arg.equals("--")) { - return null; - } - if (arg.length() > 1 && arg.charAt(1) != '-') { - if (arg.length() > 2) { - mCurArgData = arg.substring(2); - return arg.substring(0, 2); - } else { - mCurArgData = null; - return arg; - } - } - mCurArgData = null; - return arg; - } - - private String nextOptionData() { - if (mCurArgData != null) { - return mCurArgData; - } - if (mNextArg >= mArgs.length) { - return null; - } - String data = mArgs[mNextArg]; - mNextArg++; - return data; - } - - private String nextArg() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - mNextArg++; - return arg; - } - - private static int showUsage() { - System.err.println("usage: pm path [--user USER_ID] PACKAGE"); - System.err.println(" pm dump PACKAGE"); - System.err.println(" pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]"); - System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); - System.err.println(" [--originating-uri URI] [---referrer URI]"); - System.err.println(" [--abi ABI_NAME] [--force-sdk]"); - System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); - System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]"); - System.err.println(" pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]"); - System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); - System.err.println(" [--originating-uri URI] [---referrer URI]"); - System.err.println(" [--abi ABI_NAME] [--force-sdk]"); - System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); - System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); - System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]"); - System.err.println(" pm install-commit SESSION_ID"); - System.err.println(" pm install-abandon SESSION_ID"); - System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE"); - System.err.println(" pm set-installer PACKAGE INSTALLER"); - System.err.println(" pm move-package PACKAGE [internal|UUID]"); - System.err.println(" pm move-primary-storage [internal|UUID]"); - System.err.println(" pm clear [--user USER_ID] PACKAGE"); - System.err.println(" pm enable [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm disable [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm set-user-restriction [--user USER_ID] RESTRICTION VALUE"); - System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION"); - System.err.println(" pm revoke [--user USER_ID] PACKAGE PERMISSION"); - System.err.println(" pm reset-permissions"); - System.err.println(" pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}"); - System.err.println(" pm get-app-link [--user USER_ID] PACKAGE"); - System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]"); - System.err.println(" pm get-install-location"); - System.err.println(" pm set-permission-enforced PERMISSION [true|false]"); - System.err.println(" pm trim-caches DESIRED_FREE_SPACE [internal|UUID]"); - System.err.println(" pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME"); - System.err.println(" pm remove-user USER_ID"); - System.err.println(" pm get-max-users"); - System.err.println(""); - System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'"); - System.err.println(" to display the new commands."); - System.err.println(""); - System.err.println("pm path: print the path to the .apk of the given PACKAGE."); - System.err.println(""); - System.err.println("pm dump: print system state associated with the given PACKAGE."); - System.err.println(""); - System.err.println("pm install: install a single legacy package"); - System.err.println("pm install-create: create an install session"); - System.err.println(" -l: forward lock application"); - System.err.println(" -r: allow replacement of existing application"); - System.err.println(" -t: allow test packages"); - System.err.println(" -i: specify package name of installer owning the app"); - System.err.println(" -s: install application on sdcard"); - System.err.println(" -f: install application on internal flash"); - System.err.println(" -d: allow version code downgrade (debuggable packages only)"); - System.err.println(" -p: partial application install (new split on top of existing pkg)"); - System.err.println(" -g: grant all runtime permissions"); - System.err.println(" -S: size in bytes of entire session"); - System.err.println(" --dont-kill: installing a new feature split, don't kill running app"); - System.err.println(" --originating-uri: set URI where app was downloaded from"); - System.err.println(" --referrer: set URI that instigated the install of the app"); - System.err.println(" --pkg: specify expected package name of app being installed"); - System.err.println(" --abi: override the default ABI of the platform"); - System.err.println(" --instantapp: cause the app to be installed as an ephemeral install app"); - System.err.println(" --full: cause the app to be installed as a non-ephemeral full app"); - System.err.println(" --install-location: force the install location:"); - System.err.println(" 0=auto, 1=internal only, 2=prefer external"); - System.err.println(" --force-uuid: force install on to disk volume with given UUID"); - System.err.println(" --force-sdk: allow install even when existing app targets platform"); - System.err.println(" codename but new one targets a final API level"); - System.err.println(""); - System.err.println("pm install-write: write a package into existing session; path may"); - System.err.println(" be '-' to read from stdin"); - System.err.println(" -S: size in bytes of package, required for stdin"); - System.err.println(""); - System.err.println("pm install-commit: perform install of fully staged session"); - System.err.println("pm install-abandon: abandon session"); - System.err.println(""); - System.err.println("pm set-installer: set installer package name"); - System.err.println(""); - System.err.println("pm uninstall: removes a package from the system. Options:"); - System.err.println(" -k: keep the data and cache directories around after package removal."); - System.err.println(""); - System.err.println("pm clear: deletes all data associated with a package."); - System.err.println(""); - System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:"); - System.err.println(" these commands change the enabled state of a given package or"); - System.err.println(" component (written as \"package/class\")."); - System.err.println(""); - System.err.println("pm grant, revoke: these commands either grant or revoke permissions"); - System.err.println(" to apps. The permissions must be declared as used in the app's"); - System.err.println(" manifest, be runtime permissions (protection level dangerous),"); - System.err.println(" and the app targeting SDK greater than Lollipop MR1."); - System.err.println(""); - System.err.println("pm reset-permissions: revert all runtime permissions to their default state."); - System.err.println(""); - System.err.println("pm get-install-location: returns the current install location."); - System.err.println(" 0 [auto]: Let system decide the best location"); - System.err.println(" 1 [internal]: Install on internal device storage"); - System.err.println(" 2 [external]: Install on external media"); - System.err.println(""); - System.err.println("pm set-install-location: changes the default install location."); - System.err.println(" NOTE: this is only intended for debugging; using this can cause"); - System.err.println(" applications to break and other undersireable behavior."); - System.err.println(" 0 [auto]: Let system decide the best location"); - System.err.println(" 1 [internal]: Install on internal device storage"); - System.err.println(" 2 [external]: Install on external media"); - System.err.println(""); - System.err.println("pm trim-caches: trim cache files to reach the given free space."); - System.err.println(""); - System.err.println("pm create-user: create a new user with the given USER_NAME,"); - System.err.println(" printing the new user identifier of the user."); - System.err.println(""); - System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,"); - System.err.println(" deleting all data associated with that user"); - System.err.println(""); - return 1; - } -} diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 1b2f9da22ec1..fd9465d68b37 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -55,7 +55,8 @@ statsd_common_src := \ src/storage/DropboxWriter.cpp \ src/StatsLogProcessor.cpp \ src/StatsService.cpp \ - src/stats_util.cpp + src/stats_util.cpp \ + src/guardrail/MemoryLeakTrackUtil.cpp statsd_common_c_includes := \ $(LOCAL_PATH)/src \ @@ -82,7 +83,8 @@ statsd_common_shared_libraries := \ libhidltransport \ libhwbinder \ android.hardware.power@1.0 \ - android.hardware.power@1.1 + android.hardware.power@1.1 \ + libmemunreachable # ========= # statsd @@ -178,3 +180,7 @@ statsd_common_aidl_includes:= statsd_common_c_includes:= include $(BUILD_NATIVE_TEST) + +############################## + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 1a056df8e367..65b3da1cd258 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -17,10 +17,11 @@ #define DEBUG true #include "Log.h" -#include "android-base/stringprintf.h" #include "StatsService.h" +#include "android-base/stringprintf.h" #include "config/ConfigKey.h" #include "config/ConfigManager.h" +#include "guardrail/MemoryLeakTrackUtil.h" #include "storage/DropboxReader.h" #include <android-base/file.h> @@ -226,6 +227,10 @@ status_t StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& if (!args[0].compare(String8("clear-config"))) { return cmd_remove_config_files(out); } + + if (!args[0].compare(String8("meminfo"))) { + return cmd_dump_memory_info(out); + } } print_cmd_help(out); @@ -238,6 +243,15 @@ void StatsService::print_cmd_help(FILE* out) { "[timestamp_nsec_optional]\n"); fprintf(out, "\n"); fprintf(out, "\n"); + fprintf(out, "usage: adb shell cmd stats meminfo\n"); + fprintf(out, "\n"); + fprintf(out, " Prints the malloc debug information. You need to run the following first: \n"); + fprintf(out, " # adb shell stop\n"); + fprintf(out, " # adb shell setprop libc.debug.malloc.program statsd \n"); + fprintf(out, " # adb shell setprop libc.debug.malloc.options backtrace \n"); + fprintf(out, " # adb shell start\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); fprintf(out, "usage: adb shell cmd stats print-uid-map \n"); fprintf(out, "\n"); fprintf(out, " Prints the UID, app name, version mapping.\n"); @@ -507,6 +521,13 @@ status_t StatsService::cmd_remove_config_files(FILE* out) { return NO_ERROR; } +status_t StatsService::cmd_dump_memory_info(FILE* out) { + std::string s = dumpMemInfo(100); + fprintf(out, "Memory Info\n"); + fprintf(out, "%s", s.c_str()); + return NO_ERROR; +} + Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version, const vector<String16>& app) { if (DEBUG) ALOGD("StatsService::informAllUidData was called"); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 4d768f630362..47f0bb601fef 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -160,6 +160,11 @@ private: */ status_t cmd_remove_config_files(FILE* out); + /* + * Dump memory usage by statsd. + */ + status_t cmd_dump_memory_info(FILE* out); + /** * Update a configuration. */ diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 57a92b61a5c9..20e7c600938e 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -74,6 +74,7 @@ message Atom { ActivityForegroundStateChanged activity_foreground_state_changed = 42; IsolatedUidChanged isolated_uid_changed = 43; PacketWakeupOccurred packet_wakeup_occurred = 44; + DropboxErrorChanged dropbox_error_changed = 45; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } @@ -716,6 +717,34 @@ message ActivityForegroundStateChanged { } /** + * Logs when an error is written to dropbox. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message DropboxErrorChanged { + // The uid if available. -1 means not available. + optional int32 uid = 1; + + // Tag used when recording this error to dropbox. Contains data_ or system_ prefix. + optional string tag = 2; + + // The name of the process. + optional string process_name = 3; + + // The pid if available. -1 means not available. + optional int32 pid = 4; + + // 1 indicates is instant app. -1 indicates Not applicable. + optional int32 is_instant_app = 5; + + // The activity name if available. + optional string activity_name = 6; + + // 1 indicates in foreground. -1 indicates not available. + optional int32 is_foreground = 7; +} + +/** * Pulls bytes transferred via wifi (Sum of foreground and background usage). * * Pulled from: diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 21256097a766..0c9252e2095f 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -232,7 +232,7 @@ void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfi static StatsdConfig build_fake_config() { // HACK: Hard code a test metric for counting screen on events... StatsdConfig config; - config.set_name("12345"); + config.set_name("CONFIG_12345"); int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier. int WAKE_LOCK_UID_KEY_ID = 1; @@ -263,20 +263,21 @@ static StatsdConfig build_fake_config() { // Count Screen ON events. CountMetric* metric = config.add_count_metric(); - metric->set_name("1"); + metric->set_name("METRIC_1"); metric->set_what("SCREEN_TURNED_ON"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); // Anomaly threshold for screen-on count. Alert* alert = config.add_alert(); - alert->set_name("1"); + alert->set_name("ALERT_1"); + alert->set_metric_name("METRIC_1"); alert->set_number_of_buckets(6); alert->set_trigger_if_sum_gt(10); alert->set_refractory_period_secs(30); // Count process state changes, slice by uid. metric = config.add_count_metric(); - metric->set_name("2"); + metric->set_name("METRIC_2"); metric->set_what("PROCESS_STATE_CHANGE"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); KeyMatcher* keyMatcher = metric->add_dimension(); @@ -284,14 +285,15 @@ static StatsdConfig build_fake_config() { // Anomaly threshold for background count. alert = config.add_alert(); - alert->set_name("2"); + alert->set_name("ALERT_2"); + alert->set_metric_name("METRIC_2"); alert->set_number_of_buckets(4); alert->set_trigger_if_sum_gt(30); alert->set_refractory_period_secs(20); // Count process state changes, slice by uid, while SCREEN_IS_OFF metric = config.add_count_metric(); - metric->set_name("3"); + metric->set_name("METRIC_3"); metric->set_what("PROCESS_STATE_CHANGE"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); keyMatcher = metric->add_dimension(); @@ -300,7 +302,7 @@ static StatsdConfig build_fake_config() { // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background metric = config.add_count_metric(); - metric->set_name("4"); + metric->set_name("METRIC_4"); metric->set_what("APP_GET_WL"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); keyMatcher = metric->add_dimension(); @@ -313,7 +315,7 @@ static StatsdConfig build_fake_config() { // Duration of an app holding any wl, while screen on and app in background, slice by uid DurationMetric* durationMetric = config.add_duration_metric(); - durationMetric->set_name("5"); + durationMetric->set_name("METRIC_5"); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); keyMatcher = durationMetric->add_dimension(); @@ -327,7 +329,7 @@ static StatsdConfig build_fake_config() { // max Duration of an app holding any wl, while screen on and app in background, slice by uid durationMetric = config.add_duration_metric(); - durationMetric->set_name("6"); + durationMetric->set_name("METRIC_6"); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); keyMatcher = durationMetric->add_dimension(); @@ -341,7 +343,7 @@ static StatsdConfig build_fake_config() { // Duration of an app holding any wl, while screen on and app in background durationMetric = config.add_duration_metric(); - durationMetric->set_name("7"); + durationMetric->set_name("METRIC_7"); durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); durationMetric->set_what("WL_HELD_PER_APP_PER_NAME"); @@ -353,14 +355,14 @@ static StatsdConfig build_fake_config() { // Duration of screen on time. durationMetric = config.add_duration_metric(); - durationMetric->set_name("8"); + durationMetric->set_name("METRIC_8"); durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); durationMetric->set_what("SCREEN_IS_ON"); // Value metric to count KERNEL_WAKELOCK when screen turned on ValueMetric* valueMetric = config.add_value_metric(); - valueMetric->set_name("6"); + valueMetric->set_name("METRIC_6"); valueMetric->set_what("KERNEL_WAKELOCK"); valueMetric->set_value_field(1); valueMetric->set_condition("SCREEN_IS_ON"); @@ -371,12 +373,12 @@ static StatsdConfig build_fake_config() { // Add an EventMetric to log process state change events. EventMetric* eventMetric = config.add_event_metric(); - eventMetric->set_name("9"); + eventMetric->set_name("METRIC_9"); eventMetric->set_what("SCREEN_TURNED_ON"); // Add an GaugeMetric. GaugeMetric* gaugeMetric = config.add_gauge_metric(); - gaugeMetric->set_name("10"); + gaugeMetric->set_name("METRIC_10"); gaugeMetric->set_what("DEVICE_TEMPERATURE"); gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY); gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L); diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp new file mode 100644 index 000000000000..e1947c4a5fe8 --- /dev/null +++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include <sstream> +#include "MemoryLeakTrackUtil.h" + +/* + * The code here originally resided in MediaPlayerService.cpp + */ + +// Figure out the abi based on defined macros. +#if defined(__arm__) +#define ABI_STRING "arm" +#elif defined(__aarch64__) +#define ABI_STRING "arm64" +#elif defined(__mips__) && !defined(__LP64__) +#define ABI_STRING "mips" +#elif defined(__mips__) && defined(__LP64__) +#define ABI_STRING "mips64" +#elif defined(__i386__) +#define ABI_STRING "x86" +#elif defined(__x86_64__) +#define ABI_STRING "x86_64" +#else +#error "Unsupported ABI" +#endif + +extern std::string backtrace_string(const uintptr_t* frames, size_t frame_count); + +namespace android { +namespace os { +namespace statsd { + +extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, size_t* infoSize, + size_t* totalMemory, size_t* backtraceSize); + +extern "C" void free_malloc_leak_info(uint8_t* info); + +std::string dumpMemInfo(size_t limit) { + uint8_t* info; + size_t overallSize; + size_t infoSize; + size_t totalMemory; + size_t backtraceSize; + get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize); + + size_t count; + if (info == nullptr || overallSize == 0 || infoSize == 0 || + (count = overallSize / infoSize) == 0) { + ALOGD("no malloc info, libc.debug.malloc.program property should be set"); + return std::string(); + } + + std::ostringstream oss; + oss << totalMemory << " bytes in " << count << " allocations\n"; + oss << " ABI: '" ABI_STRING "'" + << "\n\n"; + if (count > limit) count = limit; + + // The memory is sorted based on total size which is useful for finding + // worst memory offenders. For diffs, sometimes it is preferable to sort + // based on the backtrace. + for (size_t i = 0; i < count; i++) { + struct AllocEntry { + size_t size; // bit 31 is set if this is zygote allocated memory + size_t allocations; + uintptr_t backtrace[]; + }; + + const AllocEntry* const e = (AllocEntry*)(info + i * infoSize); + + oss << (e->size * e->allocations) << " bytes ( " << e->size << " bytes * " << e->allocations + << " allocations )\n"; + oss << backtrace_string(e->backtrace, backtraceSize) << "\n"; + } + oss << "\n"; + free_malloc_leak_info(info); + return oss.str(); +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h new file mode 100644 index 000000000000..444ed92cc9bb --- /dev/null +++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h @@ -0,0 +1,33 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <iostream> + +namespace android { +namespace os { +namespace statsd { +/* + * Dump the heap memory of the calling process, sorted by total size + * (allocation size * number of allocations). + * + * limit is the number of unique allocations to return. + */ +extern std::string dumpMemInfo(size_t limit); + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index d47bd4fe9e44..a5ce38915574 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -133,7 +133,7 @@ std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index b0a97b143400..a32e0cb8b274 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -163,6 +163,14 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str()); continue; } + + // If there is no duration bucket info for this key, don't include it in the report. + // For example, duration started, but condition is never turned to true. + // TODO: Only add the key to the map when we add duration buckets info for it. + if (pair.second.size() == 0) { + continue; + } + long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); @@ -172,7 +180,7 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { @@ -203,7 +211,6 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { mProto->end(mProtoToken); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)mCurrentBucketStartTimeNs); - std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto(); startNewProtoOutputStream(endTime); // TODO: Properly clear the old buckets. diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 42ac1a2bbd7b..d7b72965ae1e 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -135,7 +135,7 @@ std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 9cbe6f6eac15..0dbdd2981307 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -153,7 +153,7 @@ std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() { mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION); mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key()); if (kv.has_value_str()) { - mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str()); + mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str()); } else if (kv.has_value_int()) { mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int()); } else if (kv.has_value_bool()) { diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 2344cb4170a3..466026350ddd 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -43,12 +43,12 @@ bool handleMetricWithLogTrackers(const string what, const int metricIndex, int& logTrackerIndex) { auto logTrackerIt = logTrackerMap.find(what); if (logTrackerIt == logTrackerMap.end()) { - ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str()); + ALOGW("cannot find the LogEntryMatcher \"%s\" in config", what.c_str()); return false; } if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) { - ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the " - "\"what\" can only about one atom type.", + ALOGE("LogEntryMatcher \"%s\" has more than one tag ids. When a metric has dimension, " + "the \"what\" can only about one atom type.", what.c_str()); return false; } @@ -67,14 +67,14 @@ bool handleMetricWithConditions( unordered_map<int, std::vector<int>>& conditionToMetricMap) { auto condition_it = conditionTrackerMap.find(condition); if (condition_it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", condition.c_str()); + ALOGW("cannot find Condition \"%s\" in the config", condition.c_str()); return false; } for (const auto& link : links) { auto it = conditionTrackerMap.find(link.condition()); if (it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", link.condition().c_str()); + ALOGW("cannot find Condition \"%s\" in the config", link.condition().c_str()); return false; } allConditionTrackers[condition_it->second]->setSliced(true); @@ -110,7 +110,7 @@ bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& log new CombinationLogMatchingTracker(logMatcher.name(), index)); break; default: - ALOGE("Matcher %s malformed", logMatcher.name().c_str()); + ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str()); return false; // continue; } @@ -158,7 +158,7 @@ bool initConditions(const StatsdConfig& config, const unordered_map<string, int> break; } default: - ALOGE("Condition %s malformed", condition.name().c_str()); + ALOGE("Condition \"%s\" malformed", condition.name().c_str()); return false; } if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) { @@ -204,7 +204,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l for (int i = 0; i < config.count_metric_size(); i++) { const CountMetric& metric = config.count_metric(i); if (!metric.has_what()) { - ALOGW("cannot find what in CountMetric %s", metric.name().c_str()); + ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str()); return false; } @@ -341,7 +341,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l for (int i = 0; i < config.value_metric_size(); i++) { const ValueMetric& metric = config.value_metric(i); if (!metric.has_what()) { - ALOGW("cannot find what in ValueMetric %s", metric.name().c_str()); + ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str()); return false; } @@ -387,7 +387,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l for (int i = 0; i < config.gauge_metric_size(); i++) { const GaugeMetric& metric = config.gauge_metric(i); if (!metric.has_what()) { - ALOGW("cannot find what in ValueMetric %s", metric.name().c_str()); + ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str()); return false; } @@ -438,7 +438,7 @@ bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& me const Alert& alert = config.alert(i); const auto& itr = metricProducerMap.find(alert.metric_name()); if (itr == metricProducerMap.end()) { - ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(), + ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(), alert.metric_name().c_str()); return false; } diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 4f5df5512847..3fbcfeeee936 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -125,7 +125,7 @@ message UidMapping { } message StatsLogReport { - optional int32 metric_id = 1; + optional string metric_name = 1; optional int64 start_report_nanos = 2; diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index e75a37f98966..c8fa155de569 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -88,7 +88,7 @@ message SimpleCondition { UNKNOWN = 0; FALSE = 1; } - optional InitialValue initial_value = 5 [default = UNKNOWN]; + optional InitialValue initial_value = 5 [default = FALSE]; repeated KeyMatcher dimension = 6; } diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index 80a0068690f7..11fb011fd74e 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -70,6 +70,7 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { simpleCondition.set_start("SCREEN_TURNED_ON"); simpleCondition.set_stop("SCREEN_TURNED_OFF"); simpleCondition.set_count_nesting(false); + simpleCondition.set_initial_value(SimpleCondition_InitialValue_UNKNOWN); unordered_map<string, int> trackerNameIndexMap; trackerNameIndexMap["SCREEN_TURNED_ON"] = 0; diff --git a/cmds/statsd/tools/Android.mk b/cmds/statsd/tools/Android.mk new file mode 100644 index 000000000000..faf2d2c7d4e7 --- /dev/null +++ b/cmds/statsd/tools/Android.mk @@ -0,0 +1,20 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +# Include the sub-makefiles +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk new file mode 100644 index 000000000000..1bd5f30b2f5c --- /dev/null +++ b/cmds/statsd/tools/dogfood/Android.mk @@ -0,0 +1,34 @@ +# 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. +# +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_SRC_FILES += ../../src/stats_log.proto \ + ../../src/atoms_copy.proto + +LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/ + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := StatsdDogfood +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true +LOCAL_DEX_PREOPT := false +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml new file mode 100644 index 000000000000..cd76c9d379b5 --- /dev/null +++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.statsd.dogfood" + android:sharedUserId="android.uid.system" + android:versionCode="1" + android:versionName="1.0" > + + <uses-permission android:name="android.permission.DUMP" /> + + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" > + <activity + android:name=".MainActivity" + android:label="@string/app_name" + android:launchMode="singleTop" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..55621cc1074f --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..11ec2068be19 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..7c02b784aa5d --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..915d91441349 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml new file mode 100644 index 000000000000..39978971d6b4 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <Button + android:id="@+id/push_config" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/holo_green_light" + android:text="@string/push_config"/> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_a_wake_lock_acquire1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_get_wl1"/> + <Button android:id="@+id/app_a_wake_lock_release1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_release_wl1"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_a_wake_lock_acquire2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_get_wl2"/> + <Button android:id="@+id/app_a_wake_lock_release2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_a_release_wl2"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_b_wake_lock_acquire1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_get_wl1"/> + <Button android:id="@+id/app_b_wake_lock_release1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_release_wl1"/> + </LinearLayout> + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/app_b_wake_lock_acquire2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_get_wl2"/> + <Button android:id="@+id/app_b_wake_lock_release2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_b_release_wl2"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/plug" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/plug"/> + + <Button android:id="@+id/unplug" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/unplug"/> + </LinearLayout> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/screen_on" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screen_on"/> + + <Button android:id="@+id/screen_off" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screen_off"/> + </LinearLayout> + + <Button android:id="@+id/dump" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/holo_purple" + android:text="@string/dump"/> + + <TextView + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/report_header"/> + + <TextView + android:id="@+id/report_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config Binary files differnew file mode 100644 index 000000000000..d5b8fedfb9ec --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml new file mode 100644 index 000000000000..7690df616ca5 --- /dev/null +++ b/cmds/statsd/tools/dogfood/res/values/strings.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources> + + <string name="app_name">Statsd Dogfood</string> + + <string name="statsd_running">Statsd Running</string> + <string name="statsd_not_running">Statsd NOT Running</string> + + <string name="push_config">Push baseline config</string> + + <string name="app_a_foreground">App A foreground</string> + <string name="app_b_foreground">App B foreground</string> + + + <string name="app_a_get_wl1">App A get wl_1</string> + <string name="app_a_release_wl1">App A release wl_1</string> + + <string name="app_a_get_wl2">App A get wl_2</string> + <string name="app_a_release_wl2">App A release wl_2</string> + + <string name="app_b_get_wl1">App B get wl_1</string> + <string name="app_b_release_wl1">App B release wl_1</string> + + <string name="app_b_get_wl2">App B get wl_2</string> + <string name="app_b_release_wl2">App B release wl_2</string> + + <string name="plug">Plug</string> + <string name="unplug">Unplug</string> + + <string name="screen_on">Screen On</string> + <string name="screen_off">Screen Off</string> + + <string name="dump">DumpReport</string> + <string name="report_header">Report details</string> +</resources> diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java new file mode 100644 index 000000000000..f6dea42d448d --- /dev/null +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.statsd.dogfood; + +import android.text.format.DateFormat; + +import com.android.os.StatsLog; + +import java.util.List; + +public class DisplayProtoUtils { + public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReport report) { + sb.append("ConfigKey: "); + if (report.hasConfigKey()) { + com.android.os.StatsLog.ConfigMetricsReport.ConfigKey key = report.getConfigKey(); + sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName()) + .append("\n"); + } + + sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); + for (StatsLog.StatsLogReport log : report.getMetricsList()) { + sb.append("\n\n"); + sb.append("metric id: ").append(log.getMetricName()).append("\n"); + sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n"); + sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n"); + + switch (log.getDataCase()) { + case DURATION_METRICS: + sb.append("Duration metric data\n"); + displayDurationMetricData(sb, log); + break; + case EVENT_METRICS: + sb.append("Event metric data\n"); + displayEventMetricData(sb, log); + break; + case COUNT_METRICS: + sb.append("Count metric data\n"); + displayCountMetricData(sb, log); + break; + case GAUGE_METRICS: + sb.append("Gauge metric data\n"); + displayGaugeMetricData(sb, log); + break; + case VALUE_METRICS: + sb.append("Value metric data\n"); + displayValueMetricData(sb, log); + break; + case DATA_NOT_SET: + sb.append("No metric data\n"); + break; + } + } + } + + public static String getDateStr(long nanoSec) { + return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); + } + + private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) { + for (com.android.os.StatsLog.KeyValuePair kv : pairs) { + sb.append(kv.getKey()).append(":"); + if (kv.hasValueBool()) { + sb.append(kv.getValueBool()); + } else if (kv.hasValueFloat()) { + sb.append(kv.getValueFloat()); + } else if (kv.hasValueInt()) { + sb.append(kv.getValueInt()); + } else if (kv.hasValueStr()) { + sb.append(kv.getValueStr()); + } + sb.append(" "); + } + } + + public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper + = log.getDurationMetrics(); + sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); + for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { + sb.append("dimension: "); + displayDimension(sb, duration.getDimensionList()); + sb.append("\n"); + + for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { + sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") + .append(getDateStr(info.getEndBucketNanos())).append("] -> ") + .append(info.getDurationNanos()).append(" ns\n"); + } + } + } + + public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n"); + StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper = + log.getEventMetrics(); + for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) { + sb.append(getDateStr(event.getTimestampNanos())).append(": "); + switch (event.getAtom().getPushedCase()) { + case SETTING_CHANGED: + sb.append("SETTING_CHANGED\n"); + break; + case SYNC_STATE_CHANGED: + sb.append("SYNC_STATE_CHANGED\n"); + break; + case AUDIO_STATE_CHANGED: + sb.append("AUDIO_STATE_CHANGED\n"); + break; + case CAMERA_STATE_CHANGED: + sb.append("CAMERA_STATE_CHANGED\n"); + break; + case ISOLATED_UID_CHANGED: + sb.append("ISOLATED_UID_CHANGED\n"); + break; + case SCREEN_STATE_CHANGED: + sb.append("SCREEN_STATE_CHANGED\n"); + break; + case SENSOR_STATE_CHANGED: + sb.append("SENSOR_STATE_CHANGED\n"); + break; + case BATTERY_LEVEL_CHANGED: + sb.append("BATTERY_LEVEL_CHANGED\n"); + break; + case PLUGGED_STATE_CHANGED: + sb.append("PLUGGED_STATE_CHANGED\n"); + break; + case WAKEUP_ALARM_OCCURRED: + sb.append("WAKEUP_ALARM_OCCURRED\n"); + break; + case BLE_SCAN_STATE_CHANGED: + sb.append("BLE_SCAN_STATE_CHANGED\n"); + break; + case CHARGING_STATE_CHANGED: + sb.append("CHARGING_STATE_CHANGED\n"); + break; + case GPS_SCAN_STATE_CHANGED: + sb.append("GPS_SCAN_STATE_CHANGED\n"); + break; + case KERNEL_WAKEUP_REPORTED: + sb.append("KERNEL_WAKEUP_REPORTED\n"); + break; + case WAKELOCK_STATE_CHANGED: + sb.append("WAKELOCK_STATE_CHANGED\n"); + break; + case WIFI_LOCK_STATE_CHANGED: + sb.append("WIFI_LOCK_STATE_CHANGED\n"); + break; + case WIFI_SCAN_STATE_CHANGED: + sb.append("WIFI_SCAN_STATE_CHANGED\n"); + break; + case BLE_SCAN_RESULT_RECEIVED: + sb.append("BLE_SCAN_RESULT_RECEIVED\n"); + break; + case DEVICE_ON_STATUS_CHANGED: + sb.append("DEVICE_ON_STATUS_CHANGED\n"); + break; + case FLASHLIGHT_STATE_CHANGED: + sb.append("FLASHLIGHT_STATE_CHANGED\n"); + break; + case SCREEN_BRIGHTNESS_CHANGED: + sb.append("SCREEN_BRIGHTNESS_CHANGED\n"); + break; + case UID_PROCESS_STATE_CHANGED: + sb.append("UID_PROCESS_STATE_CHANGED\n"); + break; + case UID_WAKELOCK_STATE_CHANGED: + sb.append("UID_WAKELOCK_STATE_CHANGED\n"); + break; + case DEVICE_TEMPERATURE_REPORTED: + sb.append("DEVICE_TEMPERATURE_REPORTED\n"); + break; + case SCHEDULED_JOB_STATE_CHANGED: + sb.append("SCHEDULED_JOB_STATE_CHANGED\n"); + break; + case MEDIA_CODEC_ACTIVITY_CHANGED: + sb.append("MEDIA_CODEC_ACTIVITY_CHANGED\n"); + break; + case WIFI_SIGNAL_STRENGTH_CHANGED: + sb.append("WIFI_SIGNAL_STRENGTH_CHANGED\n"); + break; + case PHONE_SIGNAL_STRENGTH_CHANGED: + sb.append("PHONE_SIGNAL_STRENGTH_CHANGED\n"); + break; + case DEVICE_IDLE_MODE_STATE_CHANGED: + sb.append("DEVICE_IDLE_MODE_STATE_CHANGED\n"); + break; + case BATTERY_SAVER_MODE_STATE_CHANGED: + sb.append("BATTERY_SAVER_MODE_STATE_CHANGED\n"); + break; + case PROCESS_LIFE_CYCLE_STATE_CHANGED: + sb.append("PROCESS_LIFE_CYCLE_STATE_CHANGED\n"); + break; + case ACTIVITY_FOREGROUND_STATE_CHANGED: + sb.append("ACTIVITY_FOREGROUND_STATE_CHANGED\n"); + break; + case BLE_UNOPTIMIZED_SCAN_STATE_CHANGED: + sb.append("BLE_UNOPTIMIZED_SCAN_STATE_CHANGED\n"); + break; + case LONG_PARTIAL_WAKELOCK_STATE_CHANGED: + sb.append("LONG_PARTIAL_WAKELOCK_STATE_CHANGED\n"); + break; + case PUSHED_NOT_SET: + sb.append("PUSHED_NOT_SET\n"); + break; + } + } + } + + public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper + = log.getCountMetrics(); + sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); + for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { + sb.append("dimension: "); + displayDimension(sb, count.getDimensionList()); + sb.append("\n"); + + for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { + sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-") + .append(getDateStr(info.getEndBucketNanos())).append("] -> ") + .append(info.getCount()).append("\n"); + } + } + } + + public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + sb.append("Display me!"); + } + + public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { + sb.append("Display me!"); + } +} diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java new file mode 100644 index 000000000000..5e3160e11849 --- /dev/null +++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.statsd.dogfood; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.util.Log; +import android.util.StatsLog; +import android.util.StatsManager; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; +import android.os.IStatsManager; +import android.os.ServiceManager; + +import java.io.InputStream; + +import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport; + +public class MainActivity extends Activity { + private final static String TAG = "StatsdDogfood"; + + final int[] mUids = {11111111, 2222222}; + StatsManager mStatsManager; + TextView mReportText; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + + findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(0, "wl_1"); + } + }); + + findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(1, "wl_1"); + } + }); + + findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(0, "wl_2"); + } + }); + + findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockAcquire(1, "wl_2"); + } + }); + + findViewById(R.id.app_a_wake_lock_release1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(0, "wl_1"); + } + }); + + + findViewById(R.id.app_b_wake_lock_release1).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(1, "wl_1"); + } + }); + + findViewById(R.id.app_a_wake_lock_release2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(0, "wl_2"); + } + }); + + + findViewById(R.id.app_b_wake_lock_release2).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + onWakeLockRelease(1, "wl_2"); + } + }); + + + findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 1); + } + }); + + findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 0); + } + }); + + findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 2); + } + }); + + findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 1); + } + }); + + mReportText = (TextView) findViewById(R.id.report_text); + + findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!statsdRunning()) { + return; + } + if (mStatsManager != null) { + byte[] data = mStatsManager.getData("fake"); + if (data != null) { + displayData(data); + } else { + mReportText.setText("Failed!"); + } + } + } + }); + + findViewById(R.id.push_config).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + try { + if (!statsdRunning()) { + return; + } + Resources res = getResources(); + InputStream inputStream = res.openRawResource(R.raw.statsd_baseline_config); + + byte[] config = new byte[inputStream.available()]; + inputStream.read(config); + if (mStatsManager != null) { + if (mStatsManager.addConfiguration("fake", + config, getPackageName(), MainActivity.this.getClass().getName())) { + Toast.makeText( + MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(MainActivity.this, "Config push FAILED!", + Toast.LENGTH_LONG).show(); + } + } + } catch (Exception e) { + Toast.makeText(MainActivity.this, "failed to read config", Toast.LENGTH_LONG); + } + } + }); + mStatsManager = (StatsManager) getSystemService("stats"); + } + + private boolean statsdRunning() { + if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) { + Log.d(TAG, "Statsd not running"); + Toast.makeText(MainActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show(); + return false; + } + return true; + } + + @Override + public void onNewIntent(Intent intent) { + Log.d(TAG, "new intent: " + intent.getIntExtra("pkg", 0)); + int pkg = intent.getIntExtra("pkg", 0); + String name = intent.getStringExtra("name"); + if (intent.hasExtra("acquire")) { + onWakeLockAcquire(pkg, name); + } else if (intent.hasExtra("release")) { + onWakeLockRelease(pkg, name); + } + } + + private void displayData(byte[] data) { + com.android.os.StatsLog.ConfigMetricsReport report = null; + boolean good = false; + if (data != null) { + try { + report = com.android.os.StatsLog.ConfigMetricsReport.parseFrom(data); + good = true; + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + // display it in the text view. + } + } + int size = data == null ? 0 : data.length; + StringBuilder sb = new StringBuilder(); + sb.append(good ? "Proto parsing OK!" : "Proto parsing Error!"); + sb.append(" size:").append(size).append("\n"); + + if (good && report != null) { + displayLogReport(sb, report); + mReportText.setText(sb.toString()); + } + } + + + private void onWakeLockAcquire(int id, String name) { + if (id > 1) { + Log.d(TAG, "invalid pkg id"); + return; + } + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1); + StringBuilder sb = new StringBuilder(); + sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) + .append(", ").append(name).append(", 1);"); + Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show(); + } + + private void onWakeLockRelease(int id, String name) { + if (id > 1) { + Log.d(TAG, "invalid pkg id"); + return; + } + StatsLog.write(10, mUids[id], 0, name, 0); + StringBuilder sb = new StringBuilder(); + sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) + .append(", ").append(name).append(", 0);"); + Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show(); + } +} diff --git a/core/java/Android.bp b/core/java/Android.bp index 1503445a1b02..d8c79293f9ba 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -3,7 +3,26 @@ filegroup { srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"], } -filegroup { - name: "IKeystoreService.aidl", +// only used by key_store_service +cc_library_static { + name: "libkeystore_aidl", srcs: ["android/security/IKeystoreService.aidl"], + aidl: { + export_aidl_headers: true, + include_dirs: ["frameworks/base/core/java/"], + }, + header_libs: [ + "libkeystore_headers", + ], + shared_libs: [ + "libbinder", + "libcutils", + "libhardware", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "liblog", + "libselinux", + "libutils", + ], } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index a558d6850af1..8824643db447 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -358,6 +358,11 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; + /** + * Action to lock the screen + */ + public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; + private static final String LOG_TAG = "AccessibilityService"; /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 064e97828f06..02b7f8c59ea6 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -519,11 +519,15 @@ public class ActivityManager { * process that contains activities. */ public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16; + /** @hide Process is being cached for later use and has an activity that corresponds + * to an existing recent task. */ + public static final int PROCESS_STATE_CACHED_RECENT = 17; + /** @hide Process is being cached for later use and is empty. */ - public static final int PROCESS_STATE_CACHED_EMPTY = 17; + public static final int PROCESS_STATE_CACHED_EMPTY = 18; /** @hide Process does not exist. */ - public static final int PROCESS_STATE_NONEXISTENT = 18; + public static final int PROCESS_STATE_NONEXISTENT = 19; // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 9e926bd63a6a..d1aacad30a64 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -72,6 +72,7 @@ interface INotificationManager int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getDeletedChannelCount(String pkg, int uid); void deleteNotificationChannelGroup(String pkg, String channelGroupId); + NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId); ParceledListSlice getNotificationChannelGroups(String pkg); boolean onlyHasDefaultChannel(String pkg, int uid); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index f931589b50a2..659cf169e994 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -93,6 +93,58 @@ public class NotificationManager { private static boolean localLOGV = false; /** + * Intent that is broadcast when a {@link NotificationChannel} is blocked + * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked + * (when {@link NotificationChannel#getImportance()} is anything other than + * {@link #IMPORTANCE_NONE}). + * + * This broadcast is only sent to the app that owns the channel that has changed. + * + * Input: nothing + * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID} + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = + "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"; + + /** + * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or + * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the + * object which has a new blocked state. + * + * The value will be the {@link NotificationChannel#getId()} of the channel for + * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and + * the {@link NotificationChannelGroup#getId()} of the group for + * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}. + */ + public static final String EXTRA_BLOCK_STATE_CHANGED_ID = + "android.app.extra.BLOCK_STATE_CHANGED_ID"; + + /** + * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or + * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the new blocked + * state as a boolean. + * + * The value will be {@code true} if this channel or group is now blocked and {@code false} if + * this channel or group is now unblocked. + */ + public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE"; + + + /** + * Intent that is broadcast when a {@link NotificationChannelGroup} is + * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked. + * + * This broadcast is only sent to the app that owns the channel group that has changed. + * + * Input: nothing + * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID} + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = + "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"; + + /** * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes. * This broadcast is only sent to registered receivers. * @@ -504,6 +556,20 @@ public class NotificationManager { } /** + * Returns the notification channel group settings for a given channel group id. + * + * The channel group must belong to your package, or null will be returned. + */ + public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) { + INotificationManager service = getService(); + try { + return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns all notification channel groups belonging to the calling app. */ public List<NotificationChannelGroup> getNotificationChannelGroups() { diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 530d84b420a3..7c40b4eaf375 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -244,6 +244,13 @@ public class JobInfo implements Parcelable { public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0; /** + * Allows this job to run despite doze restrictions as long as the app is in the foreground + * or on the temporary whitelist + * @hide + */ + public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1; + + /** * @hide */ public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0; @@ -1333,6 +1340,30 @@ public class JobInfo implements Parcelable { } /** + * Setting this to true indicates that this job is important while the scheduling app + * is in the foreground or on the temporary whitelist for background restrictions. + * This means that the system will relax doze restrictions on this job during this time. + * + * Apps should use this flag only for short jobs that are essential for the app to function + * properly in the foreground. + * + * Note that once the scheduling app is no longer whitelisted from background restrictions + * and in the background, or the job failed due to unsatisfied constraints, + * this job should be expected to behave like other jobs without this flag. + * + * @param importantWhileForeground whether to relax doze restrictions for this job when the + * app is in the foreground. False by default. + */ + public Builder setImportantWhileForeground(boolean importantWhileForeground) { + if (importantWhileForeground) { + mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND; + } else { + mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND); + } + return this; + } + + /** * Set whether or not to persist this job across device reboots. * * @param isPersisted True to indicate that the job will be written to @@ -1395,6 +1426,10 @@ public class JobInfo implements Parcelable { "persisted job"); } } + if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) { + throw new IllegalArgumentException("An important while foreground job cannot " + + "have a time delay"); + } if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { throw new IllegalArgumentException("An idle mode job will not respect any" + " back-off policy, so calling setBackoffCriteria with" + diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 58fb260814e5..c5f227215b17 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -33,7 +33,6 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.widget.RemoteViews; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; @@ -41,6 +40,7 @@ import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * A slice is a piece of app content and actions that can be surfaced outside of the app. @@ -54,7 +54,7 @@ public final class Slice implements Parcelable { * @hide */ @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED, - HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL}) + HINT_NO_TINT, HINT_PARTIAL}) public @interface SliceHint{ } /** @@ -62,7 +62,7 @@ public final class Slice implements Parcelable { * the content should be used in the shortcut representation of the slice (icon, label, action), * normally this should be indicated by adding the hint on the action containing that content. * - * @see SliceItem#TYPE_ACTION + * @see SliceItem#FORMAT_ACTION */ public static final String HINT_TITLE = "title"; /** @@ -90,20 +90,6 @@ public final class Slice implements Parcelable { */ public static final String HINT_SELECTED = "selected"; /** - * Hint to indicate that this is a message as part of a communication - * sequence in this slice. - */ - public static final String HINT_MESSAGE = "message"; - /** - * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}. - */ - public static final String HINT_SOURCE = "source"; - /** - * Hint that list items within this slice or subslice would appear better - * if organized horizontally. - */ - public static final String HINT_HORIZONTAL = "horizontal"; - /** * Hint to indicate that this content should not be tinted. */ public static final String HINT_NO_TINT = "no_tint"; @@ -123,23 +109,31 @@ public final class Slice implements Parcelable { */ public static final String HINT_TOGGLE = "toggle"; /** + * Hint that list items within this slice or subslice would appear better + * if organized horizontally. + */ + public static final String HINT_HORIZONTAL = "horizontal"; + /** * Hint to indicate that this slice is incomplete and an update will be sent once * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the * OS and should not be cached by apps. */ public static final String HINT_PARTIAL = "partial"; - // These two are coming over from prototyping, but we probably don't want in - // public API, at least not right now. - /** - * @hide - */ - public static final String HINT_ALT = "alt"; /** * Key to retrieve an extra added to an intent when a control is changed. * @hide */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; + /** + * Subtype to indicate that this is a message as part of a communication + * sequence in this slice. + */ + public static final String SUBTYPE_MESSAGE = "message"; + /** + * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}. + */ + public static final String SUBTYPE_SOURCE = "source"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; @@ -270,10 +264,17 @@ public final class Slice implements Parcelable { * Add a sub-slice to the slice being constructed */ public Builder addSubSlice(@NonNull Slice slice) { - List<String> hints = slice.getHints(); - slice.mSpec = null; - mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, hints.toArray( - new String[hints.size()]))); + return addSubSlice(slice, null); + } + + /** + * Add a sub-slice to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) { + mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType, + slice.getHints().toArray(new String[slice.getHints().size()]))); return this; } @@ -281,95 +282,125 @@ public final class Slice implements Parcelable { * Add an action to the slice being constructed */ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) { + return addAction(action, s, null); + } + + /** + * Add an action to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s, + @Nullable String subType) { List<String> hints = s.getHints(); s.mSpec = null; - mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, hints.toArray( + mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray( new String[hints.size()]))); return this; } /** * Add text to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Builder addText(CharSequence text, @SliceHint String... hints) { - mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints)); + public Builder addText(CharSequence text, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints)); return this; } /** * Add text to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Builder addText(CharSequence text, @SliceHint List<String> hints) { - return addText(text, hints.toArray(new String[hints.size()])); + public Builder addText(CharSequence text, @Nullable String subType, + @SliceHint List<String> hints) { + return addText(text, subType, hints.toArray(new String[hints.size()])); } /** * Add an image to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Builder addIcon(Icon icon, @SliceHint String... hints) { - mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints)); + public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint String... hints) { + mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints)); return this; } /** * Add an image to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Builder addIcon(Icon icon, @SliceHint List<String> hints) { - return addIcon(icon, hints.toArray(new String[hints.size()])); - } - - /** - * @hide This isn't final - */ - public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) { - mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints)); - return this; + public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint List<String> hints) { + return addIcon(icon, subType, hints.toArray(new String[hints.size()])); } /** * Add remote input to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Slice.Builder addRemoteInput(RemoteInput remoteInput, + public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType, @SliceHint List<String> hints) { - return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()])); + return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()])); } /** * Add remote input to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) { - mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints)); + public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT, + subType, hints)); return this; } /** * Add a color to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Builder addColor(int color, @SliceHint String... hints) { - mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints)); + public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) { + mItems.add(new SliceItem(color, SliceItem.FORMAT_COLOR, subType, hints)); return this; } /** * Add a color to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Builder addColor(int color, @SliceHint List<String> hints) { - return addColor(color, hints.toArray(new String[hints.size()])); + public Builder addColor(int color, @Nullable String subType, + @SliceHint List<String> hints) { + return addColor(color, subType, hints.toArray(new String[hints.size()])); } /** * Add a timestamp to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Slice.Builder addTimestamp(long time, @SliceHint String... hints) { - mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints)); + public Slice.Builder addTimestamp(long time, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(time, SliceItem.FORMAT_TIMESTAMP, subType, + hints)); return this; } /** * Add a timestamp to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} */ - public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) { - return addTimestamp(time, hints.toArray(new String[hints.size()])); + public Slice.Builder addTimestamp(long time, @Nullable String subType, + @SliceHint List<String> hints) { + return addTimestamp(time, subType, hints.toArray(new String[hints.size()])); } /** @@ -404,15 +435,15 @@ public final class Slice implements Parcelable { StringBuilder sb = new StringBuilder(); for (int i = 0; i < mItems.length; i++) { sb.append(indent); - if (mItems[i].getType() == SliceItem.TYPE_SLICE) { + if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_SLICE)) { sb.append("slice:\n"); sb.append(mItems[i].getSlice().toString(indent + " ")); - } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) { + } else if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_TEXT)) { sb.append("text: "); sb.append(mItems[i].getText()); sb.append("\n"); } else { - sb.append(SliceItem.typeToString(mItems[i].getType())); + sb.append(mItems[i].getFormat()); sb.append("\n"); } } diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java index 6e69b0511207..8d81199185d2 100644 --- a/core/java/android/app/slice/SliceItem.java +++ b/core/java/android/app/slice/SliceItem.java @@ -16,8 +16,8 @@ package android.app.slice; -import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; import android.graphics.drawable.Icon; @@ -38,13 +38,13 @@ import java.util.List; * * A SliceItem a piece of content and some hints about what that content * means or how it should be displayed. The types of content can be: - * <li>{@link #TYPE_SLICE}</li> - * <li>{@link #TYPE_TEXT}</li> - * <li>{@link #TYPE_IMAGE}</li> - * <li>{@link #TYPE_ACTION}</li> - * <li>{@link #TYPE_COLOR}</li> - * <li>{@link #TYPE_TIMESTAMP}</li> - * <li>{@link #TYPE_REMOTE_INPUT}</li> + * <li>{@link #FORMAT_SLICE}</li> + * <li>{@link #FORMAT_TEXT}</li> + * <li>{@link #FORMAT_IMAGE}</li> + * <li>{@link #FORMAT_ACTION}</li> + * <li>{@link #FORMAT_COLOR}</li> + * <li>{@link #FORMAT_TIMESTAMP}</li> + * <li>{@link #FORMAT_REMOTE_INPUT}</li> * * The hints that a {@link SliceItem} are a set of strings which annotate * the content. The hints that are guaranteed to be understood by the system @@ -55,68 +55,68 @@ public final class SliceItem implements Parcelable { /** * @hide */ - @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR, - TYPE_TIMESTAMP, TYPE_REMOTE_INPUT}) + @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR, + FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT}) public @interface SliceType {} /** * A {@link SliceItem} that contains a {@link Slice} */ - public static final int TYPE_SLICE = 1; + public static final String FORMAT_SLICE = "slice"; /** * A {@link SliceItem} that contains a {@link CharSequence} */ - public static final int TYPE_TEXT = 2; + public static final String FORMAT_TEXT = "text"; /** * A {@link SliceItem} that contains an {@link Icon} */ - public static final int TYPE_IMAGE = 3; + public static final String FORMAT_IMAGE = "image"; /** * A {@link SliceItem} that contains a {@link PendingIntent} * * Note: Actions contain 2 pieces of data, In addition to the pending intent, the * item contains a {@link Slice} that the action applies to. */ - public static final int TYPE_ACTION = 4; - /** - * @hide This isn't final - */ - public static final int TYPE_REMOTE_VIEW = 5; + public static final String FORMAT_ACTION = "action"; /** * A {@link SliceItem} that contains a Color int. */ - public static final int TYPE_COLOR = 6; + public static final String FORMAT_COLOR = "color"; /** * A {@link SliceItem} that contains a timestamp. */ - public static final int TYPE_TIMESTAMP = 8; + public static final String FORMAT_TIMESTAMP = "timestamp"; /** * A {@link SliceItem} that contains a {@link RemoteInput}. */ - public static final int TYPE_REMOTE_INPUT = 9; + public static final String FORMAT_REMOTE_INPUT = "input"; /** * @hide */ protected @Slice.SliceHint String[] mHints; - private final int mType; + private final String mFormat; + private final String mSubType; private final Object mObj; /** * @hide */ - public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) { + public SliceItem(Object obj, @SliceType String format, String subType, + @Slice.SliceHint String[] hints) { mHints = hints; - mType = type; + mFormat = format; + mSubType = subType; mObj = obj; } /** * @hide */ - public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) { - this(new Pair<>(intent, slice), type, hints); + public SliceItem(PendingIntent intent, Slice slice, String format, String subType, + @Slice.SliceHint String[] hints) { + this(new Pair<>(intent, slice), format, subType, hints); } /** @@ -141,26 +141,51 @@ public final class SliceItem implements Parcelable { ArrayUtils.removeElement(String.class, mHints, hint); } - public @SliceType int getType() { - return mType; + /** + * Get the format of this SliceItem. + * <p> + * The format will be one of the following types supported by the platform: + * <li>{@link #FORMAT_SLICE}</li> + * <li>{@link #FORMAT_TEXT}</li> + * <li>{@link #FORMAT_IMAGE}</li> + * <li>{@link #FORMAT_ACTION}</li> + * <li>{@link #FORMAT_COLOR}</li> + * <li>{@link #FORMAT_TIMESTAMP}</li> + * <li>{@link #FORMAT_REMOTE_INPUT}</li> + * @see #getSubType() () + */ + public String getFormat() { + return mFormat; + } + + /** + * Get the sub-type of this SliceItem. + * <p> + * Subtypes provide additional information about the type of this information beyond basic + * interpretations inferred by {@link #getFormat()}. For example a slice may contain + * many {@link #FORMAT_TEXT} items, but only some of them may be {@link Slice#SUBTYPE_MESSAGE}. + * @see #getFormat() + */ + public String getSubType() { + return mSubType; } /** - * @return The text held by this {@link #TYPE_TEXT} SliceItem + * @return The text held by this {@link #FORMAT_TEXT} SliceItem */ public CharSequence getText() { return (CharSequence) mObj; } /** - * @return The icon held by this {@link #TYPE_IMAGE} SliceItem + * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem */ public Icon getIcon() { return (Icon) mObj; } /** - * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem + * @return The pending intent held by this {@link #FORMAT_ACTION} SliceItem */ public PendingIntent getAction() { return ((Pair<PendingIntent, Slice>) mObj).first; @@ -174,31 +199,31 @@ public final class SliceItem implements Parcelable { } /** - * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem + * @return The remote input held by this {@link #FORMAT_REMOTE_INPUT} SliceItem */ public RemoteInput getRemoteInput() { return (RemoteInput) mObj; } /** - * @return The color held by this {@link #TYPE_COLOR} SliceItem + * @return The color held by this {@link #FORMAT_COLOR} SliceItem */ public int getColor() { return (Integer) mObj; } /** - * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem + * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem */ public Slice getSlice() { - if (getType() == TYPE_ACTION) { + if (getFormat() == FORMAT_ACTION) { return ((Pair<PendingIntent, Slice>) mObj).second; } return (Slice) mObj; } /** - * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem + * @return The timestamp held by this {@link #FORMAT_TIMESTAMP} SliceItem */ public long getTimestamp() { return (Long) mObj; @@ -217,8 +242,9 @@ public final class SliceItem implements Parcelable { */ public SliceItem(Parcel in) { mHints = in.readStringArray(); - mType = in.readInt(); - mObj = readObj(mType, in); + mFormat = in.readString(); + mSubType = in.readString(); + mObj = readObj(mFormat, in); } @Override @@ -229,8 +255,9 @@ public final class SliceItem implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeStringArray(mHints); - dest.writeInt(mType); - writeObj(dest, flags, mObj, mType); + dest.writeString(mFormat); + dest.writeString(mSubType); + writeObj(dest, flags, mObj, mFormat); } /** @@ -259,49 +286,54 @@ public final class SliceItem implements Parcelable { return false; } - private void writeObj(Parcel dest, int flags, Object obj, int type) { - switch (type) { - case TYPE_SLICE: - case TYPE_REMOTE_VIEW: - case TYPE_IMAGE: - case TYPE_REMOTE_INPUT: + private static String getBaseType(String type) { + int index = type.indexOf('/'); + if (index >= 0) { + return type.substring(0, index); + } + return type; + } + + private static void writeObj(Parcel dest, int flags, Object obj, String type) { + switch (getBaseType(type)) { + case FORMAT_SLICE: + case FORMAT_IMAGE: + case FORMAT_REMOTE_INPUT: ((Parcelable) obj).writeToParcel(dest, flags); break; - case TYPE_ACTION: + case FORMAT_ACTION: ((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags); ((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags); break; - case TYPE_TEXT: - TextUtils.writeToParcel((CharSequence) mObj, dest, flags); + case FORMAT_TEXT: + TextUtils.writeToParcel((CharSequence) obj, dest, flags); break; - case TYPE_COLOR: - dest.writeInt((Integer) mObj); + case FORMAT_COLOR: + dest.writeInt((Integer) obj); break; - case TYPE_TIMESTAMP: - dest.writeLong((Long) mObj); + case FORMAT_TIMESTAMP: + dest.writeLong((Long) obj); break; } } - private static Object readObj(int type, Parcel in) { - switch (type) { - case TYPE_SLICE: + private static Object readObj(String type, Parcel in) { + switch (getBaseType(type)) { + case FORMAT_SLICE: return Slice.CREATOR.createFromParcel(in); - case TYPE_TEXT: + case FORMAT_TEXT: return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - case TYPE_IMAGE: + case FORMAT_IMAGE: return Icon.CREATOR.createFromParcel(in); - case TYPE_ACTION: - return new Pair<PendingIntent, Slice>( + case FORMAT_ACTION: + return new Pair<>( PendingIntent.CREATOR.createFromParcel(in), Slice.CREATOR.createFromParcel(in)); - case TYPE_REMOTE_VIEW: - return RemoteViews.CREATOR.createFromParcel(in); - case TYPE_COLOR: + case FORMAT_COLOR: return in.readInt(); - case TYPE_TIMESTAMP: + case FORMAT_TIMESTAMP: return in.readLong(); - case TYPE_REMOTE_INPUT: + case FORMAT_REMOTE_INPUT: return RemoteInput.CREATOR.createFromParcel(in); } throw new RuntimeException("Unsupported type " + type); @@ -318,29 +350,4 @@ public final class SliceItem implements Parcelable { return new SliceItem[size]; } }; - - /** - * @hide - */ - public static String typeToString(int type) { - switch (type) { - case TYPE_SLICE: - return "Slice"; - case TYPE_TEXT: - return "Text"; - case TYPE_IMAGE: - return "Image"; - case TYPE_ACTION: - return "Action"; - case TYPE_REMOTE_VIEW: - return "RemoteView"; - case TYPE_COLOR: - return "Color"; - case TYPE_TIMESTAMP: - return "Timestamp"; - case TYPE_REMOTE_INPUT: - return "RemoteInput"; - } - return "Unrecognized type: " + type; - } } diff --git a/core/java/android/app/slice/SliceQuery.java b/core/java/android/app/slice/SliceQuery.java index 9943c492a358..20eca880f63b 100644 --- a/core/java/android/app/slice/SliceQuery.java +++ b/core/java/android/app/slice/SliceQuery.java @@ -19,6 +19,7 @@ package android.app.slice; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.Spliterators; import java.util.stream.Collectors; @@ -37,14 +38,15 @@ public class SliceQuery { */ public static SliceItem getPrimaryIcon(Slice slice) { for (SliceItem item : slice.getItems()) { - if (item.getType() == SliceItem.TYPE_IMAGE) { + if (Objects.equals(item.getFormat(), SliceItem.FORMAT_IMAGE)) { return item; } - if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST)) + if (!(compareTypes(item, SliceItem.FORMAT_SLICE) + && item.hasHint(Slice.HINT_LIST)) && !item.hasHint(Slice.HINT_ACTIONS) && !item.hasHint(Slice.HINT_LIST_ITEM) - && (item.getType() != SliceItem.TYPE_ACTION)) { - SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE); + && !compareTypes(item, SliceItem.FORMAT_ACTION)) { + SliceItem icon = SliceQuery.find(item, SliceItem.FORMAT_IMAGE); if (icon != null) { return icon; } @@ -78,23 +80,23 @@ public class SliceQuery { /** * @hide */ - public static List<SliceItem> findAll(SliceItem s, int type) { + public static List<SliceItem> findAll(SliceItem s, String type) { return findAll(s, type, (String[]) null, null); } /** * @hide */ - public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) { + public static List<SliceItem> findAll(SliceItem s, String type, String hints, String nonHints) { return findAll(s, type, new String[]{ hints }, new String[]{ nonHints }); } /** * @hide */ - public static List<SliceItem> findAll(SliceItem s, int type, String[] hints, + public static List<SliceItem> findAll(SliceItem s, String type, String[] hints, String[] nonHints) { - return stream(s).filter(item -> (type == -1 || item.getType() == type) + return stream(s).filter(item -> compareTypes(item, type) && (item.hasHints(hints) && !item.hasAnyHints(nonHints))) .collect(Collectors.toList()); } @@ -102,45 +104,45 @@ public class SliceQuery { /** * @hide */ - public static SliceItem find(Slice s, int type, String hints, String nonHints) { + public static SliceItem find(Slice s, String type, String hints, String nonHints) { return find(s, type, new String[]{ hints }, new String[]{ nonHints }); } /** * @hide */ - public static SliceItem find(Slice s, int type) { + public static SliceItem find(Slice s, String type) { return find(s, type, (String[]) null, null); } /** * @hide */ - public static SliceItem find(SliceItem s, int type) { + public static SliceItem find(SliceItem s, String type) { return find(s, type, (String[]) null, null); } /** * @hide */ - public static SliceItem find(SliceItem s, int type, String hints, String nonHints) { + public static SliceItem find(SliceItem s, String type, String hints, String nonHints) { return find(s, type, new String[]{ hints }, new String[]{ nonHints }); } /** * @hide */ - public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) { + public static SliceItem find(Slice s, String type, String[] hints, String[] nonHints) { List<String> h = s.getHints(); - return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type, - hints, nonHints); + return find(new SliceItem(s, SliceItem.FORMAT_SLICE, null, h.toArray(new String[h.size()])), + type, hints, nonHints); } /** * @hide */ - public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) { - return stream(s).filter(item -> (item.getType() == type || type == -1) + public static SliceItem find(SliceItem s, String type, String[] hints, String[] nonHints) { + return stream(s).filter(item -> compareTypes(item, type) && (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null); } @@ -159,8 +161,8 @@ public class SliceQuery { @Override public SliceItem next() { SliceItem item = items.poll(); - if (item.getType() == SliceItem.TYPE_SLICE - || item.getType() == SliceItem.TYPE_ACTION) { + if (compareTypes(item, SliceItem.FORMAT_SLICE) + || compareTypes(item, SliceItem.FORMAT_ACTION)) { items.addAll(item.getSlice().getItems()); } return item; @@ -168,4 +170,19 @@ public class SliceQuery { }; return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); } + + /** + * @hide + */ + public static boolean compareTypes(SliceItem item, String desiredType) { + final int typeLength = desiredType.length(); + if (typeLength == 3 && desiredType.equals("*/*")) { + return true; + } + if (item.getSubType() == null && desiredType.indexOf('/') < 0) { + return item.getFormat().equals(desiredType); + } + return (item.getFormat() + "/" + item.getSubType()) + .matches(desiredType.replaceAll("\\*", ".*")); + } } diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index 33386e5ce58a..7f24c51d37a1 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -145,7 +145,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { } /** - * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks + * Starts an asynchronous load of the data. When the result is ready the callbacks * will be called on the UI thread. If a previous load has been completed and is still valid * the result may be passed to the callbacks immediately. * diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 20342807f75f..edb27cd4ecf1 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import static android.os.Build.VERSION_CODES.DONUT; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; @@ -890,6 +891,29 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int versionCode; /** + * The user-visible SDK version (ex. 26) of the framework against which the application claims + * to have been compiled, or {@code 0} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + public int compileSdkVersion; + + /** + * The development codename (ex. "O", "REL") of the framework against which the application + * claims to have been compiled, or {@code null} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + @Nullable + public String compileSdkVersionCodename; + + /** * When false, indicates that all components within this application are * considered disabled, regardless of their individually set enabled status. */ @@ -1305,6 +1329,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(targetSandboxVersion); dest.writeString(classLoaderName); dest.writeStringArray(splitClassLoaderNames); + dest.writeInt(compileSdkVersion); + dest.writeString(compileSdkVersionCodename); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -1372,6 +1398,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { targetSandboxVersion = source.readInt(); classLoaderName = source.readString(); splitClassLoaderNames = source.readStringArray(); + compileSdkVersion = source.readInt(); + compileSdkVersionCodename = source.readString(); } /** diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index ba488f6a0518..f8889b6853d6 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -289,6 +290,29 @@ public class PackageInfo implements Parcelable { /** @hide */ public boolean isStaticOverlay; + /** + * The user-visible SDK version (ex. 26) of the framework against which the application claims + * to have been compiled, or {@code 0} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + public int compileSdkVersion; + + /** + * The development codename (ex. "O", "REL") of the framework against which the application + * claims to have been compiled, or {@code null} if not specified. + * <p> + * This property is the compile-time equivalent of + * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. + * + * @hide For platform use only; we don't expect developers to need to read this value. + */ + @Nullable + public String compileSdkVersionCodename; + public PackageInfo() { } @@ -344,6 +368,8 @@ public class PackageInfo implements Parcelable { dest.writeString(overlayTarget); dest.writeInt(isStaticOverlay ? 1 : 0); dest.writeInt(overlayPriority); + dest.writeInt(compileSdkVersion); + dest.writeString(compileSdkVersionCodename); } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -396,6 +422,8 @@ public class PackageInfo implements Parcelable { overlayTarget = source.readString(); isStaticOverlay = source.readInt() != 0; overlayPriority = source.readInt(); + compileSdkVersion = source.readInt(); + compileSdkVersionCodename = source.readString(); // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1c5cf15da062..d9ac6818975c 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -678,6 +678,8 @@ public class PackageParser { pi.overlayTarget = p.mOverlayTarget; pi.overlayPriority = p.mOverlayPriority; pi.isStaticOverlay = p.mIsStaticOverlay; + pi.compileSdkVersion = p.mCompileSdkVersion; + pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; if ((flags&PackageManager.GET_GIDS) != 0) { @@ -2076,6 +2078,16 @@ public class PackageParser { pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false); + pkg.mCompileSdkVersion = sa.getInteger( + com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0); + pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion; + pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0); + if (pkg.mCompileSdkVersionCodename != null) { + pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern(); + } + pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename; + sa.recycle(); return parseBaseApkCommon(pkg, null, res, parser, flags, outError); @@ -5967,6 +5979,9 @@ public class PackageParser { public boolean mIsStaticOverlay; public boolean mTrustedOverlay; + public int mCompileSdkVersion; + public String mCompileSdkVersionCodename; + /** * Data used to feed the KeySetManagerService */ @@ -6458,6 +6473,8 @@ public class PackageParser { mOverlayPriority = dest.readInt(); mIsStaticOverlay = (dest.readInt() == 1); mTrustedOverlay = (dest.readInt() == 1); + mCompileSdkVersion = dest.readInt(); + mCompileSdkVersionCodename = dest.readString(); mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); @@ -6581,6 +6598,8 @@ public class PackageParser { dest.writeInt(mOverlayPriority); dest.writeInt(mIsStaticOverlay ? 1 : 0); dest.writeInt(mTrustedOverlay ? 1 : 0); + dest.writeInt(mCompileSdkVersion); + dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mSigningKeys); dest.writeArraySet(mUpgradeKeySets); writeKeySetMapping(dest, mKeySetMapping); diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index 8b0fef4fe2bc..5adb11964c8c 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -570,6 +570,16 @@ public final class SQLiteConnectionPool implements Closeable { mAvailableNonPrimaryConnections.clear(); } + /** + * Close non-primary connections that are not currently in use. This method is safe to use + * in finalize block as it doesn't throw RuntimeExceptions. + */ + void closeAvailableNonPrimaryConnectionsAndLogExceptions() { + synchronized (mLock) { + closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); + } + } + // Can't throw. private void closeExcessConnectionsAndLogExceptionsLocked() { int availableCount = mAvailableNonPrimaryConnections.size(); diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 863fb1986e06..09bb9c69dc09 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1740,7 +1740,8 @@ public final class SQLiteDatabase extends SQLiteClosable { private int executeSql(String sql, Object[] bindArgs) throws SQLException { acquireReference(); try { - if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { + final int statementType = DatabaseUtils.getSqlStatementType(sql); + if (statementType == DatabaseUtils.STATEMENT_ATTACH) { boolean disableWal = false; synchronized (mLock) { if (!mHasAttachedDbsLocked) { @@ -1754,11 +1755,14 @@ public final class SQLiteDatabase extends SQLiteClosable { } } - SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs); - try { + try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) { return statement.executeUpdateDelete(); } finally { - statement.close(); + // If schema was updated, close non-primary connections, otherwise they might + // have outdated schema information + if (statementType == DatabaseUtils.STATEMENT_DDL) { + mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions(); + } } } finally { releaseReference(); diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index e845359a35c4..cd551bd42e0f 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -174,6 +174,11 @@ public abstract class DisplayManagerInternal { public abstract boolean isUidPresentOnDisplay(int uid, int displayId); /** + * Persist brightness slider events. + */ + public abstract void persistBrightnessSliderEvents(); + + /** * 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/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 025d46d12567..151e62de7b70 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -34,7 +34,7 @@ interface IUsbManager /* Returns a file descriptor for communicating with the USB device. * The native fd can be passed to usb_device_new() in libusbhost. */ - ParcelFileDescriptor openDevice(String deviceName); + ParcelFileDescriptor openDevice(String deviceName, String packageName); /* Returns the currently attached USB accessory */ UsbAccessory getCurrentAccessory(); @@ -55,7 +55,7 @@ interface IUsbManager void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId); /* Returns true if the caller has permission to access the device. */ - boolean hasDevicePermission(in UsbDevice device); + boolean hasDevicePermission(in UsbDevice device, String packageName); /* Returns true if the caller has permission to access the accessory. */ boolean hasAccessoryPermission(in UsbAccessory accessory); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 6ce96698e444..bdb90bcca4f8 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -344,7 +344,7 @@ public class UsbManager { public UsbDeviceConnection openDevice(UsbDevice device) { try { String deviceName = device.getDeviceName(); - ParcelFileDescriptor pfd = mService.openDevice(deviceName); + ParcelFileDescriptor pfd = mService.openDevice(deviceName, mContext.getPackageName()); if (pfd != null) { UsbDeviceConnection connection = new UsbDeviceConnection(device); boolean result = connection.open(deviceName, pfd, mContext); @@ -400,6 +400,9 @@ public class UsbManager { * Permission might have been granted temporarily via * {@link #requestPermission(UsbDevice, PendingIntent)} or * by the user choosing the caller as the default application for the device. + * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that + * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they + * have additionally the {@link android.Manifest.permission#CAMERA} permission. * * @param device to check permissions for * @return true if caller has permission @@ -409,7 +412,7 @@ public class UsbManager { return false; } try { - return mService.hasDevicePermission(device); + return mService.hasDevicePermission(device, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -450,6 +453,10 @@ public class UsbManager { * permission was granted by the user * </ul> * + * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that + * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they + * have additionally the {@link android.Manifest.permission#CAMERA} permission. + * * @param device to request permissions for * @param pi PendingIntent for returning result */ diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index 64f8f39e2bca..d6e62cf1f88d 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -15,6 +15,7 @@ */ package android.net; +import android.annotation.NonNull; import android.annotation.StringDef; import android.os.Build; import android.os.Parcel; @@ -27,8 +28,10 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** - * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to - * RFC 4301. + * This class represents a single algorithm that can be used by an {@link IpSecTransform}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ public final class IpSecAlgorithm implements Parcelable { /** @@ -39,16 +42,16 @@ public final class IpSecAlgorithm implements Parcelable { public static final String CRYPT_AES_CBC = "cbc(aes)"; /** - * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new - * applications and is provided for legacy compatibility with 3gpp infrastructure. + * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128. */ public static final String AUTH_HMAC_MD5 = "hmac(md5)"; /** - * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in - * new applications and is provided for legacy compatibility with 3gpp infrastructure. + * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> * * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160. */ @@ -69,7 +72,7 @@ public final class IpSecAlgorithm implements Parcelable { public static final String AUTH_HMAC_SHA384 = "hmac(sha384)"; /** - * SHA512 HMAC Authentication/Integrity Algorithm + * SHA512 HMAC Authentication/Integrity Algorithm. * * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512. */ @@ -80,9 +83,9 @@ public final class IpSecAlgorithm implements Parcelable { * * <p>Valid lengths for keying material are {160, 224, 288}. * - * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key - * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per - * invocation with the same key. + * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section + * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit + * salt. RFC compliance requires that the salt must be unique per invocation with the same key. * * <p>Valid ICV (truncation) lengths are {64, 96, 128}. */ @@ -105,48 +108,47 @@ public final class IpSecAlgorithm implements Parcelable { private final int mTruncLenBits; /** - * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the - * algorithm + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. * - * @param algorithm type for IpSec. - * @param key non-null Key padded to a multiple of 8 bits. + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. */ - public IpSecAlgorithm(String algorithm, byte[] key) { + public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key) { this(algorithm, key, key.length * 8); } /** - * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the - * algorithm + * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are + * defined as constants in this class. + * + * <p>This constructor only supports algorithms that use a truncation length. i.e. + * Authentication and Authenticated Encryption algorithms. * - * @param algoName precise name of the algorithm to be used. - * @param key non-null Key padded to a multiple of 8 bits. - * @param truncLenBits the number of bits of output hash to use; only meaningful for - * Authentication or Authenticated Encryption (equivalent to ICV length). + * @param algorithm name of the algorithm. + * @param key key padded to a multiple of 8 bits. + * @param truncLenBits number of bits of output hash to use. */ - public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) { - if (!isTruncationLengthValid(algoName, truncLenBits)) { + public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) { + if (!isTruncationLengthValid(algorithm, truncLenBits)) { throw new IllegalArgumentException("Unknown algorithm or invalid length"); } - mName = algoName; + mName = algorithm; mKey = key.clone(); mTruncLenBits = Math.min(truncLenBits, key.length * 8); } - /** Retrieve the algorithm name */ + /** Get the algorithm name */ public String getName() { return mName; } - /** Retrieve the key for this algorithm */ + /** Get the key for this algorithm */ public byte[] getKey() { return mKey.clone(); } - /** - * Retrieve the truncation length, in bits, for the key in this algo. By default this will be - * the length in bits of the key. - */ + /** Get the truncation length of this algorithm, in bits */ public int getTruncationLengthBits() { return mTruncLenBits; } diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index 61b13a922dd4..e6cd3fc130a0 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -20,7 +20,12 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -/** @hide */ +/** + * This class encapsulates all the configuration parameters needed to create IPsec transforms and + * policies. + * + * @hide + */ public final class IpSecConfig implements Parcelable { private static final String TAG = "IpSecConfig"; @@ -38,6 +43,9 @@ public final class IpSecConfig implements Parcelable { // for outbound packets. It may also be used to select packets. private Network mNetwork; + /** + * This class captures the parameters that specifically apply to inbound or outbound traffic. + */ public static class Flow { // Minimum requirements for identifying a transform // SPI identifying the IPsec flow in packet processing diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index eccd5f47f2dd..a9e60ec88a8e 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -19,6 +19,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.os.Binder; import android.os.ParcelFileDescriptor; @@ -37,22 +38,28 @@ import java.net.InetAddress; import java.net.Socket; /** - * This class contains methods for managing IPsec sessions, which will perform kernel-space - * encryption and decryption of socket or Network traffic. + * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply + * confidentiality (encryption) and integrity (authentication) to IP traffic. * - * <p>An IpSecManager may be obtained by calling {@link - * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link - * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE} + * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create + * transport mode security associations and apply them to individual sockets. Applications looking + * to create a VPN should use {@link VpnService}. + * + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ @SystemService(Context.IPSEC_SERVICE) public final class IpSecManager { private static final String TAG = "IpSecManager"; /** - * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. + * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * * <p>No IPsec packet may contain an SPI of 0. + * + * @hide */ + @TestApi public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; /** @hide */ @@ -66,10 +73,12 @@ public final class IpSecManager { public static final int INVALID_RESOURCE_ID = 0; /** - * Indicates that the combination of remote InetAddress and SPI was non-unique for a given - * request. If encountered, selection of a new SPI is required before a transform may be - * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random - * or reserved using reserveSecurityParameterIndex. + * Thrown to indicate that a requested SPI is in use. + * + * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on + * one device. If this error is encountered, a new SPI is required before a transform may be + * created. This error can be avoided by calling {@link + * IpSecManager#reserveSecurityParameterIndex}. */ public static final class SpiUnavailableException extends AndroidException { private final int mSpi; @@ -78,24 +87,26 @@ public final class IpSecManager { * Construct an exception indicating that a transform with the given SPI is already in use * or otherwise unavailable. * - * @param msg Description indicating the colliding SPI + * @param msg description indicating the colliding SPI * @param spi the SPI that could not be used due to a collision */ SpiUnavailableException(String msg, int spi) { - super(msg + "(spi: " + spi + ")"); + super(msg + " (spi: " + spi + ")"); mSpi = spi; } - /** Retrieve the SPI that caused a collision */ + /** Get the SPI that caused a collision. */ public int getSpi() { return mSpi; } } /** - * Indicates that the requested system resource for IPsec, such as a socket or other system - * resource is unavailable. If this exception is thrown, try releasing allocated objects of the - * type requested. + * Thrown to indicate that an IPsec resource is unavailable. + * + * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link + * IpSecTransform}, or other system resources. If this exception is thrown, users should release + * allocated objects of the type requested. */ public static final class ResourceUnavailableException extends AndroidException { @@ -106,6 +117,13 @@ public final class IpSecManager { private final IIpSecService mService; + /** + * This class represents a reserved SPI. + * + * <p>Objects of this type are used to track reserved security parameter indices. They can be + * obtained by calling {@link IpSecManager#reserveSecurityParameterIndex} and must be released + * by calling {@link #close()} when they are no longer needed. + */ public static final class SecurityParameterIndex implements AutoCloseable { private final IIpSecService mService; private final InetAddress mRemoteAddress; @@ -113,7 +131,7 @@ public final class IpSecManager { private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; private int mResourceId; - /** Return the underlying SPI held by this object */ + /** Get the underlying SPI held by this object. */ public int getSpi() { return mSpi; } @@ -135,6 +153,7 @@ public final class IpSecManager { mCloseGuard.close(); } + /** Check that the SPI was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -197,13 +216,13 @@ public final class IpSecManager { } /** - * Reserve an SPI for traffic bound towards the specified remote address. + * Reserve a random SPI for traffic bound to or from the specified remote address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated * for this user @@ -223,17 +242,18 @@ public final class IpSecManager { } /** - * Reserve an SPI for traffic bound towards the specified remote address. + * Reserve the requested SPI for traffic bound to or from the specified remote address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. - * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param requestedSpi the requested SPI, or '0' to allocate a random SPI * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated * for this user + * @throws SpiUnavailableException indicating that the requested SPI could not be reserved */ public SecurityParameterIndex reserveSecurityParameterIndex( int direction, InetAddress remoteAddress, int requestedSpi) @@ -245,16 +265,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(Socket, IpSecTransform)}; + * Apply an IPsec transform to a stream socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a stream socket - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied * @hide */ public void applyTransportModeTransform(Socket socket, IpSecTransform transform) @@ -265,16 +297,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(DatagramSocket, IpSecTransform)}; + * Apply an IPsec transform to a datagram socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a datagram socket - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied * @hide */ public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) @@ -285,16 +329,28 @@ public final class IpSecManager { } /** - * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec - * encapsulation of the traffic flowing between the socket and the remote InetAddress of that - * transform. For security reasons, attempts to send traffic to any IP address other than the - * address associated with that transform will throw an IOException. In addition, if the - * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to - * send() or receive() until the transform is removed from the socket by calling {@link - * #removeTransportModeTransform(FileDescriptor, IpSecTransform)}; + * Apply an IPsec transform to a socket. + * + * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the + * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When + * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * unprotected traffic can resume on that socket. + * + * <p>For security reasons, the destination address of any traffic on the socket must match the + * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any + * other IP address will result in an IOException. In addition, reads and writes on the socket + * will throw IOException if the user deactivates the transform (by calling {@link + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * + * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform + * will be removed. However, inbound traffic on the old transform will continue to be decrypted + * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap + * allows rekey procedures where both transforms are valid until both endpoints are using the + * new transform and all in-flight packets have been received. * * @param socket a socket file descriptor - * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. + * @param transform a transport mode {@code IpSecTransform} + * @throws IOException indicating that the transform could not be applied */ public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { @@ -323,6 +379,7 @@ public final class IpSecManager { * Applications should probably not use this API directly. Instead, they should use {@link * VpnService} to provide VPN capability in a more generic fashion. * + * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked. * @param net a {@link Network} that will be tunneled via IP Sec. * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform. * @hide @@ -330,14 +387,19 @@ public final class IpSecManager { public void applyTunnelModeTransform(Network net, IpSecTransform transform) {} /** - * Remove a transform from a given stream socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a stream socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. * - * @param socket a socket that previously had a transform applied to it. + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket * @hide */ public void removeTransportModeTransform(Socket socket, IpSecTransform transform) @@ -348,14 +410,19 @@ public final class IpSecManager { } /** - * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a datagram socket. * - * @param socket a socket that previously had a transform applied to it. + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. + * + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket * @hide */ public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) @@ -366,14 +433,19 @@ public final class IpSecManager { } /** - * Remove a transform from a given stream socket. Once removed, traffic on the socket will not - * be encypted. This allows sockets that have been used for IPsec to be reclaimed for - * communication in the clear in the event socket reuse is desired. This operation will succeed - * regardless of the underlying state of a transform. If a transform is removed, communication - * on all sockets to which that transform was applied will fail until this method is called. + * Remove an IPsec transform from a socket. + * + * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed + * regardless of the state of the transform. Removing a transform from a socket allows the + * socket to be reused for communication in the clear. * - * @param socket a socket file descriptor that previously had a transform applied to it. + * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling + * {@link IpSecTransform#close()}, then communication on the socket will fail until this method + * is called. + * + * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket + * @throws IOException indicating that the transform could not be removed from the socket */ public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { @@ -382,7 +454,7 @@ public final class IpSecManager { } } - /* Call down to activate a transform */ + /* Call down to remove a transform */ private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { try { mService.removeTransportModeTransform(pfd, transform.getResourceId()); @@ -397,6 +469,7 @@ public final class IpSecManager { * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is * lost, all traffic will drop. * + * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked. * @param net a network that currently has transform applied to it. * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given * network @@ -405,11 +478,18 @@ public final class IpSecManager { public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} /** - * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for - * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic. + * This class provides access to a UDP encapsulation Socket. * - * <p>The socket provided by this class cannot be re-bound or closed via the inner - * FileDescriptor. Instead, disposing of this socket requires a call to close(). + * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2 + * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link + * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the + * caller. The caller should not close the {@code FileDescriptor} returned by {@link + * #getSocket}, but should use {@link #close} instead. + * + * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic + * of the next user who binds to that port. To prevent this scenario, these sockets are held + * open by the system so that they may only be closed by calling {@link #close} or when the user + * process exits. */ public static final class UdpEncapsulationSocket implements AutoCloseable { private final ParcelFileDescriptor mPfd; @@ -443,7 +523,7 @@ public final class IpSecManager { mCloseGuard.open("constructor"); } - /** Access the inner UDP Encapsulation Socket */ + /** Get the wrapped socket. */ public FileDescriptor getSocket() { if (mPfd == null) { return null; @@ -451,22 +531,19 @@ public final class IpSecManager { return mPfd.getFileDescriptor(); } - /** Retrieve the port number of the inner encapsulation socket */ + /** Get the bound port of the wrapped socket. */ public int getPort() { return mPort; } - @Override /** - * Release the resources that have been reserved for this Socket. + * Close this socket. * - * <p>This method closes the underlying socket, reducing a user's allocated sockets in the - * system. This must be done as part of cleanup following use of a socket. Failure to do so - * will cause the socket to count against a total allocation limit for IpSec and eventually - * fail due to resource limits. - * - * @param fd a file descriptor previously returned as a UDP Encapsulation socket. + * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's + * resource limits, and forgetting to close them eventually will result in {@link + * ResourceUnavailableException} being thrown. */ + @Override public void close() throws IOException { try { mService.closeUdpEncapsulationSocket(mResourceId); @@ -483,6 +560,7 @@ public final class IpSecManager { mCloseGuard.close(); } + /** Check that the socket was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -499,21 +577,14 @@ public final class IpSecManager { }; /** - * Open a socket that is bound to a free UDP port on the system. - * - * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by - * the caller. This provides safe access to a socket on a port that can later be used as a UDP - * Encapsulation port. + * Open a socket for UDP encapsulation and bind to the given port. * - * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the - * socket port. Explicitly opening this port is only necessary if communication is desired on - * that port. + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. * - * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this - * method will bind to the specified port or fail. To retrieve the port number, call {@link - * android.system.Os#getsockname(FileDescriptor)}. - * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime - * of the object. + * @param port a local UDP port + * @return a socket that is bound to the given port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open */ // Returning a socket in this fashion that has been created and bound by the system // is the only safe way to ensure that a socket is both accessible to the user and @@ -533,17 +604,16 @@ public final class IpSecManager { } /** - * Open a socket that is bound to a port selected by the system. + * Open a socket for UDP encapsulation. * - * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by - * the caller. This provides safe access to a socket on a port that can later be used as a UDP - * Encapsulation port. + * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. * - * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the - * socket port. Explicitly opening this port is only necessary if communication is desired on - * that port. + * <p>The local port of the returned socket can be obtained by calling {@link + * UdpEncapsulationSocket#getPort()}. * - * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port + * @return a socket that is bound to a local port + * @throws IOException indicating that the socket could not be opened or bound + * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open */ // Returning a socket in this fashion that has been created and bound by the system // is the only safe way to ensure that a socket is both accessible to the user and @@ -556,7 +626,7 @@ public final class IpSecManager { } /** - * Retrieve an instance of an IpSecManager within you application context + * Construct an instance of IpSecManager within an application context. * * @param context the application context for this manager * @hide diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 48b5bd5c3d5b..cda4ec762caf 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -38,27 +38,29 @@ import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; /** - * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec. + * This class represents an IPsec transform, which comprises security associations in one or both + * directions. * - * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout - * the lifetime of the underlying transform. If a transform object leaves scope, the underlying - * transform may be disabled automatically, with likely undesirable results. + * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} + * object encapsulates the properties and state of an inbound and outbound IPsec security + * association. That includes, but is not limited to, algorithm choice, key material, and allocated + * system resources. * - * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array - * of traffic or may represent a transport mode transform operating on a Socket or Sockets. + * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the + * Internet Protocol</a> */ public final class IpSecTransform implements AutoCloseable { private static final String TAG = "IpSecTransform"; /** - * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies - * to traffic towards the host. + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic towards the host. */ public static final int DIRECTION_IN = 0; /** - * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies - * to traffic from the host. + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic from the host. */ public static final int DIRECTION_OUT = 1; @@ -77,16 +79,16 @@ public final class IpSecTransform implements AutoCloseable { public static final int ENCAP_NONE = 0; /** - * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad - * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. + * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP + * header and payload. This prevents traffic from being interpreted as ESP or IKEv2. * * @hide */ public static final int ENCAP_ESPINUDP_NON_IKE = 1; /** - * IpSec traffic will be encapsulated within UDP as per <a - * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. + * IPsec traffic will be encapsulated within UDP as per + * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>. * * @hide */ @@ -165,13 +167,14 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Deactivate an IpSecTransform and free all resources for that transform that are managed by - * the system for this Transform. + * Deactivate this {@code IpSecTransform} and free allocated resources. * - * <p>Deactivating a transform while it is still applied to any Socket will result in sockets - * refusing to send or receive data. This method will silently succeed if the specified - * transform has already been removed; thus, it is always safe to attempt cleanup when a - * transform is no longer needed. + * <p>Deactivating a transform while it is still applied to a socket will result in errors on + * that socket. Make sure to remove transforms by calling {@link + * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a + * socket will not deactivate it (because one transform may be applied to multiple sockets). + * + * <p>It is safe to call this method on a transform that has already been deactivated. */ public void close() { Log.d(TAG, "Removing Transform with Id " + mResourceId); @@ -197,6 +200,7 @@ public final class IpSecTransform implements AutoCloseable { } } + /** Check that the transform was closed properly. */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -264,65 +268,63 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Builder object to facilitate the creation of IpSecTransform objects. - * - * <p>Apply additional properties to the transform and then call a build() method to return an - * IpSecTransform object. - * - * @see Builder#buildTransportModeTransform(InetAddress) + * This class is used to build {@link IpSecTransform} objects. */ public static class Builder { private Context mContext; private IpSecConfig mConfig; /** - * Add an encryption algorithm to the transform for the given direction. + * Set the encryption algorithm for the given direction. * - * <p>If encryption is set for a given direction without also providing an SPI for that - * direction, creation of an IpSecTransform will fail upon calling a build() method. + * <p>If encryption is set for a direction without also providing an SPI for that direction, + * creation of an {@code IpSecTransform} will fail when attempting to build the transform. * - * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * <p>Encryption is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. */ public IpSecTransform.Builder setEncryption( @TransformDirection int direction, IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. mConfig.setEncryption(direction, algo); return this; } /** - * Add an authentication/integrity algorithm to the transform. + * Set the authentication (integrity) algorithm for the given direction. * - * <p>If authentication is set for a given direction without also providing an SPI for that - * direction, creation of an IpSecTransform will fail upon calling a build() method. + * <p>If authentication is set for a direction without also providing an SPI for that + * direction, creation of an {@code IpSecTransform} will fail when attempting to build the + * transform. * - * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * <p>Authentication is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. */ public IpSecTransform.Builder setAuthentication( @TransformDirection int direction, IpSecAlgorithm algo) { + // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. mConfig.setAuthentication(direction, algo); return this; } /** - * Add an authenticated encryption algorithm to the transform for the given direction. + * Set the authenticated encryption algorithm for the given direction. * * <p>If an authenticated encryption algorithm is set for a given direction without also - * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling - * a build() method. + * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when + * attempting to build the transform. * * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as - * referred to in RFC 4301) + * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). * * <p>Authenticated encryption is mutually exclusive with encryption and authentication. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to * be applied. */ @@ -333,19 +335,16 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Set the SPI, which uniquely identifies a particular IPsec session from others. Because - * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a - * given destination address. + * Set the SPI for the given direction. * - * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as - * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int, - * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being - * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}. + * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies + * packets to a given destination address. To prevent SPI collisions, values should be + * reserved by calling {@link IpSecManager#reserveSecurityParameterIndex}. * - * <p>Unless an SPI is set for a given direction, traffic in that direction will be - * sent/received without any IPsec applied. + * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction + * will not be encrypted or authenticated. * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed * traffic */ @@ -356,11 +355,10 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Specify the network on which this transform will emit its traffic; (otherwise it will - * emit on the default network). + * Set the {@link Network} which will carry tunneled traffic. * - * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in - * tunnel mode. + * <p>Restricts the transformed traffic to a particular {@link Network}. This is required + * for tunnel mode, otherwise tunneled traffic would be sent on the default network. * * @hide */ @@ -371,15 +369,18 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Add UDP encapsulation to an IPv4 transform + * Add UDP encapsulation to an IPv4 transform. * - * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for - * details on how UDP should be applied to IPsec. + * <p>This allows IPsec traffic to pass through a NAT. * - * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and - * receiving encapsulating traffic. - * @param remotePort the UDP port number of the remote that will send and receive - * encapsulated traffic. In the case of IKE, this is likely port 4500. + * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec + * ESP Packets</a> + * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23, + * NAT Traversal of IKEv2</a> + * + * @param localSocket a socket for sending and receiving encapsulated traffic + * @param remotePort the UDP port number of the remote host that will send and receive + * encapsulated traffic. In the case of IKEv2, this should be port 4500. */ public IpSecTransform.Builder setIpv4Encapsulation( IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { @@ -393,12 +394,15 @@ public final class IpSecTransform implements AutoCloseable { // TODO: Probably a better exception to throw for NATTKeepalive failure // TODO: Specify the needed NATT keepalive permission. /** - * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded - * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot - * be activated, then the transform will fail to activate and throw an IOException. + * Set NAT-T keepalives to be sent with a given interval. + * + * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T + * keepalive is requested but cannot be activated, then creation of an {@link + * IpSecTransform} will fail when calling the build method. + * + * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be + * between 20s and 3600s. * - * @param intervalSeconds the maximum number of seconds between keepalive packets, no less - * than 20s and no more than 3600s. * @hide */ @SystemApi @@ -408,36 +412,29 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform. - * Some parameters have interdependencies that are checked at build time. If a well-formed - * transform cannot be created from the supplied parameters, this method will throw an - * Exception. + * Build a transport mode {@link IpSecTransform}. * - * <p>Upon a successful return from this call, the provided IpSecTransform will be active - * and may be applied to sockets. If too many IpSecTransform objects are active for a given - * user this operation will fail and throw ResourceUnavailableException. To avoid these - * exceptions, unused Transform objects must be cleaned up by calling {@link - * IpSecTransform#close()} when they are no longer needed. + * <p>This builds and activates a transport mode transform. Note that an active transform + * will not affect any network traffic until it has been applied to one or more sockets. * - * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this - * socket will cause the transform to be applied. - * <p>Note that an active transform will not impact any network traffic until it has - * been applied to one or more Sockets. Calling this method is a necessary precondition - * for applying it to a socket, but is not sufficient to actually apply IPsec. + * @see IpSecManager#applyTransportModeTransform + * + * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use + * this transform * @throws IllegalArgumentException indicating that a particular combination of transform - * properties is invalid. - * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms - * may be allocated - * @throws SpiUnavailableException if the SPI collides with an existing transform - * (unlikely). - * @throws ResourceUnavailableException if the current user currently has exceeded the - * number of allowed active transforms. + * properties is invalid + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are + * active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors */ public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { mConfig.setMode(MODE_TRANSPORT); mConfig.setRemoteAddress(remoteAddress.getHostAddress()); + // FIXME: modifying a builder after calling build can change the built transform. return new IpSecTransform(mContext, mConfig).activate(); } @@ -465,9 +462,9 @@ public final class IpSecTransform implements AutoCloseable { } /** - * Create a new IpSecTransform.Builder to construct an IpSecTransform + * Create a new IpSecTransform.Builder. * - * @param context current Context + * @param context current context */ public Builder(@NonNull Context context) { Preconditions.checkNotNull(context); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index a8bd940326d6..af91e81959d1 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -222,8 +222,10 @@ public abstract class BatteryStats implements Parcelable { * - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly] * New in version 27: * - Always On Display (screen doze mode) time and power + * New in version 28: + * - Light/Deep Doze power */ - static final int CHECKIN_VERSION = 27; + static final int CHECKIN_VERSION = 28; /** * Old version, we hit 9 and ran out of room, need to remove. @@ -2696,6 +2698,18 @@ public abstract class BatteryStats implements Parcelable { public abstract long getUahDischarge(int which); /** + * @return the amount of battery discharge while the device is in light idle mode, measured in + * micro-Ampere-hours. + */ + public abstract long getUahDischargeLightDoze(int which); + + /** + * @return the amount of battery discharge while the device is in deep idle mode, measured in + * micro-Ampere-hours. + */ + public abstract long getUahDischargeDeepDoze(int which); + + /** * Returns the estimated real battery capacity, which may be less than the capacity * declared by the PowerProfile. * @return The estimated battery capacity in mAh. @@ -3327,6 +3341,8 @@ public abstract class BatteryStats implements Parcelable { final long dischargeCount = getUahDischarge(which); final long dischargeScreenOffCount = getUahDischargeScreenOff(which); final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which); + final long dischargeLightDozeCount = getUahDischargeLightDoze(which); + final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which); final StringBuilder sb = new StringBuilder(128); @@ -3497,14 +3513,16 @@ public abstract class BatteryStats implements Parcelable { getDischargeStartLevel()-getDischargeCurrentLevel(), getDischargeAmountScreenOn(), getDischargeAmountScreenOff(), dischargeCount / 1000, dischargeScreenOffCount / 1000, - getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000); + getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000, + dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000); } else { dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA, getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(), getDischargeAmountScreenOnSinceCharge(), getDischargeAmountScreenOffSinceCharge(), dischargeCount / 1000, dischargeScreenOffCount / 1000, - getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000); + getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000, + dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000); } if (reqUid < 0) { @@ -4169,6 +4187,26 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + final long dischargeLightDozeCount = getUahDischargeLightDoze(which); + if (dischargeLightDozeCount >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Device light doze discharge: "); + sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0)); + sb.append(" mAh"); + pw.println(sb.toString()); + } + + final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which); + if (dischargeDeepDozeCount >= 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Device deep doze discharge: "); + sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0)); + sb.append(" mAh"); + pw.println(sb.toString()); + } + pw.print(" Start clock time: "); pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString()); @@ -7131,6 +7169,10 @@ public abstract class BatteryStats implements Parcelable { getUahDischargeScreenOff(which) / 1000); proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE, getUahDischargeScreenDoze(which) / 1000); + proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE, + getUahDischargeLightDoze(which) / 1000); + proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE, + getUahDischargeDeepDoze(which) / 1000); proto.end(bdToken); // Time remaining diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index d0920187498f..36121d452d96 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1780,7 +1780,7 @@ public final class Parcel { final int truncatedSize = Math.min(stackTrace.length, 5); StringBuilder sb = new StringBuilder(); for (int i = 0; i < truncatedSize; i++) { - sb.append("\tat ").append(stackTrace[i]); + sb.append("\tat ").append(stackTrace[i]).append('\n'); } writeString(sb.toString()); final int payloadPosition = dataPosition(); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 0fce7a454717..5dd8d05cfb98 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -387,6 +387,12 @@ public final class PowerManager { public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6; /** + * Go to sleep reason code: Going to sleep by request of an accessibility service + * @hide + */ + public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7; + + /** * Go to sleep flag: Skip dozing state and directly go to full sleep. * @hide */ diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java index ad9fbfbfae40..6a62424cc117 100644 --- a/core/java/android/os/ShellCallback.java +++ b/core/java/android/os/ShellCallback.java @@ -105,6 +105,9 @@ public class ShellCallback implements Parcelable { ShellCallback(Parcel in) { mLocal = false; mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder()); + if (mShellCallback != null) { + Binder.allowBlocking(mShellCallback.asBinder()); + } } public static final Parcelable.Creator<ShellCallback> CREATOR diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java index d75219fdfd11..fa05a5e1b22e 100644 --- a/core/java/android/os/ShellCommand.java +++ b/core/java/android/os/ShellCommand.java @@ -91,7 +91,13 @@ public abstract class ShellCommand { mCmd = cmd; mResultReceiver = resultReceiver; - if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget); + if (DEBUG) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here); + Slog.d(TAG, "Calling uid=" + Binder.getCallingUid() + + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback()); + } int res = -1; try { res = onCommand(mCmd); @@ -227,15 +233,19 @@ public abstract class ShellCommand { * @hide */ public ParcelFileDescriptor openFileForSystem(String path, String mode) { + if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode); try { ParcelFileDescriptor pfd = getShellCallback().openFile(path, "u:r:system_server:s0", mode); if (pfd != null) { + if (DEBUG) Slog.d(TAG, "Got file: " + pfd); return pfd; } } catch (RuntimeException e) { + if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage()); getErrPrintWriter().println("Failure opening file: " + e.getMessage()); } + if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path); getErrPrintWriter().println("Error: Unable to open file: " + path); getErrPrintWriter().println("Consider using a file under /data/local/tmp/"); return null; diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index cd1b1908e4d8..d07b2ac980eb 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -381,4 +381,9 @@ public abstract class WindowManagerInternal { * Sets callback to DragDropController. */ public abstract void registerDragDropControllerCallback(IDragDropCallback callback); + + /** + * @see android.view.IWindowManager#lockNow + */ + public abstract void lockNow(); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e330916130dd..a2c55b091860 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -90,6 +90,31 @@ import java.util.concurrent.Executor; * another process. The hierarchy is inflated from a layout resource * file, and this class provides some basic operations for modifying * the content of the inflated hierarchy. + * + * <p>{@code RemoteViews} is limited to support for the following layouts:</p> + * <ul> + * <li>{@link android.widget.AdapterViewFlipper}</li> + * <li>{@link android.widget.FrameLayout}</li> + * <li>{@link android.widget.GridLayout}</li> + * <li>{@link android.widget.GridView}</li> + * <li>{@link android.widget.LinearLayout}</li> + * <li>{@link android.widget.ListView}</li> + * <li>{@link android.widget.RelativeLayout}</li> + * <li>{@link android.widget.StackView}</li> + * <li>{@link android.widget.ViewFlipper}</li> + * </ul> + * <p>And the following widgets:</p> + * <ul> + * <li>{@link android.widget.AnalogClock}</li> + * <li>{@link android.widget.Button}</li> + * <li>{@link android.widget.Chronometer}</li> + * <li>{@link android.widget.ImageButton}</li> + * <li>{@link android.widget.ImageView}</li> + * <li>{@link android.widget.ProgressBar}</li> + * <li>{@link android.widget.TextClock}</li> + * <li>{@link android.widget.TextView}</li> + * </ul> + * <p>Descendants of these classes are not supported.</p> */ public class RemoteViews implements Parcelable, Filter { diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index 7519fce467d9..fbdf17d84aa8 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -102,6 +102,7 @@ public final class ProcessState { STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 83b7d2f948f8..a1e6fd8e22f9 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -43,6 +43,7 @@ import dalvik.system.VMRuntime; import java.io.Closeable; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.util.List; @@ -118,6 +119,17 @@ public class NativeLibraryHelper { return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable); } + public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException { + final long[] apkHandles = new long[1]; + final String path = lite.baseCodePath; + apkHandles[0] = nativeOpenApkFd(fd, path); + if (apkHandles[0] == 0) { + throw new IOException("Unable to open APK " + path + " from fd " + fd); + } + + return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable); + } + Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs, boolean debuggable) { this.apkHandles = apkHandles; @@ -152,6 +164,7 @@ public class NativeLibraryHelper { } private static native long nativeOpenApk(String path); + private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath); private static native void nativeClose(long handle); private static native long nativeSumNativeBinaries(long handle, String cpuAbi, diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index 59a7995ab2ac..e765ab1eae2f 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -42,6 +42,7 @@ import com.android.internal.annotations.VisibleForTesting; import libcore.io.IoUtils; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.util.Objects; import java.util.UUID; @@ -383,9 +384,15 @@ public class PackageHelper { public static long calculateInstalledSize(PackageLite pkg, String abiOverride) throws IOException { + return calculateInstalledSize(pkg, abiOverride, null); + } + + public static long calculateInstalledSize(PackageLite pkg, String abiOverride, + FileDescriptor fd) throws IOException { NativeLibraryHelper.Handle handle = null; try { - handle = NativeLibraryHelper.Handle.create(pkg); + handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd) + : NativeLibraryHelper.Handle.create(pkg); return calculateInstalledSize(pkg, handle, abiOverride); } finally { IoUtils.closeQuietly(handle); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index f2483c0a71e5..6ede72d49806 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -120,7 +120,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS; @@ -599,6 +599,8 @@ public class BatteryStatsImpl extends BatteryStats { private LongSamplingCounter mDischargeScreenOffCounter; private LongSamplingCounter mDischargeScreenDozeCounter; private LongSamplingCounter mDischargeCounter; + private LongSamplingCounter mDischargeLightDozeCounter; + private LongSamplingCounter mDischargeDeepDozeCounter; static final int MAX_LEVEL_STEPS = 200; @@ -697,6 +699,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getUahDischargeLightDoze(int which) { + return mDischargeLightDozeCounter.getCountLocked(which); + } + + @Override + public long getUahDischargeDeepDoze(int which) { + return mDischargeDeepDozeCounter.getCountLocked(which); + } + + @Override public int getEstimatedBatteryCapacity() { return mEstimatedBatteryCapacity; } @@ -9085,6 +9097,8 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase); mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); + mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); + mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase); mOnBattery = mOnBatteryInternal = false; long uptime = mClocks.uptimeMillis() * 1000; @@ -9664,6 +9678,8 @@ public class BatteryStatsImpl extends BatteryStats { mChargeStepTracker.init(); mDischargeScreenOffCounter.reset(false); mDischargeScreenDozeCounter.reset(false); + mDischargeLightDozeCounter.reset(false); + mDischargeDeepDozeCounter.reset(false); mDischargeCounter.reset(false); } @@ -11263,6 +11279,11 @@ public class BatteryStatsImpl extends BatteryStats { if (isScreenDoze(mScreenState)) { mDischargeScreenDozeCounter.addCountLocked(chargeDiff); } + if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) { + mDischargeLightDozeCounter.addCountLocked(chargeDiff); + } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) { + mDischargeDeepDozeCounter.addCountLocked(chargeDiff); + } } mHistoryCur.batteryChargeUAh = chargeUAh; setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh); @@ -11308,6 +11329,11 @@ public class BatteryStatsImpl extends BatteryStats { if (isScreenDoze(mScreenState)) { mDischargeScreenDozeCounter.addCountLocked(chargeDiff); } + if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) { + mDischargeLightDozeCounter.addCountLocked(chargeDiff); + } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) { + mDischargeDeepDozeCounter.addCountLocked(chargeDiff); + } } mHistoryCur.batteryChargeUAh = chargeUAh; changed = true; @@ -12069,6 +12095,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.readSummaryFromParcelLocked(in); mDischargeScreenOffCounter.readSummaryFromParcelLocked(in); mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in); + mDischargeLightDozeCounter.readSummaryFromParcelLocked(in); + mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in); int NPKG = in.readInt(); if (NPKG > 0) { mDailyPackageChanges = new ArrayList<>(NPKG); @@ -12493,6 +12521,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.writeSummaryFromParcelLocked(out); mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out); mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out); + mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out); + mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out); if (mDailyPackageChanges != null) { final int NPKG = mDailyPackageChanges.size(); out.writeInt(NPKG); @@ -13044,6 +13074,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in); mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); + mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); + mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mLastWriteTime = in.readLong(); mRpmStats.clear(); @@ -13230,6 +13262,8 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.writeToParcel(out); mDischargeScreenOffCounter.writeToParcel(out); mDischargeScreenDozeCounter.writeToParcel(out); + mDischargeLightDozeCounter.writeToParcel(out); + mDischargeDeepDozeCounter.writeToParcel(out); out.writeLong(mLastWriteTime); out.writeInt(mRpmStats.size()); diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index fce5dd58d7f9..17b98dade74f 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -557,6 +557,23 @@ com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, js return reinterpret_cast<jlong>(zipFile); } +static jlong +com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass, + jobject fileDescriptor, jstring debugPathName) +{ + ScopedUtfChars debugFilePath(env, debugPathName); + + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + if (fd < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); + return 0; + } + + ZipFileRO* zipFile = ZipFileRO::openFd(fd, debugFilePath.c_str()); + + return reinterpret_cast<jlong>(zipFile); +} + static void com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle) { @@ -567,6 +584,9 @@ static const JNINativeMethod gMethods[] = { {"nativeOpenApk", "(Ljava/lang/String;)J", (void *)com_android_internal_content_NativeLibraryHelper_openApk}, + {"nativeOpenApkFd", + "(Ljava/io/FileDescriptor;Ljava/lang/String;)J", + (void *)com_android_internal_content_NativeLibraryHelper_openApkFd}, {"nativeClose", "(J)V", (void *)com_android_internal_content_NativeLibraryHelper_close}, diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto index c33c0a0e0518..0a3344fe13f1 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -120,6 +120,14 @@ message SystemProto { // via a coulomb counter. For historical reasons, total_mah_screen_doze is // a subset of total_mah_screen_off. optional int64 total_mah_screen_doze = 8; + // Total amount of battery discharged in mAh while the device was in light doze mode. + // This will only be non-zero for devices that report battery discharge + // via a coulomb counter. + optional int64 total_mah_light_doze = 9; + // Total amount of battery discharged in mAh while the device was in deep doze mode. + // This will only be non-zero for devices that report battery discharge + // via a coulomb counter. + optional int64 total_mah_deep_doze = 10; }; optional BatteryDischarge battery_discharge = 2; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 86103e4c8902..d15c0752d5cc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -485,7 +485,6 @@ <protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" /> <protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" /> <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" /> - <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" /> <protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" /> <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" /> <protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" /> @@ -504,6 +503,8 @@ <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" /> <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" /> <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" /> + <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" /> + <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" /> <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" /> <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" /> @@ -3958,6 +3959,10 @@ <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> - </application> + + <service android:name="com.android.server.display.BrightnessIdleJob" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> +</application> </manifest> diff --git a/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml new file mode 100644 index 000000000000..89e58aae6c34 --- /dev/null +++ b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?attr/dialogPreferredPadding" + android:paddingLeft="?attr/dialogPreferredPadding" + android:paddingRight="?attr/dialogPreferredPadding"> + + <CheckBox + android:id="@+id/ask_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:text="@string/unsupported_compile_sdk_show" /> +</FrameLayout> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index f26d6ed10c0c..e12f04af773b 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -21,9 +21,10 @@ for watch products. Do not translate. --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Only show settings item due to smaller real estate. --> + <!-- Show smaller list of items due to smaller real estate. --> <string-array translatable="false" name="config_globalActionsList"> - <item>assist</item> + <item>power</item> + <item>restart</item> </string-array> <!-- Base "touch slop" value used by ViewConfiguration as a diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index be0f6d9ac647..fe8ca560e5bf 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1340,6 +1340,23 @@ <p>The default value of this attribute is <code>1</code>. --> <attr name="targetSandboxVersion" format="integer" /> + <!-- The user-visible SDK version (ex. 26) of the framework against which the application was + compiled. This attribute is automatically specified by the Android build tools and should + NOT be manually specified. + <p> + This attribute is the compile-time equivalent of + {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. --> + <attr name="compileSdkVersion" format="integer" /> + + <!-- The development codename (ex. "O") of the framework against which the application was + compiled, or "REL" if the application was compiled against a release build. This attribute + is automatically specified by the Android build tools and should NOT be manually + specified. + <p> + This attribute is the compile-time equivalent of + {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. --> + <attr name="compileSdkVersionCodename" format="string" /> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -1369,6 +1386,8 @@ <attr name="isolatedSplits" /> <attr name="isFeatureSplit" /> <attr name="targetSandboxVersion" /> + <attr name="compileSdkVersion" /> + <attr name="compileSdkVersionCodename" /> </declare-styleable> <!-- The <code>application</code> tag describes application-level components diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index edd793d89afa..c10461643406 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -940,9 +940,16 @@ 1 - Global actions menu 2 - Power off (with confirmation) 3 - Power off (without confirmation) + 4 - Go to voice assist --> <integer name="config_longPressOnPowerBehavior">1</integer> + <!-- Control the behavior when the user long presses the power button for a long time. + 0 - Nothing + 1 - Global actions menu + --> + <integer name="config_veryLongPressOnPowerBehavior">0</integer> + <!-- Control the behavior when the user long presses the back button. Non-zero values are only valid for watches as part of CDD/CTS. 0 - Nothing @@ -986,6 +993,9 @@ --> <integer name="config_shortPressOnSleepBehavior">0</integer> + <!-- Time to wait while a button is pressed before triggering a very long press. --> + <integer name="config_veryLongPressTimeout">6000</integer> + <!-- Package name for default keyguard appwidget [DO NOT TRANSLATE] --> <string name="widget_default_package_name" translatable="false"></string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index fdd56c410ed2..d3533fe6e79f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2848,6 +2848,10 @@ <public name="ttcIndex" /> <public name="fontVariationSettings" /> <public name="dialogCornerRadius" /> + <!-- @hide For use by platform and tools only. Developers should not specify this value. --> + <public name="compileSdkVersion" /> + <!-- @hide For use by platform and tools only. Developers should not specify this value. --> + <public name="compileSdkVersionCodename" /> </public-group> <public-group type="style" first-id="0x010302e0"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7a3fa1a7b512..999ba7392fab 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2852,6 +2852,13 @@ <!-- [CHAR LIMIT=50] Unsupported display size dialog: check box label. --> <string name="unsupported_display_size_show">Always show</string> + <!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. --> + <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string> + <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. --> + <string name="unsupported_compile_sdk_show">Always show</string> + <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. --> + <string name="unsupported_compile_sdk_check_update">Check for update</string> + <!-- Text of the alert that is displayed when an application has violated StrictMode. --> <string name="smv_application">The app <xliff:g id="application">%1$s</xliff:g> (process <xliff:g id="process">%2$s</xliff:g>) has violated its self-enforced StrictMode policy.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 54970851f3c1..a1158162ee73 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -419,6 +419,8 @@ <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" /> <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" /> <java-symbol type="integer" name="config_longPressOnPowerBehavior" /> + <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" /> + <java-symbol type="integer" name="config_veryLongPressTimeout" /> <java-symbol type="integer" name="config_longPressOnBackBehavior" /> <java-symbol type="integer" name="config_backPanicBehavior" /> <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" /> @@ -3151,4 +3153,9 @@ <!-- From media projection --> <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" /> <java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" /> + + <!-- Compile SDK check --> + <java-symbol type="layout" name="unsupported_compile_sdk_dialog_content" /> + <java-symbol type="string" name="unsupported_compile_sdk_message" /> + <java-symbol type="string" name="unsupported_compile_sdk_check_update" /> </resources> diff --git a/data/keyboards/OWNERS b/data/keyboards/OWNERS new file mode 100644 index 000000000000..031a6c1c7a89 --- /dev/null +++ b/data/keyboards/OWNERS @@ -0,0 +1,4 @@ +set noparent + +michaelwr@google.com +svv@google.com diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 3c8736ea0c4c..0485625e81e8 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -148,11 +148,15 @@ AssetManager::~AssetManager() { int count = android_atomic_dec(&gCount); if (kIsDebug) { ALOGI("Destroying AssetManager in %p #%d\n", this, count); + } else { + ALOGV("Destroying AssetManager in %p #%d\n", this, count); } // Manually close any fd paths for which we have not yet opened their zip (which // will take ownership of the fd and close it when done). for (size_t i=0; i<mAssetPaths.size(); i++) { + ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd, + mAssetPaths[i].zip.get()); if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) { close(mAssetPaths[i].rawFd); } @@ -202,7 +206,7 @@ bool AssetManager::addAssetPath( ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); ap.isSystemAsset = isSystemAsset; - mAssetPaths.add(ap); + ssize_t apPos = mAssetPaths.add(ap); // new paths are always added at the end if (cookie) { @@ -219,7 +223,7 @@ bool AssetManager::addAssetPath( #endif if (mResources != NULL) { - appendPathToResTable(ap, appAsLib); + appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib); } return true; @@ -304,7 +308,7 @@ bool AssetManager::addAssetFd( ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string()); - mAssetPaths.add(ap); + ssize_t apPos = mAssetPaths.add(ap); // new paths are always added at the end if (cookie) { @@ -312,7 +316,7 @@ bool AssetManager::addAssetFd( } if (mResources != NULL) { - appendPathToResTable(ap, appAsLib); + appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib); } return true; @@ -442,7 +446,8 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode) i--; ALOGV("Looking for asset '%s' in '%s'\n", assetName.string(), mAssetPaths.itemAt(i).path.string()); - Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i)); + Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, + mAssetPaths.editItemAt(i)); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; } @@ -471,7 +476,7 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t i--; ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string()); Asset* pAsset = openNonAssetInPathLocked( - fileName, mode, mAssetPaths.itemAt(i)); + fileName, mode, mAssetPaths.editItemAt(i)); if (pAsset != NULL) { if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1); return pAsset != kExcludedAsset ? pAsset : NULL; @@ -493,7 +498,7 @@ Asset* AssetManager::openNonAsset(const int32_t cookie, const char* fileName, Ac ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(which).path.string()); Asset* pAsset = openNonAssetInPathLocked( - fileName, mode, mAssetPaths.itemAt(which)); + fileName, mode, mAssetPaths.editItemAt(which)); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; } @@ -527,7 +532,7 @@ FileType AssetManager::getFileType(const char* fileName) } } -bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const { +bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const { // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; @@ -645,7 +650,8 @@ const ResTable* AssetManager::getResTable(bool required) const bool onlyEmptyResources = true; const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { - bool empty = appendPathToResTable(mAssetPaths.itemAt(i)); + bool empty = appendPathToResTable( + const_cast<AssetManager*>(this)->mAssetPaths.editItemAt(i)); onlyEmptyResources = onlyEmptyResources && empty; } @@ -770,7 +776,7 @@ void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocale * be used. */ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode, - const asset_path& ap) + asset_path& ap) { Asset* pAsset = NULL; @@ -851,17 +857,19 @@ String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* roo * Return a pointer to one of our open Zip archives. Returns NULL if no * matching Zip file exists. */ -ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap) +ZipFileRO* AssetManager::getZipFileLocked(asset_path& ap) { - ALOGV("getZipFileLocked() in %p\n", this); + ALOGV("getZipFileLocked() in %p: ap=%p zip=%p", this, &ap, ap.zip.get()); if (ap.zip != NULL) { return ap.zip->getZip(); } if (ap.rawFd < 0) { + ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.string()); ap.zip = mZipSet.getSharedZip(ap.path); } else { + ALOGV("getZipFileLocked: Creating new zip from fd %d", ap.rawFd); ap.zip = SharedZip::create(ap.rawFd, ap.path); } diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index 4254614c8448..ecc5dc1ad331 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -222,16 +222,16 @@ private: bool isSystemOverlay; bool isSystemAsset; bool assumeOwnership; - mutable sp<SharedZip> zip; + sp<SharedZip> zip; }; Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode, - const asset_path& path); + asset_path& path); String8 createPathNameLocked(const asset_path& path, const char* rootDir); String8 createZipSourceNameLocked(const String8& zipFileName, const String8& dirName, const String8& fileName); - ZipFileRO* getZipFileLocked(const asset_path& path); + ZipFileRO* getZipFileLocked(asset_path& path); Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode); Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile, const ZipEntryRO entry, AccessMode mode, const String8& entryName); @@ -247,7 +247,7 @@ private: const ResTable* getResTable(bool required = true) const; void setLocaleLocked(const char* locale); void updateResourceParamsLocked() const; - bool appendPathToResTable(const asset_path& ap, bool appAsLib=false) const; + bool appendPathToResTable(asset_path& ap, bool appAsLib=false) const; Asset* openIdmapLocked(const struct asset_path& ap) const; diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index c475e122833a..306ed83c426b 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -721,14 +721,16 @@ public final class MediaFormat { /** * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is * selected in the absence of a specific user choice. - * This is currently only used for subtitle tracks, when the user selected - * 'Default' for the captioning locale. + * This is currently used in two scenarios: + * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale. + * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the + * primary item in the file. + * The associated value is an integer, where non-0 means TRUE. This is an optional * field; if not specified, DEFAULT is considered to be FALSE. */ public static final String KEY_IS_DEFAULT = "is-default"; - /** * A key for the FORCED field for subtitle tracks. True if it is a * forced subtitle track. Forced subtitle tracks are essential for the diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 91e57ee073b0..02c71b283b21 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -258,12 +258,18 @@ final public class MediaMuxer { * in include/media/stagefright/MediaMuxer.h! */ private OutputFormat() {} + /** @hide */ + public static final int MUXER_OUTPUT_FIRST = 0; /** MPEG4 media file format*/ - public static final int MUXER_OUTPUT_MPEG_4 = 0; + public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST; /** WEBM media file format*/ - public static final int MUXER_OUTPUT_WEBM = 1; + public static final int MUXER_OUTPUT_WEBM = MUXER_OUTPUT_FIRST + 1; /** 3GPP media file format*/ - public static final int MUXER_OUTPUT_3GPP = 2; + 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; + /** @hide */ + public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF; }; /** @hide */ @@ -271,6 +277,7 @@ final public class MediaMuxer { OutputFormat.MUXER_OUTPUT_MPEG_4, OutputFormat.MUXER_OUTPUT_WEBM, OutputFormat.MUXER_OUTPUT_3GPP, + OutputFormat.MUXER_OUTPUT_HEIF, }) @Retention(RetentionPolicy.SOURCE) public @interface Format {} @@ -347,8 +354,7 @@ final public class MediaMuxer { } private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException { - if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM - && format != OutputFormat.MUXER_OUTPUT_3GPP) { + if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) { throw new IllegalArgumentException("format: " + format + " is invalid"); } mNativeObject = nativeSetup(fd, format); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 104a77def44e..cb3d59c4dff6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -94,7 +94,7 @@ public class KeyguardSliceView extends LinearLayout { private void showSlice(Slice slice) { // Items will be wrapped into an action when they have tap targets. - SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION); + SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION); if (actionSlice != null) { mSlice = actionSlice.getSlice(); mSliceAction = actionSlice.getAction(); @@ -108,7 +108,7 @@ public class KeyguardSliceView extends LinearLayout { return; } - SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null); + SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null); if (title == null) { mTitle.setVisibility(GONE); } else { @@ -116,7 +116,7 @@ public class KeyguardSliceView extends LinearLayout { mTitle.setText(title.getText()); } - SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE); + SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE); if (text == null) { mText.setVisibility(GONE); } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 03018f7d8778..6ddc76b595b2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -84,7 +84,7 @@ public class KeyguardSliceProvider extends SliceProvider { @Override public Slice onBindSlice(Uri sliceUri) { - return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build(); + return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 700c01a55ad6..0f498bcfbd58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -910,6 +910,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.guest_exit_guest_dialog_remove), this); + SystemUIDialog.setWindowOnTop(this); setCanceledOnTouchOutside(false); mGuestId = guestId; mTargetId = targetId; @@ -937,6 +938,7 @@ public class UserSwitcherController { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this); + SystemUIDialog.setWindowOnTop(this); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index 4437aaca521d..4eae3426f815 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -63,7 +63,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void returnsValidSlice() { Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI)); - SliceItem text = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, + SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null /* nonHints */); Assert.assertNotNull("Slice must provide a title.", text); } diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java index 5db6f7da8102..5867006d6c18 100644 --- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java @@ -21,7 +21,11 @@ import android.app.StatusBarManager; import android.content.Context; import android.hardware.input.InputManager; import android.os.Binder; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; +import android.view.IWindowManager; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -72,6 +76,9 @@ public class GlobalActionPerformer { case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: { return toggleSplitScreen(); } + case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: { + return lockScreen(); + } } return false; } finally { @@ -153,4 +160,11 @@ public class GlobalActionPerformer { } return true; } + + private boolean lockScreen() { + mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0); + mWindowManagerService.lockNow(); + return true; + } } diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index a45a4f0c35d3..dd29a04e5f54 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -48,7 +48,6 @@ import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.ISelectBackupTransportCallback; -import android.app.backup.SelectBackupTransportCallback; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -103,6 +102,7 @@ import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.internal.BackupHandler; import com.android.server.backup.internal.BackupRequest; import com.android.server.backup.internal.ClearDataObserver; +import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.internal.Operation; import com.android.server.backup.internal.PerformInitializeTask; import com.android.server.backup.internal.ProvisionedObserver; @@ -117,6 +117,7 @@ import com.android.server.backup.params.ClearRetryParams; import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.ActiveRestoreSession; import com.android.server.backup.restore.PerformUnifiedRestoreTask; +import com.android.server.backup.transport.TransportClient; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; @@ -1585,8 +1586,27 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } + // We're using pieces of the new binding on-demand infra-structure and the old always-bound + // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line + // is using the new one and TransportManager.getCurrentTransportBinder() is using the old. + // This is weird but there is a reason. + // This is the natural place to put TransportManager.getCurrentTransportClient() because of + // the null handling below that should be the same for TransportClient. + // TransportClient.connect() would return a IBackupTransport for us (instead of using the + // old infra), but it may block and we don't want this in this thread. + // The only usage of transport in this method is for transport.transportDirName(). When the + // push-from-transport part of binding on-demand is in place we will replace the calls for + // IBackupTransport.transportDirName() with calls for + // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece + // here until we implement that. + // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder() + TransportClient transportClient = + mTransportManager.getCurrentTransportClient("BMS.requestBackup()"); IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); - if (transport == null) { + if (transportClient == null || transport == null) { + if (transportClient != null) { + mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()"); + } BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); monitor = BackupManagerMonitorUtils.monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL, @@ -1594,6 +1614,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter return BackupManager.ERROR_TRANSPORT_ABORTED; } + OnTaskFinishedListener listener = + caller -> mTransportManager.disposeOfTransportClient(transportClient, caller); + ArrayList<String> fullBackupList = new ArrayList<>(); ArrayList<String> kvBackupList = new ArrayList<>(); for (String packageName : packages) { @@ -1640,8 +1663,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0; Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP); - msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer, - monitor, true, nonIncrementalBackup); + msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer, + monitor, listener, true, nonIncrementalBackup); mBackupHandler.sendMessage(msg); return BackupManager.SUCCESS; } diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index 7a0173f669af..a2b5cb883b8a 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -16,8 +16,9 @@ package com.android.server.backup; +import android.annotation.Nullable; import android.app.backup.BackupManager; -import android.app.backup.SelectBackupTransportCallback; +import android.app.backup.BackupTransport; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -44,9 +45,11 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportClientManager; +import com.android.server.backup.transport.TransportConnectionListener; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -60,8 +63,7 @@ public class TransportManager { private static final String TAG = "BackupTransportManager"; @VisibleForTesting - /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST = - "android.backup.TRANSPORT_HOST"; + public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins @@ -72,6 +74,7 @@ public class TransportManager { private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; private final Handler mHandler; + private final TransportClientManager mTransportClientManager; /** * This listener is called after we bind to any transport. If it returns true, this is a valid @@ -95,6 +98,10 @@ public class TransportManager { @GuardedBy("mTransportLock") private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>(); + /** Names of transports we've bound to at least once */ + @GuardedBy("mTransportLock") + private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>(); + /** * Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}. */ @@ -123,6 +130,7 @@ public class TransportManager { mCurrentTransportName = defaultTransport; mTransportBoundListener = listener; mHandler = new RebindOnTimeoutHandler(looper); + mTransportClientManager = new TransportClientManager(context); } void onPackageAdded(String packageName) { @@ -204,6 +212,67 @@ public class TransportManager { return null; } + /** + * Returns the transport name associated with {@param transportClient} or {@code null} if not + * found. + */ + @Nullable + public String getTransportName(TransportClient transportClient) { + ComponentName transportComponent = transportClient.getTransportComponent(); + synchronized (mTransportLock) { + for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) { + if (transportEntry.getValue().equals(transportComponent)) { + return transportEntry.getKey(); + } + } + return null; + } + } + + /** + * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found. + * + * @param transportName The name of the transport as returned by {@link BackupTransport#name()}. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not found. + */ + @Nullable + public TransportClient getTransportClient(String transportName, String caller) { + ComponentName transportComponent = mTransportsByName.get(transportName); + if (transportComponent == null) { + Slog.w(TAG, "Transport " + transportName + " not registered"); + return null; + } + return mTransportClientManager.getTransportClient(transportComponent, caller); + } + + /** + * Returns a {@link TransportClient} for the current transport or null if not found. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not found. + */ + @Nullable + public TransportClient getCurrentTransportClient(String caller) { + return getTransportClient(mCurrentTransportName, caller); + } + + /** + * Disposes of the {@link TransportClient}. + * + * @param transportClient The {@link TransportClient} to be disposed of. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + public void disposeOfTransportClient(TransportClient transportClient, String caller) { + mTransportClientManager.disposeOfTransportClient(transportClient, caller); + } + String[] getBoundTransportNames() { synchronized (mTransportLock) { return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]); @@ -374,6 +443,7 @@ public class TransportManager { String componentShortString = component.flattenToShortString().intern(); if (success) { Slog.d(TAG, "Bound to transport: " + componentShortString); + mTransportsByName.put(mTransportName, component); mBoundTransports.put(mTransportName, component); for (TransportReadyCallback listener : mListeners) { listener.onSuccess(mTransportName); @@ -528,7 +598,7 @@ public class TransportManager { // These only exists to make it testable with Robolectric, which is not updated to API level 24 // yet. // TODO: Get rid of this once Robolectric is updated. - private static UserHandle createSystemUserHandle() { + public static UserHandle createSystemUserHandle() { return new UserHandle(UserHandle.USER_SYSTEM); } } diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 8f823004d993..9011b95cf614 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -38,6 +38,8 @@ import com.android.server.EventLogTags; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.RefactoredBackupManagerService; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.TransportManager; import com.android.server.backup.fullbackup.PerformAdbBackupTask; import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; import com.android.server.backup.params.AdbBackupParams; @@ -51,10 +53,8 @@ import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.PerformAdbRestoreTask; import com.android.server.backup.restore.PerformUnifiedRestoreTask; -import java.io.File; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; /** * Asynchronous backup/restore handler thread. @@ -81,7 +81,7 @@ public class BackupHandler extends Handler { public static final int MSG_BACKUP_RESTORE_STEP = 20; public static final int MSG_OP_COMPLETE = 21; - private RefactoredBackupManagerService backupManagerService; + private final RefactoredBackupManagerService backupManagerService; public BackupHandler( RefactoredBackupManagerService backupManagerService, Looper looper) { @@ -91,13 +91,23 @@ public class BackupHandler extends Handler { public void handleMessage(Message msg) { + TransportManager transportManager = backupManagerService.getTransportManager(); switch (msg.what) { case MSG_RUN_BACKUP: { backupManagerService.setLastBackupPass(System.currentTimeMillis()); + String callerLogString = "BH/MSG_RUN_BACKUP"; + TransportClient transportClient = + transportManager.getCurrentTransportClient(callerLogString); IBackupTransport transport = - backupManagerService.getTransportManager().getCurrentTransportBinder(); + transportClient != null + ? transportClient.connect(callerLogString) + : null; if (transport == null) { + if (transportClient != null) { + transportManager + .disposeOfTransportClient(transportClient, callerLogString); + } Slog.v(TAG, "Backup requested but no transport available"); synchronized (backupManagerService.getQueueLock()) { backupManagerService.setBackupRunning(false); @@ -138,9 +148,13 @@ public class BackupHandler extends Handler { // Spin up a backup state sequence and set it running try { String dirName = transport.transportDirName(); + OnTaskFinishedListener listener = + caller -> + transportManager + .disposeOfTransportClient(transportClient, caller); PerformBackupTask pbt = new PerformBackupTask( - backupManagerService, transport, dirName, queue, - oldJournal, null, null, Collections.<String>emptyList(), false, + backupManagerService, transportClient, dirName, queue, + oldJournal, null, null, listener, Collections.emptyList(), false, false /* nonIncremental */); Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); sendMessage(pbtMessage); @@ -157,6 +171,7 @@ public class BackupHandler extends Handler { } if (!staged) { + transportManager.disposeOfTransportClient(transportClient, callerLogString); // if we didn't actually hand off the wakelock, rewind until next time synchronized (backupManagerService.getQueueLock()) { backupManagerService.setBackupRunning(false); @@ -382,9 +397,9 @@ public class BackupHandler extends Handler { PerformBackupTask pbt = new PerformBackupTask( backupManagerService, - params.transport, params.dirName, - kvQueue, null, params.observer, params.monitor, params.fullPackages, true, - params.nonIncrementalBackup); + params.transportClient, params.dirName, + kvQueue, null, params.observer, params.monitor, params.listener, + params.fullPackages, true, params.nonIncrementalBackup); Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); sendMessage(pbtMessage); break; diff --git a/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java new file mode 100644 index 000000000000..e417f06c8a05 --- /dev/null +++ b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.internal; + +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnectionListener; + +/** Listener to be called when a task finishes, successfully or not. */ +public interface OnTaskFinishedListener { + OnTaskFinishedListener NOP = caller -> {}; + + /** + * Called when a task finishes, successfully or not. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + void onFinished(String caller); +} diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java index c0caa557b4ae..1fa215a1664a 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -63,6 +63,8 @@ import com.android.server.backup.KeyValueBackupJob; import com.android.server.backup.PackageManagerBackupAgent; import com.android.server.backup.RefactoredBackupManagerService; import com.android.server.backup.fullbackup.PerformFullTransportBackupTask; +import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportUtils; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; @@ -112,7 +114,6 @@ public class PerformBackupTask implements BackupRestoreTask { private RefactoredBackupManagerService backupManagerService; private final Object mCancelLock = new Object(); - IBackupTransport mTransport; ArrayList<BackupRequest> mQueue; ArrayList<BackupRequest> mOriginalQueue; File mStateDir; @@ -122,6 +123,8 @@ public class PerformBackupTask implements BackupRestoreTask { IBackupObserver mObserver; IBackupManagerMonitor mMonitor; + private final TransportClient mTransportClient; + private final OnTaskFinishedListener mListener; private final PerformFullTransportBackupTask mFullBackupTask; private final int mCurrentOpToken; private volatile int mEphemeralOpToken; @@ -143,17 +146,19 @@ public class PerformBackupTask implements BackupRestoreTask { private volatile boolean mCancelAll; public PerformBackupTask(RefactoredBackupManagerService backupManagerService, - IBackupTransport transport, String dirName, + TransportClient transportClient, String dirName, ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal, IBackupObserver observer, IBackupManagerMonitor monitor, - List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) { + @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups, + boolean userInitiated, boolean nonIncremental) { this.backupManagerService = backupManagerService; - mTransport = transport; + mTransportClient = transportClient; mOriginalQueue = queue; mQueue = new ArrayList<>(); mJournal = journal; mObserver = observer; mMonitor = monitor; + mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP; mPendingFullBackups = pendingFullBackups; mUserInitiated = userInitiated; mNonIncremental = nonIncremental; @@ -289,10 +294,10 @@ public class PerformBackupTask implements BackupRestoreTask { if (DEBUG) { Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets"); } - File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); try { - final String transportName = mTransport.transportDirName(); + IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()"); + final String transportName = transport.transportDirName(); EventLog.writeEvent(EventLogTags.BACKUP_START, transportName); // If we haven't stored package manager metadata yet, we must init the transport. @@ -300,7 +305,7 @@ public class PerformBackupTask implements BackupRestoreTask { Slog.i(TAG, "Initializing (wiping) backup state and transport storage"); backupManagerService.addBackupTrace("initializing transport " + transportName); backupManagerService.resetBackupState(mStateDir); // Just to make sure. - mStatus = mTransport.initializeDevice(); + mStatus = transport.initializeDevice(); backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus); if (mStatus == BackupTransport.TRANSPORT_OK) { @@ -324,7 +329,7 @@ public class PerformBackupTask implements BackupRestoreTask { PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent(); mStatus = invokeAgentForBackup( PACKAGE_MANAGER_SENTINEL, - IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); + IBackupAgent.Stub.asInterface(pmAgent.onBind())); backupManagerService.addBackupTrace("PMBA invoke: " + mStatus); // Because the PMBA is a local instance, it has already executed its @@ -445,7 +450,7 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null)); if (agent != null) { mAgentBinder = agent; - mStatus = invokeAgentForBackup(request.packageName, agent, mTransport); + mStatus = invokeAgentForBackup(request.packageName, agent); // at this point we'll either get a completion callback from the // agent, or a timeout message on the main handler. either way, we're // done here as long as we're successful so far. @@ -526,11 +531,14 @@ public class PerformBackupTask implements BackupRestoreTask { // If everything actually went through and this is the first time we've // done a backup, we can now record what the current backup dataset token // is. + String callerLogString = "PBT.finalizeBackup()"; if ((backupManagerService.getCurrentToken() == 0) && (mStatus == BackupTransport.TRANSPORT_OK)) { backupManagerService.addBackupTrace("success; recording token"); try { - backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet()); + IBackupTransport transport = + mTransportClient.connectOrThrow(callerLogString); + backupManagerService.setCurrentToken(transport.getCurrentRestoreSet()); backupManagerService.writeRestoreTokens(); } catch (Exception e) { // nothing for it at this point, unfortunately, but this will be @@ -553,13 +561,13 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("init required; rerunning"); try { final String name = backupManagerService.getTransportManager().getTransportName( - mTransport); + mTransportClient); if (name != null) { backupManagerService.getPendingInits().add(name); } else { if (DEBUG) { - Slog.w(TAG, "Couldn't find name of transport " + mTransport - + " for init"); + Slog.w(TAG, "Couldn't find name of transport " + + mTransportClient.getTransportComponent() + " for init"); } } } catch (Exception e) { @@ -577,17 +585,21 @@ public class PerformBackupTask implements BackupRestoreTask { if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK && mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) { + // TODO(brufino): Move the onFinish() call to the full-backup task + mListener.onFinished(callerLogString); Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups); // Acquiring wakelock for PerformFullTransportBackupTask before its start. backupManagerService.getWakelock().acquire(); (new Thread(mFullBackupTask, "full-transport-requested")).start(); } else if (mCancelAll) { + mListener.onFinished(callerLogString); if (mFullBackupTask != null) { mFullBackupTask.unregisterTask(); } BackupObserverUtils.sendBackupFinished(mObserver, BackupManager.ERROR_BACKUP_CANCELLED); } else { + mListener.onFinished(callerLogString); mFullBackupTask.unregisterTask(); switch (mStatus) { case BackupTransport.TRANSPORT_OK: @@ -619,8 +631,7 @@ public class PerformBackupTask implements BackupRestoreTask { // Invoke an agent's doBackup() and start a timeout message spinning on the main // handler in case it doesn't get back to us. - int invokeAgentForBackup(String packageName, IBackupAgent agent, - IBackupTransport transport) { + int invokeAgentForBackup(String packageName, IBackupAgent agent) { if (DEBUG) { Slog.d(TAG, "invokeAgentForBackup on " + packageName); } @@ -671,7 +682,10 @@ public class PerformBackupTask implements BackupRestoreTask { ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE); - final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */); + IBackupTransport transport = + mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()"); + + final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */); callingAgent = true; // Initiate the target's backup pass @@ -888,10 +902,12 @@ public class PerformBackupTask implements BackupRestoreTask { clearAgentState(); backupManagerService.addBackupTrace("operation complete"); + IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()"); ParcelFileDescriptor backupData = null; mStatus = BackupTransport.TRANSPORT_OK; long size = 0; try { + TransportUtils.checkTransport(transport); size = mBackupDataName.length(); if (size > 0) { if (mStatus == BackupTransport.TRANSPORT_OK) { @@ -899,7 +915,7 @@ public class PerformBackupTask implements BackupRestoreTask { ParcelFileDescriptor.MODE_READ_ONLY); backupManagerService.addBackupTrace("sending data to transport"); int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; - mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags); + mStatus = transport.performBackup(mCurrentPackage, backupData, flags); } // TODO - We call finishBackup() for each application backed up, because @@ -910,7 +926,7 @@ public class PerformBackupTask implements BackupRestoreTask { backupManagerService.addBackupTrace("data delivered: " + mStatus); if (mStatus == BackupTransport.TRANSPORT_OK) { backupManagerService.addBackupTrace("finishing op on transport"); - mStatus = mTransport.finishBackup(); + mStatus = transport.finishBackup(); backupManagerService.addBackupTrace("finished: " + mStatus); } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) { backupManagerService.addBackupTrace("transport rejected package"); @@ -981,8 +997,8 @@ public class PerformBackupTask implements BackupRestoreTask { } if (mAgentBinder != null) { try { - long quota = mTransport.getBackupQuota(mCurrentPackage.packageName, - false); + TransportUtils.checkTransport(transport); + long quota = transport.getBackupQuota(mCurrentPackage.packageName, false); mAgentBinder.doQuotaExceeded(size, quota); } catch (Exception e) { Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage()); @@ -1052,7 +1068,9 @@ public class PerformBackupTask implements BackupRestoreTask { // by way of retry/backoff time. long delay; try { - delay = mTransport.requestBackupTime(); + IBackupTransport transport = + mTransportClient.connectOrThrow("PBT.revertAndEndBackup()"); + delay = transport.requestBackupTime(); } catch (Exception e) { Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage()); delay = 0; // use the scheduler's default diff --git a/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java index 4fd7ddbc28f7..2ba8ec16a45c 100644 --- a/services/backup/java/com/android/server/backup/params/BackupParams.java +++ b/services/backup/java/com/android/server/backup/params/BackupParams.java @@ -19,30 +19,34 @@ package com.android.server.backup.params; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; -import com.android.internal.backup.IBackupTransport; +import com.android.server.backup.internal.OnTaskFinishedListener; +import com.android.server.backup.transport.TransportClient; import java.util.ArrayList; public class BackupParams { - public IBackupTransport transport; + public TransportClient transportClient; public String dirName; public ArrayList<String> kvPackages; public ArrayList<String> fullPackages; public IBackupObserver observer; public IBackupManagerMonitor monitor; + public OnTaskFinishedListener listener; public boolean userInitiated; public boolean nonIncrementalBackup; - public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages, - ArrayList<String> fullPackages, IBackupObserver observer, - IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) { - this.transport = transport; + public BackupParams(TransportClient transportClient, String dirName, + ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer, + IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated, + boolean nonIncrementalBackup) { + this.transportClient = transportClient; this.dirName = dirName; this.kvPackages = kvPackages; this.fullPackages = fullPackages; this.observer = observer; this.monitor = monitor; + this.listener = listener; this.userInitiated = userInitiated; this.nonIncrementalBackup = nonIncrementalBackup; } diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java new file mode 100644 index 000000000000..9c39729f0dda --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; +import com.android.server.backup.TransportManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained + * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is + * responsible for only one connection to the transport service, not more. + * + * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can + * call either {@link #connect(String)}, if you can block your thread, or {@link + * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link + * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport. + * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly + * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}. + * + * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around. + * + * <p>This class is thread-safe. + * + * @see TransportManager + */ +public class TransportClient { + private static final String TAG = "TransportClient"; + + private final Context mContext; + private final Intent mBindIntent; + private final String mIdentifier; + private final ComponentName mTransportComponent; + private final Handler mListenerHandler; + private final String mPrefixForLog; + private final Object mStateLock = new Object(); + + @GuardedBy("mStateLock") + private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>(); + + @GuardedBy("mStateLock") + @State + private int mState = State.IDLE; + + @GuardedBy("mStateLock") + private volatile IBackupTransport mTransport; + + TransportClient( + Context context, + Intent bindIntent, + ComponentName transportComponent, + String identifier) { + this(context, bindIntent, transportComponent, identifier, Handler.getMain()); + } + + @VisibleForTesting + TransportClient( + Context context, + Intent bindIntent, + ComponentName transportComponent, + String identifier, + Handler listenerHandler) { + mContext = context; + mTransportComponent = transportComponent; + mBindIntent = bindIntent; + mIdentifier = identifier; + mListenerHandler = listenerHandler; + + // For logging + String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", ""); + mPrefixForLog = classNameForLog + "#" + mIdentifier + ": "; + } + + public ComponentName getTransportComponent() { + return mTransportComponent; + } + + // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one + // of these calls, if a binding happen again the new service can be a different instance. Since + // transports are stateful, we don't want a new instance responding for an old instance's state. + private ServiceConnection mConnection = + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder binder) { + IBackupTransport transport = IBackupTransport.Stub.asInterface(binder); + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + if (mState != State.UNUSABLE) { + log(Log.DEBUG, "Transport connected"); + setStateLocked(State.CONNECTED, transport); + notifyListenersAndClearLocked(transport); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + synchronized (mStateLock) { + log(Log.ERROR, "Service disconnected: client UNUSABLE"); + setStateLocked(State.UNUSABLE, null); + // After unbindService() no calls back to mConnection + mContext.unbindService(this); + } + } + + @Override + public void onBindingDied(ComponentName name) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + log(Log.ERROR, "Binding died: client UNUSABLE"); + // After unbindService() no calls back to mConnection + switch (mState) { + case State.UNUSABLE: + break; + case State.IDLE: + log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE"); + setStateLocked(State.UNUSABLE, null); + break; + case State.BOUND_AND_CONNECTING: + setStateLocked(State.UNUSABLE, null); + mContext.unbindService(this); + notifyListenersAndClearLocked(null); + break; + case State.CONNECTED: + setStateLocked(State.UNUSABLE, null); + mContext.unbindService(this); + break; + } + } + } + }; + + /** + * Attempts to connect to the transport (if needed). + * + * <p>Note that being bound is not the same as connected. To be connected you also need to be + * bound. You go from nothing to bound, then to bound and connected. To have a usable transport + * binder instance you need to be connected. This method will attempt to connect and return an + * usable transport binder regardless of the state of the object, it may already be connected, + * or bound but not connected, not bound at all or even unusable. + * + * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or + * one of its variants) can be called or not depending on the inner state. However, it won't be + * called again if we're already bound. For example, if one was already requested but the + * framework has not yet returned (meaning we're bound but still trying to connect) it won't + * trigger another one, just piggyback on the original request. + * + * <p>It's guaranteed that you are going to get a call back to {@param listener} after this + * call. However, the {@param IBackupTransport} parameter, the transport binder, is not + * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can + * throw {@link DeadObjectException}s on method calls. You should check for both in your code. + * The reasons for a null transport binder are: + * + * <ul> + * <li>Some code called {@link #unbind(String)} before you got a callback. + * <li>The framework had already called {@link + * ServiceConnection#onServiceDisconnected(ComponentName)} or {@link + * ServiceConnection#onBindingDied(ComponentName)} on this object's connection before. + * Check the documentation of those methods for when that happens. + * <li>The framework returns false for {@link Context#bindServiceAsUser(Intent, + * ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for + * when this happens. + * </ul> + * + * For unusable transport binders check {@link DeadObjectException}. + * + * @param listener The listener that will be called with the (possibly null or unusable) {@link + * IBackupTransport} instance and this {@link TransportClient} object. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. This + * should be a human-readable short string that is easily identifiable in the logs. Ideally + * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very + * descriptive like MyHandler.handleMessage() you should put something that someone reading + * the code would understand, like MyHandler/MSG_FOO. + * @see #connect(String) + * @see DeadObjectException + * @see ServiceConnection#onServiceConnected(ComponentName, IBinder) + * @see ServiceConnection#onServiceDisconnected(ComponentName) + * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle) + */ + public void connectAsync(TransportConnectionListener listener, String caller) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + switch (mState) { + case State.UNUSABLE: + log(Log.DEBUG, caller, "Async connect: UNUSABLE client"); + notifyListener(listener, null, caller); + break; + case State.IDLE: + boolean hasBound = + mContext.bindServiceAsUser( + mBindIntent, + mConnection, + Context.BIND_AUTO_CREATE, + TransportManager.createSystemUserHandle()); + if (hasBound) { + // We don't need to set a time-out because we are guaranteed to get a call + // back in ServiceConnection, either an onServiceConnected() or + // onBindingDied(). + log(Log.DEBUG, caller, "Async connect: service bound, connecting"); + setStateLocked(State.BOUND_AND_CONNECTING, null); + mListeners.put(listener, caller); + } else { + log(Log.ERROR, "Async connect: bindService returned false"); + // mState remains State.IDLE + mContext.unbindService(mConnection); + notifyListener(listener, null, caller); + } + break; + case State.BOUND_AND_CONNECTING: + log(Log.DEBUG, caller, "Async connect: already connecting, adding listener"); + mListeners.put(listener, caller); + break; + case State.CONNECTED: + log(Log.DEBUG, caller, "Async connect: reusing transport"); + notifyListener(listener, mTransport, caller); + break; + } + } + } + + /** + * Removes the transport binding. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + */ + public void unbind(String caller) { + synchronized (mStateLock) { + checkStateIntegrityLocked(); + + log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")"); + switch (mState) { + case State.UNUSABLE: + case State.IDLE: + break; + case State.BOUND_AND_CONNECTING: + setStateLocked(State.IDLE, null); + // After unbindService() no calls back to mConnection + mContext.unbindService(mConnection); + notifyListenersAndClearLocked(null); + break; + case State.CONNECTED: + setStateLocked(State.IDLE, null); + mContext.unbindService(mConnection); + break; + } + } + } + + /** + * Attempts to connect to the transport (if needed) and returns it. + * + * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The + * same observations about state are valid here. Also, what was said about the {@link + * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return + * value of this method. + * + * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct + * threads. You can't call this from the process main-thread (it throws an exception if you do + * so). + * + * <p>In most cases only the first call to this method will block, the following calls should + * return instantly. However, this is not guaranteed. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can + * still be unusable - throws {@link DeadObjectException} on method calls + */ + @WorkerThread + @Nullable + public IBackupTransport connect(String caller) { + // If called on the main-thread this could deadlock waiting because calls to + // ServiceConnection are on the main-thread as well + Preconditions.checkState( + !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread"); + + IBackupTransport transport = mTransport; + if (transport != null) { + log(Log.DEBUG, caller, "Sync connect: reusing transport"); + return transport; + } + + // If it's already UNUSABLE we return straight away, no need to go to main-thread + synchronized (mStateLock) { + if (mState == State.UNUSABLE) { + log(Log.DEBUG, caller, "Sync connect: UNUSABLE client"); + return null; + } + } + + CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>(); + TransportConnectionListener requestListener = + (requestedTransport, transportClient) -> + transportFuture.complete(requestedTransport); + + log(Log.DEBUG, caller, "Sync connect: calling async"); + connectAsync(requestListener, caller); + + try { + return transportFuture.get(); + } catch (InterruptedException | ExecutionException e) { + String error = e.getClass().getSimpleName(); + log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage()); + return null; + } + } + + /** + * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}. + * + * <p>Same as {@link #connect(String)} except it throws instead of returning null. + * + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link #connectAsync(TransportConnectionListener, String)} for more details. + * @return A {@link IBackupTransport} transport binder instance. + * @see #connect(String) + * @throws TransportNotAvailableException if connection attempt fails. + */ + @WorkerThread + public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException { + IBackupTransport transport = connect(caller); + if (transport == null) { + log(Log.ERROR, caller, "Transport connection failed"); + throw new TransportNotAvailableException(); + } + return transport; + } + + @Override + public String toString() { + return "TransportClient{" + + mTransportComponent.flattenToShortString() + + "#" + + mIdentifier + + "}"; + } + + private void notifyListener( + TransportConnectionListener listener, IBackupTransport transport, String caller) { + log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport); + mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this)); + } + + @GuardedBy("mStateLock") + private void notifyListenersAndClearLocked(IBackupTransport transport) { + for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) { + TransportConnectionListener listener = entry.getKey(); + String caller = entry.getValue(); + notifyListener(listener, transport, caller); + } + mListeners.clear(); + } + + @GuardedBy("mStateLock") + private void setStateLocked(@State int state, @Nullable IBackupTransport transport) { + log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state)); + mState = state; + mTransport = transport; + } + + @GuardedBy("mStateLock") + private void checkStateIntegrityLocked() { + switch (mState) { + case State.UNUSABLE: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE"); + checkState( + mTransport == null, "Transport expected to be null when state = UNUSABLE"); + case State.IDLE: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE"); + checkState(mTransport == null, "Transport expected to be null when state = IDLE"); + break; + case State.BOUND_AND_CONNECTING: + checkState( + mTransport == null, + "Transport expected to be null when state = BOUND_AND_CONNECTING"); + break; + case State.CONNECTED: + checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED"); + checkState( + mTransport != null, + "Transport expected to be non-null when state = CONNECTED"); + break; + default: + checkState(false, "Unexpected state = " + stateToString(mState)); + } + } + + private void checkState(boolean assertion, String message) { + if (!assertion) { + log(Log.ERROR, message); + } + } + + private String stateToString(@State int state) { + switch (state) { + case State.UNUSABLE: + return "UNUSABLE"; + case State.IDLE: + return "IDLE"; + case State.BOUND_AND_CONNECTING: + return "BOUND_AND_CONNECTING"; + case State.CONNECTED: + return "CONNECTED"; + default: + return "<UNKNOWN = " + state + ">"; + } + } + + private void log(int priority, String message) { + TransportUtils.log(priority, TAG, message); + } + + private void log(int priority, String caller, String msg) { + TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg); + // TODO(brufino): Log in internal list for dump + // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis()); + } + + @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED}) + @Retention(RetentionPolicy.SOURCE) + private @interface State { + int UNUSABLE = 0; + int IDLE = 1; + int BOUND_AND_CONNECTING = 2; + int CONNECTED = 3; + } +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java new file mode 100644 index 000000000000..1cbe74716b03 --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.android.server.backup.TransportManager; + +/** + * Manages the creation and disposal of {@link TransportClient}s. The only class that should use + * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}. + * + * <p>TODO(brufino): Implement pool of TransportClients + */ +public class TransportClientManager { + private static final String TAG = "TransportClientManager"; + + private final Context mContext; + private final Object mTransportClientsLock = new Object(); + private int mTransportClientsCreated = 0; + + public TransportClientManager(Context context) { + mContext = context; + } + + /** + * Retrieves a {@link TransportClient} for the transport identified by {@param + * transportComponent}. + * + * @param transportComponent The {@link ComponentName} of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient}. + */ + public TransportClient getTransportClient(ComponentName transportComponent, String caller) { + Intent bindIntent = + new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent); + synchronized (mTransportClientsLock) { + TransportClient transportClient = + new TransportClient( + mContext, + bindIntent, + transportComponent, + Integer.toString(mTransportClientsCreated)); + mTransportClientsCreated++; + TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient); + return transportClient; + } + } + + /** + * Disposes of the {@link TransportClient}. + * + * @param transportClient The {@link TransportClient} to be disposed of. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + */ + public void disposeOfTransportClient(TransportClient transportClient, String caller) { + TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient); + transportClient.unbind(caller); + } +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java new file mode 100644 index 000000000000..1ccffd01d12c --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import android.annotation.Nullable; + +import com.android.internal.backup.IBackupTransport; + +/** + * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener, + * String)}. + */ +public interface TransportConnectionListener { + /** + * Called when {@link TransportClient} has a transport binder available or that it decided it + * couldn't obtain one, in which case {@param transport} is null. + * + * @param transport A {@link IBackupTransport} transport binder or null. + * @param transportClient The {@link TransportClient} used to retrieve this transport binder. + */ + void onTransportConnectionResult( + @Nullable IBackupTransport transport, TransportClient transportClient); +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java new file mode 100644 index 000000000000..a02f03c2d1cb --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import com.android.internal.backup.IBackupTransport; + +/** + * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link + * TransportClient} connection attempt fails. Check {@link + * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens. + * + * @see TransportClient#connectAsync(TransportConnectionListener, String) + */ +class TransportNotAvailableException extends Exception { + TransportNotAvailableException() { + super("Transport not available"); + } +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/java/com/android/server/backup/transport/TransportUtils.java new file mode 100644 index 000000000000..514717f46b9f --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/TransportUtils.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import android.annotation.Nullable; +import android.os.DeadObjectException; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.backup.IBackupTransport; + +/** Utility methods for transport-related operations. */ +public class TransportUtils { + + /** + * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is + * similar to a {@link DeadObjectException} coming from a dead transport binder. + */ + public static IBackupTransport checkTransport(@Nullable IBackupTransport transport) + throws TransportNotAvailableException { + if (transport == null) { + throw new TransportNotAvailableException(); + } + return transport; + } + + static void log(int priority, String tag, String message) { + log(priority, tag, null, message); + } + + static void log(int priority, String tag, @Nullable String caller, String message) { + log(priority, tag, "", caller, message); + } + + static void log( + int priority, String tag, String prefix, @Nullable String caller, String message) { + if (Log.isLoggable(tag, priority)) { + if (caller != null) { + prefix += "[" + caller + "] "; + } + Slog.println(priority, tag, prefix + message); + } + } + + private TransportUtils() {} +} diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 0fd59eaa111f..bdfccd6e9d93 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -118,7 +118,7 @@ public class LocationManagerService extends ILocationManager.Stub { private static final String TAG = "LocationManagerService"; public static final boolean D = Log.isLoggable(TAG, Log.DEBUG); - private static final String WAKELOCK_KEY = TAG; + private static final String WAKELOCK_KEY = "*location*"; // Location resolution level: no location data whatsoever private static final int RESOLUTION_LEVEL_NONE = 0; diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index e5fbdd21438c..296fb3210118 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -1,3 +1,4 @@ +# Connectivity / Networking per-file ConnectivityService.java=ek@google.com per-file ConnectivityService.java=hugobenichi@google.com per-file ConnectivityService.java=lorenzo@google.com @@ -7,3 +8,9 @@ per-file NetworkManagementService.java=lorenzo@google.com per-file NsdService.java=ek@google.com per-file NsdService.java=hugobenichi@google.com per-file NsdService.java=lorenzo@google.com + +# Vibrator +per-file VibratorService.java=michaelwr@google.com + +# Threads +per-file DisplayThread.java=michaelwr@google.com diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b3b831edcab7..ae91b8208b72 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -351,7 +351,6 @@ import android.util.AtomicFile; import android.util.StatsLog; import android.util.TimingsTraceLog; import android.util.DebugUtils; -import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.Pair; @@ -716,6 +715,8 @@ public class ActivityManagerService extends IActivityManager.Stub final AppErrors mAppErrors; + final AppWarnings mAppWarnings; + /** * Dump of the activity state at the time of the last ANR. Cleared after * {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS} @@ -1726,7 +1727,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int IDLE_UIDS_MSG = 58; static final int LOG_STACK_STATE = 60; static final int VR_MODE_CHANGE_MSG = 61; - static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62; static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63; static final int NOTIFY_VR_SLEEPING_MSG = 65; static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66; @@ -1745,7 +1745,6 @@ public class ActivityManagerService extends IActivityManager.Stub static KillHandler sKillHandler = null; CompatModeDialog mCompatModeDialog; - UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog; long mLastMemUsageReportTime = 0; /** @@ -1927,23 +1926,6 @@ public class ActivityManagerService extends IActivityManager.Stub } break; } - case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: { - synchronized (ActivityManagerService.this) { - final ActivityRecord ar = (ActivityRecord) msg.obj; - if (mUnsupportedDisplaySizeDialog != null) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } - if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked( - ar.packageName)) { - // TODO(multi-display): Show dialog on appropriate display. - mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( - ActivityManagerService.this, mUiContext, ar.info.applicationInfo); - mUnsupportedDisplaySizeDialog.show(); - } - } - break; - } case DISMISS_DIALOG_UI_MSG: { final Dialog d = (Dialog) msg.obj; d.dismiss(); @@ -2676,6 +2658,7 @@ public class ActivityManagerService extends IActivityManager.Stub GL_ES_VERSION = 0; mActivityStarter = null; mAppErrors = null; + mAppWarnings = null; mAppOpsService = mInjector.getAppOpsService(null, null); mBatteryStatsService = null; mCompatModePackages = null; @@ -2743,10 +2726,13 @@ public class ActivityManagerService extends IActivityManager.Stub mProviderMap = new ProviderMap(this); mAppErrors = new AppErrors(mUiContext, this); - // TODO: Move creation of battery stats service outside of activity manager service. File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); systemDir.mkdirs(); + + mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir); + + // TODO: Move creation of battery stats service outside of activity manager service. mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler); mBatteryStatsService.getActiveStatistics().readLocked(); mBatteryStatsService.scheduleWriteToDisk(); @@ -2793,7 +2779,7 @@ public class ActivityManagerService extends IActivityManager.Stub mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mTaskChangeNotificationController = new TaskChangeNotificationController(this, mStackSupervisor, mHandler); - mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager()); + mActivityStarter = new ActivityStarter(this); mRecentTasks = createRecentTasks(); mStackSupervisor.setRecentTasks(mRecentTasks); mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler); @@ -3310,22 +3296,25 @@ public class ActivityManagerService extends IActivityManager.Stub mUiHandler.sendMessage(msg); } - final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) { - final Configuration globalConfig = getGlobalConfiguration(); - if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE - && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) { - final Message msg = Message.obtain(); - msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG; - msg.obj = r; - mUiHandler.sendMessage(msg); - } + final AppWarnings getAppWarningsLocked() { + return mAppWarnings; + } + + /** + * Shows app warning dialogs, if necessary. + * + * @param r activity record for which the warnings may be displayed + */ + final void showAppWarningsIfNeededLocked(ActivityRecord r) { + mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r); + mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r); } private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index, String what, Object obj, ProcessRecord srcApp) { app.lastActivityTime = now; - if (app.activities.size() > 0) { + if (app.activities.size() > 0 || app.recentTasks.size() > 0) { // Don't want to touch dependent processes that are hosting activities. return index; } @@ -3389,7 +3378,7 @@ public class ActivityManagerService extends IActivityManager.Stub final void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) { final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities - || app.treatLikeActivity; + || app.treatLikeActivity || app.recentTasks.size() > 0; final boolean hasService = false; // not impl yet. app.services.size() > 0; if (!activityChange && hasActivity) { // The process has activities, so we are only allowing activity-based adjustments @@ -3493,7 +3482,8 @@ public class ActivityManagerService extends IActivityManager.Stub int nextIndex; if (hasActivity) { final int N = mLruProcesses.size(); - if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) { + if ((app.activities.size() == 0 || app.recentTasks.size() > 0) + && mLruProcessActivityStart < (N - 1)) { // Process doesn't have activities, but has clients with // activities... move it up, but one below the top (the top // should always have a real activity). @@ -5330,6 +5320,8 @@ public class ActivityManagerService extends IActivityManager.Stub // Remove this application's activities from active lists. boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app); + app.clearRecentTasks(); + app.activities.clear(); if (app.instr != null) { @@ -11715,6 +11707,15 @@ public class ActivityManagerService extends IActivityManager.Stub return true; } + /** + * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The + * PackageManager could be unavailable at construction time and therefore needs to be accessed + * on demand. + */ + IPackageManager getPackageManager() { + return AppGlobals.getPackageManager(); + } + PackageManagerInternal getPackageManagerInternalLocked() { if (mPackageManagerInt == null) { mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class); @@ -14566,6 +14567,18 @@ public class ActivityManagerService extends IActivityManager.Stub final String dropboxTag = processClass(process) + "_" + eventType; if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; + // Log to StatsLog before the rate-limiting. + // The logging below is adapated from appendDropboxProcessHeaders. + StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED, + process != null ? process.uid : -1, + dropboxTag, + processName, + process != null ? process.pid : -1, + (process != null && process.info != null) ? + (process.info.isInstantApp() ? 1 : 0) : -1, + activity != null ? activity.shortComponentName : null, + process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1); + // Rate-limit how often we're willing to do the heavy lifting below to // collect and record logs; currently 5 logs per 10 second period. final long now = SystemClock.elapsedRealtime(); @@ -19350,13 +19363,7 @@ public class ActivityManagerService extends IActivityManager.Stub mRecentTasks.removeTasksByPackageName(ssp, userId); mServices.forceStopPackageLocked(ssp, userId); - - // Hide the "unsupported display" dialog if necessary. - if (mUnsupportedDisplaySizeDialog != null && ssp.equals( - mUnsupportedDisplaySizeDialog.getPackageName())) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } + mAppWarnings.onPackageUninstalled(ssp); mCompatModePackages.handlePackageUninstalledLocked(ssp); mBatteryStatsService.notePackageUninstalled(ssp); } @@ -19435,13 +19442,8 @@ public class ActivityManagerService extends IActivityManager.Stub Uri data = intent.getData(); String ssp; if (data != null && (ssp = data.getSchemeSpecificPart()) != null) { - // Hide the "unsupported display" dialog if necessary. - if (mUnsupportedDisplaySizeDialog != null && ssp.equals( - mUnsupportedDisplaySizeDialog.getPackageName())) { - mUnsupportedDisplaySizeDialog.dismiss(); - mUnsupportedDisplaySizeDialog = null; - } mCompatModePackages.handlePackageDataClearedLocked(ssp); + mAppWarnings.onPackageDataCleared(ssp); } break; } @@ -20631,8 +20633,7 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0; if (isDensityChange && displayId == DEFAULT_DISPLAY) { - // Reset the unsupported display size dialog. - mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG); + mAppWarnings.onDensityChanged(); killAllBackgroundProcessesExcept(N, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); @@ -21013,7 +21014,7 @@ public class ActivityManagerService extends IActivityManager.Stub + " instead of expected " + app); if (r.app == null || (r.app.uid == app.uid)) { // Only fix things up when they look sane - r.app = app; + r.setProcess(app); } else { continue; } @@ -21092,6 +21093,11 @@ public class ActivityManagerService extends IActivityManager.Stub adj += minLayer; } } + if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) { + procState = ActivityManager.PROCESS_STATE_CACHED_RECENT; + app.adjType = "cch-rec"; + if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app); + } if (adj > ProcessList.PERCEPTIBLE_APP_ADJ || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { @@ -22652,6 +22658,7 @@ public class ActivityManagerService extends IActivityManager.Stub switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + case ActivityManager.PROCESS_STATE_CACHED_RECENT: // This process is a cached process holding activities... // assign it the next cached value for that type, and then // step that cached level. @@ -23376,7 +23383,7 @@ public class ActivityManagerService extends IActivityManager.Stub // has been removed. for (i=mRemovedProcesses.size()-1; i>=0; i--) { final ProcessRecord app = mRemovedProcesses.get(i); - if (app.activities.size() == 0 + if (app.activities.size() == 0 && app.recentTasks.size() == 0 && app.curReceivers.isEmpty() && app.services.size() == 0) { Slog.i( TAG, "Exiting empty application process " diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 9a16745c9ca3..5927666141d4 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -934,6 +934,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } + void setProcess(ProcessRecord proc) { + app = proc; + final ActivityRecord root = task != null ? task.getRootActivity() : null; + if (root == this) { + task.setRootProcess(proc); + } + } + AppWindowContainerController getWindowContainerController() { return mWindowContainerController; } @@ -2549,7 +2557,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } results = null; newIntents = null; - service.showUnsupportedZoomDialogIfNeededLocked(this); + service.getAppWarningsLocked().onResumeActivity(this); service.showAskCompatModeDialogLocked(this); } else { service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index c086c5292059..6985d6e36ac1 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -2602,7 +2602,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.shortComponentName); next.sleeping = false; - mService.showUnsupportedZoomDialogIfNeededLocked(next); + mService.getAppWarningsLocked().onResumeActivity(next); mService.showAskCompatModeDialogLocked(next); next.app.pendingUiClean = true; next.app.forceProcessStateUpTo(mService.mTopProcessState); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 745e9fbc9b9d..ddde4bc4957c 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1270,7 +1270,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // schedule launch ticks to collect information about slow apps. r.startLaunchTickingLocked(); - r.app = app; + r.setProcess(app); if (mKeyguardController.isKeyguardLocked()) { r.notifyUnknownVisibilityLaunched(); @@ -1358,7 +1358,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY); r.sleeping = false; r.forceNewConfig = false; - mService.showUnsupportedZoomDialogIfNeededLocked(r); + mService.getAppWarningsLocked().onStartActivity(r); mService.showAskCompatModeDialogLocked(r); r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); ProfilerInfo profilerInfo = null; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 9b8cbc1043a5..03162bbe0199 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -134,7 +134,6 @@ class ActivityStarter { private static final int INVALID_LAUNCH_MODE = -1; private final ActivityManagerService mService; - private final IPackageManager mPackageManager; private final ActivityStackSupervisor mSupervisor; private final ActivityStartInterceptor mInterceptor; @@ -234,9 +233,8 @@ class ActivityStarter { mIntentDelivered = false; } - ActivityStarter(ActivityManagerService service, IPackageManager packageManager) { + ActivityStarter(ActivityManagerService service) { mService = service; - mPackageManager = packageManager; mSupervisor = mService.mStackSupervisor; mInterceptor = new ActivityStartInterceptor(mService, mSupervisor); } @@ -379,7 +377,7 @@ class ActivityStarter { && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { try { intent.addCategory(Intent.CATEGORY_VOICE); - if (!mPackageManager.activitySupportsIntent( + if (!mService.getPackageManager().activitySupportsIntent( intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in current voice task does not support voice: " @@ -397,7 +395,7 @@ class ActivityStarter { // If the caller is starting a new voice session, just make sure the target // is actually allowing it to run this way. try { - if (!mPackageManager.activitySupportsIntent(intent.getComponent(), + if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in new voice task does not support: " diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java new file mode 100644 index 000000000000..a3c0345066de --- /dev/null +++ b/services/core/java/com/android/server/am/AppWarnings.java @@ -0,0 +1,498 @@ +/* + * 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.am; + +import android.annotation.UiThread; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AtomicFile; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * Manages warning dialogs shown during application lifecycle. + */ +class AppWarnings { + private static final String TAG = "AppWarnings"; + private static final String CONFIG_FILE_NAME = "packages-warnings.xml"; + + public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01; + public static final int FLAG_HIDE_COMPILE_SDK = 0x02; + + private final HashMap<String, Integer> mPackageFlags = new HashMap<>(); + + private final ActivityManagerService mAms; + private final Context mUiContext; + private final ConfigHandler mAmsHandler; + private final UiHandler mUiHandler; + private final AtomicFile mConfigFile; + + private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog; + private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog; + + /** + * Creates a new warning dialog manager. + * <p> + * <strong>Note:</strong> Must be called from the ActivityManagerService thread. + * + * @param ams + * @param uiContext + * @param amsHandler + * @param uiHandler + * @param systemDir + */ + public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler, + Handler uiHandler, File systemDir) { + mAms = ams; + mUiContext = uiContext; + mAmsHandler = new ConfigHandler(amsHandler.getLooper()); + mUiHandler = new UiHandler(uiHandler.getLooper()); + mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME)); + + readConfigFromFileAmsThread(); + } + + /** + * Shows the "unsupported display size" warning, if necessary. + * + * @param r activity record for which the warning may be displayed + */ + public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) { + final Configuration globalConfig = mAms.getGlobalConfiguration(); + if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE + && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) { + mUiHandler.showUnsupportedDisplaySizeDialog(r); + } + } + + /** + * Shows the "unsupported compile SDK" warning, if necessary. + * + * @param r activity record for which the warning may be displayed + */ + public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) { + if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) { + // We don't know enough about this package. Abort! + return; + } + + // If the application was built against an pre-release SDK that's older than the current + // platform OR if the current platform is pre-release and older than the SDK against which + // the application was built OR both are pre-release with the same SDK_INT but different + // codenames (e.g. simultaneous pre-release development), then we're likely to run into + // compatibility issues. Warn the user and offer to check for an update. + final int compileSdk = r.appInfo.compileSdkVersion; + final int platformSdk = Build.VERSION.SDK_INT; + final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename); + final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME); + if ((isCompileSdkPreview && compileSdk < platformSdk) + || (isPlatformSdkPreview && platformSdk < compileSdk) + || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk + && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) { + mUiHandler.showUnsupportedCompileSdkDialog(r); + } + } + + /** + * Called when an activity is being started. + * + * @param r record for the activity being started + */ + public void onStartActivity(ActivityRecord r) { + showUnsupportedCompileSdkDialogIfNeeded(r); + showUnsupportedDisplaySizeDialogIfNeeded(r); + } + + /** + * Called when an activity was previously started and is being resumed. + * + * @param r record for the activity being resumed + */ + public void onResumeActivity(ActivityRecord r) { + showUnsupportedDisplaySizeDialogIfNeeded(r); + } + + /** + * Called by ActivityManagerService when package data has been cleared. + * + * @param name the package whose data has been cleared + */ + public void onPackageDataCleared(String name) { + removePackageAndHideDialogs(name); + } + + /** + * Called by ActivityManagerService when a package has been uninstalled. + * + * @param name the package that has been uninstalled + */ + public void onPackageUninstalled(String name) { + removePackageAndHideDialogs(name); + } + + /** + * Called by ActivityManagerService when the default display density has changed. + */ + public void onDensityChanged() { + mUiHandler.hideUnsupportedDisplaySizeDialog(); + } + + /** + * Does what it says on the tin. + */ + private void removePackageAndHideDialogs(String name) { + mUiHandler.hideDialogsForPackage(name); + + synchronized (mPackageFlags) { + mPackageFlags.remove(name); + mAmsHandler.scheduleWrite(); + } + } + + /** + * Hides the "unsupported display size" warning. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + */ + @UiThread + private void hideUnsupportedDisplaySizeDialogUiThread() { + if (mUnsupportedDisplaySizeDialog != null) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + } + + /** + * Shows the "unsupported display size" warning for the given application. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param ar record for the activity that triggered the warning + */ + @UiThread + private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) { + if (mUnsupportedDisplaySizeDialog != null) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + if (ar != null && !hasPackageFlag( + ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) { + mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog( + AppWarnings.this, mUiContext, ar.info.applicationInfo); + mUnsupportedDisplaySizeDialog.show(); + } + } + + /** + * Shows the "unsupported compile SDK" warning for the given application. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param ar record for the activity that triggered the warning + */ + @UiThread + private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) { + if (mUnsupportedCompileSdkDialog != null) { + mUnsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialog = null; + } + if (ar != null && !hasPackageFlag( + ar.packageName, FLAG_HIDE_COMPILE_SDK)) { + mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog( + AppWarnings.this, mUiContext, ar.info.applicationInfo); + mUnsupportedCompileSdkDialog.show(); + } + } + + /** + * Dismisses all warnings for the given package. + * <p> + * <strong>Note:</strong> Must be called on the UI thread. + * + * @param name the package for which warnings should be dismissed, or {@code null} to dismiss + * all warnings + */ + @UiThread + private void hideDialogsForPackageUiThread(String name) { + // Hides the "unsupported display" dialog if necessary. + if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals( + mUnsupportedDisplaySizeDialog.getPackageName()))) { + mUnsupportedDisplaySizeDialog.dismiss(); + mUnsupportedDisplaySizeDialog = null; + } + + // Hides the "unsupported compile SDK" dialog if necessary. + if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals( + mUnsupportedCompileSdkDialog.getPackageName()))) { + mUnsupportedCompileSdkDialog.dismiss(); + mUnsupportedCompileSdkDialog = null; + } + } + + /** + * Returns the value of the flag for the given package. + * + * @param name the package from which to retrieve the flag + * @param flag the bitmask for the flag to retrieve + * @return {@code true} if the flag is enabled, {@code false} otherwise + */ + boolean hasPackageFlag(String name, int flag) { + return (getPackageFlags(name) & flag) == flag; + } + + /** + * Sets the flag for the given package to the specified value. + * + * @param name the package on which to set the flag + * @param flag the bitmask for flag to set + * @param enabled the value to set for the flag + */ + void setPackageFlag(String name, int flag, boolean enabled) { + synchronized (mPackageFlags) { + final int curFlags = getPackageFlags(name); + final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag); + if (curFlags != newFlags) { + if (newFlags != 0) { + mPackageFlags.put(name, newFlags); + } else { + mPackageFlags.remove(name); + } + mAmsHandler.scheduleWrite(); + } + } + } + + /** + * Returns the bitmask of flags set for the specified package. + */ + private int getPackageFlags(String name) { + synchronized (mPackageFlags) { + return mPackageFlags.getOrDefault(name, 0); + } + } + + /** + * Handles messages on the system process UI thread. + */ + private final class UiHandler extends Handler { + private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1; + private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2; + private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3; + private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4; + + public UiHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { + final ActivityRecord ar = (ActivityRecord) msg.obj; + showUnsupportedDisplaySizeDialogUiThread(ar); + } break; + case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: { + hideUnsupportedDisplaySizeDialogUiThread(); + } break; + case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: { + final ActivityRecord ar = (ActivityRecord) msg.obj; + showUnsupportedCompileSdkDialogUiThread(ar); + } break; + case MSG_HIDE_DIALOGS_FOR_PACKAGE: { + final String name = (String) msg.obj; + hideDialogsForPackageUiThread(name); + } break; + } + } + + public void showUnsupportedDisplaySizeDialog(ActivityRecord r) { + removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget(); + } + + public void hideUnsupportedDisplaySizeDialog() { + removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG); + } + + public void showUnsupportedCompileSdkDialog(ActivityRecord r) { + removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG); + obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget(); + } + + public void hideDialogsForPackage(String name) { + obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget(); + } + } + + /** + * Handles messages on the ActivityManagerService thread. + */ + private final class ConfigHandler extends Handler { + private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG; + + private static final int DELAY_MSG_WRITE = 10000; + + public ConfigHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_WRITE: + writeConfigToFileAmsThread(); + break; + } + } + + public void scheduleWrite() { + removeMessages(MSG_WRITE); + sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE); + } + } + + /** + * Writes the configuration file. + * <p> + * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you + * don't care where you're doing I/O operations. But you <i>do</i> care, don't you? + */ + private void writeConfigToFileAmsThread() { + // Create a shallow copy so that we don't have to synchronize on config. + final HashMap<String, Integer> packageFlags; + synchronized (mPackageFlags) { + packageFlags = new HashMap<>(mPackageFlags); + } + + FileOutputStream fos = null; + try { + fos = mConfigFile.startWrite(); + + final XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + out.startTag(null, "packages"); + + for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) { + String pkg = entry.getKey(); + int mode = entry.getValue(); + if (mode == 0) { + continue; + } + out.startTag(null, "package"); + out.attribute(null, "name", pkg); + out.attribute(null, "flags", Integer.toString(mode)); + out.endTag(null, "package"); + } + + out.endTag(null, "packages"); + out.endDocument(); + + mConfigFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Slog.w(TAG, "Error writing package metadata", e1); + if (fos != null) { + mConfigFile.failWrite(fos); + } + } + } + + /** + * Reads the configuration file and populates the package flags. + * <p> + * <strong>Note:</strong> Must be called from the constructor (and thus on the + * ActivityManagerService thread) since we don't synchronize on config. + */ + private void readConfigFromFileAmsThread() { + FileInputStream fis = null; + + try { + fis = mConfigFile.openRead(); + + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, StandardCharsets.UTF_8.name()); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && + eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + if (eventType == XmlPullParser.END_DOCUMENT) { + return; + } + + String tagName = parser.getName(); + if ("packages".equals(tagName)) { + eventType = parser.next(); + do { + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + if (parser.getDepth() == 2) { + if ("package".equals(tagName)) { + final String name = parser.getAttributeValue(null, "name"); + if (name != null) { + final String flags = parser.getAttributeValue( + null, "flags"); + int flagsInt = 0; + if (flags != null) { + try { + flagsInt = Integer.parseInt(flags); + } catch (NumberFormatException e) { + } + } + mPackageFlags.put(name, flagsInt); + } + } + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } + } catch (XmlPullParserException e) { + Slog.w(TAG, "Error reading package metadata", e); + } catch (java.io.IOException e) { + if (fis != null) Slog.w(TAG, "Error reading package metadata", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (java.io.IOException e1) { + } + } + } + } +} diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java index bfc0456586c1..82019fde30b7 100644 --- a/services/core/java/com/android/server/am/CompatModePackages.java +++ b/services/core/java/com/android/server/am/CompatModePackages.java @@ -60,6 +60,8 @@ public final class CompatModePackages { public static final int COMPAT_FLAG_ENABLED = 1<<1; // Unsupported zoom state: don't warn the user about unsupported zoom mode. public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2; + // Unsupported compile SDK state: don't warn the user about unsupported compile SDK. + public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3; private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>(); @@ -237,6 +239,10 @@ public final class CompatModePackages { return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0; } + public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) { + return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0; + } + public void setFrontActivityAskCompatModeLocked(boolean ask) { ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(); if (r != null) { @@ -245,22 +251,20 @@ public final class CompatModePackages { } public void setPackageAskCompatModeLocked(String packageName, boolean ask) { - int curFlags = getPackageFlags(packageName); - int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK); - if (curFlags != newFlags) { - if (newFlags != 0) { - mPackages.put(packageName, newFlags); - } else { - mPackages.remove(packageName); - } - scheduleWrite(); - } + setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask); } public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) { + setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify); + } + + public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) { + setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, notify); + } + + private void setPackageFlagLocked(String packageName, int flag, boolean set) { final int curFlags = getPackageFlags(packageName); - final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) : - (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY); + final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag); if (curFlags != newFlags) { if (newFlags != 0) { mPackages.put(packageName, newFlags); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 7810c5e40901..1a196013b124 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -400,6 +400,9 @@ final class ProcessList { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "CACC"; break; + case ActivityManager.PROCESS_STATE_CACHED_RECENT: + procState = "CRE "; + break; case ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CEM "; break; @@ -494,6 +497,7 @@ final class ProcessList { PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -515,6 +519,7 @@ final class ProcessList { PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -536,6 +541,7 @@ final class ProcessList { PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -557,6 +563,7 @@ final class ProcessList { PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; @@ -578,6 +585,7 @@ final class ProcessList { PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index e84772318e9b..9d3c2ae3617a 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -164,6 +164,8 @@ final class ProcessRecord { // all activities running in the process final ArrayList<ActivityRecord> activities = new ArrayList<>(); + // any tasks this process had run root activities in + final ArrayList<TaskRecord> recentTasks = new ArrayList<>(); // all ServiceRecord running in this process final ArraySet<ServiceRecord> services = new ArraySet<>(); // services that are currently executing code (need to remain foreground). @@ -396,6 +398,12 @@ final class ProcessRecord { pw.print(prefix); pw.print(" - "); pw.println(activities.get(i)); } } + if (recentTasks.size() > 0) { + pw.print(prefix); pw.println("Recent Tasks:"); + for (int i=0; i<recentTasks.size(); i++) { + pw.print(prefix); pw.print(" - "); pw.println(recentTasks.get(i)); + } + } if (services.size() > 0) { pw.print(prefix); pw.println("Services:"); for (int i=0; i<services.size(); i++) { @@ -512,6 +520,13 @@ final class ProcessRecord { } } + public void clearRecentTasks() { + for (int i = recentTasks.size() - 1; i >= 0; i--) { + recentTasks.get(i).clearRootProcess(); + } + recentTasks.clear(); + } + /** * This method returns true if any of the activities within the process record are interesting * to the user. See HistoryRecord.isInterestingToUserLocked() diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 949f51fe1b09..a9c6eee05bf6 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -257,6 +257,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi /** Current stack. Setter must always be used to update the value. */ private ActivityStack mStack; + /** The process that had previously hosted the root activity of this task. + * Used to know that we should try harder to keep this process around, in case the + * user wants to return to it. */ + private ProcessRecord mRootProcess; + /** Takes on same value as first root activity */ boolean isPersistable = false; int maxRecents; @@ -962,6 +967,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mService.notifyTaskPersisterLocked(this, false); } + clearRootProcess(); + // TODO: Use window container controller once tasks are better synced between AM and WM mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId); } @@ -2114,6 +2121,22 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } } + void setRootProcess(ProcessRecord proc) { + clearRootProcess(); + if (intent != null && + (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) { + mRootProcess = proc; + proc.recentTasks.add(this); + } + } + + void clearRootProcess() { + if (mRootProcess != null) { + mRootProcess.recentTasks.remove(this); + mRootProcess = null; + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); @@ -2198,6 +2221,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (lastDescription != null) { pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription); } + if (mRootProcess != null) { + pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess); + } pw.print(prefix); pw.print("stackId="); pw.println(getStackId()); pw.print(prefix + "hasBeenVisible=" + hasBeenVisible); pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode)); diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java new file mode 100644 index 000000000000..600589a40bb8 --- /dev/null +++ b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java @@ -0,0 +1,83 @@ +/* + * 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.am; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.view.Window; +import android.view.WindowManager; +import android.widget.CheckBox; + +import com.android.internal.R; +import com.android.server.utils.AppInstallerUtil; + +public class UnsupportedCompileSdkDialog { + private final AlertDialog mDialog; + private final String mPackageName; + + public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context, + ApplicationInfo appInfo) { + mPackageName = appInfo.packageName; + + final PackageManager pm = context.getPackageManager(); + final CharSequence label = appInfo.loadSafeLabel(pm); + final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message, + label, appInfo.compileSdkVersionCodename); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setPositiveButton(R.string.ok, null) + .setMessage(message) + .setView(R.layout.unsupported_compile_sdk_dialog_content); + + // If we might be able to update the app, show a button. + final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName); + if (installerIntent != null) { + builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update, + (dialog, which) -> context.startActivity(installerIntent)); + } + + // Ensure the content view is prepared. + mDialog = builder.create(); + mDialog.create(); + + final Window window = mDialog.getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_PHONE); + + // DO NOT MODIFY. Used by CTS to verify the dialog is displayed. + window.getAttributes().setTitle("UnsupportedCompileSdkDialog"); + + final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); + alwaysShow.setChecked(true); + alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( + mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked)); + } + + public String getPackageName() { + return mPackageName; + } + + public void show() { + mDialog.show(); + } + + public void dismiss() { + mDialog.dismiss(); + } +} diff --git a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java index 501cd6bbba6d..88506632d7c3 100644 --- a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java +++ b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java @@ -30,7 +30,7 @@ public class UnsupportedDisplaySizeDialog { private final AlertDialog mDialog; private final String mPackageName; - public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context, + public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context, ApplicationInfo appInfo) { mPackageName = appInfo.packageName; @@ -54,14 +54,10 @@ public class UnsupportedDisplaySizeDialog { // DO NOT MODIFY. Used by CTS to verify the dialog is displayed. window.getAttributes().setTitle("UnsupportedDisplaySizeDialog"); - final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox); + final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox); alwaysShow.setChecked(true); - alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> { - synchronized (service) { - service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked( - mPackageName, isChecked); - } - }); + alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag( + mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked)); } public String getPackageName() { diff --git a/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java new file mode 100644 index 000000000000..876acf45fda8 --- /dev/null +++ b/services/core/java/com/android/server/display/BrightnessIdleJob.java @@ -0,0 +1,81 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + + +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.hardware.display.DisplayManagerInternal; +import android.util.Slog; + +import com.android.server.LocalServices; + +import java.util.concurrent.TimeUnit; + +/** + * JobService used to persists brightness slider events when the device + * is idle and charging. + */ +public class BrightnessIdleJob extends JobService { + + // Must be unique within the system server uid. + private static final int JOB_ID = 3923512; + + public static void scheduleJob(Context context) { + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + + JobInfo pending = jobScheduler.getPendingJob(JOB_ID); + JobInfo jobInfo = + new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class)) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(TimeUnit.HOURS.toMillis(24)).build(); + + if (pending != null && !pending.equals(jobInfo)) { + jobScheduler.cancel(JOB_ID); + pending = null; + } + + if (pending == null) { + jobScheduler.schedule(jobInfo); + } + } + + public static void cancelJob(Context context) { + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + jobScheduler.cancel(JOB_ID); + } + + @Override + public boolean onStartJob(JobParameters params) { + if (BrightnessTracker.DEBUG) { + Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events"); + } + DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class); + dmi.persistBrightnessSliderEvents(); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 361d92843de9..90888f0a528d 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -61,12 +61,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; -import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -75,8 +75,8 @@ import java.util.concurrent.TimeUnit; */ public class BrightnessTracker { - private static final String TAG = "BrightnessTracker"; - private static final boolean DEBUG = false; + static final String TAG = "BrightnessTracker"; + static final boolean DEBUG = false; private static final String EVENTS_FILE = "brightness_events.xml"; private static final int MAX_EVENTS = 100; @@ -103,6 +103,8 @@ public class BrightnessTracker { @GuardedBy("mEventsLock") private RingBuffer<BrightnessChangeEvent> mEvents = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS); + @GuardedBy("mEventsLock") + private boolean mEventsDirty; private final Runnable mEventsWriter = () -> writeEvents(); private volatile boolean mWriteEventsScheduled; @@ -170,6 +172,8 @@ public class BrightnessTracker { intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); mBroadcastReceiver = new Receiver(); mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter); + + mInjector.scheduleIdleJob(mContext); } /** Stop listening for events */ @@ -181,6 +185,7 @@ public class BrightnessTracker { mInjector.unregisterSensorListener(mContext, mSensorListener); mInjector.unregisterReceiver(mContext, mBroadcastReceiver); mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver); + mInjector.cancelIdleJob(mContext); } /** @@ -211,6 +216,10 @@ public class BrightnessTracker { brightness, userId); } + public void persistEvents() { + scheduleWriteEvents(); + } + private void handleBrightnessChanged() { if (DEBUG) { Slog.d(TAG, "Brightness change"); @@ -278,6 +287,7 @@ public class BrightnessTracker { Slog.d(TAG, "Event " + event.brightness + " " + event.packageName); } synchronized (mEventsLock) { + mEventsDirty = true; mEvents.append(event); } } @@ -291,8 +301,12 @@ public class BrightnessTracker { private void writeEvents() { mWriteEventsScheduled = false; - // TODO kick off write on handler thread e.g. every 24 hours. synchronized (mEventsLock) { + if (!mEventsDirty) { + // Nothing to write + return; + } + final AtomicFile writeTo = mInjector.getFile(); if (writeTo == null) { return; @@ -301,12 +315,14 @@ public class BrightnessTracker { if (writeTo.exists()) { writeTo.delete(); } + mEventsDirty = false; } else { FileOutputStream output = null; try { output = writeTo.startWrite(); writeEventsLocked(output); writeTo.finishWrite(output); + mEventsDirty = false; } catch (IOException e) { writeTo.failWrite(output); Slog.e(TAG, "Failed to write change mEvents.", e); @@ -317,6 +333,8 @@ public class BrightnessTracker { private void readEvents() { synchronized (mEventsLock) { + // Read might prune events so mark as dirty. + mEventsDirty = true; mEvents.clear(); final AtomicFile readFrom = mInjector.getFile(); if (readFrom != null && readFrom.exists()) { @@ -344,13 +362,16 @@ public class BrightnessTracker { out.startTag(null, TAG_EVENTS); BrightnessChangeEvent[] toWrite = mEvents.toArray(); + // Clear events, code below will add back the ones that are still within the time window. + mEvents.clear(); if (DEBUG) { Slog.d(TAG, "Writing events " + toWrite.length); } - final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE; + final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE; for (int i = 0; i < toWrite.length; ++i) { int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId); if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) { + mEvents.append(toWrite[i]); out.startTag(null, TAG_EVENT); out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness)); out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp)); @@ -465,6 +486,17 @@ public class BrightnessTracker { } } + public void dump(PrintWriter pw) { + synchronized (mEventsLock) { + pw.println("BrightnessTracker state:"); + pw.println(" mEvents.size=" + mEvents.size()); + pw.println(" mEventsDirty=" + mEventsDirty); + } + synchronized (mDataCollectionLock) { + pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size()); + } + } + // Not allowed to keep the SensorEvent so used to copy the data we care about. private static class LightData { public float lux; @@ -635,5 +667,13 @@ public class BrightnessTracker { public ActivityManager.StackInfo getFocusedStack() throws RemoteException { return ActivityManager.getService().getFocusedStackInfo(); } + + public void scheduleIdleJob(Context context) { + BrightnessIdleJob.scheduleJob(context); + } + + public void cancelIdleJob(Context context) { + BrightnessIdleJob.cancelJob(context); + } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index f1e20116d4ed..7530f3ef942b 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1285,6 +1285,9 @@ public final class DisplayManagerService extends SystemService { pw.println(); mPersistentDataStore.dump(pw); + + pw.println(); + mBrightnessTracker.dump(pw); } } @@ -1921,5 +1924,10 @@ public final class DisplayManagerService extends SystemService { public boolean isUidPresentOnDisplay(int uid, int displayId) { return isUidPresentOnDisplayInternal(uid, displayId); } + + @Override + public void persistBrightnessSliderEvents() { + mBrightnessTracker.persistEvents(); + } } } diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS new file mode 100644 index 000000000000..7e7335d68d3b --- /dev/null +++ b/services/core/java/com/android/server/display/OWNERS @@ -0,0 +1 @@ +michaelwr@google.com diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS new file mode 100644 index 000000000000..0313a40f7270 --- /dev/null +++ b/services/core/java/com/android/server/input/OWNERS @@ -0,0 +1,2 @@ +michaelwr@google.com +svv@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 b9777ec0d968..4a3becbe3e07 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -151,6 +151,7 @@ public final class JobSchedulerService extends com.android.server.SystemService StorageController mStorageController; /** Need directly for sending uid state changes */ private BackgroundJobsController mBackgroundJobsController; + private DeviceIdleJobsController mDeviceIdleJobsController; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. @@ -622,15 +623,24 @@ public final class JobSchedulerService extends com.android.server.SystemService if (disabled) { cancelJobsForUid(uid, "uid gone"); } + synchronized (mLock) { + mDeviceIdleJobsController.setUidActiveLocked(uid, false); + } } @Override public void onUidActive(int uid) throws RemoteException { + synchronized (mLock) { + mDeviceIdleJobsController.setUidActiveLocked(uid, true); + } } @Override public void onUidIdle(int uid, boolean disabled) { if (disabled) { cancelJobsForUid(uid, "app uid idle"); } + synchronized (mLock) { + mDeviceIdleJobsController.setUidActiveLocked(uid, false); + } } @Override public void onUidCachedChanged(int uid, boolean cached) { @@ -939,11 +949,11 @@ public final class JobSchedulerService extends com.android.server.SystemService mControllers.add(mBatteryController); mStorageController = StorageController.get(this); mControllers.add(mStorageController); - mBackgroundJobsController = BackgroundJobsController.get(this); - mControllers.add(mBackgroundJobsController); + mControllers.add(BackgroundJobsController.get(this)); mControllers.add(AppIdleController.get(this)); mControllers.add(ContentObserverController.get(this)); - mControllers.add(DeviceIdleJobsController.get(this)); + mDeviceIdleJobsController = DeviceIdleJobsController.get(this); + mControllers.add(mDeviceIdleJobsController); // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index 1af3b39e928b..28b60e3eadda 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -250,7 +250,7 @@ public final class JobStore { /** * @param userHandle User for whom we are querying the list of jobs. - * @return A list of all the jobs scheduled by the provided user. Never null. + * @return A list of all the jobs scheduled for the provided user. Never null. */ public List<JobStatus> getJobsByUser(int userHandle) { return mJobSet.getJobsByUser(userHandle); @@ -287,6 +287,10 @@ public final class JobStore { mJobSet.forEachJob(uid, functor); } + public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) { + mJobSet.forEachJobForSourceUid(sourceUid, functor); + } + public interface JobStatusFunctor { public void process(JobStatus jobStatus); } @@ -979,9 +983,12 @@ public final class JobStore { static final class JobSet { // Key is the getUid() originator of the jobs in each sheaf private SparseArray<ArraySet<JobStatus>> mJobs; + // Same data but with the key as getSourceUid() of the jobs in each sheaf + private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid; public JobSet() { mJobs = new SparseArray<ArraySet<JobStatus>>(); + mJobsPerSourceUid = new SparseArray<>(); } public List<JobStatus> getJobsByUid(int uid) { @@ -995,10 +1002,10 @@ public final class JobStore { // By user, not by uid, so we need to traverse by key and check public List<JobStatus> getJobsByUser(int userId) { - ArrayList<JobStatus> result = new ArrayList<JobStatus>(); - for (int i = mJobs.size() - 1; i >= 0; i--) { - if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) { - ArraySet<JobStatus> jobs = mJobs.valueAt(i); + final ArrayList<JobStatus> result = new ArrayList<JobStatus>(); + for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) { + final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i); if (jobs != null) { result.addAll(jobs); } @@ -1009,32 +1016,60 @@ public final class JobStore { public boolean add(JobStatus job) { final int uid = job.getUid(); + final int sourceUid = job.getSourceUid(); ArraySet<JobStatus> jobs = mJobs.get(uid); if (jobs == null) { jobs = new ArraySet<JobStatus>(); mJobs.put(uid, jobs); } - return jobs.add(job); + ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); + if (jobsForSourceUid == null) { + jobsForSourceUid = new ArraySet<>(); + mJobsPerSourceUid.put(sourceUid, jobsForSourceUid); + } + return jobs.add(job) && jobsForSourceUid.add(job); } public boolean remove(JobStatus job) { final int uid = job.getUid(); - ArraySet<JobStatus> jobs = mJobs.get(uid); - boolean didRemove = (jobs != null) ? jobs.remove(job) : false; - if (didRemove && jobs.size() == 0) { - // no more jobs for this uid; let the now-empty set object be GC'd. - mJobs.remove(uid); + final ArraySet<JobStatus> jobs = mJobs.get(uid); + final int sourceUid = job.getSourceUid(); + final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); + boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job); + if (didRemove) { + if (jobs.size() == 0) { + // no more jobs for this uid; let the now-empty set object be GC'd. + mJobs.remove(uid); + } + if (jobsForSourceUid.size() == 0) { + mJobsPerSourceUid.remove(sourceUid); + } + return true; } - return didRemove; + return false; } - // Remove the jobs all users not specified by the whitelist of user ids + /** + * Removes the jobs of all users not specified by the whitelist of user ids. + * The jobs scheduled by non existent users will not be removed if they were + */ public void removeJobsOfNonUsers(int[] whitelist) { - for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) { - int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex)); - // check if job's user id is not in the whitelist + for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) { + final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex)); if (!ArrayUtils.contains(whitelist, jobUserId)) { - mJobs.removeAt(jobIndex); + mJobsPerSourceUid.removeAt(jobSetIndex); + } + } + for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) { + final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex); + for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) { + final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId(); + if (!ArrayUtils.contains(whitelist, jobUserId)) { + jobsForUid.removeAt(jobIndex); + } + } + if (jobsForUid.size() == 0) { + mJobs.removeAt(jobSetIndex); } } } @@ -1077,6 +1112,7 @@ public final class JobStore { public void clear() { mJobs.clear(); + mJobsPerSourceUid.clear(); } public int size() { @@ -1112,8 +1148,17 @@ public final class JobStore { } } - public void forEachJob(int uid, JobStatusFunctor functor) { - ArraySet<JobStatus> jobs = mJobs.get(uid); + public void forEachJob(int callingUid, JobStatusFunctor functor) { + ArraySet<JobStatus> jobs = mJobs.get(callingUid); + if (jobs != null) { + for (int i = jobs.size() - 1; i >= 0; i--) { + functor.process(jobs.valueAt(i)); + } + } + } + + public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) { + final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid); if (jobs != null) { for (int i = jobs.size() - 1; i >= 0; i--) { functor.process(jobs.valueAt(i)); diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java index 374ab43ca736..b7eb9e063591 100644 --- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -16,14 +16,19 @@ package com.android.server.job.controllers; +import android.app.job.JobInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseBooleanArray; import com.android.internal.util.ArrayUtils; import com.android.server.DeviceIdleController; @@ -42,11 +47,22 @@ public final class DeviceIdleJobsController extends StateController { private static final String LOG_TAG = "DeviceIdleJobsController"; private static final boolean LOG_DEBUG = false; + private static final long BACKGROUND_JOBS_DELAY = 3000; + + static final int PROCESS_BACKGROUND_JOBS = 1; // Singleton factory private static Object sCreationLock = new Object(); private static DeviceIdleJobsController sController; + /** + * These are jobs added with a special flag to indicate that they should be exempted from doze + * when the app is temp whitelisted or in the foreground. + */ + private final ArraySet<JobStatus> mAllowInIdleJobs; + private final SparseBooleanArray mForegroundUids; + private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor; + private final DeviceIdleJobsDelayHandler mHandler; private final JobSchedulerService mJobSchedulerService; private final PowerManager mPowerManager; private final DeviceIdleController.LocalService mLocalDeviceIdleController; @@ -57,14 +73,6 @@ public final class DeviceIdleJobsController extends StateController { private boolean mDeviceIdleMode; private int[] mDeviceIdleWhitelistAppIds; private int[] mPowerSaveTempWhitelistAppIds; - // These jobs were added when the app was in temp whitelist, these should be exempted from doze - private final ArraySet<JobStatus> mTempWhitelistedJobs; - - final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() { - @Override public void process(JobStatus jobStatus) { - updateTaskStateLocked(jobStatus); - } - }; /** * Returns a singleton for the DeviceIdleJobsController @@ -108,8 +116,8 @@ public final class DeviceIdleJobsController extends StateController { + Arrays.toString(mPowerSaveTempWhitelistAppIds)); } boolean changed = false; - for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) { - changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i)); + for (int i = 0; i < mAllowInIdleJobs.size(); i++) { + changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i)); } if (changed) { mStateChangedListener.onControllerStateChanged(); @@ -125,6 +133,7 @@ public final class DeviceIdleJobsController extends StateController { super(jobSchedulerService, context, lock); mJobSchedulerService = jobSchedulerService; + mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper()); // Register for device idle mode changes mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mLocalDeviceIdleController = @@ -132,7 +141,9 @@ public final class DeviceIdleJobsController extends StateController { mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); mPowerSaveTempWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); - mTempWhitelistedJobs = new ArraySet<>(); + mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); + mAllowInIdleJobs = new ArraySet<>(); + mForegroundUids = new SparseBooleanArray(); final IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); @@ -150,7 +161,20 @@ public final class DeviceIdleJobsController extends StateController { } mDeviceIdleMode = enabled; if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode); - mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor); + if (enabled) { + mHandler.removeMessages(PROCESS_BACKGROUND_JOBS); + mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); + } else { + // When coming out of doze, process all foreground uids immediately, while others + // will be processed after a delay of 3 seconds. + for (int i = 0; i < mForegroundUids.size(); i++) { + if (mForegroundUids.valueAt(i)) { + mJobSchedulerService.getJobStore().forEachJobForSourceUid( + mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor); + } + } + mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY); + } } // Inform the job scheduler service about idle mode changes if (changed) { @@ -159,11 +183,30 @@ public final class DeviceIdleJobsController extends StateController { } /** + * Called by jobscheduler service to report uid state changes between active and idle + */ + public void setUidActiveLocked(int uid, boolean active) { + final boolean changed = (active != mForegroundUids.get(uid)); + if (!changed) { + return; + } + if (LOG_DEBUG) { + Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive")); + } + mForegroundUids.put(uid, active); + mDeviceIdleUpdateFunctor.mChanged = false; + mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor); + if (mDeviceIdleUpdateFunctor.mChanged) { + mStateChangedListener.onControllerStateChanged(); + } + } + + /** * Checks if the given job's scheduling app id exists in the device idle user whitelist. */ boolean isWhitelistedLocked(JobStatus job) { - return ArrayUtils.contains(mDeviceIdleWhitelistAppIds, - UserHandle.getAppId(job.getSourceUid())); + return Arrays.binarySearch(mDeviceIdleWhitelistAppIds, + UserHandle.getAppId(job.getSourceUid())) >= 0; } /** @@ -175,31 +218,33 @@ public final class DeviceIdleJobsController extends StateController { } private boolean updateTaskStateLocked(JobStatus task) { - final boolean whitelisted = isWhitelistedLocked(task) - || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task)); - final boolean enableTask = !mDeviceIdleMode || whitelisted; + final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) + && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task)); + final boolean whitelisted = isWhitelistedLocked(task); + final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle; return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted); } @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { - if (isTempWhitelistedLocked(jobStatus)) { - mTempWhitelistedJobs.add(jobStatus); - jobStatus.setDeviceNotDozingConstraintSatisfied(true, true); - } else { - updateTaskStateLocked(jobStatus); + if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { + mAllowInIdleJobs.add(jobStatus); } + updateTaskStateLocked(jobStatus); } @Override public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { - mTempWhitelistedJobs.remove(jobStatus); + if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { + mAllowInIdleJobs.remove(jobStatus); + } } @Override public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("DeviceIdleJobsController"); + pw.println("mDeviceIdleMode=" + mDeviceIdleMode); mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { @Override public void process(JobStatus jobStatus) { if (!jobStatus.shouldDump(filterUid)) { @@ -217,8 +262,42 @@ public final class DeviceIdleJobsController extends StateController { if (jobStatus.dozeWhitelisted) { pw.print(" WHITELISTED"); } + if (mAllowInIdleJobs.contains(jobStatus)) { + pw.print(" ALLOWED_IN_DOZE"); + } pw.println(); } }); } + + final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor { + boolean mChanged; + + @Override + public void process(JobStatus jobStatus) { + mChanged |= updateTaskStateLocked(jobStatus); + } + } + + final class DeviceIdleJobsDelayHandler extends Handler { + public DeviceIdleJobsDelayHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case PROCESS_BACKGROUND_JOBS: + // Just process all the jobs, the ones in foreground should already be running. + synchronized (mLock) { + mDeviceIdleUpdateFunctor.mChanged = false; + mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor); + if (mDeviceIdleUpdateFunctor.mChanged) { + mStateChangedListener.onControllerStateChanged(); + } + } + break; + } + } + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS new file mode 100644 index 000000000000..7e7335d68d3b --- /dev/null +++ b/services/core/java/com/android/server/lights/OWNERS @@ -0,0 +1 @@ +michaelwr@google.com diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6ba1d8de79cf..08da568d5931 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -16,15 +16,21 @@ package com.android.server.notification; +import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; +import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; -import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_NULL; import static android.service.notification.NotificationListenerService + .HINT_HOST_DISABLE_CALL_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; +import static android.service.notification.NotificationListenerService + .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; +import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_ADDED; import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_DELETED; @@ -32,12 +38,13 @@ import static android.service.notification.NotificationListenerService .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; -import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_ERROR; -import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationListenerService + .REASON_GROUP_SUMMARY_CANCELED; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED; @@ -48,14 +55,10 @@ import static android.service.notification.NotificationListenerService.REASON_SN import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; -import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; - import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; @@ -68,17 +71,17 @@ import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; -import android.app.NotificationChannelGroup; -import android.app.backup.BackupManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.Notification; import android.app.NotificationChannel; -import android.app.NotificationManager.Policy; +import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.app.backup.BackupManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -119,8 +122,8 @@ import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.os.Vibrator; import android.os.VibrationEffect; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.Condition; @@ -174,9 +177,9 @@ import com.android.server.SystemService; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.ManagedServices.ManagedServiceInfo; +import com.android.server.notification.ManagedServices.UserProfiles; import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.notification.ManagedServices.UserProfiles; import libcore.io.IoUtils; @@ -196,7 +199,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; @@ -1520,7 +1522,11 @@ public class NotificationManagerService extends SystemService { } } } + final NotificationChannel preUpdate = + mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true); + mRankingHelper.updateNotificationChannel(pkg, uid, channel, true); + maybeNotifyChannelOwner(pkg, uid, preUpdate, channel); if (!fromListener) { final NotificationChannel modifiedChannel = @@ -1533,12 +1539,40 @@ public class NotificationManagerService extends SystemService { savePolicyFile(); } + private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate, + NotificationChannel update) { + try { + if ((preUpdate.getImportance() == IMPORTANCE_NONE + && update.getImportance() != IMPORTANCE_NONE) + || (preUpdate.getImportance() != IMPORTANCE_NONE + && update.getImportance() == IMPORTANCE_NONE)) { + getContext().sendBroadcastAsUser( + new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED) + .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID, + update.getId()) + .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, + update.getImportance() == IMPORTANCE_NONE) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(pkg), + UserHandle.of(UserHandle.getUserId(uid)), null); + } + } catch (SecurityException e) { + Slog.w(TAG, "Can't notify app about channel change", e); + } + } + private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromApp, boolean fromListener) { Preconditions.checkNotNull(group); Preconditions.checkNotNull(pkg); + + final NotificationChannelGroup preUpdate = + mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid); mRankingHelper.createNotificationChannelGroup(pkg, uid, group, fromApp); + if (!fromApp) { + maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group); + } if (!fromListener) { mListeners.notifyNotificationChannelGroupChanged(pkg, UserHandle.of(UserHandle.getCallingUserId()), group, @@ -1546,6 +1580,25 @@ public class NotificationManagerService extends SystemService { } } + private void maybeNotifyChannelGroupOwner(String pkg, int uid, + NotificationChannelGroup preUpdate, NotificationChannelGroup update) { + try { + if (preUpdate.isBlocked() != update.isBlocked()) { + getContext().sendBroadcastAsUser( + new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED) + .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID, + update.getId()) + .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, + update.isBlocked()) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(pkg), + UserHandle.of(UserHandle.getUserId(uid)), null); + } + } catch (SecurityException e) { + Slog.w(TAG, "Can't notify app about group change", e); + } + } + private ArrayList<ComponentName> getSuppressors() { ArrayList<ComponentName> names = new ArrayList<ComponentName>(); for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) { @@ -1924,11 +1977,18 @@ public class NotificationManagerService extends SystemService { } @Override + public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) { + checkCallerIsSystemOrSameApp(pkg); + return mRankingHelper.getNotificationChannelGroupWithChannels( + pkg, Binder.getCallingUid(), groupId, false); + } + + @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups( String pkg) { checkCallerIsSystemOrSameApp(pkg); - return new ParceledListSlice<>(new ArrayList( - mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid()))); + return mRankingHelper.getNotificationChannelGroups( + pkg, Binder.getCallingUid(), false, false); } @Override @@ -1998,7 +2058,7 @@ public class NotificationManagerService extends SystemService { public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage( String pkg, int uid, boolean includeDeleted) { checkCallerIsSystem(); - return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted); + return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true); } @Override diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index b9c0d90741f3..b1b0bf26d9ee 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -36,7 +36,7 @@ public interface RankingConfig { void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp); ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted); + int uid, boolean includeDeleted, boolean includeNonGrouped); void createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp); void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index d566a45011d7..c0dccb53c08a 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -750,12 +750,15 @@ public class RankingHelper implements RankingConfig { int uid) { Preconditions.checkNotNull(pkg); Record r = getRecord(pkg, uid); + if (r == null) { + return null; + } return r.groups.get(groupId); } @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted) { + int uid, boolean includeDeleted, boolean includeNonGrouped) { Preconditions.checkNotNull(pkg); Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); Record r = getRecord(pkg, uid); @@ -783,7 +786,7 @@ public class RankingHelper implements RankingConfig { } } } - if (nonGrouped.getChannels().size() > 0) { + if (includeNonGrouped && nonGrouped.getChannels().size() > 0) { groups.put(null, nonGrouped); } return new ParceledListSlice<>(new ArrayList<>(groups.values())); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bbe59eb84904..2e44e7973cab 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3585,7 +3585,7 @@ public class PackageManagerService extends IPackageManager.Stub final int N = list.size(); for (int i = 0; i < N; i++) { ResolveInfo info = list.get(i); - if (packageName.equals(info.activityInfo.packageName)) { + if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) { return true; } } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index ee773a515b0f..44f36d1734c8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -264,7 +264,7 @@ class PackageManagerShellCommand extends ShellCommand { PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null, null, null); params.sessionParams.setSize(PackageHelper.calculateInstalledSize( - pkgLite, params.sessionParams.abiOverride)); + pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor())); } catch (PackageParserException | IOException e) { getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath); throw new IllegalArgumentException( @@ -1169,11 +1169,17 @@ class PackageManagerShellCommand extends ShellCommand { } List<String> failedPackages = new ArrayList<>(); + int index = 0; for (String packageName : packageNames) { if (clearProfileData) { mInterface.clearApplicationProfileData(packageName); } + if (allPackages) { + pw.println(++index + "/" + packageNames.size() + ": " + packageName); + pw.flush(); + } + boolean result = secondaryDex ? mInterface.performDexOptSecondary(packageName, targetCompilerFilter, forceCompilation) diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7748ae4f3fe0..384070cf4777 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -304,6 +304,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int LONG_PRESS_POWER_SHUT_OFF = 2; static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3; + static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4; + + static final int VERY_LONG_PRESS_POWER_NOTHING = 0; + static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int MULTI_PRESS_POWER_NOTHING = 0; static final int MULTI_PRESS_POWER_THEATER_MODE = 1; @@ -569,6 +573,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mLidControlsSleep; int mShortPressOnPowerBehavior; int mLongPressOnPowerBehavior; + int mVeryLongPressOnPowerBehavior; int mDoublePressOnPowerBehavior; int mTriplePressOnPowerBehavior; int mLongPressOnBackBehavior; @@ -586,6 +591,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHasSoftInput = false; boolean mTranslucentDecorEnabled = true; boolean mUseTvRouting; + int mVeryLongPressTimeout; private boolean mHandleVolumeKeysInWM; @@ -796,6 +802,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_HANDLE_ALL_APPS = 26; private static final int MSG_LAUNCH_ASSIST = 27; private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28; + private static final int MSG_POWER_VERY_LONG_PRESS = 29; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; @@ -855,6 +862,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_POWER_LONG_PRESS: powerLongPress(); break; + case MSG_POWER_VERY_LONG_PRESS: + powerVeryLongPress(); + break; case MSG_UPDATE_DREAMING_SLEEP_TOKEN: updateDreamingSleepToken(msg.arg1 != 0); break; @@ -1299,6 +1309,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + + if (hasVeryLongPressOnPowerBehavior()) { + Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); + longMsg.setAsynchronous(true); + mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); + } } } else { wakeUpFromPowerKey(event.getDownTime()); @@ -1308,6 +1324,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + + if (hasVeryLongPressOnPowerBehavior()) { + Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); + longMsg.setAsynchronous(true); + mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); + } + mBeganFromNonInteractive = true; } else { final int maxCount = getMaxMultiPressPowerCount(); @@ -1369,6 +1392,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; mHandler.removeMessages(MSG_POWER_LONG_PRESS); } + if (hasVeryLongPressOnPowerBehavior()) { + mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS); + } } private void cancelPendingBackKeyAction() { @@ -1516,6 +1542,29 @@ public class PhoneWindowManager implements WindowManagerPolicy { sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); break; + case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + final boolean keyguardActive = mKeyguardDelegate == null + ? false + : mKeyguardDelegate.isShowing(); + if (!keyguardActive) { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } + break; + } + } + + private void powerVeryLongPress() { + switch (mVeryLongPressOnPowerBehavior) { + case VERY_LONG_PRESS_POWER_NOTHING: + break; + case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + showGlobalActionsInternal(); + break; } } @@ -1574,6 +1623,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING; } + private boolean hasVeryLongPressOnPowerBehavior() { + return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING; + } + private boolean hasLongPressOnBackBehavior() { return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING; } @@ -1979,12 +2032,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_shortPressOnPowerBehavior); mLongPressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnPowerBehavior); + mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressOnPowerBehavior); mDoublePressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_doublePressOnPowerBehavior); mTriplePressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_triplePressOnPowerBehavior); mShortPressOnSleepBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnSleepBehavior); + mVeryLongPressTimeout = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressTimeout); mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION; @@ -8193,6 +8250,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print("mLongPressOnPowerBehavior="); pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior)); pw.print(prefix); + pw.print("mVeryLongPressOnPowerBehavior="); + pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior)); + pw.print(prefix); pw.print("mDoublePressOnPowerBehavior="); pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior)); pw.print(prefix); @@ -8445,6 +8505,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { return Integer.toString(behavior); } } + + private static String veryLongPressOnPowerBehaviorToString(int behavior) { + switch (behavior) { + case VERY_LONG_PRESS_POWER_NOTHING: + return "VERY_LONG_PRESS_POWER_NOTHING"; + case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: + return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS"; + default: + return Integer.toString(behavior); + } + } + private static String multiPressOnPowerBehaviorToString(int behavior) { switch (behavior) { case MULTI_PRESS_POWER_NOTHING: diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index 3992f8a566ea..87c92744cdab 100644 --- a/services/core/java/com/android/server/power/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.power.batterysaver.CpuFrequencies; import java.io.PrintWriter; import java.util.ArrayList; @@ -40,15 +41,14 @@ import java.util.List; /** * Class to decide whether to turn on battery saver mode for specific service * - * TODO: We should probably make {@link #mFilesForInteractive} and {@link #mFilesForNoninteractive} - * less flexible and just take a list of "CPU number - frequency" pairs. Being able to write - * anything under /sys/ and /proc/ is too loose. - * - * Test: atest BatterySaverPolicyTest + * Test: + atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java */ public class BatterySaverPolicy extends ContentObserver { private static final String TAG = "BatterySaverPolicy"; + public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE. + // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode. public static final int GPS_MODE_NO_CHANGE = 0; // Value of batterySaverGpsMode such that GPS is disabled when battery saver mode @@ -64,18 +64,26 @@ public class BatterySaverPolicy extends ContentObserver { private static final String KEY_FIREWALL_DISABLED = "firewall_disabled"; private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled"; private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled"; + private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled"; private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor"; private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred"; private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred"; private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby"; private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled"; - private static final String KEY_FILE_FOR_INTERACTIVE_PREFIX = "file-on:"; - private static final String KEY_FILE_FOR_NONINTERACTIVE_PREFIX = "file-off:"; + private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i"; + private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n"; + + private final Object mLock = new Object(); - private static String mSettings; - private static String mDeviceSpecificSettings; - private static String mDeviceSpecificSettingsSource; // For dump() only. + @GuardedBy("mLock") + private String mSettings; + + @GuardedBy("mLock") + private String mDeviceSpecificSettings; + + @GuardedBy("mLock") + private String mDeviceSpecificSettingsSource; // For dump() only. /** * {@code true} if vibration is disabled in battery saver mode. @@ -83,6 +91,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_VIBRATION_DISABLED */ + @GuardedBy("mLock") private boolean mVibrationDisabled; /** @@ -91,6 +100,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ANIMATION_DISABLED */ + @GuardedBy("mLock") private boolean mAnimationDisabled; /** @@ -100,6 +110,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_SOUNDTRIGGER_DISABLED */ + @GuardedBy("mLock") private boolean mSoundTriggerDisabled; /** @@ -108,6 +119,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_FULLBACKUP_DEFERRED */ + @GuardedBy("mLock") private boolean mFullBackupDeferred; /** @@ -116,6 +128,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_KEYVALUE_DEFERRED */ + @GuardedBy("mLock") private boolean mKeyValueBackupDeferred; /** @@ -124,6 +137,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_FIREWALL_DISABLED */ + @GuardedBy("mLock") private boolean mFireWallDisabled; /** @@ -132,6 +146,7 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ADJUST_BRIGHTNESS_DISABLED */ + @GuardedBy("mLock") private boolean mAdjustBrightnessDisabled; /** @@ -140,14 +155,22 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_DATASAVER_DISABLED */ + @GuardedBy("mLock") private boolean mDataSaverDisabled; /** + * {@code true} if launch boost should be disabled on battery saver. + */ + @GuardedBy("mLock") + private boolean mLaunchBoostDisabled; + + /** * This is the flag to decide the gps mode in battery saver mode. * * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_GPS_MODE */ + @GuardedBy("mLock") private int mGpsMode; /** @@ -157,20 +180,21 @@ public class BatterySaverPolicy extends ContentObserver { * @see Settings.Global#BATTERY_SAVER_CONSTANTS * @see #KEY_ADJUST_BRIGHTNESS_FACTOR */ + @GuardedBy("mLock") private float mAdjustBrightnessFactor; /** * Whether to put all apps in the stand-by mode. */ + @GuardedBy("mLock") private boolean mForceAllAppsStandby; /** * Weather to show non-essential sensors (e.g. edge sensors) or not. */ + @GuardedBy("mLock") private boolean mOptionalSensorsDisabled; - private final Object mLock = new Object(); - @GuardedBy("mLock") private Context mContext; @@ -227,7 +251,11 @@ public class BatterySaverPolicy extends ContentObserver { @VisibleForTesting String getGlobalSetting(String key) { - return Settings.Global.getString(mContentResolver, key); + final ContentResolver cr; + synchronized (mLock) { + cr = mContentResolver; + } + return Settings.Global.getString(cr, key); } @VisibleForTesting @@ -273,6 +301,11 @@ public class BatterySaverPolicy extends ContentObserver { mSettings = setting; mDeviceSpecificSettings = deviceSpecificSetting; + if (DEBUG) { + Slog.i(TAG, "mSettings=" + mSettings); + Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings); + } + final KeyValueListParser parser = new KeyValueListParser(','); // Non-device-specific parameters. @@ -291,6 +324,7 @@ public class BatterySaverPolicy extends ContentObserver { mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false); mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f); mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true); + mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true); mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true); mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true); @@ -307,29 +341,11 @@ public class BatterySaverPolicy extends ContentObserver { + deviceSpecificSetting); } - mFilesForInteractive = collectParams(parser, KEY_FILE_FOR_INTERACTIVE_PREFIX); - mFilesForNoninteractive = collectParams(parser, KEY_FILE_FOR_NONINTERACTIVE_PREFIX); - } + mFilesForInteractive = (new CpuFrequencies()).parseString( + parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap(); - private static ArrayMap<String, String> collectParams( - KeyValueListParser parser, String prefix) { - final ArrayMap<String, String> ret = new ArrayMap<>(); - - for (int i = parser.size() - 1; i >= 0; i--) { - final String key = parser.keyAt(i); - if (!key.startsWith(prefix)) { - continue; - } - final String path = key.substring(prefix.length()); - - if (!(path.startsWith("/sys/") || path.startsWith("/proc/"))) { - Slog.wtf(TAG, "Invalid path: " + path); - continue; - } - - ret.put(path, parser.getString(key, "")); - } - return ret; + mFilesForNoninteractive = (new CpuFrequencies()).parseString( + parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap(); } /** @@ -395,14 +411,20 @@ public class BatterySaverPolicy extends ContentObserver { } } + public boolean isLaunchBoostDisabled() { + synchronized (mLock) { + return mLaunchBoostDisabled; + } + } + public void dump(PrintWriter pw) { synchronized (mLock) { pw.println(); pw.println("Battery saver policy"); - pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS); - pw.println(" value: " + mSettings); - pw.println(" Settings " + mDeviceSpecificSettingsSource); - pw.println(" value: " + mDeviceSpecificSettings); + pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS); + pw.println(" value: " + mSettings); + pw.println(" Settings: " + mDeviceSpecificSettingsSource); + pw.println(" value: " + mDeviceSpecificSettings); pw.println(); pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled); @@ -411,6 +433,7 @@ public class BatterySaverPolicy extends ContentObserver { pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred); pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled); pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled); + pw.println(" " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled); pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled); pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor); pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode); diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS new file mode 100644 index 000000000000..b4300a930cdf --- /dev/null +++ b/services/core/java/com/android/server/power/OWNERS @@ -0,0 +1,3 @@ +michaelwr@google.com + +per-file BatterySaverPolicy.java=omakoto@google.com diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 584761c3c0ef..2c87a4034300 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -1457,6 +1457,10 @@ public final class PowerManagerService extends SystemService case PowerManager.GO_TO_SLEEP_REASON_HDMI: Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")..."); break; + case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY: + Slog.i(TAG, "Going to sleep by an accessibility service request (uid " + + uid +")..."); + break; default: Slog.i(TAG, "Going to sleep by application request (uid " + uid +")..."); reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION; @@ -3105,7 +3109,16 @@ public final class PowerManagerService extends SystemService mIsVrModeEnabled = enabled; } - private static void powerHintInternal(int hintId, int data) { + private void powerHintInternal(int hintId, int data) { + // Maybe filter the event. + switch (hintId) { + case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate. + if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) { + return; + } + break; + } + nativeSendPowerHint(hintId, data); } diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java index 3db6a25f5413..ae01ea5721d8 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java @@ -49,7 +49,7 @@ import java.util.ArrayList; public class BatterySaverController implements BatterySaverPolicyListener { static final String TAG = "BatterySaverController"; - static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE + static final boolean DEBUG = BatterySaverPolicy.DEBUG; private final Object mLock = new Object(); private final Context mContext; @@ -174,6 +174,13 @@ public class BatterySaverController implements BatterySaverPolicyListener { } /** + * @return true if launch boost should currently be disabled. + */ + public boolean isLaunchBoostDisabled() { + return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled(); + } + + /** * Dispatch power save events to the listeners. * * This method is always called on the handler thread. diff --git a/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java new file mode 100644 index 000000000000..1629486b9273 --- /dev/null +++ b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java @@ -0,0 +1,101 @@ +/* + * 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.power.batterysaver; + +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Map; + + +/** + * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator, + * and convert them into a map of "filename -> value" that should be written to + * /sys/.../scaling_max_freq. + * + * Example input: "0:1900800/4:2500000", which will be converted into: + * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800" + * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000" + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java + */ +public class CpuFrequencies { + private static final String TAG = "CpuFrequencies"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>(); + + public CpuFrequencies() { + } + + /** + * Parse a string. + */ + public CpuFrequencies parseString(String cpuNumberAndFrequencies) { + synchronized (mLock) { + mCoreAndFrequencies.clear(); + try { + for (String pair : cpuNumberAndFrequencies.split("/")) { + final String[] coreAndFreq = pair.split(":", 2); + + if (coreAndFreq.length != 2) { + throw new IllegalArgumentException("Wrong format"); + } + final int core = Integer.parseInt(coreAndFreq[0]); + final long freq = Long.parseLong(coreAndFreq[1]); + + mCoreAndFrequencies.put(core, freq); + } + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e); + } + } + return this; + } + + /** + * Return a new map containing the filename-value pairs. + */ + public ArrayMap<String, String> toSysFileMap() { + final ArrayMap<String, String> map = new ArrayMap<>(); + addToSysFileMap(map); + return map; + } + + /** + * Add the filename-value pairs to an existing map. + */ + public void addToSysFileMap(Map<String, String> map) { + synchronized (mLock) { + final int size = mCoreAndFrequencies.size(); + + for (int i = 0; i < size; i++) { + final int core = mCoreAndFrequencies.keyAt(i); + final long freq = mCoreAndFrequencies.valueAt(i); + + final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) + + "/cpufreq/scaling_max_freq"; + + map.put(file, Long.toString(freq)); + } + } + } +} diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java index cfe8fc490e0a..cc1b540e669b 100644 --- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java +++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java @@ -16,40 +16,259 @@ package com.android.server.power.batterysaver; import android.content.Context; +import android.os.Handler; +import android.os.Looper; import android.util.ArrayMap; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.IoThread; + +import libcore.io.IoUtils; + +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; + /** * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files - * with retry and to restore the original values. + * with retries. It also support restoring to the file original values. * - * TODO Implement it + * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current + * frequency happens to be above the new max frequency. + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java */ public class FileUpdater { private static final String TAG = BatterySaverController.TAG; private static final boolean DEBUG = BatterySaverController.DEBUG; + // Don't do disk access with this lock held. private final Object mLock = new Object(); + private final Context mContext; + private final Handler mHandler; + + /** + * Filename -> value map that holds pending writes. + */ + @GuardedBy("mLock") + private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>(); + + /** + * Filename -> value that holds the original value of each file. + */ + @GuardedBy("mLock") + private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>(); + + /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */ + @GuardedBy("mLock") + private int mRetries = 0; + + private final int MAX_RETRIES; + + private final long RETRY_INTERVAL_MS; + + /** + * "Official" constructor. Don't use the other constructor in the production code. + */ public FileUpdater(Context context) { + this(context, IoThread.get().getLooper(), 10, 5000); + } + + /** + * Constructor for test. + */ + @VisibleForTesting + FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) { mContext = context; + mHandler = new Handler(looper); + + MAX_RETRIES = maxRetries; + RETRY_INTERVAL_MS = retryIntervalMs; } + /** + * Write values to files. (Note the actual writes happen ASAP but asynchronously.) + */ public void writeFiles(ArrayMap<String, String> fileValues) { - if (DEBUG) { - final int size = fileValues.size(); - for (int i = 0; i < size; i++) { - Slog.d(TAG, "Writing '" + fileValues.valueAt(i) - + "' to '" + fileValues.keyAt(i) + "'"); + synchronized (mLock) { + for (int i = fileValues.size() - 1; i >= 0; i--) { + final String file = fileValues.keyAt(i); + final String value = fileValues.valueAt(i); + + if (DEBUG) { + Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'"); + } + + mPendingWrites.put(file, value); + } + mRetries = 0; + + mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); + mHandler.post(mHandleWriteOnHandlerRunnable); } } + /** + * Restore the default values. + */ public void restoreDefault() { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Resetting file default values."); + } + mPendingWrites.clear(); + + writeFiles(mDefaultValues); + } + } + + private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler(); + + /** Convert map keys into a single string for debug messages. */ + private String getKeysString(Map<String, String> source) { + return new ArrayList<>(source.keySet()).toString(); + } + + /** Clone an ArrayMap. */ + private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) { + return new ArrayMap<>(source); + } + + /** + * Called on the handler and writes {@link #mPendingWrites} to the disk. + * + * When it about to write to each file for the first time, it'll read the file and store + * the original value in {@link #mDefaultValues}. + */ + private void handleWriteOnHandler() { + // We don't want to access the disk with the lock held, so copy the pending writes to + // a local map. + final ArrayMap<String, String> writes; + synchronized (mLock) { + if (mPendingWrites.size() == 0) { + return; + } + + if (DEBUG) { + Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " + + getKeysString(mPendingWrites)); + } + + writes = cloneMap(mPendingWrites); + } + + // Then write. + + boolean needRetry = false; + + final int size = writes.size(); + for (int i = 0; i < size; i++) { + final String file = writes.keyAt(i); + final String value = writes.valueAt(i); + + // Make sure the default value is loaded. + if (!ensureDefaultLoaded(file)) { + continue; + } + + // Write to the file. When succeeded, remove it from the pending list. + // Otherwise, schedule a retry. + try { + injectWriteToFile(file, value); + + removePendingWrite(file); + } catch (IOException e) { + needRetry = true; + } + } + if (needRetry) { + scheduleRetry(); + } + } + + private void removePendingWrite(String file) { + synchronized (mLock) { + mPendingWrites.remove(file); + } + } + + private void scheduleRetry() { + synchronized (mLock) { + if (mPendingWrites.size() == 0) { + return; // Shouldn't happen but just in case. + } + + mRetries++; + if (mRetries > MAX_RETRIES) { + doWtf("Gave up writing files: " + getKeysString(mPendingWrites)); + return; + } + + mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); + mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS); + } + } + + /** + * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}. + * + * @return true if the default value is loaded. false if the file cannot be read. + */ + private boolean ensureDefaultLoaded(String file) { + // Has the default already? + synchronized (mLock) { + if (mDefaultValues.containsKey(file)) { + return true; + } + } + final String originalValue; + try { + originalValue = injectReadFromFileTrimmed(file); + } catch (IOException e) { + // If the file is not readable, assume can't write too. + injectWtf("Unable to read from file", e); + + removePendingWrite(file); + return false; + } + synchronized (mLock) { + mDefaultValues.put(file, originalValue); + } + return true; + } + + @VisibleForTesting + String injectReadFromFileTrimmed(String file) throws IOException { + return IoUtils.readFileAsString(file).trim(); + } + + @VisibleForTesting + void injectWriteToFile(String file, String value) throws IOException { if (DEBUG) { - Slog.d(TAG, "Resetting file default values"); + Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'"); + } + try (FileWriter out = new FileWriter(file)) { + out.write(value); + } catch (IOException e) { + Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage()); + throw e; } } + + private void doWtf(String message) { + injectWtf(message, null); + } + + @VisibleForTesting + void injectWtf(String message, Throwable e) { + Slog.wtf(TAG, message, e); + } } diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS new file mode 100644 index 000000000000..09136dcc2feb --- /dev/null +++ b/services/core/java/com/android/server/power/batterysaver/OWNERS @@ -0,0 +1 @@ +omakoto@google.com diff --git a/services/core/java/com/android/server/utils/AppInstallerUtil.java b/services/core/java/com/android/server/utils/AppInstallerUtil.java new file mode 100644 index 000000000000..af7ff41f4455 --- /dev/null +++ b/services/core/java/com/android/server/utils/AppInstallerUtil.java @@ -0,0 +1,71 @@ +/* + * 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.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.util.Log; + +public class AppInstallerUtil { + private static final String LOG_TAG = "AppInstallerUtil"; + + private static Intent resolveIntent(Context context, Intent i) { + ResolveInfo result = context.getPackageManager().resolveActivity(i, 0); + return result != null ? new Intent(i.getAction()) + .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null; + } + + /** + * Returns the package name of the app which installed a given packageName, if available. + */ + public static String getInstallerPackageName(Context context, String packageName) { + String installerPackageName = null; + try { + installerPackageName = + context.getPackageManager().getInstallerPackageName(packageName); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e); + } + if (installerPackageName == null) { + return null; + } + return installerPackageName; + } + + /** + * Returns an intent to launcher the installer for a given package name. + */ + public static Intent createIntent(Context context, String installerPackageName, + String packageName) { + Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName); + final Intent result = resolveIntent(context, intent); + if (result != null) { + result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + return result; + } + return null; + } + + /** + * Convenience method that looks up the installerPackageName. + */ + public static Intent createIntent(Context context, String packageName) { + String installerPackageName = getInstallerPackageName(context, packageName); + return createIntent(context, installerPackageName, packageName); + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 465653938672..e123bef133d6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7455,6 +7455,11 @@ public class WindowManagerService extends IWindowManager.Stub public void registerDragDropControllerCallback(IDragDropCallback callback) { mDragDropController.registerCallback(callback); } + + @Override + public void lockNow() { + WindowManagerService.this.lockNow(null); + } } void registerAppFreezeListener(AppFreezeListener listener) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 52b7a25684f1..730ec3795ac0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2109,7 +2109,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (task != null) { return task.getDimmer(); } - return getStack().getDimmer(); + TaskStack taskStack = getStack(); + if (taskStack != null) { + return taskStack.getDimmer(); + } + return null; } /** Returns true if the replacement window was removed. */ @@ -4390,10 +4394,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mToken.makeChildSurface(this); } - - @Override - void prepareSurfaces() { - mIsDimming = false; + private void applyDims(Dimmer dimmer) { if (!mAnimatingExit && mAppDied) { mIsDimming = true; getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW); @@ -4402,6 +4403,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mIsDimming = true; getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount); } + } + + @Override + void prepareSurfaces() { + final Dimmer dimmer = getDimmer(); + mIsDimming = false; + if (dimmer != null) { + applyDims(dimmer); + } mWinAnimator.prepareSurfaceLocked(true); super.prepareSurfaces(); diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java new file mode 100644 index 000000000000..54d233a94815 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.transport; + +import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import com.android.internal.backup.IBackupTransport; + +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 org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 23) +@Presubmit +public class TransportClientTest { + private static final String PACKAGE_NAME = "some.package.name"; + private static final ComponentName TRANSPORT_COMPONENT = + new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport"); + + @Mock private Context mContext; + @Mock private TransportConnectionListener mTransportConnectionListener; + @Mock private TransportConnectionListener mTransportConnectionListener2; + @Mock private IBackupTransport.Stub mIBackupTransport; + private TransportClient mTransportClient; + private Intent mBindIntent; + private ShadowLooper mShadowLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Looper mainLooper = Looper.getMainLooper(); + mShadowLooper = shadowOf(mainLooper); + mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT); + mTransportClient = + new TransportClient( + mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper)); + + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(true); + } + + // TODO: Testing implementation? Remove? + @Test + public void testConnectAsync_callsBindService() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + verify(mContext) + .bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class)); + } + + @Test + public void testConnectAsync_callsListenerWhenConnected() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + // Simulate framework connecting + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + verify(mTransportConnectionListener2) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener2) + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception { + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(false); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportConnectionListener) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception { + when(mContext.bindServiceAsUser( + eq(mBindIntent), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class))) + .thenReturn(false); + + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + verify(mContext).unbindService(eq(connection)); + } + + @Test + public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + connection.onServiceDisconnected(TRANSPORT_COMPONENT); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + + verify(mTransportConnectionListener2) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener() + throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + connection.onServiceDisconnected(TRANSPORT_COMPONENT); + connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport); + + mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + + // Yes, it should return null because the object became unusable, check design doc + verify(mTransportConnectionListener2) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } + + // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests + /*@Test + public void testConnectAsync_callsListenerIfBindingDies() throws Exception { + mTransportClient.connectAsync(mTransportListener, "caller"); + + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onBindingDied(TRANSPORT_COMPONENT); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); + } + + @Test + public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies() + throws Exception { + mTransportClient.connectAsync(mTransportListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + + mTransportClient.connectAsync(mTransportListener2, "caller2"); + + connection.onBindingDied(TRANSPORT_COMPONENT); + + mShadowLooper.runToEndOfTasks(); + verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); + verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient)); + }*/ + + private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { + ArgumentCaptor<ServiceConnection> connectionCaptor = + ArgumentCaptor.forClass(ServiceConnection.class); + verify(context) + .bindServiceAsUser( + any(Intent.class), + connectionCaptor.capture(), + anyInt(), + any(UserHandle.class)); + return connectionCaptor.getValue(); + } +} diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index c145e828c49c..55ec133a29da 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16,8 +16,10 @@ package com.android.server.notification; +import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -940,6 +942,120 @@ public class NotificationManagerServiceTest extends NotificationTestCase { } @Test + public void testUpdateChannelNotifyCreatorBlock() throws Exception { + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + + NotificationChannel updatedChannel = + new NotificationChannel(mTestNotificationChannel.getId(), + mTestNotificationChannel.getName(), IMPORTANCE_NONE); + + mBinderService.updateNotificationChannelForPackage(PKG, 0, updatedChannel); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateChannelNotifyCreatorUnblock() throws Exception { + NotificationChannel existingChannel = + new NotificationChannel(mTestNotificationChannel.getId(), + mTestNotificationChannel.getName(), IMPORTANCE_NONE); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(existingChannel); + + mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateChannelNoNotifyCreatorOtherChanges() throws Exception { + NotificationChannel existingChannel = + new NotificationChannel(mTestNotificationChannel.getId(), + mTestNotificationChannel.getName(), IMPORTANCE_MAX); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(existingChannel); + + mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel); + verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null)); + } + + @Test + public void testUpdateGroupNotifyCreatorBlock() throws Exception { + NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + .thenReturn(existing); + + NotificationChannelGroup updated = new NotificationChannelGroup("id", "name"); + updated.setBlocked(true); + + mBinderService.updateNotificationChannelGroupForPackage(PKG, 0, updated); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(existing.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateGroupNotifyCreatorUnblock() throws Exception { + NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); + existing.setBlocked(true); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + .thenReturn(existing); + + mBinderService.updateNotificationChannelGroupForPackage( + PKG, 0, new NotificationChannelGroup("id", "name")); + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null)); + + assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED, + captor.getValue().getAction()); + assertEquals(PKG, captor.getValue().getPackage()); + assertEquals(existing.getId(), captor.getValue().getStringExtra( + NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID)); + assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)); + } + + @Test + public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception { + NotificationChannelGroup existing = new NotificationChannelGroup("id", "name"); + mService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt())) + .thenReturn(existing); + + mBinderService.updateNotificationChannelGroupForPackage( + PKG, 0, new NotificationChannelGroup("id", "new name")); + verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null)); + } + + @Test public void testCreateChannelNotifyListener() throws Exception { List<String> associations = new ArrayList<>(); associations.add("a"); @@ -1040,6 +1156,9 @@ public class NotificationManagerServiceTest extends NotificationTestCase { List<String> associations = new ArrayList<>(); associations.add("a"); when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); mBinderService.updateNotificationChannelFromPrivilegedListener( null, PKG, Process.myUserHandle(), mTestNotificationChannel); diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 3c02e23eed2a..2d03f111e528 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -372,7 +372,7 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false)); List<NotificationChannelGroup> actualGroups = - mHelper.getNotificationChannelGroups(PKG, UID, false).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList(); boolean foundNcg = false; for (NotificationChannelGroup actual : actualGroups) { if (ncg.getId().equals(actual.getId())) { @@ -442,7 +442,7 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false)); List<NotificationChannelGroup> actualGroups = - mHelper.getNotificationChannelGroups(PKG, UID, false).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList(); boolean foundNcg = false; for (NotificationChannelGroup actual : actualGroups) { if (ncg.getId().equals(actual.getId())) { @@ -1281,7 +1281,8 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID}); - assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size()); + assertEquals(0, + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size()); } @Test @@ -1370,7 +1371,7 @@ public class RankingHelperTest extends NotificationTestCase { mHelper.createNotificationChannel(PKG, UID, channel3, true); List<NotificationChannelGroup> actual = - mHelper.getNotificationChannelGroups(PKG, UID, true).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); assertEquals(3, actual.size()); for (NotificationChannelGroup group : actual) { if (group.getId() == null) { @@ -1402,13 +1403,13 @@ public class RankingHelperTest extends NotificationTestCase { new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); channel1.setGroup(ncg.getId()); mHelper.createNotificationChannel(PKG, UID, channel1, true); - mHelper.getNotificationChannelGroups(PKG, UID, true).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); channel1.setImportance(IMPORTANCE_LOW); mHelper.updateNotificationChannel(PKG, UID, channel1, true); List<NotificationChannelGroup> actual = - mHelper.getNotificationChannelGroups(PKG, UID, true).getList(); + mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList(); assertEquals(2, actual.size()); for (NotificationChannelGroup group : actual) { diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml index 3ac56bb5d8bc..57da0af42a88 100644 --- a/services/tests/servicestests/res/values/strings.xml +++ b/services/tests/servicestests/res/values/strings.xml @@ -30,6 +30,6 @@ <string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string> <string name="config_batterySaverDeviceSpecificConfig_1"></string> - <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string> - <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string> + <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string> + <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string> </resources> diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java index f9933fb63f12..bc503c49a99a 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java @@ -95,8 +95,7 @@ public class ActivityStarterTests extends ActivityTestsBase { public void setUp() throws Exception { super.setUp(); mService = createActivityManagerService(); - mPackageManager = mock(IPackageManager.class); - mStarter = new ActivityStarter(mService, mPackageManager); + mStarter = new ActivityStarter(mService); } @Test @@ -178,7 +177,7 @@ public class ActivityStarterTests extends ActivityTestsBase { int expectedResult) { final ActivityManagerService service = createActivityManagerService(); final IPackageManager packageManager = mock(IPackageManager.class); - final ActivityStarter starter = new ActivityStarter(service, packageManager); + final ActivityStarter starter = new ActivityStarter(service); final IApplicationThread caller = mock(IApplicationThread.class); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 9c949ad34844..9683e229d50f 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -36,6 +36,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; @@ -90,6 +91,7 @@ public class ActivityTestsBase { protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) { service = spy(service); + doReturn(mock(IPackageManager.class)).when(service).getPackageManager(); service.mWindowManager = prepareMockWindowManager(); return service; } diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index d9fac8768c6b..6938e0f681cd 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -99,10 +99,12 @@ public class BrightnessTrackerTest { assertNotNull(mInjector.mSensorListener); assertNotNull(mInjector.mSettingsObserver); assertNotNull(mInjector.mBroadcastReceiver); + assertTrue(mInjector.mIdleScheduled); mTracker.stop(); assertNull(mInjector.mSensorListener); assertNull(mInjector.mSettingsObserver); assertNull(mInjector.mBroadcastReceiver); + assertFalse(mInjector.mIdleScheduled); } @Test @@ -399,6 +401,52 @@ public class BrightnessTrackerTest { } @Test + public void testWritePrunesOldEvents() throws Exception { + final int brightness = 20; + + mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness); + mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); + mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339); + + startTracker(mTracker); + mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), + batteryChangeEvent(30, 100)); + mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f)); + mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1)); + mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f)); + final long sensorTime = mInjector.currentTimeMillis(); + mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS)); + + // 31 days later + mInjector.incrementTime(TimeUnit.DAYS.toMillis(31)); + mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f)); + mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS)); + final long eventTime = mInjector.currentTimeMillis(); + + List<BrightnessChangeEvent> events = mTracker.getEvents(0).getList(); + assertEquals(2, events.size()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mTracker.writeEventsLocked(baos); + events = mTracker.getEvents(0).getList(); + mTracker.stop(); + + assertEquals(1, events.size()); + BrightnessChangeEvent event = events.get(0); + assertEquals(eventTime, event.timeStamp); + + // We will keep one of the old sensor events because we keep 1 event outside the window. + assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f); + assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps); + assertEquals(brightness, event.brightness); + assertEquals(0.3, event.batteryLevel, 0.01f); + assertTrue(event.nightMode); + assertEquals(3339, event.colorTemperature); + } + + @Test public void testParcelUnParcel() { Parcel parcel = Parcel.obtain(); BrightnessChangeEvent event = new BrightnessChangeEvent(); @@ -516,6 +564,7 @@ public class BrightnessTrackerTest { long mCurrentTimeMillis = System.currentTimeMillis(); long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); Handler mHandler; + boolean mIdleScheduled; public TestInjector(Handler handler) { mHandler = handler; @@ -636,5 +685,14 @@ public class BrightnessTrackerTest { focusedStack.topActivity = new ComponentName("a.package", "a.class"); return focusedStack; } + + public void scheduleIdleJob(Context context) { + // Don't actually schedule jobs during unit tests. + mIdleScheduled = true; + } + + public void cancelIdleJob(Context context) { + mIdleScheduled = false; + } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index fa8feb048fd6..1f9a243d0f22 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -197,6 +197,8 @@ public class PackageParserTest { assertEquals(a.coreApp, b.coreApp); assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers); assertEquals(a.mTrustedOverlay, b.mTrustedOverlay); + assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion); + assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename); assertEquals(a.use32bitAbi, b.use32bitAbi); assertEquals(a.packageName, b.packageName); assertTrue(Arrays.equals(a.splitNames, b.splitNames)); 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 0db19e452650..20cf733a860b 100644 --- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java @@ -237,21 +237,27 @@ public class BatterySaverPolicyTest extends AndroidTestCase { mBatterySaverPolicy.onChange(); assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}"); assertThat(mBatterySaverPolicy.getFileValues(false).toString()) - .isEqualTo("{/sys/a=1, /sys/b=2}"); - + .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " + + "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}"); mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3; mBatterySaverPolicy.onChange(); - assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/c=4}"); - assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{/sys/a=3}"); + assertThat(mBatterySaverPolicy.getFileValues(true).toString()) + .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " + + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}"); + assertThat(mBatterySaverPolicy.getFileValues(false).toString()) + .isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}"); mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, - "file-on:/proc/z=4"); + "cpufreq-i=3:1234567890/4:014/5:015"); mBatterySaverPolicy.onChange(); - assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/z=4}"); + assertThat(mBatterySaverPolicy.getFileValues(true).toString()) + .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " + + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " + + "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}"); assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}"); } } diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java new file mode 100644 index 000000000000..f72ec3411461 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java @@ -0,0 +1,69 @@ +/* + * 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.power.batterysaver; + +import static org.junit.Assert.assertEquals; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class CpuFrequenciesTest { + private void check(ArrayMap<String, String> expected, String config) { + assertEquals(expected, (new CpuFrequencies().parseString(config)) + .toSysFileMap()); + } + + @Test + public void test() { + check(new ArrayMap<>(), ""); + + final ArrayMap<String, String> expected = new ArrayMap<>(); + + expected.clear(); + expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0"); + check(expected, "0:0"); + + expected.clear(); + expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0"); + expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1"); + check(expected, "0:0/1:1"); + + expected.clear(); + expected.put("/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq", "0"); + expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1234567890"); + check(expected, "2:0/1:1234567890"); + + expected.clear(); + expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "1900800"); + expected.put("/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq", "1958400"); + check(expected, "0:1900800/4:1958400"); + + check(expected, "0:1900800/4:1958400/"); // Shouldn't crash. + check(expected, "0:1900800/4:1958400/1"); // Shouldn't crash. + check(expected, "0:1900800/4:1958400/a:1"); // Shouldn't crash. + check(expected, "0:1900800/4:1958400/1:"); // Shouldn't crash. + check(expected, "0:1900800/4:1958400/1:b"); // Shouldn't crash. + } +} diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java new file mode 100644 index 000000000000..7e2a7d221c22 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java @@ -0,0 +1,337 @@ +/* + * 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.power.batterysaver; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class FileUpdaterTest { + + private class FileUpdaterTestable extends FileUpdater { + FileUpdaterTestable(Context context, Looper looper, int maxRetries, int retryIntervalMs) { + super(context, looper, maxRetries, retryIntervalMs); + } + + @Override + String injectReadFromFileTrimmed(String file) throws IOException { + return mInjector.injectReadFromFileTrimmed(file); + } + + @Override + void injectWriteToFile(String file, String value) throws IOException { + mInjector.injectWriteToFile(file, value); + } + + @Override + void injectWtf(String message, Throwable e) { + mInjector.injectWtf(message, e); + } + } + + private interface Injector { + String injectReadFromFileTrimmed(String file) throws IOException; + void injectWriteToFile(String file, String value) throws IOException; + void injectWtf(String message, Throwable e); + } + + private Handler mMainHandler; + + @Mock + private Injector mInjector; + + private static final int MAX_RETRIES = 3; + + private FileUpdaterTestable mInstance; + + public static <T> T anyOrNull(Class<T> clazz) { + return ArgumentMatchers.argThat(value -> true); + } + + public static String anyOrNullString() { + return ArgumentMatchers.argThat(value -> true); + } + + @Before + public void setUp() { + mMainHandler = new Handler(Looper.getMainLooper()); + + MockitoAnnotations.initMocks(this); + + mInstance = newInstance(); + } + + private FileUpdaterTestable newInstance() { + return new FileUpdaterTestable( + InstrumentationRegistry.getContext(), + Looper.getMainLooper(), + MAX_RETRIES, + 0 /* retry with no delays*/); + } + + private void waitUntilMainHandlerDrain() throws Exception { + final CountDownLatch l = new CountDownLatch(1); + mMainHandler.post(() -> l.countDown()); + assertTrue(l.await(5, TimeUnit.SECONDS)); + } + + private void veriryWtf(int times) { + verify(mInjector, times(times)).injectWtf(anyOrNullString(), anyOrNull(Throwable.class)); + } + + @Test + public void testNoWrites() throws Exception { + doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); + doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); + doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3"); + + // Write + final ArrayMap<String, String> values = new ArrayMap<>(); + + mInstance.writeFiles(values); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString()); + + // Reset to default + mInstance.restoreDefault(); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString()); + + // No WTF should have happened. + veriryWtf(0); + } + + @Test + public void testSimpleWrite() throws Exception { + doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); + doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); + doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3"); + + // Write + final ArrayMap<String, String> values = new ArrayMap<>(); + values.put("file1", "11"); + + mInstance.writeFiles(values); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "11"); + + // Reset to default + mInstance.restoreDefault(); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "111"); + + // No WTF should have happened. + veriryWtf(0); + } + + @Test + public void testMultiWrites() throws Exception { + doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); + doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); + doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3"); + + // Write + final ArrayMap<String, String> values = new ArrayMap<>(); + values.put("file1", "11"); + values.put("file2", "22"); + values.put("file3", "33"); + + mInstance.writeFiles(values); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "11"); + verify(mInjector, times(1)).injectWriteToFile("file2", "22"); + verify(mInjector, times(1)).injectWriteToFile("file3", "33"); + + // Reset to default + mInstance.restoreDefault(); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "111"); + verify(mInjector, times(1)).injectWriteToFile("file2", "222"); + verify(mInjector, times(1)).injectWriteToFile("file3", "333"); + + // No WTF should have happened. + veriryWtf(0); + } + + @Test + public void testCantReadDefault() throws Exception { + doThrow(new IOException("can't read")).when(mInjector).injectReadFromFileTrimmed("file1"); + doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); + + // Write + final ArrayMap<String, String> values = new ArrayMap<>(); + values.put("file1", "11"); + values.put("file2", "22"); + + mInstance.writeFiles(values); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(0)).injectWriteToFile("file1", "11"); + verify(mInjector, times(1)).injectWriteToFile("file2", "22"); + + veriryWtf(1); + + // Reset to default + mInstance.restoreDefault(); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(0)).injectWriteToFile("file1", "111"); + verify(mInjector, times(1)).injectWriteToFile("file2", "222"); + + veriryWtf(1); + } + + @Test + public void testWriteGiveUp() throws Exception { + doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); + doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); + doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1"); + + doThrow(new IOException("can't write")).when(mInjector).injectWriteToFile( + eq("fail1"), eq("33")); + + // Write + final ArrayMap<String, String> values = new ArrayMap<>(); + values.put("file1", "11"); + values.put("file2", "22"); + values.put("fail1", "33"); + + mInstance.writeFiles(values); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "11"); + verify(mInjector, times(1)).injectWriteToFile("file2", "22"); + + verify(mInjector, times(MAX_RETRIES + 1)).injectWriteToFile("fail1", "33"); + + // 1 WTF. + veriryWtf(1); + + // Reset to default + mInstance.restoreDefault(); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "111"); + verify(mInjector, times(1)).injectWriteToFile("file2", "222"); + + verify(mInjector, times(1)).injectWriteToFile("fail1", "333"); + + // No further WTF. + veriryWtf(1); + } + + @Test + public void testSuccessWithRetry() throws Exception { + doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); + doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); + doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1"); + + final AtomicInteger counter = new AtomicInteger(); + doAnswer((inv) -> { + if (counter.getAndIncrement() <= 1) { + throw new IOException(); + } + return null; + }).when(mInjector).injectWriteToFile(eq("fail1"), eq("33")); + + // Write + final ArrayMap<String, String> values = new ArrayMap<>(); + values.put("file1", "11"); + values.put("file2", "22"); + values.put("fail1", "33"); + + mInstance.writeFiles(values); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "11"); + verify(mInjector, times(1)).injectWriteToFile("file2", "22"); + + // Should succeed after 2 retries. + verify(mInjector, times(3)).injectWriteToFile("fail1", "33"); + + // No WTF. + veriryWtf(0); + + // Reset to default + mInstance.restoreDefault(); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "111"); + verify(mInjector, times(1)).injectWriteToFile("file2", "222"); + verify(mInjector, times(1)).injectWriteToFile("fail1", "333"); + + // Still no WTF. + veriryWtf(0); + } + + @Test + public void testAll() throws Exception { + // Run multiple tests on the single target instance. + + reset(mInjector); + testSimpleWrite(); + + reset(mInjector); + testWriteGiveUp(); + + reset(mInjector); + testMultiWrites(); + + reset(mInjector); + testSuccessWithRetry(); + + reset(mInjector); + testMultiWrites(); + } +} diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java index 011817e99714..884ba70b90f4 100644 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java +++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java @@ -27,12 +27,11 @@ import android.util.Log; public class TestJobActivity extends Activity { private static final String TAG = TestJobActivity.class.getSimpleName(); - public static final String EXTRA_JOB_ID_KEY = - "com.android.servicestests.apps.jobtestapp.extra.JOB_ID"; - public static final String ACTION_START_JOB = - "com.android.servicestests.apps.jobtestapp.action.START_JOB"; - public static final String ACTION_CANCEL_JOBS = - "com.android.servicestests.apps.jobtestapp.action.CANCEL_JOBS"; + private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; + + public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID"; + public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB"; + public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS"; public static final int JOB_INITIAL_BACKOFF = 10_000; public static final int JOB_MINIMUM_LATENCY = 5_000; @@ -59,6 +58,8 @@ public class TestJobActivity extends Activity { Log.d(TAG, "Successfully scheduled job with id " + jobId); } break; + default: + Log.e(TAG, "Unknown action " + intent.getAction()); } finish(); } diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 095fdc63975c..9bc9cd04957c 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -323,7 +323,8 @@ public class UsbHostManager { } /* Opens the specified USB device */ - public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) { + public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings, + String packageName, int uid) { synchronized (mLock) { if (isBlackListed(deviceName)) { throw new SecurityException("USB device is on a restricted bus"); @@ -334,7 +335,7 @@ public class UsbHostManager { throw new IllegalArgumentException( "device " + deviceName + " does not exist or is restricted"); } - settings.checkPermission(device); + settings.checkPermission(device, packageName, uid); return nativeOpenDevice(deviceName); } } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index e4fcea77fa44..17de83f0cac9 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -232,7 +232,7 @@ public class UsbService extends IUsbManager.Stub { /* Opens the specified USB device (host mode) */ @Override - public ParcelFileDescriptor openDevice(String deviceName) { + public ParcelFileDescriptor openDevice(String deviceName, String packageName) { ParcelFileDescriptor fd = null; if (mHostManager != null) { @@ -242,7 +242,8 @@ public class UsbService extends IUsbManager.Stub { boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked(); if (isCurrentUser) { - fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt)); + fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt), + packageName, Binder.getCallingUid()); } else { Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt + " as user is not active."); @@ -308,9 +309,10 @@ public class UsbService extends IUsbManager.Stub { } @Override - public boolean hasDevicePermission(UsbDevice device) { + public boolean hasDevicePermission(UsbDevice device, String packageName) { final int userId = UserHandle.getCallingUserId(); - return getSettingsForUser(userId).hasPermission(device); + return getSettingsForUser(userId).hasPermission(device, packageName, + Binder.getCallingUid()); } @Override @@ -322,7 +324,8 @@ public class UsbService extends IUsbManager.Stub { @Override public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) { final int userId = UserHandle.getCallingUserId(); - getSettingsForUser(userId).requestPermission(device, packageName, pi); + getSettingsForUser(userId).requestPermission(device, packageName, pi, + Binder.getCallingUid()); } @Override diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java index 96c5211cecf4..11e43e308e8a 100644 --- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java @@ -26,6 +26,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbManager; import android.os.Binder; import android.os.Process; @@ -95,10 +97,70 @@ class UsbUserSettingsManager { } } + /** + * Check whether a particular device or any of its interfaces + * is of class VIDEO. + * + * @param device The device that needs to get scanned + * @return True in case a VIDEO device or interface is present, + * False otherwise. + */ + private boolean isCameraDevicePresent(UsbDevice device) { + if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + + for (int i = 0; i < device.getInterfaceCount(); i++) { + UsbInterface iface = device.getInterface(i); + if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + } + + return false; + } + + /** + * Check for camera permission of the calling process. + * + * @param packageName Package name of the caller. + * @param uid Linux uid of the calling process. + * + * @return True in case camera permission is available, False otherwise. + */ + private boolean isCameraPermissionGranted(String packageName, int uid) { + int targetSdkVersion = android.os.Build.VERSION_CODES.P; + try { + ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); + // compare uid with packageName to foil apps pretending to be someone else + if (aInfo.uid != uid) { + Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid); + return false; + } + targetSdkVersion = aInfo.targetSdkVersion; + } catch (PackageManager.NameNotFoundException e) { + Slog.i(TAG, "Package not found, likely due to invalid package name!"); + return false; + } + + if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) { + int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA); + if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) { + Slog.i(TAG, "Camera permission required for USB video class devices"); + return false; + } + } - public boolean hasPermission(UsbDevice device) { + return true; + } + + public boolean hasPermission(UsbDevice device, String packageName, int uid) { synchronized (mLock) { - int uid = Binder.getCallingUid(); + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, uid)) { + return false; + } + } if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { return true; } @@ -124,8 +186,8 @@ class UsbUserSettingsManager { } } - public void checkPermission(UsbDevice device) { - if (!hasPermission(device)) { + public void checkPermission(UsbDevice device, String packageName, int uid) { + if (!hasPermission(device, packageName, uid)) { throw new SecurityException("User has not given permission to device " + device); } } @@ -166,11 +228,11 @@ class UsbUserSettingsManager { } } - public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) { + public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) { Intent intent = new Intent(); // respond immediately if permission has already been granted - if (hasPermission(device)) { + if (hasPermission(device, packageName, uid)) { intent.putExtra(UsbManager.EXTRA_DEVICE, device); intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); try { @@ -180,6 +242,18 @@ class UsbUserSettingsManager { } return; } + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, uid)) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); + try { + pi.send(mUserContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + } // start UsbPermissionActivity so user can choose an activity intent.putExtra(UsbManager.EXTRA_DEVICE, device); diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 132b234ab1e4..ffcef8966654 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -346,22 +346,20 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) { can_be_conditional &= CollectLocations(location, keep_set, &locations); } - for (const UsageLocation& location : entry.second) { - printer.Print("# Referenced at ").Println(location.source.to_string()); - } if (keep_set.conditional_keep_rules_ && can_be_conditional) { - printer.Println("-if class **.R$layout {"); - printer.Indent(); for (const UsageLocation& location : locations) { - printer.Print("int ") + printer.Print("# Referenced at ").Println(location.source.to_string()); + printer.Print("-if class **.R$layout { int ") .Print(JavaClassGenerator::TransformToFieldName(location.name.entry)) - .Println(";"); + .Println("; }"); + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); } - printer.Undent(); - printer.Println("}"); - printer.Println(); + } else { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); } - printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); printer.Println(); } diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index c2f11d96b9f2..a9e1e9d06470 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -66,11 +66,11 @@ interface IWifiManager List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult); - int addOrUpdateNetwork(in WifiConfiguration config, String packageName); + int addOrUpdateNetwork(in WifiConfiguration config); - boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName); + boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config); - boolean removePasspointConfiguration(in String fqdn, String packageName); + boolean removePasspointConfiguration(in String fqdn); List<PasspointConfiguration> getPasspointConfigurations(); @@ -80,21 +80,21 @@ interface IWifiManager void deauthenticateNetwork(long holdoff, boolean ess); - boolean removeNetwork(int netId, String packageName); + boolean removeNetwork(int netId); - boolean enableNetwork(int netId, boolean disableOthers, String packageName); + boolean enableNetwork(int netId, boolean disableOthers); - boolean disableNetwork(int netId, String packageName); + boolean disableNetwork(int netId); - void startScan(in ScanSettings requested, in WorkSource ws, String packageName); + void startScan(in ScanSettings requested, in WorkSource ws, in String packageName); List<ScanResult> getScanResults(String callingPackage); - void disconnect(String packageName); + void disconnect(); - void reconnect(String packageName); + void reconnect(); - void reassociate(String packageName); + void reassociate(); WifiInfo getConnectionInfo(String callingPackage); @@ -108,7 +108,7 @@ interface IWifiManager boolean isDualBandSupported(); - boolean saveConfiguration(String packageName); + boolean saveConfiguration(); DhcpInfo getDhcpInfo(); @@ -134,9 +134,9 @@ interface IWifiManager boolean stopSoftAp(); - int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, String packageName); + int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, in String packageName); - void stopLocalOnlyHotspot(String packageName); + void stopLocalOnlyHotspot(); void startWatchLocalOnlyHotspot(in Messenger messenger, in IBinder binder); @@ -146,9 +146,9 @@ interface IWifiManager WifiConfiguration getWifiApConfiguration(); - void setWifiApConfiguration(in WifiConfiguration wifiConfig, String packageName); + void setWifiApConfiguration(in WifiConfiguration wifiConfig); - Messenger getWifiServiceMessenger(String packageName); + Messenger getWifiServiceMessenger(); void enableTdls(String remoteIPAddress, boolean enable); @@ -166,16 +166,16 @@ interface IWifiManager void setAllowScansWithTraffic(int enabled); int getAllowScansWithTraffic(); - boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName); + boolean setEnableAutoJoinWhenAssociated(boolean enabled); boolean getEnableAutoJoinWhenAssociated(); void enableWifiConnectivityManager(boolean enabled); WifiConnectionStatistics getConnectionStatistics(); - void disableEphemeralNetwork(String SSID, String packageName); + void disableEphemeralNetwork(String SSID); - void factoryReset(String packageName); + void factoryReset(); Network getCurrentNetwork(); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 66fabf33ddbb..183cf0d10d49 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1128,7 +1128,7 @@ public class WifiManager { */ private int addOrUpdateNetwork(WifiConfiguration config) { try { - return mService.addOrUpdateNetwork(config, mContext.getOpPackageName()); + return mService.addOrUpdateNetwork(config); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1149,7 +1149,7 @@ public class WifiManager { */ public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) { try { - if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) { + if (!mService.addOrUpdatePasspointConfiguration(config)) { throw new IllegalArgumentException(); } } catch (RemoteException e) { @@ -1166,7 +1166,7 @@ public class WifiManager { */ public void removePasspointConfiguration(String fqdn) { try { - if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) { + if (!mService.removePasspointConfiguration(fqdn)) { throw new IllegalArgumentException(); } } catch (RemoteException e) { @@ -1252,7 +1252,7 @@ public class WifiManager { */ public boolean removeNetwork(int netId) { try { - return mService.removeNetwork(netId, mContext.getOpPackageName()); + return mService.removeNetwork(netId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1298,7 +1298,7 @@ public class WifiManager { boolean success; try { - success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName()); + success = mService.enableNetwork(netId, attemptConnect); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1324,7 +1324,7 @@ public class WifiManager { */ public boolean disableNetwork(int netId) { try { - return mService.disableNetwork(netId, mContext.getOpPackageName()); + return mService.disableNetwork(netId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1337,7 +1337,7 @@ public class WifiManager { */ public boolean disconnect() { try { - mService.disconnect(mContext.getOpPackageName()); + mService.disconnect(); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1352,7 +1352,7 @@ public class WifiManager { */ public boolean reconnect() { try { - mService.reconnect(mContext.getOpPackageName()); + mService.reconnect(); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1367,7 +1367,7 @@ public class WifiManager { */ public boolean reassociate() { try { - mService.reassociate(mContext.getOpPackageName()); + mService.reassociate(); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1740,7 +1740,7 @@ public class WifiManager { @Deprecated public boolean saveConfiguration() { try { - return mService.saveConfiguration(mContext.getOpPackageName()); + return mService.saveConfiguration(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2056,7 +2056,7 @@ public class WifiManager { } mLOHSCallbackProxy = null; try { - mService.stopLocalOnlyHotspot(mContext.getOpPackageName()); + mService.stopLocalOnlyHotspot(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2175,7 +2175,7 @@ public class WifiManager { @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) { try { - mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName()); + mService.setWifiApConfiguration(wifiConfig); return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2947,7 +2947,7 @@ public class WifiManager { public void disableEphemeralNetwork(String SSID) { if (SSID == null) throw new IllegalArgumentException("SSID cannot be null"); try { - mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName()); + mService.disableEphemeralNetwork(SSID); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2986,7 +2986,7 @@ public class WifiManager { */ public Messenger getWifiServiceMessenger() { try { - return mService.getWifiServiceMessenger(mContext.getOpPackageName()); + return mService.getWifiServiceMessenger(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3516,7 +3516,7 @@ public class WifiManager { */ public void factoryReset() { try { - mService.factoryReset(mContext.getOpPackageName()); + mService.factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3543,7 +3543,7 @@ public class WifiManager { */ public boolean setEnableAutoJoinWhenAssociated(boolean enabled) { try { - return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName()); + return mService.setEnableAutoJoinWhenAssociated(enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 3cad59053308..0df5615385fe 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -137,7 +137,7 @@ public class WifiManagerTest { assertEquals(mApConfig, callback.mRes.getWifiConfiguration()); callback.mRes.close(); - verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME); + verify(mWifiService).stopLocalOnlyHotspot(); } /** @@ -157,7 +157,7 @@ public class WifiManagerTest { assertEquals(mApConfig, res.getWifiConfiguration()); } - verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME); + verify(mWifiService).stopLocalOnlyHotspot(); } /** @@ -548,7 +548,7 @@ public class WifiManagerTest { anyString())).thenReturn(REQUEST_REGISTERED); mWifiManager.startLocalOnlyHotspot(callback, mHandler); mWifiManager.cancelLocalOnlyHotspotRequest(); - verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME); + verify(mWifiService).stopLocalOnlyHotspot(); } /** @@ -570,7 +570,7 @@ public class WifiManagerTest { anyString())).thenReturn(REQUEST_REGISTERED); mWifiManager.startLocalOnlyHotspot(callback, mHandler); mWifiManager.cancelLocalOnlyHotspotRequest(); - verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME); + verify(mWifiService).stopLocalOnlyHotspot(); mLooper.dispatchAll(); assertEquals(ERROR_NOT_SET, callback.mFailureReason); assertFalse(callback.mOnStartedCalled); @@ -594,7 +594,7 @@ public class WifiManagerTest { assertFalse(callback.mOnStoppedCalled); assertEquals(null, callback.mRes); mWifiManager.cancelLocalOnlyHotspotRequest(); - verify(mWifiService, never()).stopLocalOnlyHotspot(anyString()); + verify(mWifiService, never()).stopLocalOnlyHotspot(); } /** |