diff options
295 files changed, 10235 insertions, 4132 deletions
diff --git a/Android.bp b/Android.bp index 7e97e66a9e80..c2024084d95d 100644 --- a/Android.bp +++ b/Android.bp @@ -1067,6 +1067,7 @@ java_library { "core/java/android/annotation/IntDef.java", "core/java/android/annotation/NonNull.java", "core/java/android/annotation/SystemApi.java", + "core/java/android/annotation/TestApi.java", "core/java/android/os/HwBinder.java", "core/java/android/os/HwBlob.java", "core/java/android/os/HwParcel.java", diff --git a/api/current.txt b/api/current.txt index 584aedf297ec..86e3021d1822 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11191,6 +11191,7 @@ package android.content.pm { method public void registerCallback(android.content.pm.LauncherApps.Callback); method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); + method public boolean shouldHideFromSuggestions(java.lang.String, android.os.UserHandle); method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); @@ -13736,6 +13737,7 @@ package android.graphics { method public void drawTextOnPath(java.lang.String, android.graphics.Path, float, float, android.graphics.Paint); method public void drawTextRun(char[], int, int, int, int, float, float, boolean, android.graphics.Paint); method public void drawTextRun(java.lang.CharSequence, int, int, int, int, float, float, boolean, android.graphics.Paint); + method public void drawTextRun(android.graphics.text.MeasuredText, int, int, int, int, float, float, boolean, android.graphics.Paint); method public void drawVertices(android.graphics.Canvas.VertexMode, int, float[], int, float[], int, int[], int, short[], int, int, android.graphics.Paint); method public void enableZ(); method public boolean getClipBounds(android.graphics.Rect); @@ -24175,8 +24177,11 @@ package android.media { public static final class MediaCodec.CryptoException extends java.lang.RuntimeException { ctor public MediaCodec.CryptoException(int, java.lang.String); method public int getErrorCode(); + field public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8 field public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4 + field public static final int ERROR_INSUFFICIENT_SECURITY = 7; // 0x7 field public static final int ERROR_KEY_EXPIRED = 2; // 0x2 + field public static final int ERROR_LOST_STATE = 9; // 0x9 field public static final int ERROR_NO_KEY = 1; // 0x1 field public static final int ERROR_RESOURCE_BUSY = 3; // 0x3 field public static final int ERROR_SESSION_NOT_OPENED = 5; // 0x5 @@ -24517,6 +24522,22 @@ package android.media { field public static final int REGULAR_CODECS = 0; // 0x0 } + public class MediaController2 implements java.lang.AutoCloseable { + ctor public MediaController2(android.content.Context, android.media.Session2Token); + ctor public MediaController2(android.content.Context, android.media.Session2Token, java.util.concurrent.Executor, android.media.MediaController2.ControllerCallback); + method public void cancelSessionCommand(java.lang.Object); + method public void close(); + method public java.lang.Object sendSessionCommand(android.media.Session2Command, android.os.Bundle); + } + + public static abstract class MediaController2.ControllerCallback { + ctor public MediaController2.ControllerCallback(); + method public void onCommandResult(android.media.MediaController2, java.lang.Object, android.media.Session2Command, android.media.Session2Command.Result); + method public void onConnected(android.media.MediaController2, android.media.Session2CommandGroup); + method public void onDisconnected(android.media.MediaController2); + method public android.media.Session2Command.Result onSessionCommand(android.media.MediaController2, android.media.Session2Command, android.os.Bundle); + } + public final class MediaCrypto { ctor public MediaCrypto(java.util.UUID, byte[]) throws android.media.MediaCryptoException; method protected void finalize(); @@ -24624,6 +24645,7 @@ package android.media { method public void setOnEventListener(android.media.MediaDrm.OnEventListener); method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler); method public void setOnKeyStatusChangeListener(android.media.MediaDrm.OnKeyStatusChangeListener, android.os.Handler); + method public void setOnSessionLostStateListener(android.media.MediaDrm.OnSessionLostStateListener, android.os.Handler); method public void setPropertyByteArray(java.lang.String, byte[]); method public void setPropertyString(java.lang.String, java.lang.String); field public static final deprecated int EVENT_KEY_EXPIRED = 3; // 0x3 @@ -24742,6 +24764,10 @@ package android.media { method public abstract void onKeyStatusChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean); } + public static abstract interface MediaDrm.OnSessionLostStateListener { + method public abstract void onSessionLostState(android.media.MediaDrm, byte[]); + } + public static final class MediaDrm.ProvisionRequest { method public byte[] getData(); method public java.lang.String getDefaultUrl(); @@ -24750,6 +24776,12 @@ package android.media { public static abstract class MediaDrm.SecurityLevel implements java.lang.annotation.Annotation { } + public static final class MediaDrm.SessionException extends java.lang.RuntimeException { + ctor public MediaDrm.SessionException(int, java.lang.String); + method public int getErrorCode(); + field public static final int ERROR_RESOURCE_CONTENTION = 1; // 0x1 + } + public class MediaDrmException extends java.lang.Exception { ctor public MediaDrmException(java.lang.String); } @@ -25805,6 +25837,37 @@ package android.media { method public abstract void onScanCompleted(java.lang.String, android.net.Uri); } + public class MediaSession2 implements java.lang.AutoCloseable { + method public void broadcastSessionCommand(android.media.Session2Command, android.os.Bundle); + method public void cancelSessionCommand(android.media.MediaSession2.ControllerInfo, java.lang.Object); + method public void close(); + method public java.lang.String getSessionId(); + method public android.media.Session2Token getSessionToken(); + method public java.lang.Object sendSessionCommand(android.media.MediaSession2.ControllerInfo, android.media.Session2Command, android.os.Bundle); + } + + public static final class MediaSession2.Builder { + ctor public MediaSession2.Builder(android.content.Context); + method public android.media.MediaSession2 build(); + method public android.media.MediaSession2.Builder setId(java.lang.String); + method public android.media.MediaSession2.Builder setSessionActivity(android.app.PendingIntent); + method public android.media.MediaSession2.Builder setSessionCallback(java.util.concurrent.Executor, android.media.MediaSession2.SessionCallback); + } + + public static final class MediaSession2.ControllerInfo { + method public java.lang.String getPackageName(); + method public android.media.session.MediaSessionManager.RemoteUserInfo getRemoteUserInfo(); + method public int getUid(); + } + + public static abstract class MediaSession2.SessionCallback { + ctor public MediaSession2.SessionCallback(); + method public void onCommandResult(android.media.MediaSession2, android.media.MediaSession2.ControllerInfo, java.lang.Object, android.media.Session2Command, android.media.Session2Command.Result); + method public android.media.Session2CommandGroup onConnect(android.media.MediaSession2, android.media.MediaSession2.ControllerInfo); + method public void onDisconnected(android.media.MediaSession2, android.media.MediaSession2.ControllerInfo); + method public android.media.Session2Command.Result onSessionCommand(android.media.MediaSession2, android.media.MediaSession2.ControllerInfo, android.media.Session2Command, android.os.Bundle); + } + public final class MediaSync { ctor public MediaSync(); method public android.view.Surface createInputSurface(); @@ -26118,6 +26181,19 @@ package android.media { method public android.media.Session2CommandGroup.Builder removeCommand(int); } + public final class Session2Token implements android.os.Parcelable { + ctor public Session2Token(android.content.Context, android.content.ComponentName); + method public int describeContents(); + method public java.lang.String getPackageName(); + method public java.lang.String getServiceName(); + method public int getType(); + method public int getUid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.media.Session2Token> CREATOR; + field public static final int TYPE_SESSION = 0; // 0x0 + field public static final int TYPE_SESSION_SERVICE = 1; // 0x1 + } + public class SoundPool { ctor public deprecated SoundPool(int, int, int); method public final void autoPause(); @@ -29941,12 +30017,16 @@ package android.net.wifi.aware { method public android.net.NetworkSpecifier build(); method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setDiscoverySession(android.net.wifi.aware.DiscoverySession); method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPeerHandle(android.net.wifi.aware.PeerHandle); + method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPort(int); method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPskPassphrase(java.lang.String); + method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setTransportProtocol(int); } public final class WifiAwareNetworkInfo implements android.os.Parcelable android.net.TransportInfo { method public int describeContents(); method public java.net.Inet6Address getPeerIpv6Addr(); + method public int getPort(); + method public int getTransportProtocol(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.aware.WifiAwareNetworkInfo> CREATOR; } @@ -43053,10 +43133,12 @@ package android.telecom { field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 field public static final java.lang.String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED"; field public static final java.lang.String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED"; + field public static final java.lang.String EVENT_RTT_AUDIO_INDICATION_CHANGED = "android.telecom.event.RTT_AUDIO_INDICATION_CHANGED"; field public static final java.lang.String EXTRA_ANSWERING_DROPS_FG_CALL = "android.telecom.extra.ANSWERING_DROPS_FG_CALL"; field public static final java.lang.String EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME = "android.telecom.extra.ANSWERING_DROPS_FG_CALL_APP_NAME"; field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT"; field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS"; + field public static final java.lang.String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT"; field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER"; field public static final java.lang.String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE"; field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20 @@ -44332,6 +44414,7 @@ package android.telephony { method public int describeContents(); method public int getCdmaDbm(); method public int getCdmaEcio(); + method public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths(); method public int getEvdoDbm(); method public int getEvdoEcio(); method public int getEvdoSnr(); @@ -44774,6 +44857,9 @@ package android.telephony { public static abstract class TelephonyManager.CellInfoCallback { ctor public TelephonyManager.CellInfoCallback(); method public abstract void onCellInfo(java.util.List<android.telephony.CellInfo>); + method public void onError(int, java.lang.Throwable); + field public static final int ERROR_MODEM_ERROR = 2; // 0x2 + field public static final int ERROR_TIMEOUT = 1; // 0x1 } public static abstract class TelephonyManager.UssdResponseCallback { @@ -52930,15 +53016,15 @@ package android.view.inspector { package android.view.textclassifier { - public final class ConversationActions implements android.os.Parcelable { - ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>, java.lang.String); + public final class ConversationAction implements android.os.Parcelable { method public int describeContents(); - method public java.util.List<android.view.textclassifier.ConversationActions.ConversationAction> getConversationActions(); - method public java.lang.String getId(); + method public android.app.RemoteAction getAction(); + method public float getConfidenceScore(); + method public android.os.Bundle getExtras(); + method public java.lang.CharSequence getTextReply(); + method public java.lang.String getType(); method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions> CREATOR; - field public static final java.lang.String HINT_FOR_IN_APP = "in_app"; - field public static final java.lang.String HINT_FOR_NOTIFICATION = "notification"; + field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationAction> CREATOR; field public static final java.lang.String TYPE_CALL_PHONE = "call_phone"; field public static final java.lang.String TYPE_CREATE_REMINDER = "create_reminder"; field public static final java.lang.String TYPE_OPEN_URL = "open_url"; @@ -52951,24 +53037,22 @@ package android.view.textclassifier { field public static final java.lang.String TYPE_VIEW_MAP = "view_map"; } - public static final class ConversationActions.ConversationAction implements android.os.Parcelable { - method public int describeContents(); - method public android.app.RemoteAction getAction(); - method public float getConfidenceScore(); - method public android.os.Bundle getExtras(); - method public java.lang.CharSequence getTextReply(); - method public java.lang.String getType(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.ConversationAction> CREATOR; + public static final class ConversationAction.Builder { + ctor public ConversationAction.Builder(java.lang.String); + method public android.view.textclassifier.ConversationAction build(); + method public android.view.textclassifier.ConversationAction.Builder setAction(android.app.RemoteAction); + method public android.view.textclassifier.ConversationAction.Builder setConfidenceScore(float); + method public android.view.textclassifier.ConversationAction.Builder setExtras(android.os.Bundle); + method public android.view.textclassifier.ConversationAction.Builder setTextReply(java.lang.CharSequence); } - public static final class ConversationActions.ConversationAction.Builder { - ctor public ConversationActions.ConversationAction.Builder(java.lang.String); - method public android.view.textclassifier.ConversationActions.ConversationAction build(); - method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setAction(android.app.RemoteAction); - method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setConfidenceScore(float); - method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setExtras(android.os.Bundle); - method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setTextReply(java.lang.CharSequence); + public final class ConversationActions implements android.os.Parcelable { + ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationAction>, java.lang.String); + method public int describeContents(); + method public java.util.List<android.view.textclassifier.ConversationAction> getConversationActions(); + method public java.lang.String getId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions> CREATOR; } public static final class ConversationActions.Message implements android.os.Parcelable { @@ -52998,9 +53082,11 @@ package android.view.textclassifier { method public java.lang.String getConversationId(); method public java.util.List<java.lang.String> getHints(); method public int getMaxSuggestions(); - method public android.view.textclassifier.ConversationActions.TypeConfig getTypeConfig(); + method public android.view.textclassifier.TextClassifier.EntityConfig getTypeConfig(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Request> CREATOR; + field public static final java.lang.String HINT_FOR_IN_APP = "in_app"; + field public static final java.lang.String HINT_FOR_NOTIFICATION = "notification"; } public static final class ConversationActions.Request.Builder { @@ -53009,23 +53095,7 @@ package android.view.textclassifier { method public android.view.textclassifier.ConversationActions.Request.Builder setConversationId(java.lang.String); method public android.view.textclassifier.ConversationActions.Request.Builder setHints(java.util.List<java.lang.String>); method public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(int); - method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.ConversationActions.TypeConfig); - } - - public static final class ConversationActions.TypeConfig implements android.os.Parcelable { - method public int describeContents(); - method public java.util.Collection<java.lang.String> resolveTypes(java.util.Collection<java.lang.String>); - method public boolean shouldIncludeTypesFromTextClassifier(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.TypeConfig> CREATOR; - } - - public static final class ConversationActions.TypeConfig.Builder { - ctor public ConversationActions.TypeConfig.Builder(); - method public android.view.textclassifier.ConversationActions.TypeConfig build(); - method public android.view.textclassifier.ConversationActions.TypeConfig.Builder includeTypesFromTextClassifier(boolean); - method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setExcludedTypes(java.util.Collection<java.lang.String>); - method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setIncludedTypes(java.util.Collection<java.lang.String>); + method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.TextClassifier.EntityConfig); } public final class SelectionEvent implements android.os.Parcelable { @@ -53199,16 +53269,26 @@ package android.view.textclassifier { } public static final class TextClassifier.EntityConfig implements android.os.Parcelable { - method public static android.view.textclassifier.TextClassifier.EntityConfig create(java.util.Collection<java.lang.String>, java.util.Collection<java.lang.String>, java.util.Collection<java.lang.String>); - method public static android.view.textclassifier.TextClassifier.EntityConfig createWithExplicitEntityList(java.util.Collection<java.lang.String>); - method public static android.view.textclassifier.TextClassifier.EntityConfig createWithHints(java.util.Collection<java.lang.String>); + method public static deprecated android.view.textclassifier.TextClassifier.EntityConfig create(java.util.Collection<java.lang.String>, java.util.Collection<java.lang.String>, java.util.Collection<java.lang.String>); + method public static deprecated android.view.textclassifier.TextClassifier.EntityConfig createWithExplicitEntityList(java.util.Collection<java.lang.String>); + method public static deprecated android.view.textclassifier.TextClassifier.EntityConfig createWithHints(java.util.Collection<java.lang.String>); method public int describeContents(); method public java.util.Collection<java.lang.String> getHints(); method public java.util.Collection<java.lang.String> resolveEntityListModifications(java.util.Collection<java.lang.String>); + method public boolean shouldIncludeTypesFromTextClassifier(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR; } + public static final class TextClassifier.EntityConfig.Builder { + ctor public TextClassifier.EntityConfig.Builder(); + method public android.view.textclassifier.TextClassifier.EntityConfig build(); + method public android.view.textclassifier.TextClassifier.EntityConfig.Builder includeTypesFromTextClassifier(boolean); + method public android.view.textclassifier.TextClassifier.EntityConfig.Builder setExcludedTypes(java.util.Collection<java.lang.String>); + method public android.view.textclassifier.TextClassifier.EntityConfig.Builder setHints(java.util.Collection<java.lang.String>); + method public android.view.textclassifier.TextClassifier.EntityConfig.Builder setIncludedTypes(java.util.Collection<java.lang.String>); + } + public final class TextClassifierEvent implements android.os.Parcelable { method public int describeContents(); method public int[] getActionIndices(); diff --git a/api/system-current.txt b/api/system-current.txt index 9a1c804e067a..78f3471c3e86 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1047,7 +1047,6 @@ package android.app.role { method public void removeRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); method public boolean removeRoleHolderFromController(java.lang.String, java.lang.String); method public void setRoleNamesFromController(java.util.List<java.lang.String>); - field public static final java.lang.String EXTRA_REQUEST_ROLE_NAME = "android.app.role.extra.REQUEST_ROLE_NAME"; } public abstract interface RoleManagerCallback { @@ -1254,6 +1253,7 @@ package android.content { field public static final java.lang.String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS"; field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"; field public static final java.lang.String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS"; + field public static final java.lang.String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP"; field public static final java.lang.String ACTION_MANAGE_PERMISSIONS = "android.intent.action.MANAGE_PERMISSIONS"; field public static final java.lang.String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS"; field public static final java.lang.String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES"; @@ -1284,11 +1284,12 @@ package android.content { field public static final java.lang.String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE"; field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID"; field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; - field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; field public static final java.lang.String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME"; + field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON"; field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; + field public static final java.lang.String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME"; field public static final java.lang.String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; field public static final java.lang.String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; field public static final java.lang.String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; @@ -1301,6 +1302,26 @@ package android.content { } +package android.content.om { + + public final class OverlayInfo implements android.os.Parcelable { + method public int describeContents(); + method public boolean isEnabled(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR; + field public final java.lang.String category; + field public final java.lang.String packageName; + field public final java.lang.String targetPackageName; + field public final int userId; + } + + public class OverlayManager { + method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(java.lang.String, int); + method public boolean setEnabledExclusiveInCategory(java.lang.String, int); + } + +} + package android.content.pm { public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { @@ -1441,6 +1462,7 @@ package android.content.pm { method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public void sendDeviceCustomizationReadyBroadcast(); method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); + method public java.lang.String[] setDistractingPackageRestrictions(java.lang.String[], int); method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence); method public deprecated java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, java.lang.String); method public java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, android.content.pm.SuspendDialogInfo); @@ -1508,6 +1530,9 @@ package android.content.pm { field public static final int MATCH_ANY_USER = 4194304; // 0x400000 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_INSTANT = 8388608; // 0x800000 + field public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 1; // 0x1 + field public static final int RESTRICTION_HIDE_NOTIFICATIONS = 2; // 0x2 + field public static final int RESTRICTION_NONE = 0; // 0x0 } public static abstract class PackageManager.DexModuleRegisterCallback { @@ -1515,6 +1540,9 @@ package android.content.pm { method public abstract void onDexModuleRegistered(java.lang.String, boolean, java.lang.String); } + public static abstract class PackageManager.DistractionRestriction implements java.lang.annotation.Annotation { + } + public static abstract interface PackageManager.OnPermissionsChangedListener { method public abstract void onPermissionsChanged(int); } @@ -4860,6 +4888,14 @@ package android.permission { field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionPresentationInfo> CREATOR; } + public final class RuntimePermissionUsageInfo implements android.os.Parcelable { + ctor public RuntimePermissionUsageInfo(java.lang.CharSequence, int); + method public int describeContents(); + method public java.lang.CharSequence getName(); + method public int getAppAccessCount(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionUsageInfo> CREATOR; + } } package android.permissionpresenterservice { @@ -4981,6 +5017,7 @@ package android.provider { method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener); method public static void resetToDefaults(int, java.lang.String); method public static boolean setProperty(java.lang.String, java.lang.String, java.lang.String, boolean); + field public static final java.lang.String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot"; } public static abstract interface DeviceConfig.OnPropertyChangedListener { @@ -6181,6 +6218,40 @@ package android.telephony { field public static final int WWAN = 1; // 0x1 } + public class CallAttributes implements android.os.Parcelable { + ctor public CallAttributes(android.telephony.PreciseCallState, int, android.telephony.CallQuality); + method public int describeContents(); + method public android.telephony.CallQuality getCallQuality(); + method public int getNetworkType(); + method public android.telephony.PreciseCallState getPreciseCallState(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR; + } + + public final class CallQuality implements android.os.Parcelable { + ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int); + method public int describeContents(); + method public int getAverageRelativeJitter(); + method public int getAverageRoundTripTime(); + method public int getCallDuration(); + method public int getCodecType(); + method public int getDownlinkCallQualityLevel(); + method public int getMaxRelativeJitter(); + method public int getNumRtpPacketsNotReceived(); + method public int getNumRtpPacketsReceived(); + method public int getNumRtpPacketsTransmitted(); + method public int getNumRtpPacketsTransmittedLost(); + method public int getUplinkCallQualityLevel(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CALL_QUALITY_BAD = 4; // 0x4 + field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0 + field public static final int CALL_QUALITY_FAIR = 2; // 0x2 + field public static final int CALL_QUALITY_GOOD = 1; // 0x1 + field public static final int CALL_QUALITY_NOT_AVAILABLE = 5; // 0x5 + field public static final int CALL_QUALITY_POOR = 3; // 0x3 + field public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR; + } + public class CarrierConfigManager { method public static android.os.PersistableBundle getDefaultConfig(); method public void overrideConfig(int, android.os.PersistableBundle); @@ -6446,12 +6517,14 @@ package android.telephony { } public class PhoneStateListener { + method public void onCallAttributesChanged(android.telephony.CallAttributes); method public void onCallDisconnectCauseChanged(int, int); method public void onPreciseCallStateChanged(android.telephony.PreciseCallState); method public void onPreciseDataConnectionStateChanged(android.telephony.PreciseDataConnectionState); method public void onRadioPowerStateChanged(int); method public void onSrvccStateChanged(int); method public void onVoiceActivationStateChanged(int); + field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000 field public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 field public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800 field public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000 @@ -7178,6 +7251,7 @@ package android.telephony.ims { method public void callSessionInviteParticipantsRequestDelivered(); method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo); method public void callSessionMayHandover(int, int); + method public void callSessionRttAudioIndicatorChanged(android.telephony.ims.ImsStreamMediaProfile); method public void callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase); method public void callSessionMergeFailed(android.telephony.ims.ImsReasonInfo); method public void callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile); @@ -7595,10 +7669,12 @@ package android.telephony.ims { method public int describeContents(); method public int getAudioDirection(); method public int getAudioQuality(); + method public boolean getRttAudioSpeech(); method public int getRttMode(); method public int getVideoDirection(); method public int getVideoQuality(); method public boolean isRttCall(); + method public void setRttAudioSpeech(boolean); method public void setRttMode(int); method public void writeToParcel(android.os.Parcel, int); field public static final int AUDIO_QUALITY_AMR = 1; // 0x1 diff --git a/api/test-current.txt b/api/test-current.txt index 55705b6589d7..1ef309686389 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -421,6 +421,7 @@ package android.content.pm { public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000 + field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000 field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000 field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000 field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000 @@ -790,6 +791,134 @@ package android.os { method public static java.io.File getStorageDirectory(); } + public abstract class HwBinder implements android.os.IHwBinder { + ctor public HwBinder(); + method public static final void configureRpcThreadpool(long, boolean); + method public static void enableInstrumentation(); + method public static final android.os.IHwBinder getService(java.lang.String, java.lang.String) throws java.util.NoSuchElementException, android.os.RemoteException; + method public static final android.os.IHwBinder getService(java.lang.String, java.lang.String, boolean) throws java.util.NoSuchElementException, android.os.RemoteException; + method public static final void joinRpcThreadpool(); + method public abstract void onTransact(int, android.os.HwParcel, android.os.HwParcel, int) throws android.os.RemoteException; + method public final void registerService(java.lang.String) throws android.os.RemoteException; + method public final void transact(int, android.os.HwParcel, android.os.HwParcel, int) throws android.os.RemoteException; + } + + public class HwBlob { + ctor public HwBlob(int); + method public final void copyToBoolArray(long, boolean[], int); + method public final void copyToDoubleArray(long, double[], int); + method public final void copyToFloatArray(long, float[], int); + method public final void copyToInt16Array(long, short[], int); + method public final void copyToInt32Array(long, int[], int); + method public final void copyToInt64Array(long, long[], int); + method public final void copyToInt8Array(long, byte[], int); + method public final boolean getBool(long); + method public final double getDouble(long); + method public final float getFloat(long); + method public final short getInt16(long); + method public final int getInt32(long); + method public final long getInt64(long); + method public final byte getInt8(long); + method public final java.lang.String getString(long); + method public final long handle(); + method public final void putBlob(long, android.os.HwBlob); + method public final void putBool(long, boolean); + method public final void putBoolArray(long, boolean[]); + method public final void putDouble(long, double); + method public final void putDoubleArray(long, double[]); + method public final void putFloat(long, float); + method public final void putFloatArray(long, float[]); + method public final void putInt16(long, short); + method public final void putInt16Array(long, short[]); + method public final void putInt32(long, int); + method public final void putInt32Array(long, int[]); + method public final void putInt64(long, long); + method public final void putInt64Array(long, long[]); + method public final void putInt8(long, byte); + method public final void putInt8Array(long, byte[]); + method public final void putNativeHandle(long, android.os.NativeHandle); + method public final void putString(long, java.lang.String); + method public static java.lang.Boolean[] wrapArray(boolean[]); + method public static java.lang.Long[] wrapArray(long[]); + method public static java.lang.Byte[] wrapArray(byte[]); + method public static java.lang.Short[] wrapArray(short[]); + method public static java.lang.Integer[] wrapArray(int[]); + method public static java.lang.Float[] wrapArray(float[]); + method public static java.lang.Double[] wrapArray(double[]); + } + + public class HwParcel { + ctor public HwParcel(); + method public final void enforceInterface(java.lang.String); + method public final boolean readBool(); + method public final java.util.ArrayList<java.lang.Boolean> readBoolVector(); + method public final android.os.HwBlob readBuffer(long); + method public final double readDouble(); + method public final java.util.ArrayList<java.lang.Double> readDoubleVector(); + method public final android.os.HwBlob readEmbeddedBuffer(long, long, long, boolean); + method public final android.os.NativeHandle readEmbeddedNativeHandle(long, long); + method public final float readFloat(); + method public final java.util.ArrayList<java.lang.Float> readFloatVector(); + method public final short readInt16(); + method public final java.util.ArrayList<java.lang.Short> readInt16Vector(); + method public final int readInt32(); + method public final java.util.ArrayList<java.lang.Integer> readInt32Vector(); + method public final long readInt64(); + method public final java.util.ArrayList<java.lang.Long> readInt64Vector(); + method public final byte readInt8(); + method public final java.util.ArrayList<java.lang.Byte> readInt8Vector(); + method public final android.os.NativeHandle readNativeHandle(); + method public final java.util.ArrayList<android.os.NativeHandle> readNativeHandleVector(); + method public final java.lang.String readString(); + method public final java.util.ArrayList<java.lang.String> readStringVector(); + method public final android.os.IHwBinder readStrongBinder(); + method public final void release(); + method public final void releaseTemporaryStorage(); + method public final void send(); + method public final void verifySuccess(); + method public final void writeBool(boolean); + method public final void writeBoolVector(java.util.ArrayList<java.lang.Boolean>); + method public final void writeBuffer(android.os.HwBlob); + method public final void writeDouble(double); + method public final void writeDoubleVector(java.util.ArrayList<java.lang.Double>); + method public final void writeFloat(float); + method public final void writeFloatVector(java.util.ArrayList<java.lang.Float>); + method public final void writeInt16(short); + method public final void writeInt16Vector(java.util.ArrayList<java.lang.Short>); + method public final void writeInt32(int); + method public final void writeInt32Vector(java.util.ArrayList<java.lang.Integer>); + method public final void writeInt64(long); + method public final void writeInt64Vector(java.util.ArrayList<java.lang.Long>); + method public final void writeInt8(byte); + method public final void writeInt8Vector(java.util.ArrayList<java.lang.Byte>); + method public final void writeInterfaceToken(java.lang.String); + method public final void writeNativeHandle(android.os.NativeHandle); + method public final void writeNativeHandleVector(java.util.ArrayList<android.os.NativeHandle>); + method public final void writeStatus(int); + method public final void writeString(java.lang.String); + method public final void writeStringVector(java.util.ArrayList<java.lang.String>); + method public final void writeStrongBinder(android.os.IHwBinder); + field public static final int STATUS_SUCCESS = 0; // 0x0 + } + + public static abstract class HwParcel.Status implements java.lang.annotation.Annotation { + } + + public abstract interface IHwBinder { + method public abstract boolean linkToDeath(android.os.IHwBinder.DeathRecipient, long); + method public abstract android.os.IHwInterface queryLocalInterface(java.lang.String); + method public abstract void transact(int, android.os.HwParcel, android.os.HwParcel, int) throws android.os.RemoteException; + method public abstract boolean unlinkToDeath(android.os.IHwBinder.DeathRecipient); + } + + public static abstract interface IHwBinder.DeathRecipient { + method public abstract void serviceDied(long); + } + + public abstract interface IHwInterface { + method public abstract android.os.IHwBinder asBinder(); + } + public class IncidentManager { method public void reportIncident(android.os.IncidentReportArgs); } @@ -810,6 +939,18 @@ package android.os { field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR; } + public final class NativeHandle implements java.io.Closeable { + ctor public NativeHandle(); + ctor public NativeHandle(java.io.FileDescriptor, boolean); + ctor public NativeHandle(java.io.FileDescriptor[], int[], boolean); + method public void close() throws java.io.IOException; + method public android.os.NativeHandle dup() throws java.io.IOException; + method public java.io.FileDescriptor getFileDescriptor(); + method public java.io.FileDescriptor[] getFileDescriptors(); + method public int[] getInts(); + method public boolean hasSingleFileDescriptor(); + } + public final class MessageQueue { method public int postSyncBarrier(); method public void removeSyncBarrier(int); @@ -1252,6 +1393,71 @@ package android.service.autofill { } +package android.service.autofill.augmented { + + public abstract class AugmentedAutofillService extends android.app.Service { + ctor public AugmentedAutofillService(); + method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); + method protected void dump(java.io.PrintWriter, java.lang.String[]); + method public void onFillRequest(android.service.autofill.augmented.FillRequest, android.os.CancellationSignal, android.service.autofill.augmented.FillController, android.service.autofill.augmented.FillCallback); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService"; + } + + public final class FillCallback { + method public void onSuccess(android.service.autofill.augmented.FillResponse); + } + + public final class FillController { + method public void autofill(java.util.List<android.util.Pair<android.view.autofill.AutofillId, android.view.autofill.AutofillValue>>); + } + + public final class FillRequest { + method public android.content.ComponentName getActivityComponent(); + method public android.view.autofill.AutofillId getFocusedId(); + method public android.view.autofill.AutofillValue getFocusedValue(); + method public android.service.autofill.augmented.PresentationParams getPresentationParams(); + method public int getTaskId(); + } + + public final class FillResponse implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR; + } + + public static final class FillResponse.Builder { + ctor public FillResponse.Builder(); + method public android.service.autofill.augmented.FillResponse build(); + method public android.service.autofill.augmented.FillResponse.Builder setFillWindow(android.service.autofill.augmented.FillWindow); + method public android.service.autofill.augmented.FillResponse.Builder setIgnoredIds(java.util.List<android.view.autofill.AutofillId>); + } + + public final class FillWindow implements java.lang.AutoCloseable { + ctor public FillWindow(); + method public void destroy(); + method public boolean update(android.service.autofill.augmented.PresentationParams.Area, android.view.View, long); + field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L + } + + public abstract class PresentationParams { + method public int getFlags(); + method public android.service.autofill.augmented.PresentationParams.Area getFullArea(); + method public android.service.autofill.augmented.PresentationParams.Area getSuggestionArea(); + field public static final int FLAG_HINT_GRAVITY_BOTTOM = 2; // 0x2 + field public static final int FLAG_HINT_GRAVITY_LEFT = 4; // 0x4 + field public static final int FLAG_HINT_GRAVITY_RIGHT = 8; // 0x8 + field public static final int FLAG_HINT_GRAVITY_TOP = 1; // 0x1 + field public static final int FLAG_HOST_IME = 16; // 0x10 + field public static final int FLAG_HOST_SYSTEM = 32; // 0x20 + } + + public static abstract class PresentationParams.Area { + method public android.graphics.Rect getBounds(); + method public android.service.autofill.augmented.PresentationParams.Area getSubArea(android.graphics.Rect); + } + +} + package android.service.notification { public final class Adjustment implements android.os.Parcelable { @@ -1853,6 +2059,10 @@ package android.view.autofill { ctor public AutofillId(android.view.autofill.AutofillId, int); } + public final class AutofillManager { + field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0 + } + } package android.view.inputmethod { diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp index ec2decf782e2..b78e942d4049 100644 --- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -89,6 +89,7 @@ void RawPrintVisitor::visit(const IdmapData::TypeEntry& type_entry) { } } +// NOLINTNEXTLINE(cert-dcl50-cpp) void RawPrintVisitor::print(uint16_t value, const char* fmt, ...) { va_list ap; va_start(ap, fmt); @@ -101,6 +102,7 @@ void RawPrintVisitor::print(uint16_t value, const char* fmt, ...) { offset_ += sizeof(uint16_t); } +// NOLINTNEXTLINE(cert-dcl50-cpp) void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) { va_list ap; va_start(ap, fmt); @@ -113,6 +115,7 @@ void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) { offset_ += sizeof(uint32_t); } +// NOLINTNEXTLINE(cert-dcl50-cpp) void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) { va_list ap; va_start(ap, fmt); diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 8fa298060d60..3d74f8b207af 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -81,8 +81,7 @@ static sk_sp<SkColorSpace> dataSpaceToColorSpace(ui::Dataspace d) case ui::Dataspace::V0_SRGB: return SkColorSpace::MakeSRGB(); case ui::Dataspace::DISPLAY_P3: - return SkColorSpace::MakeRGB( - SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut); + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); default: return nullptr; } diff --git a/cmds/statsd/src/anomaly/subscriber_util.cpp b/cmds/statsd/src/anomaly/subscriber_util.cpp index 9d37cdb2d4d7..ad5eae361f1c 100644 --- a/cmds/statsd/src/anomaly/subscriber_util.cpp +++ b/cmds/statsd/src/anomaly/subscriber_util.cpp @@ -57,7 +57,7 @@ void triggerSubscribers(const int64_t rule_id, break; case Subscription::SubscriberInformationCase::kPerfettoDetails: if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details(), - rule_id, configKey)) { + subscription.id(), rule_id, configKey)) { ALOGW("Failed to generate perfetto traces."); } break; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 786d8d1437fc..f9828a2b5302 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -174,13 +174,14 @@ message Atom { WifiEnabledStateChanged wifi_enabled_state_changed = 113; WifiRunningStateChanged wifi_running_state_changed = 114; AppCompacted app_compacted = 115; - NetworkDnsEventReported network_dns_event_Reported = 116; + NetworkDnsEventReported network_dns_event_reported = 116; DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = 117; DocsUIPickResultReported docs_ui_pick_result_reported = 118; DocsUISearchModeReported docs_ui_search_mode_reported = 119; DocsUISearchTypeReported docs_ui_search_type_reported = 120; DataStallEvent data_stall_event = 121; RescuePartyResetReported rescue_party_reset_reported = 122; + SignedConfigReported signed_config_reported = 123; } // Pulled events will start at field 10000. @@ -3802,7 +3803,7 @@ message NetworkDnsEventReported { //bionic/libc/include/netdb.h //system/netd/resolv/include/netd_resolv/resolv.h enum ReturnCode { - EAI_NOERR = 0; + EAI_NO_ERROR = 0; EAI_ADDRFAMILY = 1; EAI_AGAIN = 2; EAI_BADFLAGS = 3; @@ -3859,3 +3860,44 @@ message RescuePartyResetReported { // The rescue level of this reset. A value of 0 indicates missing or unknown level information. optional int32 rescue_level = 1; } + +/** + * Logs when signed config is received from an APK, and if that config was applied successfully. + * Logged from: + * frameworks/base/services/core/java/com/android/server/signedconfig/SignedConfigService.java + */ +message SignedConfigReported { + enum Type { + UNKNOWN_TYPE = 0; + GLOBAL_SETTINGS = 1; + } + optional Type type = 1; + + // The final status of the signed config received. + enum Status { + UNKNOWN_STATUS = 0; + APPLIED = 1; + BASE64_FAILURE_CONFIG = 2; + BASE64_FAILURE_SIGNATURE = 3; + SECURITY_EXCEPTION = 4; + INVALID_CONFIG = 5; + OLD_CONFIG = 6; + SIGNATURE_CHECK_FAILED = 7; + NOT_APPLICABLE = 8; + } + optional Status status = 2; + + // The version of the signed config processed. + optional int32 version = 3; + + // The package name that the config was extracted from. + optional string from_package = 4; + + enum Key { + NO_KEY = 0; + DEBUG = 1; + PRODUCTION = 2; + } + // Which key was used to verify the config. + optional Key verified_with = 5; +} diff --git a/cmds/statsd/src/external/Perfetto.cpp b/cmds/statsd/src/external/Perfetto.cpp index 42cc543f18a8..0c4c3301e61e 100644 --- a/cmds/statsd/src/external/Perfetto.cpp +++ b/cmds/statsd/src/external/Perfetto.cpp @@ -39,6 +39,7 @@ namespace os { namespace statsd { bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, + int64_t subscription_id, int64_t alert_id, const ConfigKey& configKey) { VLOG("Starting trace collection through perfetto"); @@ -48,9 +49,11 @@ bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, return false; } - char alertId[20]; - char configId[20]; - char configUid[20]; + char subscriptionId[25]; + char alertId[25]; + char configId[25]; + char configUid[25]; + snprintf(subscriptionId, sizeof(subscriptionId), "%" PRId64, subscription_id); snprintf(alertId, sizeof(alertId), "%" PRId64, alert_id); snprintf(configId, sizeof(configId), "%" PRId64, configKey.GetId()); snprintf(configUid, sizeof(configUid), "%d", configKey.GetUid()); @@ -94,7 +97,7 @@ bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, execl("/system/bin/perfetto", "perfetto", "--background", "--config", "-", "--dropbox", kDropboxTag, "--alert-id", alertId, "--config-id", configId, "--config-uid", - configUid, nullptr); + configUid, "--subscription-id", subscriptionId, nullptr); // execl() doesn't return in case of success, if we get here something // failed. diff --git a/cmds/statsd/src/external/Perfetto.h b/cmds/statsd/src/external/Perfetto.h index 1e7f728e1357..ab2c1954e2a0 100644 --- a/cmds/statsd/src/external/Perfetto.h +++ b/cmds/statsd/src/external/Perfetto.h @@ -32,6 +32,7 @@ class PerfettoDetails; // Declared in statsd_config.pb.h // This method returns immediately after passing the config and does NOT wait // for the full duration of the trace. bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config, + int64_t subscription_id, int64_t alert_id, const ConfigKey& configKey); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 347670dd6b14..98c5a0fb59be 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1023,7 +1023,9 @@ public class Activity extends ContextThemeWrapper * * @return The content capture manager */ - @NonNull private ContentCaptureManager getContentCaptureManager() { + @Nullable private ContentCaptureManager getContentCaptureManager() { + // ContextCapture disabled for system apps + if (getApplicationInfo().isSystemApp()) return null; if (mContentCaptureManager == null) { mContentCaptureManager = getSystemService(ContentCaptureManager.class); } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c90e40411240..1f01e2698fb5 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -317,4 +317,7 @@ public abstract class ActivityManagerInternal { /** Returns true if the given uid is the app in the foreground. */ public abstract boolean isAppForeground(int uid); + + /** Remove pending backup for the given userId. */ + public abstract void clearPendingBackup(int userId); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 94983e1c3672..9bcb36f28d17 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2276,6 +2276,16 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public String[] setDistractingPackageRestrictions(String[] packages, int distractionFlags) { + try { + return mPM.setDistractingPackageRestrictionsAsUser(packages, distractionFlags, + mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public String[] setPackagesSuspended(String[] packageNames, boolean suspended, PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 347973ea3387..fb65da14a087 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -317,7 +317,6 @@ interface IActivityManager { */ void requestWifiBugReport(in String shareTitle, in String shareDescription); - void clearPendingBackup(); Intent getIntentForIntentSender(in IIntentSender sender); // This is not public because you need to be very careful in how you // manage your activity to make sure it is always the uid you expect. diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d827f6cb9654..3adafd725a10 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -45,6 +45,8 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.IRestrictionsManager; import android.content.RestrictionsManager; +import android.content.om.IOverlayManager; +import android.content.om.OverlayManager; import android.content.pm.CrossProfileApps; import android.content.pm.ICrossProfileApps; import android.content.pm.IShortcutService; @@ -1053,6 +1055,14 @@ final class SystemServiceRegistry { return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b)); }}); + registerService(Context.OVERLAY_SERVICE, OverlayManager.class, + new CachedServiceFetcher<OverlayManager>() { + @Override + public OverlayManager createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE); + return new OverlayManager(ctx, IOverlayManager.Stub.asInterface(b)); + }}); + registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class, new CachedServiceFetcher<NetworkWatchlistManager>() { @Override diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index 27581fc0088a..a6abe0b30f79 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -179,16 +179,6 @@ public final class RoleManager { public static final String ACTION_REQUEST_ROLE = "android.app.role.action.REQUEST_ROLE"; /** - * The name of the requested role. - * <p> - * <strong>Type:</strong> String - * - * @hide - */ - @SystemApi - public static final String EXTRA_REQUEST_ROLE_NAME = "android.app.role.extra.REQUEST_ROLE_NAME"; - - /** * The permission required to manage records of role holders in {@link RoleManager} directly. * * @hide @@ -236,7 +226,7 @@ public final class RoleManager { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); Intent intent = new Intent(ACTION_REQUEST_ROLE); intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName()); - intent.putExtra(EXTRA_REQUEST_ROLE_NAME, roleName); + intent.putExtra(Intent.EXTRA_ROLE_NAME, roleName); return intent; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e37126b8c2e7..d27cce535c3b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1833,6 +1833,37 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.REVIEW_PERMISSIONS"; /** + * Activity action: Launch UI to manage a default app. + * <p> + * Input: {@link #EXTRA_ROLE_NAME} specifies the role of the default app which will be managed + * by the launched UI. + * </p> + * <p> + * Output: Nothing. + * </p> + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @SystemApi + public static final String ACTION_MANAGE_DEFAULT_APP = + "android.intent.action.MANAGE_DEFAULT_APP"; + + /** + * Intent extra: A role name. + * <p> + * Type: String + * </p> + * + * @see android.app.role.RoleManager + * + * @hide + */ + @SystemApi + public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME"; + + /** * Activity action: Launch UI to manage special app accesses. * <p> * Input: Nothing. @@ -2411,6 +2442,25 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED"; /** + * Broadcast Action: Distracting packages have been changed. + * <p>Includes the following extras: + * <ul> + * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been changed. + * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been changed. + * <li> {@link #EXTRA_DISTRACTION_RESTRICTIONS} the new restrictions set on these packages. + * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. It is only sent to registered receivers. + * + * @see PackageManager#setDistractingPackageRestrictions(String[], int) + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DISTRACTING_PACKAGES_CHANGED = + "android.intent.action.DISTRACTING_PACKAGES_CHANGED"; + + /** * Broadcast Action: Sent to a package that has been suspended by the system. This is sent * whenever a package is put into a suspended state or any of its app extras change while in the * suspended state. @@ -5120,6 +5170,17 @@ public class Intent implements Parcelable, Cloneable { "android.intent.extra.changed_uid_list"; /** + * An integer denoting a bitwise combination of restrictions set on distracting packages via + * {@link PackageManager#setDistractingPackageRestrictions(String[], int)} + * + * @hide + * @see PackageManager.DistractionRestriction + * @see PackageManager#setDistractingPackageRestrictions(String[], int) + */ + public static final String EXTRA_DISTRACTION_RESTRICTIONS = + "android.intent.extra.distraction_restrictions"; + + /** * @hide * Magic extra system code can use when binding, to give a label for * who it is that has bound to a service. This is an integer giving diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index dd550032df7c..1989f06ca76b 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -18,8 +18,7 @@ package android.content.om; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; -import android.os.Build; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -32,8 +31,10 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi public final class OverlayInfo implements Parcelable { + /** @hide */ @IntDef(prefix = "STATE_", value = { STATE_UNKNOWN, STATE_MISSING_TARGET, @@ -44,6 +45,7 @@ public final class OverlayInfo implements Parcelable { STATE_TARGET_UPGRADING, STATE_OVERLAY_UPGRADING, }) + /** @hide */ @Retention(RetentionPolicy.SOURCE) public @interface State {} @@ -52,17 +54,23 @@ public final class OverlayInfo implements Parcelable { * objects exposed outside the {@link * com.android.server.om.OverlayManagerService} should never have this * state. + * + * @hide */ public static final int STATE_UNKNOWN = -1; /** * The target package of the overlay is not installed. The overlay cannot be enabled. + * + * @hide */ public static final int STATE_MISSING_TARGET = 0; /** * Creation of idmap file failed (e.g. no matching resources). The overlay * cannot be enabled. + * + * @hide */ public static final int STATE_NO_IDMAP = 1; @@ -70,6 +78,7 @@ public final class OverlayInfo implements Parcelable { * The overlay is currently disabled. It can be enabled. * * @see IOverlayManager#setEnabled + * @hide */ public static final int STATE_DISABLED = 2; @@ -77,18 +86,21 @@ public final class OverlayInfo implements Parcelable { * The overlay is currently enabled. It can be disabled. * * @see IOverlayManager#setEnabled + * @hide */ public static final int STATE_ENABLED = 3; /** * The target package is currently being upgraded; the state will change * once the package installation has finished. + * @hide */ public static final int STATE_TARGET_UPGRADING = 4; /** * The overlay package is currently being upgraded; the state will change * once the package installation has finished. + * @hide */ public static final int STATE_OVERLAY_UPGRADING = 5; @@ -96,6 +108,7 @@ public final class OverlayInfo implements Parcelable { * The overlay package is currently enabled because it is marked as * 'static'. It cannot be disabled but will change state if for instance * its target is uninstalled. + * @hide */ public static final int STATE_ENABLED_STATIC = 6; @@ -103,40 +116,52 @@ public final class OverlayInfo implements Parcelable { * Overlay category: theme. * <p> * Change how Android (including the status bar, dialogs, ...) looks. + * + * @hide */ public static final String CATEGORY_THEME = "android.theme"; /** * Package name of the overlay package + * + * @hide */ - @UnsupportedAppUsage + @SystemApi public final String packageName; /** * Package name of the target package + * + * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @SystemApi public final String targetPackageName; /** * Category of the overlay package + * + * @hide */ + @SystemApi public final String category; /** * Full path to the base APK for this overlay package + * @hide */ public final String baseCodePath; /** * The state of this OverlayInfo as defined by the STATE_* constants in this class. + * @hide */ - @UnsupportedAppUsage public final @State int state; /** * User handle for which this overlay applies + * @hide */ + @SystemApi public final int userId; /** @@ -161,12 +186,15 @@ public final class OverlayInfo implements Parcelable { * * @param source the source OverlayInfo to base the new instance on * @param state the new state for the source OverlayInfo + * + * @hide */ public OverlayInfo(@NonNull OverlayInfo source, @State int state) { this(source.packageName, source.targetPackageName, source.category, source.baseCodePath, state, source.userId, source.priority, source.isStatic); } + /** @hide */ public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName, @NonNull String category, @NonNull String baseCodePath, int state, int userId, int priority, boolean isStatic) { @@ -181,6 +209,7 @@ public final class OverlayInfo implements Parcelable { ensureValidState(); } + /** @hide */ public OverlayInfo(Parcel source) { packageName = source.readString(); targetPackageName = source.readString(); @@ -255,8 +284,9 @@ public final class OverlayInfo implements Parcelable { * Disabled overlay packages are installed but are currently not in use. * * @return true if the overlay is enabled, else false. + * @hide */ - @UnsupportedAppUsage + @SystemApi public boolean isEnabled() { switch (state) { case STATE_ENABLED: @@ -272,6 +302,7 @@ public final class OverlayInfo implements Parcelable { * debugging purposes. * * @return a human readable String representing the state. + * @hide */ public static String stateToString(@State int state) { switch (state) { diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java new file mode 100644 index 000000000000..7a2220bfddb6 --- /dev/null +++ b/core/java/android/content/om/OverlayManager.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.om; + +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; + +import java.util.List; + +/** + * Updates OverlayManager state; gets information about installed overlay packages. + * @hide + */ +@SystemApi +@SystemService(Context.OVERLAY_SERVICE) +public class OverlayManager { + + private final IOverlayManager mService; + private final Context mContext; + + /** + * Creates a new instance. + * + * @param context The current context in which to operate. + * @param service The backing system service. + * + * @hide + */ + public OverlayManager(Context context, IOverlayManager service) { + mContext = context; + mService = service; + } + + /** @hide */ + public OverlayManager(Context context) { + this(context, IOverlayManager.Stub.asInterface( + ServiceManager.getService(Context.OVERLAY_SERVICE))); + } + /** + * Request that an overlay package is enabled and any other overlay packages with the same + * target package and category are disabled. + * + * @param packageName the name of the overlay package to enable. + * @param userId The user for which to change the overlay. + * @return true if the system successfully registered the request, false otherwise. + * + * @hide + */ + @SystemApi + public boolean setEnabledExclusiveInCategory(@Nullable final String packageName, + int userId) { + try { + return mService.setEnabledExclusiveInCategory(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns information about all overlays for the given target package for + * the specified user. The returned list is ordered according to the + * overlay priority with the highest priority at the end of the list. + * + * @param targetPackageName The name of the target package. + * @param userId The user to get the OverlayInfos for. + * @return A list of OverlayInfo objects; if no overlays exist for the + * requested package, an empty list is returned. + * + * @hide + */ + @SystemApi + public List<OverlayInfo> getOverlayInfosForTarget(@Nullable final String targetPackageName, + int userId) { + try { + return mService.getOverlayInfosForTarget(targetPackageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index ba7710b8ef48..db2b6fd235d3 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -69,6 +69,7 @@ interface ILauncherApps { int userId); boolean hasShortcutHostPermission(String callingPackage); + boolean shouldHideFromSuggestions(String packageName, in UserHandle user); ParceledListSlice getShortcutConfigActivities( String callingPackage, String packageName, in UserHandle user); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 64a4479be7ee..d5c3b26b094d 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -273,6 +273,9 @@ interface IPackageManager { void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage); + String[] setDistractingPackageRestrictionsAsUser(in String[] packageNames, int restrictionFlags, + int userId); + String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended, in PersistableBundle appExtras, in PersistableBundle launcherExtras, in SuspendDialogInfo dialogInfo, String callingPackage, int userId); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 44e652f10094..983ea9f847cd 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -697,6 +697,26 @@ public class LauncherApps { } /** + * Returns whether a package should be hidden from suggestions to the user. Currently, this + * could be done because the package was marked as distracting to the user via + * {@code PackageManager.setDistractingPackageRestrictions(String[], int)}. + * + * @param packageName The package for which to check. + * @param user the {@link UserHandle} of the profile. + * @return + */ + public boolean shouldHideFromSuggestions(@NonNull String packageName, + @NonNull UserHandle user) { + Preconditions.checkNotNull(packageName, "packageName"); + Preconditions.checkNotNull(user, "user"); + try { + return mService.shouldHideFromSuggestions(packageName, user); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns {@link ApplicationInfo} about an application installed for a specific user profile. * * @param packageName The package name of the application diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2aeb68da365b..636a70f040da 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5889,6 +5889,74 @@ public abstract class PackageManager { public abstract boolean isSignedByExactly(String packageName, KeySet ks); /** + * Flag to denote no restrictions. This should be used to clear any restrictions that may have + * been previously set for the package. + * @see PackageManager.DistractionRestriction + * @hide + */ + @SystemApi + public static final int RESTRICTION_NONE = 0x0; + + /** + * Flag to denote that a package should be hidden from any suggestions to the user. + * @see PackageManager.DistractionRestriction + * @hide + */ + @SystemApi + public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 0x00000001; + + /** + * Flag to denote that a package's notifications should be hidden. + * @see PackageManager.DistractionRestriction + * @hide + */ + @SystemApi + public static final int RESTRICTION_HIDE_NOTIFICATIONS = 0x00000002; + + /** + * Restriction flags to set on a package that is considered as distracting to the user. + * These should help the user to restrict their usage of these apps. + * + * @see #setDistractingPackageRestrictions(String[], int) + * @hide + */ + @SystemApi + @IntDef(flag = true, prefix = {"RESTRICTION_"}, value = { + RESTRICTION_NONE, + RESTRICTION_HIDE_FROM_SUGGESTIONS, + RESTRICTION_HIDE_NOTIFICATIONS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DistractionRestriction {} + + /** + * Mark or unmark the given packages as distracting to the user. + * These packages can have certain restrictions set that should discourage the user to launch + * them often. For example, notifications from such an app can be hidden, or the app can be + * removed from launcher suggestions, so the user is able to restrict their use of these apps. + * + * <p>The caller must hold {@link android.Manifest.permission#SUSPEND_APPS} to use this API. + * + * @param packages Packages to mark as distracting. + * @param restrictionFlags Any combination of {@link DistractionRestriction restrictions} to + * impose on the given packages. {@link #RESTRICTION_NONE} can be used + * to clear any existing restrictions. + * @return A list of packages that could not have the {@code restrictionFlags} set. The system + * may prevent restricting critical packages to preserve normal device function. + * + * @see DistractionRestriction + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) + @NonNull + public String[] setDistractingPackageRestrictions(@NonNull String[] packages, + @DistractionRestriction int restrictionFlags) { + throw new UnsupportedOperationException( + "setDistractingPackageRestrictions not implemented"); + } + + /** * Puts the package in a suspended state, where attempts at starting activities are denied. * * <p>It doesn't remove the data or the actual package file. The application's notifications @@ -5911,8 +5979,7 @@ public abstract class PackageManager { * {@link PersistableBundle} objects to be shared with the apps being suspended and the * launcher to support customization that they might need to handle the suspended state. * - * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} or - * {@link Manifest.permission#MANAGE_USERS} to use this api.</p> + * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API. * * @param packageNames The names of the packages to set the suspended status. * @param suspended If set to {@code true}, the packages will be suspended, if set to @@ -5955,7 +6022,7 @@ public abstract class PackageManager { * <p>When the user tries to launch a suspended app, a system dialog alerting them that the app * is suspended will be shown instead. * The caller can optionally customize the dialog by passing a {@link SuspendDialogInfo} object - * to this api. This dialog will have a button that starts the + * to this API. This dialog will have a button that starts the * {@link Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS} intent if the suspending app declares an * activity which handles this action. * @@ -5966,7 +6033,7 @@ public abstract class PackageManager { * {@link PersistableBundle} objects to be shared with the apps being suspended and the * launcher to support customization that they might need to handle the suspended state. * - * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this api. + * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API. * * @param packageNames The names of the packages to set the suspended status. * @param suspended If set to {@code true}, the packages will be suspended, if set to @@ -6005,7 +6072,7 @@ public abstract class PackageManager { * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, * SuspendDialogInfo) setPackagesSuspended}. The platform prevents suspending certain critical * packages to keep the device in a functioning state, e.g. the default dialer. - * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this api. + * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this API. * * <p> * Note that this set of critical packages can change with time, so even though a package name diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 83979e925be1..83563d0fd9f6 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -284,6 +284,17 @@ public abstract class PackageManagerInternal { public abstract SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage, int userId); /** + * Gets any distraction flags set via + * {@link PackageManager#setDistractingPackageRestrictions(String[], int)} + * + * @param packageName + * @param userId + * @return A bitwise OR of any of the {@link PackageManager.DistractionRestriction} + */ + public abstract @PackageManager.DistractionRestriction int getDistractingPackageRestrictions( + String packageName, int userId); + + /** * Do a straight uid lookup for the given package/application in the given user. * @see PackageManager#getPackageUidAsUser(String, int, int) * @return The app's uid, or < 0 if the package was not found in that user diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index 74dd08fc1d6b..249b6919fc28 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -54,6 +54,7 @@ public class PackageUserState { public boolean stopped; public boolean notLaunched; public boolean hidden; // Is the app restricted by owner / admin + public int distractionFlags; public boolean suspended; public String suspendingPackage; public SuspendDialogInfo dialogInfo; @@ -92,6 +93,7 @@ public class PackageUserState { stopped = o.stopped; notLaunched = o.notLaunched; hidden = o.hidden; + distractionFlags = o.distractionFlags; suspended = o.suspended; suspendingPackage = o.suspendingPackage; dialogInfo = o.dialogInfo; @@ -222,6 +224,9 @@ public class PackageUserState { if (hidden != oldState.hidden) { return false; } + if (distractionFlags != oldState.distractionFlags) { + return false; + } if (suspended != oldState.suspended) { return false; } diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index bb8c92dba71a..a3395ac9fd13 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -158,6 +158,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { * @hide */ @SystemApi + @TestApi public static final int PROTECTION_FLAG_OEM = 0x4000; /** diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index b520d2c14edc..f5d288e6a233 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -16,6 +16,8 @@ package android.hardware.hdmi; +import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; + import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -27,6 +29,7 @@ import android.annotation.SystemService; import android.content.Context; import android.content.pm.PackageManager; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; @@ -264,6 +267,10 @@ public final class HdmiControlManager { private final boolean mHasTvDevice; // True if we have a logical device of type audio system hosted in the system. private final boolean mHasAudioSystemDevice; + // True if we have a logical device of type audio system hosted in the system. + private final boolean mHasSwitchDevice; + // True if it's a switch device. + private final boolean mIsSwitchDevice; /** * {@hide} - hide this constructor because it has a parameter of type IHdmiControlService, @@ -283,6 +290,9 @@ public final class HdmiControlManager { mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV); mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK); mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); + mIsSwitchDevice = SystemProperties.getBoolean( + PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); } private static boolean hasDeviceType(int[] types, int type) { @@ -319,6 +329,9 @@ public final class HdmiControlManager { return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null; case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null; + case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH: + return (mHasSwitchDevice || mIsSwitchDevice) + ? new HdmiSwitchClient(mService) : null; default: return null; } @@ -373,6 +386,24 @@ public final class HdmiControlManager { } /** + * Gets an object that represents an HDMI-CEC logical device of type switch on the system. + * + * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also + * possible to communicate with other logical devices hosted in the same system if the system is + * configured to host more than one type of HDMI-CEC logical devices. + * + * @return {@link HdmiSwitchClient} instance. {@code null} on failure. + * + * TODO(b/110094868): unhide for Q + * @hide + */ + @Nullable + @SuppressLint("Doclava125") + public HdmiSwitchClient getSwitchClient() { + return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); + } + + /** * Controls standby mode of the system. It will also try to turn on/off the connected devices if * necessary. * diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java new file mode 100644 index 000000000000..1ac29736f964 --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.hdmi; + +import android.annotation.NonNull; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +/** + * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which + * acts as switch. + * + * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, + * but it is used by all Android TV devices that have multiple HDMI inputs, + * even if it is not a "pure" swicth and has another device type like TV or Player. + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ +public class HdmiSwitchClient extends HdmiClient { + + private static final String TAG = "HdmiSwitchClient"; + + /* package */ HdmiSwitchClient(IHdmiControlService service) { + super(service); + } + + private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) { + return new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + callback.onComplete(result); + } + }; + } + + /** @hide */ + // TODO(b/110094868): unhide for Q + @Override + public int getDeviceType() { + return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH; + } + + /** + * Selects a CEC logical device to be a new active source. + * + * @param logicalAddress logical address of the device to select + * @param callback callback to get the result with + * @throws {@link IllegalArgumentException} if the {@code callback} is null + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null."); + } + try { + mService.deviceSelect(logicalAddress, getCallbackWrapper(callback)); + } catch (RemoteException e) { + Log.e(TAG, "failed to select device: ", e); + } + } + + /** + * Selects a HDMI port to be a new route path. + * + * @param portId HDMI port to select + * @param callback callback to get the result with + * @throws {@link IllegalArgumentException} if the {@code callback} is null + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public void portSelect(int portId, @NonNull SelectCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("Callback must not be null"); + } + try { + mService.portSelect(portId, getCallbackWrapper(callback)); + } catch (RemoteException e) { + Log.e(TAG, "failed to select port: ", e); + } + } + + /** + * Returns all the CEC devices connected to the device. + * + * <p>This only applies to device with multiple HDMI inputs + * + * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if + * there is none. + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public List<HdmiDeviceInfo> getDeviceList() { + try { + return mService.getDeviceList(); + } catch (RemoteException e) { + Log.e("TAG", "Failed to call getDeviceList():", e); + return Collections.<HdmiDeviceInfo>emptyList(); + } + } + + /** + * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}. + * + * @hide + * TODO(b/110094868): unhide and add @SystemApi for Q + */ + public interface SelectCallback { + + /** + * Called when the operation is finished. + * + * @param result the result value of {@link #deviceSelect} or {@link #portSelect}. + */ + void onComplete(int result); + } +} diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 4a466f300718..617125b3f904 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -66,6 +66,7 @@ public final class LinkProperties implements Parcelable { private int mMtu; // in the format "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max" private String mTcpBufferSizes; + private IpPrefix mNat64Prefix; private static final int MIN_MTU = 68; private static final int MIN_MTU_V6 = 1280; @@ -760,6 +761,32 @@ public final class LinkProperties implements Parcelable { } /** + * Returns the NAT64 prefix in use on this link, if any. + * + * @return the NAT64 prefix. + * @hide + */ + public @Nullable IpPrefix getNat64Prefix() { + return mNat64Prefix; + } + + /** + * Sets the NAT64 prefix in use on this link. + * + * Currently, only 96-bit prefixes (i.e., where the 32-bit IPv4 address is at the end of the + * 128-bit IPv6 address) are supported. + * + * @param prefix the NAT64 prefix. + * @hide + */ + public void setNat64Prefix(IpPrefix prefix) { + if (prefix != null && prefix.getPrefixLength() != 96) { + throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix); + } + mNat64Prefix = prefix; // IpPrefix objects are immutable. + } + + /** * Adds a stacked link. * * If there is already a stacked link with the same interface name as link, @@ -831,6 +858,7 @@ public final class LinkProperties implements Parcelable { mStackedLinks.clear(); mMtu = 0; mTcpBufferSizes = null; + mNat64Prefix = null; } /** @@ -908,6 +936,11 @@ public final class LinkProperties implements Parcelable { resultJoiner.add(mHttpProxy.toString()); } + if (mNat64Prefix != null) { + resultJoiner.add("Nat64Prefix:"); + resultJoiner.add(mNat64Prefix.toString()); + } + final Collection<LinkProperties> stackedLinksValues = mStackedLinks.values(); if (!stackedLinksValues.isEmpty()) { final StringJoiner stackedLinksJoiner = new StringJoiner(",", "Stacked: [", "]"); @@ -1295,6 +1328,17 @@ public final class LinkProperties implements Parcelable { } /** + * Compares this {@code LinkProperties} NAT64 prefix against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalNat64Prefix(LinkProperties target) { + return Objects.equals(mNat64Prefix, target.mNat64Prefix); + } + + /** * Compares this {@code LinkProperties} instance against the target * LinkProperties in {@code obj}. Two LinkPropertieses are equal if * all their fields are equal in values. @@ -1330,7 +1374,8 @@ public final class LinkProperties implements Parcelable { && isIdenticalHttpProxy(target) && isIdenticalStackedLinks(target) && isIdenticalMtu(target) - && isIdenticalTcpBufferSizes(target); + && isIdenticalTcpBufferSizes(target) + && isIdenticalNat64Prefix(target); } /** @@ -1443,7 +1488,8 @@ public final class LinkProperties implements Parcelable { + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode()) + (mUsePrivateDns ? 57 : 0) + mPcscfs.size() * 67 - + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode()); + + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode()) + + Objects.hash(mNat64Prefix); } /** @@ -1484,6 +1530,8 @@ public final class LinkProperties implements Parcelable { } else { dest.writeByte((byte)0); } + dest.writeParcelable(mNat64Prefix, 0); + ArrayList<LinkProperties> stackedLinks = new ArrayList<>(mStackedLinks.values()); dest.writeList(stackedLinks); } @@ -1535,6 +1583,7 @@ public final class LinkProperties implements Parcelable { if (in.readByte() == 1) { netProp.setHttpProxy(in.readParcelable(null)); } + netProp.setNat64Prefix(in.readParcelable(null)); ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>(); in.readList(stackedLinks, LinkProperties.class.getClassLoader()); for (LinkProperties stackedLink: stackedLinks) { diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 1343d24d0d94..b15a4d3170b3 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -114,7 +114,6 @@ public class BugreportManager { } } - // TODO(b/111441001) Connect up with BugreportListener methods. private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { @@ -130,6 +129,23 @@ public class BugreportManager { } @Override + public void onProgress(int progress) throws RemoteException { + // TODO(b/111441001): implement + } + + @Override + public void onError(int errorCode) throws RemoteException { + // TODO(b/111441001): implement + } + + @Override + public void onFinished(long durationMs, String title, String description) + throws RemoteException { + // TODO(b/111441001): implement + } + + // Old methods; should go away + @Override public void onProgressUpdated(int progress) throws RemoteException { // TODO(b/111441001): implement } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 3de3494e7ea7..9e3e83e14a48 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.SystemApi; +import android.annotation.TestApi; import libcore.util.NativeAllocationRegistry; @@ -24,6 +25,7 @@ import java.util.NoSuchElementException; /** @hide */ @SystemApi +@TestApi public abstract class HwBinder implements IHwBinder { private static final String TAG = "HwBinder"; diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java index 6a5bb1c0a988..0ec63b5815f1 100644 --- a/core/java/android/os/HwBlob.java +++ b/core/java/android/os/HwBlob.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import libcore.util.NativeAllocationRegistry; @@ -28,6 +29,7 @@ import libcore.util.NativeAllocationRegistry; * @hide */ @SystemApi +@TestApi public class HwBlob { private static final String TAG = "HwBlob"; diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java index 7a51db2dc5f9..7919a00166bf 100644 --- a/core/java/android/os/HwParcel.java +++ b/core/java/android/os/HwParcel.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.IntDef; import android.annotation.SystemApi; +import android.annotation.TestApi; import libcore.util.NativeAllocationRegistry; @@ -28,6 +29,7 @@ import java.util.Arrays; /** @hide */ @SystemApi +@TestApi public class HwParcel { private static final String TAG = "HwParcel"; diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java index 249eb3aa3456..46fa6ef3b783 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -17,9 +17,11 @@ package android.os; import android.annotation.SystemApi; +import android.annotation.TestApi; /** @hide */ @SystemApi +@TestApi public interface IHwBinder { /** * Process a hwbinder transaction. diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java index f9edd5bf8883..0a5a71550b06 100644 --- a/core/java/android/os/IHwInterface.java +++ b/core/java/android/os/IHwInterface.java @@ -17,8 +17,11 @@ package android.os; import android.annotation.SystemApi; +import android.annotation.TestApi; + /** @hide */ @SystemApi +@TestApi public interface IHwInterface { /** * @return the binder object that corresponds to this interface. diff --git a/core/java/android/os/NativeHandle.java b/core/java/android/os/NativeHandle.java index f7ffc37f085f..f13bf5fccd38 100644 --- a/core/java/android/os/NativeHandle.java +++ b/core/java/android/os/NativeHandle.java @@ -20,6 +20,7 @@ import static android.system.OsConstants.F_DUPFD_CLOEXEC; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.system.ErrnoException; import android.system.Os; @@ -32,6 +33,7 @@ import java.io.FileDescriptor; * @hide */ @SystemApi +@TestApi public final class NativeHandle implements Closeable { // whether this object owns mFds private boolean mOwn = false; diff --git a/core/java/android/os/ParcelableException.aidl b/core/java/android/os/ParcelableException.aidl new file mode 100644 index 000000000000..d21492212341 --- /dev/null +++ b/core/java/android/os/ParcelableException.aidl @@ -0,0 +1,18 @@ +/* Copyright 2018, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.os; + +parcelable ParcelableException; diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 0e18b445fd01..7a7bd83089f7 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -31,4 +31,5 @@ oneway interface IPermissionController { void revokeRuntimePermission(String packageName, String permissionName); void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted, boolean countSystem, in RemoteCallback callback); + void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback); } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index d7332ae92fb5..0865b623028c 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -18,6 +18,7 @@ package android.permission; import static android.permission.PermissionControllerService.SERVICE_INTERFACE; +import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkNotNull; @@ -146,6 +147,20 @@ public final class PermissionControllerManager { void onCountPermissionApps(int numApps); } + /** + * Callback for delivering the result of {@link #getPermissionUsages}. + * + * @hide + */ + public interface OnPermissionUsageResultCallback { + /** + * The result for {@link #getPermissionUsages}. + * + * @param users The users list. + */ + void onPermissionUsageResult(@NonNull List<RuntimePermissionUsageInfo> users); + } + private final @NonNull Context mContext; /** @@ -264,6 +279,28 @@ public final class PermissionControllerManager { } /** + * Count how many apps have used permissions. + * + * @param countSystem Also count system apps + * @param numMillis The number of milliseconds in the past to check for uses + * @param executor Executor on which to invoke the callback + * @param callback Callback to receive the result + * + * @hide + */ + @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) + public void getPermissionUsages(boolean countSystem, long numMillis, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnPermissionUsageResultCallback callback) { + checkArgumentNonnegative(numMillis); + checkNotNull(executor); + checkNotNull(callback); + + sRemoteService.scheduleRequest(new PendingGetPermissionUsagesRequest(sRemoteService, + countSystem, numMillis, executor, callback)); + } + + /** * A connection to the remote service */ static final class RemoteService extends @@ -326,6 +363,7 @@ public final class PermissionControllerManager { private final boolean mDoDryRun; private final int mReason; private final @NonNull String mCallingPackage; + private final @NonNull Executor mExecutor; private final @NonNull OnRevokeRuntimePermissionsCallback mCallback; private final @NonNull RemoteCallback mRemoteCallback; @@ -341,6 +379,7 @@ public final class PermissionControllerManager { mDoDryRun = doDryRun; mReason = reason; mCallingPackage = callingPackage; + mExecutor = executor; mCallback = callback; mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { @@ -375,7 +414,13 @@ public final class PermissionControllerManager { @Override protected void onTimeout(RemoteService remoteService) { - mCallback.onRevokeRuntimePermissions(Collections.emptyMap()); + long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallback.onRevokeRuntimePermissions(Collections.emptyMap())); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override @@ -521,4 +566,61 @@ public final class PermissionControllerManager { } } } + + /** + * Request for {@link #getPermissionUsages} + */ + private static final class PendingGetPermissionUsagesRequest extends + AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> { + private final @NonNull OnPermissionUsageResultCallback mCallback; + private final boolean mCountSystem; + private final long mNumMillis; + + private final @NonNull RemoteCallback mRemoteCallback; + + private PendingGetPermissionUsagesRequest(@NonNull RemoteService service, + boolean countSystem, long numMillis, @NonNull @CallbackExecutor Executor executor, + @NonNull OnPermissionUsageResultCallback callback) { + super(service); + + mCountSystem = countSystem; + mNumMillis = numMillis; + mCallback = callback; + + mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { + long token = Binder.clearCallingIdentity(); + try { + final List<RuntimePermissionUsageInfo> reportedUsers; + List<RuntimePermissionUsageInfo> users = null; + if (result != null) { + users = result.getParcelableArrayList(KEY_RESULT); + } else { + users = Collections.emptyList(); + } + reportedUsers = users; + + callback.onPermissionUsageResult(reportedUsers); + } finally { + Binder.restoreCallingIdentity(token); + + finish(); + } + }), null); + } + + @Override + protected void onTimeout(RemoteService remoteService) { + mCallback.onPermissionUsageResult(Collections.emptyList()); + } + + @Override + public void run() { + try { + getService().getServiceInterface().getPermissionUsages(mCountSystem, mNumMillis, + mRemoteCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error counting permission users", e); + } + } + } } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index f621737e5ed4..75d61e6fe570 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -17,6 +17,7 @@ package android.permission; import static com.android.internal.util.Preconditions.checkArgument; +import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -112,6 +113,17 @@ public abstract class PermissionControllerService extends Service { public abstract int onCountPermissionApps(@NonNull List<String> permissionNames, boolean countOnlyGranted, boolean countSystem); + /** + * Count how many apps have used permissions. + * + * @param countSystem Also count system apps + * @param numMillis The number of milliseconds in the past to check for uses + * + * @return descriptions of the users of permissions + */ + public abstract @NonNull List<RuntimePermissionUsageInfo> + onPermissionUsageResult(boolean countSystem, long numMillis); + @Override public final IBinder onBind(Intent intent) { return new IPermissionController.Stub() { @@ -187,6 +199,20 @@ public abstract class PermissionControllerService extends Service { PermissionControllerService.this, permissionNames, countOnlyGranted, countSystem, callback)); } + + @Override + public void getPermissionUsages(boolean countSystem, long numMillis, + RemoteCallback callback) { + checkArgumentNonnegative(numMillis); + checkNotNull(callback, "callback"); + + enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null); + + mHandler.sendMessage( + obtainMessage(PermissionControllerService::getPermissionUsages, + PermissionControllerService.this, countSystem, numMillis, + callback)); + } }; } @@ -230,4 +256,17 @@ public abstract class PermissionControllerService extends Service { result.putInt(PermissionControllerManager.KEY_RESULT, numApps); callback.sendResult(result); } + + private void getPermissionUsages(boolean countSystem, long numMillis, + @NonNull RemoteCallback callback) { + List<RuntimePermissionUsageInfo> users = + onPermissionUsageResult(countSystem, numMillis); + if (users != null && !users.isEmpty()) { + Bundle result = new Bundle(); + result.putParcelableList(PermissionControllerManager.KEY_RESULT, users); + callback.sendResult(result); + } else { + callback.sendResult(null); + } + } } diff --git a/core/java/android/permission/RuntimePermissionUsageInfo.aidl b/core/java/android/permission/RuntimePermissionUsageInfo.aidl new file mode 100644 index 000000000000..88820ddbc092 --- /dev/null +++ b/core/java/android/permission/RuntimePermissionUsageInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission; + +parcelable RuntimePermissionUsageInfo;
\ No newline at end of file diff --git a/core/java/android/permission/RuntimePermissionUsageInfo.java b/core/java/android/permission/RuntimePermissionUsageInfo.java new file mode 100644 index 000000000000..af1a1bec37cf --- /dev/null +++ b/core/java/android/permission/RuntimePermissionUsageInfo.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission; + +import static com.android.internal.util.Preconditions.checkArgumentNonnegative; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains information about how a runtime permission + * is used. A single runtime permission presented to the user may + * correspond to multiple platform defined permissions, e.g. the + * location permission may control both the coarse and fine platform + * permissions. + * + * @hide + */ +@SystemApi +public final class RuntimePermissionUsageInfo implements Parcelable { + private final @NonNull CharSequence mName; + private final int mNumUsers; + + /** + * Creates a new instance. + * + * @param name The permission group name. + * @param numUsers The number of apps that have used this permission. + */ + public RuntimePermissionUsageInfo(@NonNull CharSequence name, int numUsers) { + checkNotNull(name); + checkArgumentNonnegative(numUsers); + + mName = name; + mNumUsers = numUsers; + } + + private RuntimePermissionUsageInfo(Parcel parcel) { + this(parcel.readCharSequence(), parcel.readInt()); + } + + /** + * @return The number of apps that accessed this permission + */ + public int getAppAccessCount() { + return mNumUsers; + } + + /** + * Gets the permission group name. + * + * @return The name. + */ + public @NonNull CharSequence getName() { + return mName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeCharSequence(mName); + parcel.writeInt(mNumUsers); + } + + public static final Creator<RuntimePermissionUsageInfo> CREATOR = + new Creator<RuntimePermissionUsageInfo>() { + public RuntimePermissionUsageInfo createFromParcel(Parcel source) { + return new RuntimePermissionUsageInfo(source); + } + + public RuntimePermissionUsageInfo[] newArray(int size) { + return new RuntimePermissionUsageInfo[size]; + } + }; +} diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 4e207ed6556e..c79589577366 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -48,6 +48,15 @@ public final class DeviceConfig { */ public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config"); + /** + * Namespace for all input-related features that are used at the native level. + * These features are applied at reboot. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index a9f4034c37af..01169612a201 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Service; import android.content.ComponentName; import android.content.Intent; @@ -58,6 +59,9 @@ import java.util.List; * @hide */ @SystemApi +@TestApi +// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service +// in the same package as the test, and that module is compiled with SDK=test_current public abstract class AugmentedAutofillService extends Service { private static final String TAG = AugmentedAutofillService.class.getSimpleName(); @@ -268,7 +272,6 @@ public abstract class AugmentedAutofillService extends Service { * * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused. */ - // TODO(b/111330312): might not be needed when using IME @GuardedBy("mLock") private AutofillId mLastShownId; diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index 620ec59c9e55..bfb4aadaeed9 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -20,6 +20,7 @@ import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.util.Log; @@ -29,6 +30,9 @@ import android.util.Log; * @hide */ @SystemApi +@TestApi +//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service +//in the same package as the test, and that module is compiled with SDK=test_current public final class FillCallback { private static final String TAG = FillCallback.class.getSimpleName(); diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java index e65cf47e1645..d7bc893f884a 100644 --- a/core/java/android/service/autofill/augmented/FillController.java +++ b/core/java/android/service/autofill/augmented/FillController.java @@ -19,6 +19,7 @@ import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.RemoteException; import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.util.Log; @@ -36,6 +37,9 @@ import java.util.List; * @hide */ @SystemApi +@TestApi +//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service +//in the same package as the test, and that module is compiled with SDK=test_current public final class FillController { private static final String TAG = "FillController"; diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java index 57d2cc85f7ea..dad506763641 100644 --- a/core/java/android/service/autofill/augmented/FillRequest.java +++ b/core/java/android/service/autofill/augmented/FillRequest.java @@ -18,6 +18,7 @@ package android.service.autofill.augmented; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ComponentName; import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.view.autofill.AutofillId; @@ -29,6 +30,9 @@ import android.view.autofill.AutofillValue; */ @SystemApi // TODO(b/111330312): pass a requestId and/or sessionId +@TestApi +// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service +// in the same package as the test, and that module is compiled with SDK=test_current public final class FillRequest { final AutofillProxy mProxy; diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java index 1ecfab4edc12..5285132b31a5 100644 --- a/core/java/android/service/autofill/augmented/FillResponse.java +++ b/core/java/android/service/autofill/augmented/FillResponse.java @@ -18,6 +18,7 @@ package android.service.autofill.augmented; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.view.autofill.AutofillId; @@ -30,6 +31,9 @@ import java.util.List; * @hide */ @SystemApi +@TestApi +//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service +//in the same package as the test, and that module is compiled with SDK=test_current public final class FillResponse implements Parcelable { private final FillWindow mFillWindow; @@ -50,6 +54,9 @@ public final class FillResponse implements Parcelable { * @hide */ @SystemApi + @TestApi + //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service + //in the same package as the test, and that module is compiled with SDK=test_current public static final class Builder { private FillWindow mFillWindow; diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index bad7ddd3193b..33b88e42edb7 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -20,6 +20,7 @@ import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG; import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Dialog; import android.graphics.Rect; import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; @@ -61,6 +62,9 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi +@TestApi +//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service +//in the same package as the test, and that module is compiled with SDK=test_current public final class FillWindow implements AutoCloseable { private static final String TAG = "FillWindow"; diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java index 0124ecca740d..b60064e9dd69 100644 --- a/core/java/android/service/autofill/augmented/PresentationParams.java +++ b/core/java/android/service/autofill/augmented/PresentationParams.java @@ -19,6 +19,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.graphics.Rect; import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.util.DebugUtils; @@ -48,6 +49,9 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi +@TestApi +//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service +//in the same package as the test, and that module is compiled with SDK=test_current public abstract class PresentationParams { /** @@ -147,8 +151,11 @@ public abstract class PresentationParams { * Area associated with a {@link PresentationParams Smart Suggestions} provider. * * @hide - * */ + */ @SystemApi + @TestApi + //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service + //in the same package as the test, and that module is compiled with SDK=test_current public abstract static class Area { /** @hide */ diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 96ef8ba1a241..ccd0fc179f0e 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -22,6 +22,7 @@ import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.graphics.FrameInfo; +import android.graphics.Insets; import android.hardware.display.DisplayManagerGlobal; import android.os.Build; import android.os.Handler; @@ -199,7 +200,7 @@ public final class Choreographer { * @hide */ private static final String[] CALLBACK_TRACE_TITLES = { - "input", "animation", "traversal", "commit" + "input", "animation", "insets_animation", "traversal", "commit" }; /** @@ -209,18 +210,33 @@ public final class Choreographer { public static final int CALLBACK_INPUT = 0; /** - * Callback type: Animation callback. Runs before traversals. + * Callback type: Animation callback. Runs before {@link #CALLBACK_INSETS_ANIMATION}. * @hide */ @TestApi public static final int CALLBACK_ANIMATION = 1; /** + * Callback type: Animation callback to handle inset updates. This is separate from + * {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via + * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then + * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress} + * that contains all the combined updated insets. + * <p> + * Both input and animation may change insets, so we need to run this after these callbacks, but + * before traversals. + * <p> + * Runs before traversals. + * @hide + */ + public static final int CALLBACK_INSETS_ANIMATION = 2; + + /** * Callback type: Traversal callback. Handles layout and draw. Runs * after all other asynchronous messages have been handled. * @hide */ - public static final int CALLBACK_TRAVERSAL = 2; + public static final int CALLBACK_TRAVERSAL = 3; /** * Callback type: Commit callback. Handles post-draw operations for the frame. @@ -232,7 +248,7 @@ public final class Choreographer { * to the view hierarchy state) actually took effect. * @hide */ - public static final int CALLBACK_COMMIT = 3; + public static final int CALLBACK_COMMIT = 4; private static final int CALLBACK_LAST = CALLBACK_COMMIT; @@ -704,6 +720,7 @@ public final class Choreographer { mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); + doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 7b9f78e70050..ce71b07da805 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -45,7 +45,9 @@ import java.util.function.Supplier; * @hide */ @VisibleForTesting -public class InsetsAnimationControlImpl implements WindowInsetsAnimationController { +public class InsetsAnimationControlImpl implements WindowInsetsAnimationController { + + private final Rect mTmpFrame = new Rect(); private final WindowInsetsAnimationControlListener mListener; private final SparseArray<InsetsSourceConsumer> mConsumers; @@ -61,19 +63,23 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private final InsetsState mInitialInsetsState; private final @InsetType int mTypes; private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier; - + private final InsetsController mController; + private final WindowInsetsAnimationListener.InsetsAnimation mAnimation; private Insets mCurrentInsets; + private Insets mPendingInsets; @VisibleForTesting public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetType int types, - Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier) { + Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier, + InsetsController controller) { mConsumers = consumers; mListener = listener; mTypes = types; mTransactionApplierSupplier = transactionApplierSupplier; - mInitialInsetsState = new InsetsState(state); + mController = controller; + mInitialInsetsState = new InsetsState(state, true /* copySources */); mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */); mHiddenInsets = calculateInsets(mInitialInsetsState, frame, consumers, false /* shown */, null /* typeSideMap */); @@ -83,6 +89,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll // TODO: Check for controllability first and wait for IME if needed. listener.onReady(this, types); + + mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets, + mShownInsets); + mController.dispatchAnimationStarted(mAnimation); } @Override @@ -108,29 +118,35 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll @Override public void changeInsets(Insets insets) { - insets = sanitize(insets); - final Insets offset = Insets.subtract(mShownInsets, insets); + mPendingInsets = sanitize(insets); + mController.scheduleApplyChangeInsets(); + } + + void applyChangeInsets(InsetsState state) { + final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); ArrayList<SurfaceParams> params = new ArrayList<>(); if (offset.left != 0) { - updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params); + updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params, state); } if (offset.top != 0) { - updateLeashesForSide(INSET_SIDE_TOP, offset.top, params); + updateLeashesForSide(INSET_SIDE_TOP, offset.top, params, state); } if (offset.right != 0) { - updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params); + updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params, state); } if (offset.bottom != 0) { - updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params); + updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params, state); } SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get(); applier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); - mCurrentInsets = insets; + mCurrentInsets = mPendingInsets; } @Override public void finish(int shownTypes) { // TODO + + mController.dispatchAnimationFinished(mAnimation); } private Insets calculateInsets(InsetsState state, Rect frame, @@ -146,7 +162,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll @Nullable @InsetSide SparseIntArray typeSideMap) { return state.calculateInsets(frame, false /* isScreenRound */, false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap) - .getSystemWindowInsets(); + .getInsets(mTypes); } private Insets sanitize(Insets insets) { @@ -154,7 +170,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } private void updateLeashesForSide(@InsetSide int side, int inset, - ArrayList<SurfaceParams> surfaceParams) { + ArrayList<SurfaceParams> surfaceParams, InsetsState state) { ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side); // TODO: Implement behavior when inset spans over multiple types for (int i = items.size() - 1; i >= 0; i--) { @@ -162,24 +178,32 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll final InsetsSource source = mInitialInsetsState.getSource(consumer.getType()); final SurfaceControl leash = consumer.getControl().getLeash(); mTmpMatrix.setTranslate(source.getFrame().left, source.getFrame().top); - addTranslationToMatrix(side, inset, mTmpMatrix); + + mTmpFrame.set(source.getFrame()); + addTranslationToMatrix(side, inset, mTmpMatrix, mTmpFrame); + + state.getSource(source.getType()).setFrame(mTmpFrame); surfaceParams.add(new SurfaceParams(leash, 1f, mTmpMatrix, null, 0, 0f)); } } - private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m) { + private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m, Rect frame) { switch (side) { case INSET_SIDE_LEFT: m.postTranslate(-inset, 0); + frame.offset(-inset, 0); break; case INSET_SIDE_TOP: m.postTranslate(0, -inset); + frame.offset(0, -inset); break; case INSET_SIDE_RIGHT: m.postTranslate(inset, 0); + frame.offset(inset, 0); break; case INSET_SIDE_BOTTOM: m.postTranslate(0, inset); + frame.offset(0, inset); break; } } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 01af37e75cb9..c2ade764ca81 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Insets; import android.graphics.Rect; import android.os.RemoteException; import android.util.ArraySet; @@ -49,9 +50,29 @@ public class InsetsController implements WindowInsetsController { private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>(); + private WindowInsets mLastInsets; + + private boolean mAnimCallbackScheduled; + + private final Runnable mAnimCallback; public InsetsController(ViewRootImpl viewRoot) { mViewRoot = viewRoot; + mAnimCallback = () -> { + mAnimCallbackScheduled = false; + if (mAnimationControls.isEmpty()) { + return; + } + + InsetsState state = new InsetsState(mState, true /* copySources */); + for (int i = mAnimationControls.size() - 1; i >= 0; i--) { + mAnimationControls.get(i).applyChangeInsets(state); + } + WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(), + mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(), + null /* typeSideMap */); + mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets); + }; } void onFrameChanged(Rect frame) { @@ -82,8 +103,9 @@ public class InsetsController implements WindowInsetsController { @VisibleForTesting public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeNavBar, DisplayCutout cutout) { - return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout, + mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout, null /* typeSideMap */); + return mLastInsets; } /** @@ -148,7 +170,7 @@ public class InsetsController implements WindowInsetsController { } final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers, mFrame, mState, listener, types, - () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView)); + () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this); mAnimationControls.add(controller); } @@ -200,4 +222,20 @@ public class InsetsController implements WindowInsetsController { pw.println(prefix); pw.println("InsetsController:"); mState.dump(prefix + " ", pw); } + + void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) { + mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation); + } + + void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) { + mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation); + } + + void scheduleApplyChangeInsets() { + if (!mAnimCallbackScheduled) { + mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, + mAnimCallback, null /* token*/); + mAnimCallbackScheduled = true; + } + } } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 093191499921..cf8c0707828d 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -107,6 +107,10 @@ public class InsetsState implements Parcelable { set(copy); } + public InsetsState(InsetsState copy, boolean copySources) { + set(copy, copySources); + } + /** * Calculates {@link WindowInsets} based on the current source configuration. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cd0e5794a5c7..abefd551331e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -96,6 +96,7 @@ import android.view.AccessibilityIterators.ParagraphTextSegmentIterator; import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.AccessibilityIterators.WordTextSegmentIterator; import android.view.ContextMenu.ContextMenuInfo; +import android.view.WindowInsetsAnimationListener.InsetsAnimation; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; @@ -4547,6 +4548,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnCapturedPointerListener mOnCapturedPointerListener; private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners; + + private WindowInsetsAnimationListener mWindowInsetsAnimationListener; } @UnsupportedAppUsage @@ -10488,6 +10491,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that + * cause insets. + * + * @param listener The listener to set. + * @hide pending unhide + */ + public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) { + getListenerInfo().mWindowInsetsAnimationListener = listener; + } + + void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) { + if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) { + mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation); + } + } + + WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) { + if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) { + return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets); + } else { + return insets; + } + } + + void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) { + if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) { + mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation); + } + } + + /** * Compute the view's coordinate within the surface. * * <p>Computes the coordinates of this view in its surface. The argument diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 9d11397498cf..0986cfa454b6 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -51,6 +51,7 @@ import android.util.Pools; import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.WindowInsetsAnimationListener.InsetsAnimation; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -7139,6 +7140,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return insets; } + @Override + void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationStarted(animation); + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).dispatchWindowInsetsAnimationStarted(animation); + } + } + + @Override + WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) { + insets = super.dispatchWindowInsetsAnimationProgress(insets); + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).dispatchWindowInsetsAnimationProgress(insets); + } + return insets; + } + + @Override + void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationFinished(animation); + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).dispatchWindowInsetsAnimationFinished(animation); + } + } + /** * Returns the animation listener to which layout animation events are * sent. diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java index 9de517dac5de..cf4415d2b2ba 100644 --- a/core/java/android/view/WindowInsetsAnimationController.java +++ b/core/java/android/view/WindowInsetsAnimationController.java @@ -19,6 +19,7 @@ package android.view; import android.annotation.NonNull; import android.graphics.Insets; import android.view.WindowInsets.Type.InsetType; +import android.view.WindowInsetsAnimationListener.InsetsAnimation; /** * Interface to control a window inset animation frame-by-frame. @@ -28,8 +29,13 @@ public interface WindowInsetsAnimationController { /** * Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden. + * <p> + * If there are any animation listeners registered, this value is the same as + * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks. * * @return Insets when the windows this animation is controlling are fully hidden. + * + * @see InsetsAnimation#getLowerBound() */ @NonNull Insets getHiddenStateInsets(); @@ -38,8 +44,13 @@ public interface WindowInsetsAnimationController { * <p> * In case the size of a window causing insets is changing in the middle of the animation, we * execute that height change after this animation has finished. + * <p> + * If there are any animation listeners registered, this value is the same as + * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks. * * @return Insets when the windows this animation is controlling are fully shown. + * + * @see InsetsAnimation#getUpperBound() */ @NonNull Insets getShownStateInsets(); @@ -59,8 +70,11 @@ public interface WindowInsetsAnimationController { * <p> * Note that this will <b>not</b> inform the view system of a full inset change via * {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the - * animation. If you'd like to animate views during a window inset animation, use - * TODO add link to animation listeners. + * animation. If you'd like to animate views during a window inset animation, register a + * {@link WindowInsetsAnimationListener} by calling + * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be + * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during + * the animation. * <p> * {@link View#dispatchApplyWindowInsets} will instead be called once the animation has * finished, i.e. once {@link #finish} has been called. @@ -70,6 +84,9 @@ public interface WindowInsetsAnimationController { * the resulting insets of that configuration will match the passed in parameter. * Note that these insets are being clamped to the range from * {@link #getHiddenStateInsets} to {@link #getShownStateInsets} + * + * @see WindowInsetsAnimationListener + * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener) */ void changeInsets(@NonNull Insets insets); diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java new file mode 100644 index 000000000000..682ab5bfb63c --- /dev/null +++ b/core/java/android/view/WindowInsetsAnimationListener.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.graphics.Insets; + +/** + * Interface that allows the application to listen to animation events for windows that cause + * insets. + * @hide pending unhide + */ +public interface WindowInsetsAnimationListener { + + /** + * Called when an inset animation gets started. + * + * @param animation The animation that is about to start. + */ + void onStarted(InsetsAnimation animation); + + /** + * Called when the insets change as part of running an animation. Note that even if multiple + * animations for different types are running, there will only be one progress callback per + * frame. The {@code insets} passed as an argument represents the overall state and will include + * all types, regardless of whether they are animating or not. + * <p> + * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy, + * and then traverse it and invoke the callback of the specific {@link View} being traversed. + * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)} + * to indicate that a part of the insets have been used to offset or clip its children, and the + * children shouldn't worry about that part anymore. + * + * @param insets The current insets. + * @return The insets to dispatch to the subtree of the hierarchy. + */ + WindowInsets onProgress(WindowInsets insets); + + /** + * Called when an inset animation has finished. + * + * @param animation The animation that has finished running. + */ + void onFinished(InsetsAnimation animation); + + /** + * Class representing an animation of a set of windows that cause insets. + */ + class InsetsAnimation { + + private final @WindowInsets.Type.InsetType int mTypeMask; + private final Insets mLowerBound; + private final Insets mUpperBound; + + /** + * @hide + */ + InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) { + mTypeMask = typeMask; + mLowerBound = lowerBound; + mUpperBound = upperBound; + } + + /** + * @return The bitmask of {@link WindowInsets.Type.InsetType}s that are animating. + */ + public @WindowInsets.Type.InsetType int getTypeMask() { + return mTypeMask; + } + + /** + * Queries the lower inset bound of the animation. If the animation is about showing or + * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper + * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This + * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and + * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets + * invoked because of an animation that originates from + * {@link WindowInsetsAnimationController}. + * <p> + * However, if the size of a window that causes insets is changing, these are the + * lower/upper bounds of that size animation. + * <p> + * There are no overlapping animations for a specific type, but there may be two animations + * running at the same time for different inset types. + * + * @see #getUpperBound() + * @see WindowInsetsAnimationController#getHiddenStateInsets + * TODO: It's a bit weird that these are global per window but onProgress is hierarchical. + * TODO: If multiple types are animating, querying the bound per type isn't possible. Should + * we: + * 1. Offer bounds by type here? + * 2. Restrict one animation to one single type only? + * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't + * fill in the insets for the types from the other animation into the WindowInsets object + * as it's changing as well. + */ + public Insets getLowerBound() { + return mLowerBound; + } + + /** + * @see #getLowerBound() + * @see WindowInsetsAnimationController#getShownStateInsets + */ + public Insets getUpperBound() { + return mUpperBound; + } + } +} diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java index cb1d89c54d9a..9c935af09cca 100644 --- a/core/java/android/view/autofill/AutofillId.java +++ b/core/java/android/view/autofill/AutofillId.java @@ -15,6 +15,7 @@ */ package android.view.autofill; +import android.annotation.NonNull; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -25,33 +26,47 @@ import android.view.View; */ public final class AutofillId implements Parcelable { + /** @hide */ + public static final int NO_SESSION = 0; + + private static final int FLAG_IS_VIRTUAL = 0x1; + private static final int FLAG_HAS_SESSION = 0x2; + private final int mViewId; - private final boolean mVirtual; + private final int mFlags; private final int mVirtualId; + private final int mSessionId; /** @hide */ @TestApi public AutofillId(int id) { - mVirtual = false; - mViewId = id; - mVirtualId = View.NO_ID; + this(/* flags= */ 0, id, View.NO_ID, NO_SESSION); } /** @hide */ @TestApi - public AutofillId(AutofillId parent, int virtualChildId) { - mVirtual = true; - mViewId = parent.mViewId; - mVirtualId = virtualChildId; + public AutofillId(@NonNull AutofillId parent, int virtualChildId) { + this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION); } /** @hide */ public AutofillId(int parentId, int virtualChildId) { - mVirtual = true; + this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION); + } + + /** @hide */ + public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) { + this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId); + } + + private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) { + mFlags = flags; mViewId = parentId; mVirtualId = virtualChildId; + mSessionId = sessionId; } + /** @hide */ public int getViewId() { return mViewId; @@ -64,7 +79,16 @@ public final class AutofillId implements Parcelable { /** @hide */ public boolean isVirtual() { - return mVirtual; + return (mFlags & FLAG_IS_VIRTUAL) != 0; + } + + private boolean hasSession() { + return (mFlags & FLAG_HAS_SESSION) != 0; + } + + /** @hide */ + public int getSessionId() { + return mSessionId; } ///////////////////////////////// @@ -77,6 +101,7 @@ public final class AutofillId implements Parcelable { int result = 1; result = prime * result + mViewId; result = prime * result + mVirtualId; + result = prime * result + mSessionId; return result; } @@ -88,15 +113,19 @@ public final class AutofillId implements Parcelable { final AutofillId other = (AutofillId) obj; if (mViewId != other.mViewId) return false; if (mVirtualId != other.mVirtualId) return false; + if (mSessionId != other.mSessionId) return false; return true; } @Override public String toString() { final StringBuilder builder = new StringBuilder().append(mViewId); - if (mVirtual) { + if (isVirtual()) { builder.append(':').append(mVirtualId); } + if (hasSession()) { + builder.append('@').append(mSessionId); + } return builder.toString(); } @@ -108,21 +137,24 @@ public final class AutofillId implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mViewId); - parcel.writeInt(mVirtual ? 1 : 0); - parcel.writeInt(mVirtualId); - } - - private AutofillId(Parcel parcel) { - mViewId = parcel.readInt(); - mVirtual = parcel.readInt() == 1; - mVirtualId = parcel.readInt(); + parcel.writeInt(mFlags); + if (isVirtual()) { + parcel.writeInt(mVirtualId); + } + if (hasSession()) { + parcel.writeInt(mSessionId); + } } public static final Parcelable.Creator<AutofillId> CREATOR = new Parcelable.Creator<AutofillId>() { @Override public AutofillId createFromParcel(Parcel source) { - return new AutofillId(source); + final int viewId = source.readInt(); + final int flags = source.readInt(); + final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID; + final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION; + return new AutofillId(flags, viewId, virtualId, sessionId); } @Override diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 90ccc257b134..93941d0d6edb 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -26,6 +26,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -329,6 +330,12 @@ public final class AutofillManager { private static final int SYNC_CALLS_TIMEOUT_MS = 5000; /** + * @hide + */ + @TestApi + public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes + + /** * Makes an authentication id from a request id and a dataset id. * * @param requestId The request id. diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 6890beaf4c16..d9a8416dcb4d 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -28,6 +28,7 @@ import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; @@ -107,7 +108,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** @hide */ @Nullable - protected final String mId = UUID.randomUUID().toString(); + protected final String mId; private int mState = STATE_UNKNOWN; @@ -123,6 +124,13 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** @hide */ protected ContentCaptureSession() { + this(UUID.randomUUID().toString()); + } + + /** @hide */ + @VisibleForTesting + public ContentCaptureSession(@NonNull String id) { + mId = Preconditions.checkNotNull(id); mCloseGuard.open("destroy"); } @@ -140,6 +148,13 @@ public abstract class ContentCaptureSession implements AutoCloseable { return mContentCaptureSessionId; } + /** @hide */ + @VisibleForTesting + public int getIdAsInt() { + // TODO(b/121197119): use sessionId instead of hashcode once it's changed to int + return mId.hashCode(); + } + /** * Creates a new {@link ContentCaptureSession}. * @@ -315,9 +330,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) { Preconditions.checkNotNull(parentId); Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children"); - // TODO(b/121197119): we need to add the session id to the AutofillId to make them unique - // per session - return new AutofillId(parentId, virtualChildId); + return new AutofillId(parentId, virtualChildId, getIdAsInt()); } /** @@ -333,8 +346,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { @NonNull public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) { - // TODO(b/121197119): use the constructor that takes a session id / assert on unit test. - return new ViewNode.ViewStructureImpl(parentId, virtualId); + return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt()); } boolean isContentCaptureEnabled() { diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java index b7a486afc118..ddfecb0fae14 100644 --- a/core/java/android/view/contentcapture/ViewNode.java +++ b/core/java/android/view/contentcapture/ViewNode.java @@ -672,9 +672,9 @@ public final class ViewNode extends AssistStructure.ViewNode { } @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk. - public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) { + public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) { mNode.mParentAutofillId = Preconditions.checkNotNull(parentId); - mNode.mAutofillId = new AutofillId(parentId, virtualId); + mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId); } @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk. diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java new file mode 100644 index 000000000000..1a6e5d8e8b03 --- /dev/null +++ b/core/java/android/view/textclassifier/ConversationAction.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view.textclassifier; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.app.RemoteAction; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; + +/** Represents the action suggested by a {@link TextClassifier} on a given conversation. */ +public final class ConversationAction implements Parcelable { + + /** @hide */ + @Retention(SOURCE) + @StringDef( + value = { + TYPE_VIEW_CALENDAR, + TYPE_VIEW_MAP, + TYPE_TRACK_FLIGHT, + TYPE_OPEN_URL, + TYPE_SEND_SMS, + TYPE_CALL_PHONE, + TYPE_SEND_EMAIL, + TYPE_TEXT_REPLY, + TYPE_CREATE_REMINDER, + TYPE_SHARE_LOCATION + }, + prefix = "TYPE_") + public @interface ActionType {} + + /** + * Indicates an action to view a calendar at a specified time. + */ + public static final String TYPE_VIEW_CALENDAR = "view_calendar"; + /** + * Indicates an action to view the map at a specified location. + */ + public static final String TYPE_VIEW_MAP = "view_map"; + /** + * Indicates an action to track a flight. + */ + public static final String TYPE_TRACK_FLIGHT = "track_flight"; + /** + * Indicates an action to open an URL. + */ + public static final String TYPE_OPEN_URL = "open_url"; + /** + * Indicates an action to send a SMS. + */ + public static final String TYPE_SEND_SMS = "send_sms"; + /** + * Indicates an action to call a phone number. + */ + public static final String TYPE_CALL_PHONE = "call_phone"; + /** + * Indicates an action to send an email. + */ + public static final String TYPE_SEND_EMAIL = "send_email"; + /** + * Indicates an action to reply with a text message. + */ + public static final String TYPE_TEXT_REPLY = "text_reply"; + /** + * Indicates an action to create a reminder. + */ + public static final String TYPE_CREATE_REMINDER = "create_reminder"; + /** + * Indicates an action to reply with a location. + */ + public static final String TYPE_SHARE_LOCATION = "share_location"; + + public static final Creator<ConversationAction> CREATOR = + new Creator<ConversationAction>() { + @Override + public ConversationAction createFromParcel(Parcel in) { + return new ConversationAction(in); + } + + @Override + public ConversationAction[] newArray(int size) { + return new ConversationAction[size]; + } + }; + + @NonNull + @ActionType + private final String mType; + @NonNull + private final CharSequence mTextReply; + @Nullable + private final RemoteAction mAction; + + @FloatRange(from = 0, to = 1) + private final float mScore; + + @NonNull + private final Bundle mExtras; + + private ConversationAction( + @NonNull String type, + @Nullable RemoteAction action, + @Nullable CharSequence textReply, + float score, + @NonNull Bundle extras) { + mType = Preconditions.checkNotNull(type); + mAction = action; + mTextReply = textReply; + mScore = score; + mExtras = Preconditions.checkNotNull(extras); + } + + private ConversationAction(Parcel in) { + mType = in.readString(); + mAction = in.readParcelable(null); + mTextReply = in.readCharSequence(); + mScore = in.readFloat(); + mExtras = in.readBundle(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mType); + parcel.writeParcelable(mAction, flags); + parcel.writeCharSequence(mTextReply); + parcel.writeFloat(mScore); + parcel.writeBundle(mExtras); + } + + @Override + public int describeContents() { + return 0; + } + + /** Returns the type of this action, for example, {@link #TYPE_VIEW_CALENDAR}. */ + @NonNull + @ActionType + public String getType() { + return mType; + } + + /** + * Returns a RemoteAction object, which contains the icon, label and a PendingIntent, for + * the specified action type. + */ + @Nullable + public RemoteAction getAction() { + return mAction; + } + + /** + * Returns the confidence score for the specified action. The value ranges from 0 (low + * confidence) to 1 (high confidence). + */ + @FloatRange(from = 0, to = 1) + public float getConfidenceScore() { + return mScore; + } + + /** + * Returns the text reply that could be sent as a reply to the given conversation. + * <p> + * This is only available when the type of the action is {@link #TYPE_TEXT_REPLY}. + */ + @Nullable + public CharSequence getTextReply() { + return mTextReply; + } + + /** + * Returns the extended data related to this conversation action. + * + * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should + * prefer to hold a reference to the returned bundle rather than frequently calling this + * method. + */ + @NonNull + public Bundle getExtras() { + return mExtras.deepCopy(); + } + + /** Builder class to construct {@link ConversationAction}. */ + public static final class Builder { + @Nullable + @ActionType + private String mType; + @Nullable + private RemoteAction mAction; + @Nullable + private CharSequence mTextReply; + private float mScore; + @Nullable + private Bundle mExtras; + + public Builder(@NonNull @ActionType String actionType) { + mType = Preconditions.checkNotNull(actionType); + } + + /** + * Sets an action that may be performed on the given conversation. + */ + @NonNull + public Builder setAction(@Nullable RemoteAction action) { + mAction = action; + return this; + } + + /** + * Sets a text reply that may be performed on the given conversation. + */ + @NonNull + public Builder setTextReply(@Nullable CharSequence textReply) { + mTextReply = textReply; + return this; + } + + /** Sets the confident score. */ + @NonNull + public Builder setConfidenceScore(@FloatRange(from = 0, to = 1) float score) { + mScore = score; + return this; + } + + /** + * Sets the extended data for the conversation action object. + */ + @NonNull + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** Builds the {@link ConversationAction} object. */ + @NonNull + public ConversationAction build() { + return new ConversationAction( + mType, + mAction, + mTextReply, + mScore, + mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); + } + } +} diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 3f690f70d275..f7c1a2640dc5 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -17,18 +17,15 @@ package android.view.textclassifier; import static java.lang.annotation.RetentionPolicy.SOURCE; -import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.app.Person; -import android.app.RemoteAction; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.SpannedString; -import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; @@ -37,10 +34,8 @@ import java.lang.annotation.Retention; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Set; /** * Represents a list of actions suggested by a {@link TextClassifier} on a given conversation. @@ -62,83 +57,6 @@ public final class ConversationActions implements Parcelable { } }; - /** @hide */ - @Retention(SOURCE) - @StringDef( - value = { - TYPE_VIEW_CALENDAR, - TYPE_VIEW_MAP, - TYPE_TRACK_FLIGHT, - TYPE_OPEN_URL, - TYPE_SEND_SMS, - TYPE_CALL_PHONE, - TYPE_SEND_EMAIL, - TYPE_TEXT_REPLY, - TYPE_CREATE_REMINDER, - TYPE_SHARE_LOCATION - }, - prefix = "TYPE_") - public @interface ActionType {} - - /** - * Indicates an action to view a calendar at a specified time. - */ - public static final String TYPE_VIEW_CALENDAR = "view_calendar"; - /** - * Indicates an action to view the map at a specified location. - */ - public static final String TYPE_VIEW_MAP = "view_map"; - /** - * Indicates an action to track a flight. - */ - public static final String TYPE_TRACK_FLIGHT = "track_flight"; - /** - * Indicates an action to open an URL. - */ - public static final String TYPE_OPEN_URL = "open_url"; - /** - * Indicates an action to send a SMS. - */ - public static final String TYPE_SEND_SMS = "send_sms"; - /** - * Indicates an action to call a phone number. - */ - public static final String TYPE_CALL_PHONE = "call_phone"; - /** - * Indicates an action to send an email. - */ - public static final String TYPE_SEND_EMAIL = "send_email"; - /** - * Indicates an action to reply with a text message. - */ - public static final String TYPE_TEXT_REPLY = "text_reply"; - /** - * Indicates an action to create a reminder. - */ - public static final String TYPE_CREATE_REMINDER = "create_reminder"; - /** - * Indicates an action to reply with a location. - */ - public static final String TYPE_SHARE_LOCATION = "share_location"; - - /** @hide */ - @Retention(SOURCE) - @StringDef( - value = { - HINT_FOR_NOTIFICATION, - HINT_FOR_IN_APP, - }, - prefix = "HINT_") - public @interface Hint {} - /** - * To indicate the generated actions will be used within the app. - */ - public static final String HINT_FOR_IN_APP = "in_app"; - /** - * To indicate the generated actions will be used for notification. - */ - public static final String HINT_FOR_NOTIFICATION = "notification"; - private final List<ConversationAction> mConversationActions; private final String mId; @@ -184,182 +102,6 @@ public final class ConversationActions implements Parcelable { return mId; } - /** Represents the action suggested by a {@link TextClassifier} on a given conversation. */ - public static final class ConversationAction implements Parcelable { - - public static final Creator<ConversationAction> CREATOR = - new Creator<ConversationAction>() { - @Override - public ConversationAction createFromParcel(Parcel in) { - return new ConversationAction(in); - } - - @Override - public ConversationAction[] newArray(int size) { - return new ConversationAction[size]; - } - }; - - @NonNull - @ActionType - private final String mType; - @NonNull - private final CharSequence mTextReply; - @Nullable - private final RemoteAction mAction; - - @FloatRange(from = 0, to = 1) - private final float mScore; - - @NonNull - private final Bundle mExtras; - - private ConversationAction( - @NonNull String type, - @Nullable RemoteAction action, - @Nullable CharSequence textReply, - float score, - @NonNull Bundle extras) { - mType = Preconditions.checkNotNull(type); - mAction = action; - mTextReply = textReply; - mScore = score; - mExtras = Preconditions.checkNotNull(extras); - } - - private ConversationAction(Parcel in) { - mType = in.readString(); - mAction = in.readParcelable(null); - mTextReply = in.readCharSequence(); - mScore = in.readFloat(); - mExtras = in.readBundle(); - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(mType); - parcel.writeParcelable(mAction, flags); - parcel.writeCharSequence(mTextReply); - parcel.writeFloat(mScore); - parcel.writeBundle(mExtras); - } - - @Override - public int describeContents() { - return 0; - } - - @NonNull - @ActionType - /** Returns the type of this action, for example, {@link #TYPE_VIEW_CALENDAR}. */ - public String getType() { - return mType; - } - - @Nullable - /** - * Returns a RemoteAction object, which contains the icon, label and a PendingIntent, for - * the specified action type. - */ - public RemoteAction getAction() { - return mAction; - } - - /** - * Returns the confidence score for the specified action. The value ranges from 0 (low - * confidence) to 1 (high confidence). - */ - @FloatRange(from = 0, to = 1) - public float getConfidenceScore() { - return mScore; - } - - /** - * Returns the text reply that could be sent as a reply to the given conversation. - * <p> - * This is only available when the type of the action is {@link #TYPE_TEXT_REPLY}. - */ - @Nullable - public CharSequence getTextReply() { - return mTextReply; - } - - /** - * Returns the extended data related to this conversation action. - * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. - */ - @NonNull - public Bundle getExtras() { - return mExtras.deepCopy(); - } - - /** Builder class to construct {@link ConversationAction}. */ - public static final class Builder { - @Nullable - @ActionType - private String mType; - @Nullable - private RemoteAction mAction; - @Nullable - private CharSequence mTextReply; - private float mScore; - @Nullable - private Bundle mExtras; - - public Builder(@NonNull @ActionType String actionType) { - mType = Preconditions.checkNotNull(actionType); - } - - /** - * Sets an action that may be performed on the given conversation. - */ - @NonNull - public Builder setAction(@Nullable RemoteAction action) { - mAction = action; - return this; - } - - /** - * Sets a text reply that may be performed on the given conversation. - */ - @NonNull - public Builder setTextReply(@Nullable CharSequence textReply) { - mTextReply = textReply; - return this; - } - - /** Sets the confident score. */ - @NonNull - public Builder setConfidenceScore(@FloatRange(from = 0, to = 1) float score) { - mScore = score; - return this; - } - - /** - * Sets the extended data for the conversation action object. - */ - @NonNull - public Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** Builds the {@link ConversationAction} object. */ - @NonNull - public ConversationAction build() { - return new ConversationAction( - mType, - mAction, - mTextReply, - mScore, - mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); - } - } - } - /** Represents a message in the conversation. */ public static final class Message implements Parcelable { /** @@ -538,156 +280,36 @@ public final class ConversationActions implements Parcelable { } } - /** Configuration object for specifying what action types to identify. */ - public static final class TypeConfig implements Parcelable { - @NonNull - @ActionType - private final Set<String> mExcludedTypes; - @NonNull - @ActionType - private final Set<String> mIncludedTypes; - private final boolean mIncludeTypesFromTextClassifier; - - private TypeConfig( - @NonNull Set<String> includedTypes, - @NonNull Set<String> excludedTypes, - boolean includeTypesFromTextClassifier) { - mIncludedTypes = Preconditions.checkNotNull(includedTypes); - mExcludedTypes = Preconditions.checkNotNull(excludedTypes); - mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; - } - - private TypeConfig(Parcel in) { - mIncludedTypes = new ArraySet<>(in.createStringArrayList()); - mExcludedTypes = new ArraySet<>(in.createStringArrayList()); - mIncludeTypesFromTextClassifier = in.readByte() != 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeStringList(new ArrayList<>(mIncludedTypes)); - parcel.writeStringList(new ArrayList<>(mExcludedTypes)); - parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0)); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Creator<TypeConfig> CREATOR = - new Creator<TypeConfig>() { - @Override - public TypeConfig createFromParcel(Parcel in) { - return new TypeConfig(in); - } + /** + * A request object for generating conversation action suggestions. + * + * @see TextClassifier#suggestConversationActions(Request) + */ + public static final class Request implements Parcelable { - @Override - public TypeConfig[] newArray(int size) { - return new TypeConfig[size]; - } - }; + /** @hide */ + @Retention(SOURCE) + @StringDef( + value = { + HINT_FOR_NOTIFICATION, + HINT_FOR_IN_APP, + }, + prefix = "HINT_") + public @interface Hint {} /** - * Returns a final list of types that the text classifier should look for. - * - * <p>NOTE: This method is intended for use by a text classifier. - * - * @param defaultTypes types the text classifier thinks should be included before factoring - * in the included/excluded types given by the client. + * To indicate the generated actions will be used within the app. */ - @NonNull - public Collection<String> resolveTypes(@Nullable Collection<String> defaultTypes) { - Set<String> types = new ArraySet<>(); - if (mIncludeTypesFromTextClassifier && defaultTypes != null) { - types.addAll(defaultTypes); - } - types.addAll(mIncludedTypes); - types.removeAll(mExcludedTypes); - return Collections.unmodifiableCollection(types); - } - + public static final String HINT_FOR_IN_APP = "in_app"; /** - * Return whether the client allows the text classifier to include its own list of default - * types. If this function returns {@code true}, the text classifier can consider specifying - * a default list of entity types in {@link #resolveTypes(Collection)}. - * - * <p>NOTE: This method is intended for use by a text classifier. - * - * @see #resolveTypes(Collection) + * To indicate the generated actions will be used for notification. */ - public boolean shouldIncludeTypesFromTextClassifier() { - return mIncludeTypesFromTextClassifier; - } - - /** Builder class to construct the {@link TypeConfig} object. */ - public static final class Builder { - @Nullable - private Collection<String> mExcludedTypes; - @Nullable - private Collection<String> mIncludedTypes; - private boolean mIncludeTypesFromTextClassifier = true; - - /** - * Sets a collection of types that are explicitly included, for example, {@link - * #TYPE_VIEW_CALENDAR}. - */ - @NonNull - public Builder setIncludedTypes( - @Nullable @ActionType Collection<String> includedTypes) { - mIncludedTypes = includedTypes; - return this; - } - - /** - * Sets a collection of types that are explicitly excluded, for example, {@link - * #TYPE_VIEW_CALENDAR}. - */ - @NonNull - public Builder setExcludedTypes( - @Nullable @ActionType Collection<String> excludedTypes) { - mExcludedTypes = excludedTypes; - return this; - } - - /** - * Specifies whether or not to include the types suggested by the text classifier. By - * default, it is included. - */ - @NonNull - public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) { - mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; - return this; - } - - /** - * Combines all of the options that have been set and returns a new {@link TypeConfig} - * object. - */ - @NonNull - public TypeConfig build() { - return new TypeConfig( - mIncludedTypes == null - ? Collections.emptySet() - : new ArraySet<>(mIncludedTypes), - mExcludedTypes == null - ? Collections.emptySet() - : new ArraySet<>(mExcludedTypes), - mIncludeTypesFromTextClassifier); - } - } - } + public static final String HINT_FOR_NOTIFICATION = "notification"; - /** - * A request object for generating conversation action suggestions. - * - * @see TextClassifier#suggestConversationActions(Request) - */ - public static final class Request implements Parcelable { @NonNull private final List<Message> mConversation; @NonNull - private final TypeConfig mTypeConfig; + private final TextClassifier.EntityConfig mTypeConfig; private final int mMaxSuggestions; @NonNull @Hint @@ -699,7 +321,7 @@ public final class ConversationActions implements Parcelable { private Request( @NonNull List<Message> conversation, - @NonNull TypeConfig typeConfig, + @NonNull TextClassifier.EntityConfig typeConfig, int maxSuggestions, String conversationId, @Nullable @Hint List<String> hints) { @@ -713,7 +335,7 @@ public final class ConversationActions implements Parcelable { private static Request readFromParcel(Parcel in) { List<Message> conversation = new ArrayList<>(); in.readParcelableList(conversation, null); - TypeConfig typeConfig = in.readParcelable(null); + TextClassifier.EntityConfig typeConfig = in.readParcelable(null); int maxSuggestions = in.readInt(); String conversationId = in.readString(); List<String> hints = new ArrayList<>(); @@ -760,7 +382,7 @@ public final class ConversationActions implements Parcelable { /** Returns the type config. */ @NonNull - public TypeConfig getTypeConfig() { + public TextClassifier.EntityConfig getTypeConfig() { return mTypeConfig; } @@ -820,7 +442,7 @@ public final class ConversationActions implements Parcelable { @NonNull private List<Message> mConversation; @Nullable - private TypeConfig mTypeConfig; + private TextClassifier.EntityConfig mTypeConfig; private int mMaxSuggestions; @Nullable private String mConversationId; @@ -849,7 +471,7 @@ public final class ConversationActions implements Parcelable { /** Sets the type config. */ @NonNull - public Builder setTypeConfig(@Nullable TypeConfig typeConfig) { + public Builder setTypeConfig(@Nullable TextClassifier.EntityConfig typeConfig) { mTypeConfig = typeConfig; return this; } @@ -879,7 +501,9 @@ public final class ConversationActions implements Parcelable { public Request build() { return new Request( Collections.unmodifiableList(mConversation), - mTypeConfig == null ? new TypeConfig.Builder().build() : mTypeConfig, + mTypeConfig == null + ? new TextClassifier.EntityConfig.Builder().build() + : mTypeConfig, mMaxSuggestions, mConversationId, mHints == null diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 50801a2b3e3f..ce680ecbd119 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -117,15 +117,15 @@ public final class TextClassificationConstants { .add(TextClassifier.TYPE_FLIGHT_NUMBER).toString(); private static final String CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES = new StringJoiner(ENTITY_LIST_DELIMITER) - .add(ConversationActions.TYPE_TEXT_REPLY) - .add(ConversationActions.TYPE_CREATE_REMINDER) - .add(ConversationActions.TYPE_CALL_PHONE) - .add(ConversationActions.TYPE_OPEN_URL) - .add(ConversationActions.TYPE_SEND_EMAIL) - .add(ConversationActions.TYPE_SEND_SMS) - .add(ConversationActions.TYPE_TRACK_FLIGHT) - .add(ConversationActions.TYPE_VIEW_CALENDAR) - .add(ConversationActions.TYPE_VIEW_MAP) + .add(ConversationAction.TYPE_TEXT_REPLY) + .add(ConversationAction.TYPE_CREATE_REMINDER) + .add(ConversationAction.TYPE_CALL_PHONE) + .add(ConversationAction.TYPE_OPEN_URL) + .add(ConversationAction.TYPE_SEND_EMAIL) + .add(ConversationAction.TYPE_SEND_SMS) + .add(ConversationAction.TYPE_TRACK_FLIGHT) + .add(ConversationAction.TYPE_VIEW_CALENDAR) + .add(ConversationAction.TYPE_VIEW_MAP) .toString(); private final boolean mSystemTextClassifierEnabled; diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 8709e09bbf55..5a5613605e36 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -32,7 +32,6 @@ import android.text.style.URLSpan; import android.text.util.Linkify; import android.text.util.Linkify.LinkifyMask; import android.util.ArrayMap; -import android.util.ArraySet; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -43,6 +42,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -324,7 +324,7 @@ public interface TextClassifier { } /** - * Detects the language of the specified text. + * Detects the language of the text in the given request. * * <p><strong>NOTE: </strong>Call on a worker thread. * @@ -403,42 +403,59 @@ public interface TextClassifier { default void dump(@NonNull IndentingPrintWriter printWriter) {} /** - * Configuration object for specifying what entities to identify. + * Configuration object for specifying what entity types to identify. * * Configs are initially based on a predefined preset, and can be modified from there. */ final class EntityConfig implements Parcelable { - private final Collection<String> mHints; - private final Collection<String> mExcludedEntityTypes; - private final Collection<String> mIncludedEntityTypes; - private final boolean mUseHints; - - private EntityConfig(boolean useHints, Collection<String> hints, - Collection<String> includedEntityTypes, Collection<String> excludedEntityTypes) { - mHints = hints == null - ? Collections.EMPTY_LIST - : Collections.unmodifiableCollection(new ArraySet<>(hints)); - mExcludedEntityTypes = excludedEntityTypes == null - ? Collections.EMPTY_LIST : new ArraySet<>(excludedEntityTypes); - mIncludedEntityTypes = includedEntityTypes == null - ? Collections.EMPTY_LIST : new ArraySet<>(includedEntityTypes); - mUseHints = useHints; + private final List<String> mIncludedTypes; + private final List<String> mExcludedTypes; + private final List<String> mHints; + private final boolean mIncludeTypesFromTextClassifier; + + private EntityConfig( + List<String> includedEntityTypes, + List<String> excludedEntityTypes, + List<String> hints, + boolean includeTypesFromTextClassifier) { + mIncludedTypes = Preconditions.checkNotNull(includedEntityTypes); + mExcludedTypes = Preconditions.checkNotNull(excludedEntityTypes); + mHints = Preconditions.checkNotNull(hints); + mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; + } + + private EntityConfig(Parcel in) { + mIncludedTypes = new ArrayList<>(); + in.readStringList(mIncludedTypes); + mExcludedTypes = new ArrayList<>(); + in.readStringList(mExcludedTypes); + List<String> tmpHints = new ArrayList<>(); + in.readStringList(tmpHints); + mHints = Collections.unmodifiableList(tmpHints); + mIncludeTypesFromTextClassifier = in.readByte() != 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeStringList(mIncludedTypes); + parcel.writeStringList(mExcludedTypes); + parcel.writeStringList(mHints); + parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0)); } /** * Creates an EntityConfig. * * @param hints Hints for the TextClassifier to determine what types of entities to find. + * + * @deprecated Use {@link Builder} instead. */ + @Deprecated public static EntityConfig createWithHints(@Nullable Collection<String> hints) { - return new EntityConfig(/* useHints */ true, hints, - /* includedEntityTypes */null, /* excludedEntityTypes */ null); - } - - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - public static EntityConfig create(@Nullable Collection<String> hints) { - return createWithHints(hints); + return new EntityConfig.Builder() + .includeTypesFromTextClassifier(true) + .setHints(hints) + .build(); } /** @@ -450,12 +467,19 @@ public interface TextClassifier { * * * Note that if an entity has been excluded, the exclusion will take precedence. + * + * @deprecated Use {@link Builder} instead. */ + @Deprecated public static EntityConfig create(@Nullable Collection<String> hints, @Nullable Collection<String> includedEntityTypes, @Nullable Collection<String> excludedEntityTypes) { - return new EntityConfig(/* useHints */ true, hints, - includedEntityTypes, excludedEntityTypes); + return new EntityConfig.Builder() + .setIncludedTypes(includedEntityTypes) + .setExcludedTypes(excludedEntityTypes) + .setHints(hints) + .includeTypesFromTextClassifier(true) + .build(); } /** @@ -463,34 +487,33 @@ public interface TextClassifier { * * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find. * + * @deprecated Use {@link Builder} instead. */ + @Deprecated public static EntityConfig createWithExplicitEntityList( @Nullable Collection<String> entityTypes) { - return new EntityConfig(/* useHints */ false, /* hints */ null, - /* includedEntityTypes */ entityTypes, /* excludedEntityTypes */ null); - } - - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - public static EntityConfig createWithEntityList(@Nullable Collection<String> entityTypes) { - return createWithExplicitEntityList(entityTypes); + return new EntityConfig.Builder() + .setIncludedTypes(entityTypes) + .includeTypesFromTextClassifier(false) + .build(); } /** - * Returns a list of the final set of entities to find. + * Returns a final list of entity types to find. * - * @param entities Entities we think should be found before factoring in includes/excludes + * @param entityTypes Entity types we think should be found before factoring in + * includes/excludes * * This method is intended for use by TextClassifier implementations. */ public Collection<String> resolveEntityListModifications( - @NonNull Collection<String> entities) { - final Set<String> finalSet = new HashSet(); - if (mUseHints) { - finalSet.addAll(entities); + @NonNull Collection<String> entityTypes) { + final Set<String> finalSet = new HashSet<>(); + if (mIncludeTypesFromTextClassifier) { + finalSet.addAll(entityTypes); } - finalSet.addAll(mIncludedEntityTypes); - finalSet.removeAll(mExcludedEntityTypes); + finalSet.addAll(mIncludedTypes); + finalSet.removeAll(mExcludedTypes); return finalSet; } @@ -503,17 +526,22 @@ public interface TextClassifier { return mHints; } - @Override - public int describeContents() { - return 0; + /** + * Return whether the client allows the text classifier to include its own list of + * default types. If this function returns {@code true}, a default list of types suggested + * from a text classifier will be taking into account. + * + * <p>NOTE: This method is intended for use by a text classifier. + * + * @see #resolveEntityListModifications(Collection) + */ + public boolean shouldIncludeTypesFromTextClassifier() { + return mIncludeTypesFromTextClassifier; } @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStringList(new ArrayList<>(mHints)); - dest.writeStringList(new ArrayList<>(mExcludedEntityTypes)); - dest.writeStringList(new ArrayList<>(mIncludedEntityTypes)); - dest.writeInt(mUseHints ? 1 : 0); + public int describeContents() { + return 0; } public static final Parcelable.Creator<EntityConfig> CREATOR = @@ -529,11 +557,75 @@ public interface TextClassifier { } }; - private EntityConfig(Parcel in) { - mHints = new ArraySet<>(in.createStringArrayList()); - mExcludedEntityTypes = new ArraySet<>(in.createStringArrayList()); - mIncludedEntityTypes = new ArraySet<>(in.createStringArrayList()); - mUseHints = in.readInt() == 1; + + + /** Builder class to construct the {@link EntityConfig} object. */ + public static final class Builder { + @Nullable + private Collection<String> mIncludedTypes; + @Nullable + private Collection<String> mExcludedTypes; + @Nullable + private Collection<String> mHints; + private boolean mIncludeTypesFromTextClassifier = true; + + /** + * Sets a collection of types that are explicitly included. + */ + @NonNull + public Builder setIncludedTypes(@Nullable Collection<String> includedTypes) { + mIncludedTypes = includedTypes; + return this; + } + + /** + * Sets a collection of types that are explicitly excluded. + */ + @NonNull + public Builder setExcludedTypes(@Nullable Collection<String> excludedTypes) { + mExcludedTypes = excludedTypes; + return this; + } + + /** + * Specifies whether or not to include the types suggested by the text classifier. By + * default, it is included. + */ + @NonNull + public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) { + mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; + return this; + } + + + /** + * Sets the hints for the TextClassifier to determine what types of entities to find. + * These hints will only be used if {@link #includeTypesFromTextClassifier} is + * set to be true. + */ + public Builder setHints(Collection<String> hints) { + mHints = hints; + return this; + } + + /** + * Combines all of the options that have been set and returns a new {@link EntityConfig} + * object. + */ + @NonNull + public EntityConfig build() { + return new EntityConfig( + mIncludedTypes == null + ? Collections.emptyList() + : new ArrayList<>(mIncludedTypes), + mExcludedTypes == null + ? Collections.emptyList() + : new ArrayList<>(mExcludedTypes), + mHints == null + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList<>(mHints)), + mIncludeTypesFromTextClassifier); + } } } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index d5b9eb1d09dc..9ab963e372b7 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -393,7 +393,7 @@ public final class TextClassifierImpl implements TextClassifier { actionsImpl.suggestActions(nativeConversation, null); Collection<String> expectedTypes = resolveActionTypesFromRequest(request); - List<ConversationActions.ConversationAction> conversationActions = new ArrayList<>(); + List<ConversationAction> conversationActions = new ArrayList<>(); int maxSuggestions = nativeSuggestions.length; if (request.getMaxSuggestions() > 0) { maxSuggestions = Math.min(request.getMaxSuggestions(), nativeSuggestions.length); @@ -405,7 +405,7 @@ public final class TextClassifierImpl implements TextClassifier { continue; } conversationActions.add( - new ConversationActions.ConversationAction.Builder(actionType) + new ConversationAction.Builder(actionType) .setTextReply(nativeSuggestion.getResponseText()) .setConfidenceScore(nativeSuggestion.getScore()) .build()); @@ -445,10 +445,10 @@ public final class TextClassifierImpl implements TextClassifier { private Collection<String> resolveActionTypesFromRequest(ConversationActions.Request request) { List<String> defaultActionTypes = - request.getHints().contains(ConversationActions.HINT_FOR_NOTIFICATION) + request.getHints().contains(ConversationActions.Request.HINT_FOR_NOTIFICATION) ? mSettings.getNotificationConversationActionTypes() : mSettings.getInAppConversationActionTypes(); - return request.getTypeConfig().resolveTypes(defaultActionTypes); + return request.getTypeConfig().resolveEntityListModifications(defaultActionTypes); } private AnnotatorModel getAnnotatorImpl(LocaleList localeList) diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 9b9b77196a53..8e88c510ec31 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -23,6 +23,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.service.procstats.ProcessStatsAvailablePagesProto; import android.service.procstats.ProcessStatsPackageProto; import android.service.procstats.ProcessStatsSectionProto; import android.text.format.DateFormat; @@ -178,7 +179,7 @@ public final class ProcessStats implements Parcelable { {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"}; // Current version of the parcel format. - private static final int PARCEL_VERSION = 35; + private static final int PARCEL_VERSION = 36; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535454; @@ -237,10 +238,11 @@ public final class ProcessStats implements Parcelable { ArrayList<String> mIndexToCommonString; private static final Pattern sPageTypeRegex = Pattern.compile( - "^Node\\s+(\\d+),.*. type\\s+(\\w+)\\s+([\\s\\d]+?)\\s*$"); - private final ArrayList<Integer> mPageTypeZones = new ArrayList<Integer>(); - private final ArrayList<String> mPageTypeLabels = new ArrayList<String>(); - private final ArrayList<int[]> mPageTypeSizes = new ArrayList<int[]>(); + "^Node\\s+(\\d+),.* zone\\s+(\\w+),.* type\\s+(\\w+)\\s+([\\s\\d]+?)\\s*$"); + private final ArrayList<Integer> mPageTypeNodes = new ArrayList<>(); + private final ArrayList<String> mPageTypeZones = new ArrayList<>(); + private final ArrayList<String> mPageTypeLabels = new ArrayList<>(); + private final ArrayList<int[]> mPageTypeSizes = new ArrayList<>(); public ProcessStats(boolean running) { mRunning = running; @@ -621,6 +623,7 @@ public final class ProcessStats implements Parcelable { try { reader = new BufferedReader(new FileReader("/proc/pagetypeinfo")); final Matcher matcher = sPageTypeRegex.matcher(""); + mPageTypeNodes.clear(); mPageTypeZones.clear(); mPageTypeLabels.clear(); mPageTypeSizes.clear(); @@ -631,16 +634,18 @@ public final class ProcessStats implements Parcelable { } matcher.reset(line); if (matcher.matches()) { - final Integer zone = Integer.valueOf(matcher.group(1), 10); - if (zone == null) { + final Integer node = Integer.valueOf(matcher.group(1), 10); + if (node == null) { continue; } - mPageTypeZones.add(zone); - mPageTypeLabels.add(matcher.group(2)); - mPageTypeSizes.add(splitAndParseNumbers(matcher.group(3))); + mPageTypeNodes.add(node); + mPageTypeZones.add(matcher.group(2)); + mPageTypeLabels.add(matcher.group(3)); + mPageTypeSizes.add(splitAndParseNumbers(matcher.group(4))); } } } catch (IOException ex) { + mPageTypeNodes.clear(); mPageTypeZones.clear(); mPageTypeLabels.clear(); mPageTypeSizes.clear(); @@ -935,7 +940,8 @@ public final class ProcessStats implements Parcelable { final int NPAGETYPES = mPageTypeLabels.size(); out.writeInt(NPAGETYPES); for (int i=0; i<NPAGETYPES; i++) { - out.writeInt(mPageTypeZones.get(i)); + out.writeInt(mPageTypeNodes.get(i)); + out.writeString(mPageTypeZones.get(i)); out.writeString(mPageTypeLabels.get(i)); out.writeIntArray(mPageTypeSizes.get(i)); } @@ -1244,6 +1250,8 @@ public final class ProcessStats implements Parcelable { // Fragmentation info final int NPAGETYPES = in.readInt(); + mPageTypeNodes.clear(); + mPageTypeNodes.ensureCapacity(NPAGETYPES); mPageTypeZones.clear(); mPageTypeZones.ensureCapacity(NPAGETYPES); mPageTypeLabels.clear(); @@ -1251,7 +1259,8 @@ public final class ProcessStats implements Parcelable { mPageTypeSizes.clear(); mPageTypeSizes.ensureCapacity(NPAGETYPES); for (int i=0; i<NPAGETYPES; i++) { - mPageTypeZones.add(in.readInt()); + mPageTypeNodes.add(in.readInt()); + mPageTypeZones.add(in.readString()); mPageTypeLabels.add(in.readString()); mPageTypeSizes.add(in.createIntArray()); } @@ -1764,7 +1773,8 @@ public final class ProcessStats implements Parcelable { pw.println("Available pages by page size:"); final int NPAGETYPES = mPageTypeLabels.size(); for (int i=0; i<NPAGETYPES; i++) { - pw.format("Zone %3d %14s ", mPageTypeZones.get(i), mPageTypeLabels.get(i)); + pw.format("Node %3d Zone %7s %14s ", mPageTypeNodes.get(i), mPageTypeZones.get(i), + mPageTypeLabels.get(i)); final int[] sizes = mPageTypeSizes.get(i); final int N = sizes == null ? 0 : sizes.length; for (int j=0; j<N; j++) { @@ -2095,6 +2105,9 @@ public final class ProcessStats implements Parcelable { pw.print(","); pw.print(mPageTypeZones.get(i)); pw.print(","); + // Wasn't included in original output. + //pw.print(mPageTypeNodes.get(i)); + //pw.print(","); final int[] sizes = mPageTypeSizes.get(i); final int N = sizes == null ? 0 : sizes.length; for (int j=0; j<N; j++) { @@ -2135,6 +2148,20 @@ public final class ProcessStats implements Parcelable { proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL); } + final int NPAGETYPES = mPageTypeLabels.size(); + for (int i = 0; i < NPAGETYPES; i++) { + final long token = proto.start(ProcessStatsSectionProto.AVAILABLE_PAGES); + proto.write(ProcessStatsAvailablePagesProto.NODE, mPageTypeNodes.get(i)); + proto.write(ProcessStatsAvailablePagesProto.ZONE, mPageTypeZones.get(i)); + proto.write(ProcessStatsAvailablePagesProto.LABEL, mPageTypeLabels.get(i)); + final int[] sizes = mPageTypeSizes.get(i); + final int N = sizes == null ? 0 : sizes.length; + for (int j = 0; j < N; j++) { + proto.write(ProcessStatsAvailablePagesProto.PAGES_PER_ORDER, sizes[j]); + } + proto.end(token); + } + final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); if ((section & REPORT_PROC_STATS) != 0) { for (int ip = 0; ip < procMap.size(); ip++) { diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java index e8ac2239c3fd..594595843ca7 100644 --- a/core/java/com/android/internal/infra/AbstractRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractRemoteService.java @@ -213,6 +213,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I } mService = null; mServiceDied = true; + cancelScheduledUnbind(); @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning final S castService = (S) this; mVultureCallback.onServiceDied(castService); diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java index a319d83b077d..b529bbe51e93 100644 --- a/core/java/com/android/internal/os/RoSystemProperties.java +++ b/core/java/com/android/internal/os/RoSystemProperties.java @@ -30,6 +30,7 @@ public class RoSystemProperties { public static final String CONTROL_PRIVAPP_PERMISSIONS = SystemProperties.get("ro.control_privapp_permissions"); + // ------ ro.hdmi.* -------- // /** * Property to indicate if a CEC audio device should forward volume keys when system audio * mode is off. @@ -38,6 +39,14 @@ public class RoSystemProperties { SystemProperties.getBoolean( "ro.hdmi.cec_audio_device_forward_volume_keys_system_audio_mode_off", false); + /** + * Property to indicate if the current device is a cec switch device. + * + * <p> Default is false. + */ + public static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH = + "ro.hdmi.property_is_device_hdmi_cec_switch"; + // ------ ro.config.* -------- // public static final boolean CONFIG_AVOID_GFX_ACCEL = SystemProperties.getBoolean("ro.config.avoid_gfx_accel", false); diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java index a403c068c7c4..e0ba317f5eaa 100644 --- a/core/java/com/android/internal/util/ContrastColorUtil.java +++ b/core/java/com/android/internal/util/ContrastColorUtil.java @@ -586,7 +586,7 @@ public class ContrastColorUtil { * * @param color the base color to use * @param amount the amount from 1 to 100 how much to modify the color - * @return the now color that was modified + * @return the new color that was modified */ public static int getShiftedColor(int color, int amount) { final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); @@ -599,6 +599,19 @@ public class ContrastColorUtil { return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]); } + /** + * Blends the provided color with white to create a muted version. + * + * @param color the color to mute + * @param alpha the amount from 0 to 1 to set the alpha component of the white scrim + * @return the new color that was modified + */ + public static int getMutedColor(int color, float alpha) { + int whiteScrim = ColorUtilsFromCompat.setAlphaComponent( + Color.WHITE, (int) (255 * alpha)); + return compositeColors(whiteScrim, color); + } + private static boolean shouldUseDark(int backgroundColor, boolean defaultBackgroundIsDark) { if (backgroundColor == Notification.COLOR_DEFAULT) { return !defaultBackgroundIsDark; @@ -675,6 +688,18 @@ public class ContrastColorUtil { } /** + * Set the alpha component of {@code color} to be {@code alpha}. + */ + @ColorInt + public static int setAlphaComponent(@ColorInt int color, + @IntRange(from = 0x0, to = 0xFF) int alpha) { + if (alpha < 0 || alpha > 255) { + throw new IllegalArgumentException("alpha must be between 0 and 255."); + } + return (color & 0x00ffffff) | (alpha << 24); + } + + /** * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}. * <p>Defined as the Y component in the XYZ representation of {@code color}.</p> */ diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index eb7338a7ce58..92aae27d32ea 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -8,7 +8,6 @@ #include "SkImageInfo.h" #include "SkColor.h" #include "SkColorSpace.h" -#include "SkMatrix44.h" #include "GraphicsJNI.h" #include "SkStream.h" @@ -356,8 +355,8 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, if (xyzD50 == nullptr || transferParameters == nullptr) { colorSpace = SkColorSpace::MakeSRGB(); } else { - SkColorSpaceTransferFn p = GraphicsJNI::getNativeTransferParameters(env, transferParameters); - SkMatrix44 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50); + skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters); + skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50); colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix); } @@ -549,8 +548,7 @@ static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, if (skbitmap.colorType() == kRGBA_F16_SkColorType) { // Convert to P3 before encoding. This matches SkAndroidCodec::computeOutputColorSpace // for wide gamuts. - auto cs = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::kDCIP3_D65_Gamut); + auto cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); auto info = skbitmap.info().makeColorType(kRGBA_8888_SkColorType) .makeColorSpace(std::move(cs)); SkBitmap p3; @@ -717,7 +715,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { #endif // Dup the file descriptor so we can keep a reference to it after the Parcel // is disposed. - int dupFd = dup(blob.fd()); + int dupFd = fcntl(blob.fd(), F_DUPFD_CLOEXEC, 0); if (dupFd < 0) { ALOGE("Error allocating dup fd. Error:%d", errno); blob.release(); @@ -910,32 +908,32 @@ static jboolean Bitmap_getColorSpace(JNIEnv* env, jobject, jlong bitmapHandle, SkColorSpace* colorSpace = bitmapHolder->info().colorSpace(); if (colorSpace == nullptr) return JNI_FALSE; - SkMatrix44 xyzMatrix(SkMatrix44::kUninitialized_Constructor); + skcms_Matrix3x3 xyzMatrix; if (!colorSpace->toXYZD50(&xyzMatrix)) return JNI_FALSE; jfloat* xyz = env->GetFloatArrayElements(xyzArray, NULL); - xyz[0] = xyzMatrix.getFloat(0, 0); - xyz[1] = xyzMatrix.getFloat(1, 0); - xyz[2] = xyzMatrix.getFloat(2, 0); - xyz[3] = xyzMatrix.getFloat(0, 1); - xyz[4] = xyzMatrix.getFloat(1, 1); - xyz[5] = xyzMatrix.getFloat(2, 1); - xyz[6] = xyzMatrix.getFloat(0, 2); - xyz[7] = xyzMatrix.getFloat(1, 2); - xyz[8] = xyzMatrix.getFloat(2, 2); + xyz[0] = xyzMatrix.vals[0][0]; + xyz[1] = xyzMatrix.vals[1][0]; + xyz[2] = xyzMatrix.vals[2][0]; + xyz[3] = xyzMatrix.vals[0][1]; + xyz[4] = xyzMatrix.vals[1][1]; + xyz[5] = xyzMatrix.vals[2][1]; + xyz[6] = xyzMatrix.vals[0][2]; + xyz[7] = xyzMatrix.vals[1][2]; + xyz[8] = xyzMatrix.vals[2][2]; env->ReleaseFloatArrayElements(xyzArray, xyz, 0); - SkColorSpaceTransferFn transferParams; + skcms_TransferFunction transferParams; if (!colorSpace->isNumericalTransferFn(&transferParams)) return JNI_FALSE; jfloat* params = env->GetFloatArrayElements(paramsArray, NULL); - params[0] = transferParams.fA; - params[1] = transferParams.fB; - params[2] = transferParams.fC; - params[3] = transferParams.fD; - params[4] = transferParams.fE; - params[5] = transferParams.fF; - params[6] = transferParams.fG; + params[0] = transferParams.a; + params[1] = transferParams.b; + params[2] = transferParams.c; + params[3] = transferParams.d; + params[4] = transferParams.e; + params[5] = transferParams.f; + params[6] = transferParams.g; env->ReleaseFloatArrayElements(paramsArray, params, 0); return JNI_TRUE; @@ -1121,8 +1119,8 @@ static jobject Bitmap_createHardwareBitmap(JNIEnv* env, jobject, jobject graphic static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer, jfloatArray xyzD50, jobject transferParameters) { - SkColorSpaceTransferFn p = GraphicsJNI::getNativeTransferParameters(env, transferParameters); - SkMatrix44 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50); + skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters); + skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50); sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix); AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, hardwareBuffer); diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 67d0c8aced61..ebeb5b267ccc 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -424,30 +424,30 @@ jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region) /////////////////////////////////////////////////////////////////////////////// -SkColorSpaceTransferFn GraphicsJNI::getNativeTransferParameters(JNIEnv* env, jobject transferParams) { - SkColorSpaceTransferFn p; - p.fA = (float) env->GetDoubleField(transferParams, gTransferParams_aFieldID); - p.fB = (float) env->GetDoubleField(transferParams, gTransferParams_bFieldID); - p.fC = (float) env->GetDoubleField(transferParams, gTransferParams_cFieldID); - p.fD = (float) env->GetDoubleField(transferParams, gTransferParams_dFieldID); - p.fE = (float) env->GetDoubleField(transferParams, gTransferParams_eFieldID); - p.fF = (float) env->GetDoubleField(transferParams, gTransferParams_fFieldID); - p.fG = (float) env->GetDoubleField(transferParams, gTransferParams_gFieldID); +skcms_TransferFunction GraphicsJNI::getNativeTransferParameters(JNIEnv* env, jobject transferParams) { + skcms_TransferFunction p; + p.a = (float) env->GetDoubleField(transferParams, gTransferParams_aFieldID); + p.b = (float) env->GetDoubleField(transferParams, gTransferParams_bFieldID); + p.c = (float) env->GetDoubleField(transferParams, gTransferParams_cFieldID); + p.d = (float) env->GetDoubleField(transferParams, gTransferParams_dFieldID); + p.e = (float) env->GetDoubleField(transferParams, gTransferParams_eFieldID); + p.f = (float) env->GetDoubleField(transferParams, gTransferParams_fFieldID); + p.g = (float) env->GetDoubleField(transferParams, gTransferParams_gFieldID); return p; } -SkMatrix44 GraphicsJNI::getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) { - SkMatrix44 xyzMatrix(SkMatrix44::kIdentity_Constructor); +skcms_Matrix3x3 GraphicsJNI::getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) { + skcms_Matrix3x3 xyzMatrix; jfloat* array = env->GetFloatArrayElements(xyzD50, NULL); - xyzMatrix.setFloat(0, 0, array[0]); - xyzMatrix.setFloat(1, 0, array[1]); - xyzMatrix.setFloat(2, 0, array[2]); - xyzMatrix.setFloat(0, 1, array[3]); - xyzMatrix.setFloat(1, 1, array[4]); - xyzMatrix.setFloat(2, 1, array[5]); - xyzMatrix.setFloat(0, 2, array[6]); - xyzMatrix.setFloat(1, 2, array[7]); - xyzMatrix.setFloat(2, 2, array[8]); + xyzMatrix.vals[0][0] = array[0]; + xyzMatrix.vals[1][0] = array[1]; + xyzMatrix.vals[2][0] = array[2]; + xyzMatrix.vals[0][1] = array[3]; + xyzMatrix.vals[1][1] = array[4]; + xyzMatrix.vals[2][1] = array[5]; + xyzMatrix.vals[0][2] = array[6]; + xyzMatrix.vals[1][2] = array[7]; + xyzMatrix.vals[2][2] = array[8]; env->ReleaseFloatArrayElements(xyzD50, array, 0); return xyzMatrix; } @@ -472,8 +472,8 @@ sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(JNIEnv* env, jobject colorS jfloatArray xyzD50 = (jfloatArray) env->CallObjectMethod(colorSpaceD50, gColorSpaceRGB_getTransformMethodID); - SkMatrix44 xyzMatrix = getNativeXYZMatrix(env, xyzD50); - SkColorSpaceTransferFn transferFunction = getNativeTransferParameters(env, transferParams); + skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50); + skcms_TransferFunction transferFunction = getNativeTransferParameters(env, transferParams); return SkColorSpace::MakeRGB(transferFunction, xyzMatrix); } @@ -499,30 +499,30 @@ jobject GraphicsJNI::getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColor } else if (decodeColorSpace.get() != nullptr) { // Try to match against known RGB color spaces using the CIE XYZ D50 // conversion matrix and numerical transfer function parameters - SkMatrix44 xyzMatrix(SkMatrix44::kUninitialized_Constructor); + skcms_Matrix3x3 xyzMatrix; LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix)); - SkColorSpaceTransferFn transferParams; + skcms_TransferFunction transferParams; // We can only handle numerical transfer functions at the moment LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams)); jobject params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID, - transferParams.fA, transferParams.fB, transferParams.fC, - transferParams.fD, transferParams.fE, transferParams.fF, - transferParams.fG); + transferParams.a, transferParams.b, transferParams.c, + transferParams.d, transferParams.e, transferParams.f, + transferParams.g); jfloatArray xyzArray = env->NewFloatArray(9); jfloat xyz[9] = { - xyzMatrix.getFloat(0, 0), - xyzMatrix.getFloat(1, 0), - xyzMatrix.getFloat(2, 0), - xyzMatrix.getFloat(0, 1), - xyzMatrix.getFloat(1, 1), - xyzMatrix.getFloat(2, 1), - xyzMatrix.getFloat(0, 2), - xyzMatrix.getFloat(1, 2), - xyzMatrix.getFloat(2, 2) + xyzMatrix.vals[0][0], + xyzMatrix.vals[1][0], + xyzMatrix.vals[2][0], + xyzMatrix.vals[0][1], + xyzMatrix.vals[1][1], + xyzMatrix.vals[2][1], + xyzMatrix.vals[0][2], + xyzMatrix.vals[1][2], + xyzMatrix.vals[2][2] }; env->SetFloatArrayRegion(xyzArray, 0, 9, xyz); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index b0bd68336e08..699d153874a2 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -10,7 +10,6 @@ #include "SkPoint.h" #include "SkRect.h" #include "SkColorSpace.h" -#include "SkMatrix44.h" #include <jni.h> #include <hwui/Canvas.h> #include <hwui/Bitmap.h> @@ -101,8 +100,8 @@ public: int srcStride, int x, int y, int width, int height, SkBitmap* dstBitmap); - static SkColorSpaceTransferFn getNativeTransferParameters(JNIEnv* env, jobject transferParams); - static SkMatrix44 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50); + static skcms_TransferFunction getNativeTransferParameters(JNIEnv* env, jobject transferParams); + static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50); static sk_sp<SkColorSpace> getNativeColorSpace(JNIEnv* env, jobject colorSpace); static jobject getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColorSpace, diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 888dab19c247..7d63ec9a2ecf 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -29,7 +29,6 @@ #include <time.h> #include <unistd.h> -#include <atomic> #include <iomanip> #include <string> #include <vector> @@ -42,6 +41,7 @@ #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include "jni.h" +#include <meminfo/procmeminfo.h> #include <meminfo/sysmeminfo.h> #include <memtrack/memtrack.h> #include <memunreachable/memunreachable.h> @@ -150,14 +150,6 @@ struct stats_t { int swappedOutPss; }; -enum pss_rollup_support { - PSS_ROLLUP_UNTRIED, - PSS_ROLLUP_SUPPORTED, - PSS_ROLLUP_UNSUPPORTED -}; - -static std::atomic<pss_rollup_support> g_pss_rollup_support; - #define BINDER_STATS "/proc/binder/stats" static jlong android_os_Debug_getNativeHeapSize(JNIEnv *env, jobject clazz) @@ -555,37 +547,9 @@ static void android_os_Debug_getDirtyPages(JNIEnv *env, jobject clazz, jobject o android_os_Debug_getDirtyPagesPid(env, clazz, getpid(), object); } -UniqueFile OpenSmapsOrRollup(int pid) -{ - enum pss_rollup_support rollup_support = - g_pss_rollup_support.load(std::memory_order_relaxed); - if (rollup_support != PSS_ROLLUP_UNSUPPORTED) { - std::string smaps_rollup_path = - base::StringPrintf("/proc/%d/smaps_rollup", pid); - UniqueFile fp_rollup = MakeUniqueFile(smaps_rollup_path.c_str(), "re"); - if (fp_rollup == nullptr && errno != ENOENT) { - return fp_rollup; // Actual error, not just old kernel. - } - if (fp_rollup != nullptr) { - if (rollup_support == PSS_ROLLUP_UNTRIED) { - ALOGI("using rollup pss collection"); - g_pss_rollup_support.store(PSS_ROLLUP_SUPPORTED, - std::memory_order_relaxed); - } - return fp_rollup; - } - g_pss_rollup_support.store(PSS_ROLLUP_UNSUPPORTED, - std::memory_order_relaxed); - } - - std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid); - return MakeUniqueFile(smaps_path.c_str(), "re"); -} - static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, jlongArray outUssSwapPssRss, jlongArray outMemtrack) { - char lineBuffer[1024]; jlong pss = 0; jlong rss = 0; jlong swapPss = 0; @@ -597,59 +561,14 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, pss = uss = rss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other; } - { - UniqueFile fp = OpenSmapsOrRollup(pid); - - if (fp != nullptr) { - char* line; - - while (true) { - if (fgets(lineBuffer, sizeof (lineBuffer), fp.get()) == NULL) { - break; - } - line = lineBuffer; - - switch (line[0]) { - case 'P': - if (strncmp(line, "Pss:", 4) == 0) { - char* c = line + 4; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; - } - pss += atoi(c); - } else if (strncmp(line, "Private_Clean:", 14) == 0 - || strncmp(line, "Private_Dirty:", 14) == 0) { - char* c = line + 14; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; - } - uss += atoi(c); - } - break; - case 'R': - if (strncmp(line, "Rss:", 4) == 0) { - char* c = line + 4; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; - } - rss += atoi(c); - } - break; - case 'S': - if (strncmp(line, "SwapPss:", 8) == 0) { - char* c = line + 8; - jlong lSwapPss; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; - } - lSwapPss = atoi(c); - swapPss += lSwapPss; - pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP - } - break; - } - } - } + ::android::meminfo::ProcMemInfo proc_mem(pid); + ::android::meminfo::MemUsage stats; + if (proc_mem.SmapsOrRollup(&stats)) { + pss += stats.pss; + uss += stats.uss; + rss += stats.rss; + swapPss = stats.swap_pss; + pss += swapPss; // Also in swap, those pages would be accounted as Pss without SWAP } if (outUssSwapPssRss != NULL) { @@ -686,34 +605,6 @@ static jlong android_os_Debug_getPss(JNIEnv *env, jobject clazz) return android_os_Debug_getPssPid(env, clazz, getpid(), NULL, NULL); } -static long get_allocated_vmalloc_memory() { - char line[1024]; - - long vmalloc_allocated_size = 0; - - UniqueFile fp = MakeUniqueFile("/proc/vmallocinfo", "re"); - if (fp == nullptr) { - return 0; - } - - while (true) { - if (fgets(line, 1024, fp.get()) == NULL) { - break; - } - - // check to see if there are pages mapped in vmalloc area - if (!strstr(line, "pages=")) { - continue; - } - - long nr_pages; - if (sscanf(line, "%*x-%*x %*ld %*s pages=%ld", &nr_pages) == 1) { - vmalloc_allocated_size += (nr_pages * getpagesize()); - } - } - return vmalloc_allocated_size; -} - // The 1:1 mapping of MEMINFO_* enums here must match with the constants from // Debug.java. enum { @@ -763,9 +654,8 @@ static void android_os_Debug_getMemInfo(JNIEnv *env, jobject clazz, jlongArray o if (outArray != NULL) { outLen = MEMINFO_COUNT; for (int i = 0; i < outLen; i++) { - // TODO: move get_allocated_vmalloc_memory() to libmeminfo if (i == MEMINFO_VMALLOC_USED) { - outArray[i] = get_allocated_vmalloc_memory() / 1024; + outArray[i] = smi.ReadVmallocInfo() / 1024; continue; } outArray[i] = mem[i]; @@ -775,7 +665,6 @@ static void android_os_Debug_getMemInfo(JNIEnv *env, jobject clazz, jlongArray o env->ReleaseLongArrayElements(out, outArray, 0); } - static jint read_binder_stat(const char* stat) { UniqueFile fp = MakeUniqueFile(BINDER_STATS, "re"); diff --git a/core/jni/android_os_Debug.h b/core/jni/android_os_Debug.h index c7b731bdb615..747776a1b89b 100644 --- a/core/jni/android_os_Debug.h +++ b/core/jni/android_os_Debug.h @@ -19,6 +19,7 @@ #include <memory> #include <stdio.h> +#include <meminfo/meminfo.h> #include <android-base/stringprintf.h> #include <android-base/unique_fd.h> @@ -34,8 +35,6 @@ inline UniqueFile MakeUniqueFile(const char* path, const char* mode) { return UniqueFile(fopen(path, mode), safeFclose); } -UniqueFile OpenSmapsOrRollup(int pid); - } // namespace android #endif // ANDROID_OS_HW_BLOB_H diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index 7ef06dc54945..3b59321024fd 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -473,7 +473,7 @@ static jobject android_os_Parcel_readFileDescriptor(JNIEnv* env, jclass clazz, j if (parcel != NULL) { int fd = parcel->readFileDescriptor(); if (fd < 0) return NULL; - fd = dup(fd); + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); if (fd < 0) return NULL; return jniCreateFileDescriptor(env, fd); } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 0c1a8aa18370..798825fbe4a9 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -25,6 +25,7 @@ #include <cutils/sched_policy.h> #include <utils/String8.h> #include <utils/Vector.h> +#include <meminfo/procmeminfo.h> #include <meminfo/sysmeminfo.h> #include <processgroup/processgroup.h> @@ -1083,21 +1084,12 @@ static jlong android_os_Process_getElapsedCpuTime(JNIEnv* env, jobject clazz) static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid) { - UniqueFile file = OpenSmapsOrRollup(pid); - if (file == nullptr) { + ::android::meminfo::ProcMemInfo proc_mem(pid); + uint64_t pss; + if (!proc_mem.SmapsOrRollupPss(&pss)) { return (jlong) -1; } - // Tally up all of the Pss from the various maps - char line[256]; - jlong pss = 0; - while (fgets(line, sizeof(line), file.get())) { - jlong v; - if (sscanf(line, "Pss: %" SCNd64 " kB", &v) == 1) { - pss += v; - } - } - // Return the Pss value in bytes, not kilobytes return pss * 1024; } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index 3e1c5a334184..2f2f62392157 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -29,6 +29,12 @@ enum Action { // ACTION: Settings > Any preference is changed ACTION_SETTINGS_PREFERENCE_CHANGE = 853; + + // ACTION: Tap & Pay -> Default Application Setting -> Use Forground + ACTION_NFC_PAYMENT_FOREGROUND_SETTING = 1622; + + // ACTION: Tap & Pay -> Default Application Setting -> Use Default + ACTION_NFC_PAYMENT_ALWAYS_SETTING = 1623; } /** diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index 4ecf52ce5012..7f96d701cdbf 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -111,6 +111,7 @@ message PackageProto { optional EnabledState enabled_state = 7; optional string last_disabled_app_caller = 8; optional string suspending_package = 9; + optional int32 distraction_flags = 10; } // Name of package. e.g. "com.android.providers.telephony". diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto index 71ebcc1e3659..da801ffcc99a 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -43,7 +43,7 @@ message ProcessStatsServiceDumpProto { * Data model from /frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java * This proto is defined based on the writeToParcel method. * - * Next Tag: 10 + * Next Tag: 11 */ message ProcessStatsSectionProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -76,6 +76,9 @@ message ProcessStatsSectionProto { } repeated Status status = 7; + // Number of pages available of various types and sizes, representation fragmentation. + repeated ProcessStatsAvailablePagesProto available_pages = 10; + // Stats for each process. repeated ProcessStatsProto process_stats = 8; @@ -83,6 +86,24 @@ message ProcessStatsSectionProto { repeated ProcessStatsPackageProto package_stats = 9; } +// Next Tag: 5 +message ProcessStatsAvailablePagesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + // Node these pages are in (as per /proc/pagetypeinfo) + optional int32 node = 1; + + // Zone these pages are in (as per /proc/pagetypeinfo) + optional string zone = 2; + + // Label for the type of these pages (as per /proc/pagetypeinfo) + optional string label = 3; + + // Distribution of number of pages available by order size. First entry in array is + // order 0, second is order 1, etc. Each order increase is a doubling of page size. + repeated int32 pages_per_order = 4; +} + // Next Tag: 10 message ProcessStatsStateProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8147b4a2a009..2f3a4910bf83 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -51,6 +51,7 @@ <protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" /> <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" /> <protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" /> + <protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" /> <protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" /> <protected-broadcast android:name="android.intent.action.UID_REMOVED" /> <protected-broadcast android:name="android.intent.action.QUERY_PACKAGE_RESTART" /> @@ -2268,6 +2269,11 @@ <permission android:name="android.permission.START_ANY_ACTIVITY" android:protectionLevel="signature" /> + <!-- Allows an application to start activities from background + @hide --> + <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" + android:protectionLevel="signature|privileged|vendorPrivileged|oem" /> + <!-- @SystemApi Must be required by activities that handle the intent action {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system. diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index d520f151c2fa..81ca9109c643 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.InsetsState.TYPE_NAVIGATION_BAR; import static android.view.InsetsState.TYPE_TOP_BAR; import static junit.framework.Assert.assertEquals; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.graphics.Insets; @@ -83,7 +84,7 @@ public class InsetsAnimationControlImplTest { consumers.put(TYPE_NAVIGATION_BAR, navConsumer); mController = new InsetsAnimationControlImpl(consumers, new Rect(0, 0, 500, 500), state, mMockListener, WindowInsets.Type.systemBars(), - () -> mMockTransactionApplier); + () -> mMockTransactionApplier, mock(InsetsController.class)); } @Test diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java new file mode 100644 index 000000000000..7619af23375f --- /dev/null +++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view.autofill; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.view.View; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AutofillIdTest { + + @Test + public void testNonVirtual() { + final AutofillId id = new AutofillId(42); + assertThat(id.getViewId()).isEqualTo(42); + assertThat(id.isVirtual()).isFalse(); + assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID); + + final AutofillId clone = cloneThroughParcel(id); + assertThat(clone.getViewId()).isEqualTo(42); + assertThat(clone.isVirtual()).isFalse(); + assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID); + } + + @Test + public void testVirtual() { + final AutofillId id = new AutofillId(42, 108); + assertThat(id.getViewId()).isEqualTo(42); + assertThat(id.isVirtual()).isTrue(); + assertThat(id.getVirtualChildId()).isEqualTo(108); + + final AutofillId clone = cloneThroughParcel(id); + assertThat(clone.getViewId()).isEqualTo(42); + assertThat(clone.isVirtual()).isTrue(); + assertThat(clone.getVirtualChildId()).isEqualTo(108); + } + + @Test + public void testVirtual_parentObjectConstructor() { + assertThrows(NullPointerException.class, () -> new AutofillId(null, 108)); + + final AutofillId id = new AutofillId(new AutofillId(42), 108); + assertThat(id.getViewId()).isEqualTo(42); + assertThat(id.isVirtual()).isTrue(); + assertThat(id.getVirtualChildId()).isEqualTo(108); + + final AutofillId clone = cloneThroughParcel(id); + assertThat(clone.getViewId()).isEqualTo(42); + assertThat(clone.isVirtual()).isTrue(); + assertThat(clone.getVirtualChildId()).isEqualTo(108); + } + + @Test + public void testVirtual_withSession() { + final AutofillId id = new AutofillId(new AutofillId(42), 108, 666); + assertThat(id.getViewId()).isEqualTo(42); + assertThat(id.isVirtual()).isTrue(); + assertThat(id.getVirtualChildId()).isEqualTo(108); + assertThat(id.getSessionId()).isEqualTo(666); + + final AutofillId clone = cloneThroughParcel(id); + assertThat(clone.getViewId()).isEqualTo(42); + assertThat(clone.isVirtual()).isTrue(); + assertThat(clone.getVirtualChildId()).isEqualTo(108); + assertThat(clone.getSessionId()).isEqualTo(666); + } + + @Test + public void testEqualsHashCode() { + final AutofillId realId = new AutofillId(42); + final AutofillId realIdSame = new AutofillId(42); + assertThat(realId).isEqualTo(realIdSame); + assertThat(realIdSame).isEqualTo(realId); + assertThat(realId.hashCode()).isEqualTo(realIdSame.hashCode()); + + final AutofillId realIdDifferent = new AutofillId(108); + assertThat(realId).isNotEqualTo(realIdDifferent); + assertThat(realIdDifferent).isNotEqualTo(realId); + + final AutofillId virtualId = new AutofillId(42, 1); + final AutofillId virtualIdSame = new AutofillId(42, 1); + assertThat(virtualId).isEqualTo(virtualIdSame); + assertThat(virtualIdSame).isEqualTo(virtualId); + assertThat(virtualId.hashCode()).isEqualTo(virtualIdSame.hashCode()); + assertThat(virtualId).isNotEqualTo(realId); + assertThat(realId).isNotEqualTo(virtualId); + + final AutofillId virtualIdDifferentChild = new AutofillId(42, 2); + assertThat(virtualIdDifferentChild).isNotEqualTo(virtualId); + assertThat(virtualId).isNotEqualTo(virtualIdDifferentChild); + assertThat(virtualIdDifferentChild).isNotEqualTo(realId); + assertThat(realId).isNotEqualTo(virtualIdDifferentChild); + + final AutofillId virtualIdDifferentParent = new AutofillId(108, 1); + assertThat(virtualIdDifferentParent).isNotEqualTo(virtualId); + assertThat(virtualId).isNotEqualTo(virtualIdDifferentParent); + assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild); + assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent); + + final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108); + assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId); + assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession); + assertThat(virtualIdDifferentSession).isNotEqualTo(realId); + assertThat(realId).isNotEqualTo(virtualIdDifferentSession); + + final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108); + assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession); + assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession); + assertThat(sameVirtualIdDifferentSession.hashCode()) + .isEqualTo(virtualIdDifferentSession.hashCode()); + } + + private AutofillId cloneThroughParcel(AutofillId id) { + Parcel parcel = Parcel.obtain(); + + try { + // Write to parcel + parcel.setDataPosition(0); // Sanity / paranoid check + id.writeToParcel(parcel, 0); + + // Read from parcel + parcel.setDataPosition(0); + AutofillId clone = AutofillId.CREATOR.createFromParcel(parcel); + assertThat(clone).isNotNull(); + return clone; + } finally { + parcel.recycle(); + } + } +} diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 59f3a4ccc8a2..73cceaeee547 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -19,11 +19,14 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; +import android.view.View; +import android.view.ViewStructure; import android.view.autofill.AutofillId; +import android.view.contentcapture.ViewNode.ViewStructureImpl; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Spy; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; /** @@ -35,34 +38,104 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class ContentCaptureSessionTest { - /** - * Uses a spy as ContentCaptureSession is abstract but (so far) we're testing its concrete - * methods. - */ - @Spy - private ContentCaptureSession mMockSession; + private ContentCaptureSession mSession1 = new MyContentCaptureSession("111"); + + private ContentCaptureSession mSession2 = new MyContentCaptureSession("2222"); + + @Mock + private View mMockView; @Test public void testNewAutofillId_invalid() { - assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42)); + assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42)); assertThrows(IllegalArgumentException.class, - () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42)); + () -> mSession1.newAutofillId(new AutofillId(42, 42), 42)); } @Test public void testNewAutofillId_valid() { final AutofillId parentId = new AutofillId(42); - final AutofillId childId = mMockSession.newAutofillId(parentId, 108); + final AutofillId childId = mSession1.newAutofillId(parentId, 108); assertThat(childId.getViewId()).isEqualTo(42); assertThat(childId.getVirtualChildId()).isEqualTo(108); - // TODO(b/121197119): assert session id + assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt()); + } + + @Test + public void testNewAutofillId_differentSessions() { + assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check + final AutofillId parentId = new AutofillId(42); + final AutofillId childId1 = mSession1.newAutofillId(parentId, 108); + final AutofillId childId2 = mSession2.newAutofillId(parentId, 108); + assertThat(childId1).isNotEqualTo(childId2); + assertThat(childId2).isNotEqualTo(childId1); } @Test public void testNotifyXXX_null() { - assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null)); - assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null)); + assertThrows(NullPointerException.class, () -> mSession1.notifyViewAppeared(null)); + assertThrows(NullPointerException.class, () -> mSession1.notifyViewDisappeared(null)); assertThrows(NullPointerException.class, - () -> mMockSession.notifyViewTextChanged(null, "whatever", 0)); + () -> mSession1.notifyViewTextChanged(null, "whatever", 0)); + } + + @Test + public void testNewViewStructure() { + assertThat(mMockView.getAutofillId()).isNotNull(); // sanity check + final ViewStructure structure = mSession1.newViewStructure(mMockView); + assertThat(structure).isNotNull(); + assertThat(structure.getAutofillId()).isEqualTo(mMockView.getAutofillId()); + } + + @Test + public void testNewVirtualViewStructure() { + final AutofillId parentId = new AutofillId(42); + final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108); + assertThat(structure).isNotNull(); + final AutofillId childId = mSession1.newAutofillId(parentId, 108); + assertThat(structure.getAutofillId()).isEqualTo(childId); + } + + // Cannot use @Spy because we need to pass the session id on constructor + private class MyContentCaptureSession extends ContentCaptureSession { + + private MyContentCaptureSession(String id) { + super(id); + } + + @Override + MainContentCaptureSession getMainCaptureSession() { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + ContentCaptureSession newChild(ContentCaptureContext context) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + void flush() { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + void onDestroy() { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + void internalNotifyViewAppeared(ViewStructureImpl node) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + void internalNotifyViewDisappeared(AutofillId id) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override + void internalNotifyViewTextChanged(AutofillId id, CharSequence text, int flags) { + throw new UnsupportedOperationException("should not have been called"); + } } } diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java index 995946b940f8..bbfe01c379f1 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java @@ -110,10 +110,10 @@ public class ViewNodeTest { @Test public void testAutofillIdMethods_explicitIdsConstructor() { AutofillId initialParentId = new AutofillId(42); - ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108); + ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108, 666); ViewNode node = structure.getNode(); - assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108)); + assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108, 666)); assertThat(node.getParentAutofillId()).isEqualTo(initialParentId); AutofillId newChildId = new AutofillId(108); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 82eaf88fb07a..ec6101c09b7b 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -377,10 +377,10 @@ public class TextClassifierTest { ConversationActions.Message.PERSON_USER_REMOTE) .setText("Where are you?") .build(); - ConversationActions.TypeConfig typeConfig = - new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) + TextClassifier.EntityConfig typeConfig = + new TextClassifier.EntityConfig.Builder().includeTypesFromTextClassifier(false) .setIncludedTypes( - Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY)) + Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY)) .build(); ConversationActions.Request request = new ConversationActions.Request.Builder(Collections.singletonList(message)) @@ -391,10 +391,10 @@ public class TextClassifierTest { ConversationActions conversationActions = mClassifier.suggestConversationActions(request); assertTrue(conversationActions.getConversationActions().size() > 0); assertTrue(conversationActions.getConversationActions().size() == 1); - for (ConversationActions.ConversationAction conversationAction : + for (ConversationAction conversationAction : conversationActions.getConversationActions()) { assertThat(conversationAction, - isConversationAction(ConversationActions.TYPE_TEXT_REPLY)); + isConversationAction(ConversationAction.TYPE_TEXT_REPLY)); } }*/ @@ -406,10 +406,10 @@ public class TextClassifierTest { ConversationActions.Message.PERSON_USER_REMOTE) .setText("Where are you?") .build(); - ConversationActions.TypeConfig typeConfig = - new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) + TextClassifier.EntityConfig typeConfig = + new TextClassifier.EntityConfig.Builder().includeTypesFromTextClassifier(false) .setIncludedTypes( - Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY)) + Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY)) .build(); ConversationActions.Request request = new ConversationActions.Request.Builder(Collections.singletonList(message)) @@ -418,10 +418,10 @@ public class TextClassifierTest { ConversationActions conversationActions = mClassifier.suggestConversationActions(request); assertTrue(conversationActions.getConversationActions().size() > 1); - for (ConversationActions.ConversationAction conversationAction : + for (ConversationAction conversationAction : conversationActions.getConversationActions()) { assertThat(conversationAction, - isConversationAction(ConversationActions.TYPE_TEXT_REPLY)); + isConversationAction(ConversationAction.TYPE_TEXT_REPLY)); } } @@ -524,20 +524,19 @@ public class TextClassifierTest { }; } - private static Matcher<ConversationActions.ConversationAction> isConversationAction( - String actionType) { - return new BaseMatcher<ConversationActions.ConversationAction>() { + private static Matcher<ConversationAction> isConversationAction(String actionType) { + return new BaseMatcher<ConversationAction>() { @Override public boolean matches(Object o) { - if (!(o instanceof ConversationActions.ConversationAction)) { + if (!(o instanceof ConversationAction)) { return false; } - ConversationActions.ConversationAction conversationAction = - (ConversationActions.ConversationAction) o; + ConversationAction conversationAction = + (ConversationAction) o; if (!actionType.equals(conversationAction.getType())) { return false; } - if (ConversationActions.TYPE_TEXT_REPLY.equals(actionType)) { + if (ConversationAction.TYPE_TEXT_REPLY.equals(actionType)) { if (conversationAction.getTextReply() == null) { return false; } diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java index 344f79dcc48e..0597a89e87d4 100644 --- a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java @@ -26,7 +26,7 @@ import static com.google.common.truth.Truth.assertThat; import android.metrics.LogMaker; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.textclassifier.ConversationActions; +import android.view.textclassifier.ConversationAction; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassifierEvent; import android.view.textclassifier.TextClassifierEventTronLogger; @@ -69,7 +69,7 @@ public class TextClassifierEventTronLoggerTest { new TextClassifierEvent.Builder( TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS, TextClassifierEvent.TYPE_SMART_ACTION) - .setEntityType(ConversationActions.TYPE_CALL_PHONE) + .setEntityType(ConversationAction.TYPE_CALL_PHONE) .setEventTime(EVENT_TIME) .setEventContext(textClassificationContext) .build(); @@ -84,7 +84,7 @@ public class TextClassifierEventTronLoggerTest { assertThat(logMaker.getType()).isEqualTo( ACTION_TEXT_SELECTION_SMART_SHARE); assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE)) - .isEqualTo(ConversationActions.TYPE_CALL_PHONE); + .isEqualTo(ConversationAction.TYPE_CALL_PHONE); assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME)) .isEqualTo(EVENT_TIME); assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME); diff --git a/core/tests/overlaytests/device/Android.mk b/core/tests/overlaytests/device/Android.mk index 680ebeb6108f..563074903cfe 100644 --- a/core/tests/overlaytests/device/Android.mk +++ b/core/tests/overlaytests/device/Android.mk @@ -19,7 +19,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayDeviceTests LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_STATIC_JAVA_LIBRARIES := android-support-test +LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_TARGET_REQUIRED_MODULES := \ OverlayDeviceTests_AppOverlayOne \ diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml index d14fdf5ee819..4881636c7095 100644 --- a/core/tests/overlaytests/device/AndroidManifest.xml +++ b/core/tests/overlaytests/device/AndroidManifest.xml @@ -23,7 +23,7 @@ <uses-library android:name="android.test.runner"/> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.overlaytest" android:label="Runtime resource overlay tests" /> </manifest> diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java index 5a86885459f2..91fcdbbb18ce 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java @@ -25,10 +25,11 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.os.LocaleList; import android.os.ParcelFileDescriptor; -import android.support.test.InstrumentationRegistry; import android.util.AttributeSet; import android.util.Xml; +import androidx.test.InstrumentationRegistry; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java index f35e511cdcf4..cd3ed9dd5e86 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java @@ -16,7 +16,7 @@ package com.android.overlaytest; -import android.support.test.filters.MediumTest; +import androidx.test.filters.MediumTest; import org.junit.BeforeClass; import org.junit.runner.RunWith; diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java index 037449fc2c05..c0d4281b7e0d 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java @@ -16,7 +16,7 @@ package com.android.overlaytest; -import android.support.test.filters.MediumTest; +import androidx.test.filters.MediumTest; import org.junit.BeforeClass; import org.junit.runner.RunWith; diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java index f657b5cef0e5..33c7b2591fe2 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java @@ -16,7 +16,7 @@ package com.android.overlaytest; -import android.support.test.filters.MediumTest; +import androidx.test.filters.MediumTest; import org.junit.BeforeClass; import org.junit.runner.RunWith; diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 25dabad6d34c..6ce81481c1d7 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -39,6 +39,41 @@ prebuilt_etc { name: "privapp-permissions-platform.xml", sub_dir: "permissions", src: "privapp-permissions-platform.xml", + required: [ + "privapp_whitelist_com.android.settings.intelligence", + ], +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.carrierconfig", + product_specific: true, + sub_dir: "permissions", + src: "com.android.carrierconfig.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.contacts", + product_specific: true, + sub_dir: "permissions", + src: "com.android.contacts.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.launcher3", + product_specific: true, + sub_dir: "permissions", + src: "com.android.launcher3.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.provision", + product_specific: true, + sub_dir: "permissions", + src: "com.android.provision.xml", + filename_from_src: true, } prebuilt_etc { @@ -50,6 +85,21 @@ prebuilt_etc { } prebuilt_etc { + name: "privapp_whitelist_com.android.settings.intelligence", + sub_dir: "permissions", + src: "com.android.settings.intelligence.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.storagemanager", + product_specific: true, + sub_dir: "permissions", + src: "com.android.storagemanager.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "privapp_whitelist_com.android.systemui", product_specific: true, sub_dir: "permissions", diff --git a/data/etc/com.android.carrierconfig.xml b/data/etc/com.android.carrierconfig.xml new file mode 100644 index 000000000000..17efb0315040 --- /dev/null +++ b/data/etc/com.android.carrierconfig.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.carrierconfig"> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/com.android.contacts.xml b/data/etc/com.android.contacts.xml new file mode 100644 index 000000000000..78eae40348e6 --- /dev/null +++ b/data/etc/com.android.contacts.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.contacts"> + <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> + <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/com.android.launcher3.xml b/data/etc/com.android.launcher3.xml new file mode 100644 index 000000000000..337e153722ac --- /dev/null +++ b/data/etc/com.android.launcher3.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.launcher3"> + <permission name="android.permission.BIND_APPWIDGET"/> + <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/> + <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/com.android.provision.xml b/data/etc/com.android.provision.xml new file mode 100644 index 000000000000..05404ef73732 --- /dev/null +++ b/data/etc/com.android.provision.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.provision"> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/com.android.settings.intelligence.xml b/data/etc/com.android.settings.intelligence.xml new file mode 100644 index 000000000000..6ca30c1e82fb --- /dev/null +++ b/data/etc/com.android.settings.intelligence.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.settings.intelligence"> + <permission name="android.permission.MANAGE_FINGERPRINT"/> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.READ_SEARCH_INDEXABLES"/> + <permission name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/com.android.storagemanager.xml b/data/etc/com.android.storagemanager.xml new file mode 100644 index 000000000000..e85a82c983df --- /dev/null +++ b/data/etc/com.android.storagemanager.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.storagemanager"> + <permission name="android.permission.DELETE_PACKAGES"/> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.USE_RESERVED_DISK"/> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> +</permissions> diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 221708b8c94d..3562a8f81408 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -44,6 +44,7 @@ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/> + <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> <permission name="android.permission.START_ACTIVITY_AS_CALLER"/> <permission name="android.permission.START_TASKS_FROM_RECENTS"/> <permission name="android.permission.STATUS_BAR"/> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index fbe613db47da..597d14ac286e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -33,10 +33,6 @@ applications that come with the platform <permission name="android.permission.CRYPT_KEEPER"/> </privapp-permissions> - <privapp-permissions package="com.android.carrierconfig"> - <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> - </privapp-permissions> - <privapp-permissions package="com.android.cellbroadcastreceiver"> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> @@ -45,11 +41,6 @@ applications that come with the platform <permission name="android.permission.RECEIVE_EMERGENCY_BROADCAST"/> </privapp-permissions> - <privapp-permissions package="com.android.contacts"> - <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> - <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> - </privapp-permissions> - <privapp-permissions package="com.android.defcontainer"> <permission name="android.permission.ACCESS_CACHE_FILESYSTEM"/> <permission name="android.permission.ALLOCATE_AGGRESSIVE"/> @@ -79,12 +70,6 @@ applications that come with the platform <permission name="android.permission.WRITE_MEDIA_STORAGE"/> </privapp-permissions> - <privapp-permissions package="com.android.launcher3"> - <permission name="android.permission.BIND_APPWIDGET"/> - <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/> - <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> - </privapp-permissions> - <privapp-permissions package="com.android.location.fused"> <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/> </privapp-permissions> @@ -238,10 +223,6 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> </privapp-permissions> - <privapp-permissions package="com.android.provision"> - <permission name="android.permission.WRITE_SECURE_SETTINGS"/> - </privapp-permissions> - <privapp-permissions package="com.android.mainline.networkstack"> <permission name="android.permission.ACCESS_NETWORK_CONDITIONS"/> <permission name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"/> @@ -280,13 +261,6 @@ applications that come with the platform <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> </privapp-permissions> - <privapp-permissions package="com.android.settings.intelligence"> - <permission name="android.permission.MANAGE_FINGERPRINT"/> - <permission name="android.permission.MODIFY_PHONE_STATE"/> - <permission name="android.permission.READ_SEARCH_INDEXABLES"/> - <permission name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"/> - </privapp-permissions> - <privapp-permissions package="com.android.sharedstoragebackup"> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> </privapp-permissions> @@ -342,6 +316,7 @@ applications that come with the platform <permission name="android.permission.SET_TIME"/> <permission name="android.permission.SET_TIME_ZONE"/> <permission name="android.permission.SIGNAL_PERSISTENT_PROCESSES"/> + <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> <permission name="android.permission.START_TASKS_FROM_RECENTS" /> <permission name="android.permission.STOP_APP_SWITCHES"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> @@ -356,15 +331,6 @@ applications that come with the platform <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/> </privapp-permissions> - <privapp-permissions package="com.android.storagemanager"> - <permission name="android.permission.DELETE_PACKAGES"/> - <permission name="android.permission.INTERACT_ACROSS_USERS"/> - <permission name="android.permission.MANAGE_USERS"/> - <permission name="android.permission.PACKAGE_USAGE_STATS"/> - <permission name="android.permission.USE_RESERVED_DISK"/> - <permission name="android.permission.WRITE_SECURE_SETTINGS"/> - </privapp-permissions> - <privapp-permissions package="com.android.tv"> <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/> <permission name="android.permission.DVB_DEVICE"/> diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index ca9dc475f7a1..65aaba17a55d 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.Size; import android.annotation.UnsupportedAppUsage; import android.graphics.Canvas.VertexMode; +import android.graphics.text.MeasuredText; import android.text.GraphicsOperations; import android.text.MeasuredParagraph; import android.text.PrecomputedText; @@ -554,14 +555,12 @@ public abstract class BaseCanvas { final int paraStart = pt.getParagraphStart(paraIndex); final MeasuredParagraph mp = pt.getMeasuredParagraph(paraIndex); // Only support the text in the same paragraph. - nDrawTextRun(mNativeCanvasWrapper, - mp.getChars(), - start - paraStart, - end - start, - contextStart - paraStart, - contextEnd - contextStart, - x, y, isRtl, paint.getNativeInstance(), - mp.getMeasuredText().getNativePtr()); + drawTextRun(mp.getMeasuredText(), + start - paraStart, + end - paraStart, + contextStart - paraStart, + contextEnd - paraStart, + x, y, isRtl, paint); return; } } @@ -576,6 +575,14 @@ public abstract class BaseCanvas { } } + public void drawTextRun(@NonNull MeasuredText measuredText, int start, int end, + int contextStart, int contextEnd, float x, float y, boolean isRtl, + @NonNull Paint paint) { + nDrawTextRun(mNativeCanvasWrapper, measuredText.getChars(), start, end - start, + contextStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(), + measuredText.getNativePtr()); + } + public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts, int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors, int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount, diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index 901c2116884b..4f6093500cad 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -20,6 +20,7 @@ import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.graphics.text.MeasuredText; import android.text.GraphicsOperations; import android.text.MeasuredParagraph; import android.text.PrecomputedText; @@ -522,14 +523,12 @@ public class BaseRecordingCanvas extends Canvas { final int paraStart = pt.getParagraphStart(paraIndex); final MeasuredParagraph mp = pt.getMeasuredParagraph(paraIndex); // Only support if the target is in the same paragraph. - nDrawTextRun(mNativeCanvasWrapper, - mp.getChars(), + drawTextRun(mp.getMeasuredText(), start - paraStart, - end - start, + end - paraStart, contextStart - paraStart, - contextEnd - contextStart, - x, y, isRtl, paint.getNativeInstance(), - mp.getMeasuredText().getNativePtr()); + contextEnd - paraStart, + x, y, isRtl, paint); return; } } @@ -545,6 +544,15 @@ public class BaseRecordingCanvas extends Canvas { } @Override + public void drawTextRun(@NonNull MeasuredText measuredText, int start, int end, + int contextStart, int contextEnd, float x, float y, boolean isRtl, + @NonNull Paint paint) { + nDrawTextRun(mNativeCanvasWrapper, measuredText.getChars(), start, end - start, + contextStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(), + measuredText.getNativePtr()); + } + + @Override public final void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts, int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors, int colorOffset, @Nullable short[] indices, int indexOffset, diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 63a806e53556..8c1bae2a0527 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; import android.annotation.UnsupportedAppUsage; +import android.graphics.text.MeasuredText; import android.os.Build; import dalvik.annotation.optimization.CriticalNative; @@ -2122,7 +2123,8 @@ public class Canvas extends BaseCanvas { * the text next to it. * <p> * All text outside the range {@code contextStart..contextEnd} is ignored. The text between - * {@code start} and {@code end} will be laid out and drawn. + * {@code start} and {@code end} will be laid out and drawn. The context range is useful for + * contextual shaping, e.g. Kerning, Arabic contextural form. * <p> * The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is * suitable only for runs of a single direction. Alignment of the text is as determined by the @@ -2151,6 +2153,31 @@ public class Canvas extends BaseCanvas { } /** + * Draw a run of text, all in a single direction, with optional context for complex text + * shaping. + * <p> + * See {@link #drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} for + * more details. This method uses a {@link MeasuredText} rather than CharSequence to represent + * the string. + * + * @param text the text to render + * @param start the start of the text to render. Data before this position can be used for + * shaping context. + * @param end the end of the text to render. Data at or after this position can be used for + * shaping context. + * @param contextStart the index of the start of the shaping context + * @param contextEnd the index of the end of the shaping context + * @param x the x position at which to draw the text + * @param y the y position at which to draw the text + * @param isRtl whether the run is in RTL direction + * @param paint the paint + */ + public void drawTextRun(@NonNull MeasuredText text, int start, int end, int contextStart, + int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) { + super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, isRtl, paint); + } + + /** * Draw the array of vertices, interpreted as triangles (based on mode). The verts array is * required, and specifies the x,y pairs for each vertex. If texs is non-null, then it is used * to specify the coordinate in shader coordinates to use at each vertex (the paint must have a diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index f0efb581dab6..95317a4d0bf2 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -984,11 +984,12 @@ public abstract class ColorSpace { * {@link Named#SRGB sRGB} primaries. * </li> * <li> - * Its white point is withing 1e-3 of the CIE standard + * Its white point is within 1e-3 of the CIE standard * illuminant {@link #ILLUMINANT_D65 D65}. * </li> * <li>Its opto-electronic transfer function is not linear.</li> * <li>Its electro-optical transfer function is not linear.</li> + * <li>Its transfer functions yield values within 1e-3 of {@link Named#SRGB}.</li> * <li>Its range is \([0..1]\).</li> * </ul> * <p>This method always returns true for {@link Named#SRGB}.</p> @@ -3145,19 +3146,35 @@ public abstract class ColorSpace { float max, @IntRange(from = MIN_ID, to = MAX_ID) int id) { if (id == 0) return true; - if (!compare(primaries, SRGB_PRIMARIES)) { + if (!ColorSpace.compare(primaries, SRGB_PRIMARIES)) { return false; } - if (!compare(whitePoint, ILLUMINANT_D65)) { + if (!ColorSpace.compare(whitePoint, ILLUMINANT_D65)) { return false; } - if (OETF.applyAsDouble(0.5) < 0.5001) return false; - if (EOTF.applyAsDouble(0.5) > 0.5001) return false; + if (min != 0.0f) return false; if (max != 1.0f) return false; + + // We would have already returned true if this was SRGB itself, so + // it is safe to reference it here. + ColorSpace.Rgb srgb = (ColorSpace.Rgb) get(Named.SRGB); + + for (double x = 0.0; x <= 1.0; x += 1 / 255.0) { + if (!compare(x, OETF, srgb.mOetf)) return false; + if (!compare(x, EOTF, srgb.mEotf)) return false; + } + return true; } + private static boolean compare(double point, @NonNull DoubleUnaryOperator a, + @NonNull DoubleUnaryOperator b) { + double rA = a.applyAsDouble(point); + double rB = b.applyAsDouble(point); + return Math.abs(rA - rB) <= 1e-3; + } + /** * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form * a wide color gamut. A color gamut is considered wide if its area is > 90% diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index 57e3491895e6..3dc1f2cd56c5 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -51,7 +51,7 @@ class XmlAttributeFinder class BagAttributeFinder : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> { public: - BagAttributeFinder(const ResolvedBag* bag) + explicit BagAttributeFinder(const ResolvedBag* bag) : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr, bag != nullptr ? bag->entries + bag->entry_count : nullptr) { } diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 5694115f61aa..a99e77f8dbb9 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -94,7 +94,7 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor if (size < 0) { result = UNKNOWN_ERROR; } else { - int dupAshmemFd = ::dup(ashmemFd); + int dupAshmemFd = ::fcntl(ashmemFd, F_DUPFD_CLOEXEC, 0); if (dupAshmemFd < 0) { result = -errno; } else { diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp index 5d243da2097c..5be2105fe404 100644 --- a/libs/androidfw/ZipUtils.cpp +++ b/libs/androidfw/ZipUtils.cpp @@ -37,7 +37,7 @@ using namespace android; // TODO: This can go away once the only remaining usage in aapt goes away. class FileReader : public zip_archive::Reader { public: - FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) { + explicit FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) { } bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const { diff --git a/libs/androidfw/include/androidfw/AssetDir.h b/libs/androidfw/include/androidfw/AssetDir.h index 7aef02dc4133..ce6e066d6a16 100644 --- a/libs/androidfw/include/androidfw/AssetDir.h +++ b/libs/androidfw/include/androidfw/AssetDir.h @@ -78,7 +78,7 @@ private: class FileInfo { public: FileInfo(void) {} - FileInfo(const String8& path) // useful for e.g. svect.indexOf + explicit FileInfo(const String8& path) // useful for e.g. svect.indexOf : mFileName(path), mFileType(kFileTypeUnknown) {} ~FileInfo(void) {} diff --git a/libs/androidfw/include/androidfw/BackupHelpers.h b/libs/androidfw/include/androidfw/BackupHelpers.h index fc1ad4717c16..2da247b77c0a 100644 --- a/libs/androidfw/include/androidfw/BackupHelpers.h +++ b/libs/androidfw/include/androidfw/BackupHelpers.h @@ -67,7 +67,7 @@ struct FileRec { class BackupDataWriter { public: - BackupDataWriter(int fd); + explicit BackupDataWriter(int fd); // does not close fd ~BackupDataWriter(); @@ -104,7 +104,7 @@ private: class BackupDataReader { public: - BackupDataReader(int fd); + explicit BackupDataReader(int fd); // does not close fd ~BackupDataReader(); diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h index 29424c4462aa..6fa089aeb12c 100644 --- a/libs/androidfw/include/androidfw/ConfigDescription.h +++ b/libs/androidfw/include/androidfw/ConfigDescription.h @@ -82,7 +82,7 @@ struct ConfigDescription : public ResTable_config { static void ApplyVersionForCompatibility(ConfigDescription* config); ConfigDescription(); - ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit) + ConfigDescription(const android::ResTable_config& o); // NOLINT(google-explicit-constructor) ConfigDescription(const ConfigDescription& o); ConfigDescription(ConfigDescription&& o) noexcept; diff --git a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h index e1dfb9490c60..bf35aa3c15bb 100644 --- a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h +++ b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h @@ -22,7 +22,7 @@ namespace android { class DisplayEventDispatcher : public LooperCallback { public: - DisplayEventDispatcher(const sp<Looper>& looper, + explicit DisplayEventDispatcher(const sp<Looper>& looper, ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp); status_t initialize(); diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index cf2d8fb3251c..9b05d1f64e08 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -693,7 +693,7 @@ class ResXMLTree; class ResXMLParser { public: - ResXMLParser(const ResXMLTree& tree); + explicit ResXMLParser(const ResXMLTree& tree); enum event_code_t { BAD_DOCUMENT = -1, @@ -806,7 +806,7 @@ public: * The tree stores a clone of the specified DynamicRefTable, so any changes to the original * DynamicRefTable will not affect this tree after instantiation. **/ - ResXMLTree(const DynamicRefTable* dynamicRefTable); + explicit ResXMLTree(const DynamicRefTable* dynamicRefTable); ResXMLTree(); ~ResXMLTree(); @@ -1844,7 +1844,7 @@ public: class Theme { public: - Theme(const ResTable& table); + explicit Theme(const ResTable& table); ~Theme(); inline const ResTable& getResTable() const { return mTable; } diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h index a33865f4d34f..921877dc4982 100644 --- a/libs/androidfw/include/androidfw/StringPiece.h +++ b/libs/androidfw/include/androidfw/StringPiece.h @@ -52,8 +52,8 @@ class BasicStringPiece { BasicStringPiece(); BasicStringPiece(const BasicStringPiece<TChar>& str); - BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit) - BasicStringPiece(const TChar* str); // NOLINT(implicit) + BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(google-explicit-constructor) + BasicStringPiece(const TChar* str); // NOLINT(google-explicit-constructor) BasicStringPiece(const TChar* str, size_t len); BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); diff --git a/libs/androidfw/include/androidfw/TypeWrappers.h b/libs/androidfw/include/androidfw/TypeWrappers.h index 5cfe54e5759d..fb2fad619011 100644 --- a/libs/androidfw/include/androidfw/TypeWrappers.h +++ b/libs/androidfw/include/androidfw/TypeWrappers.h @@ -23,7 +23,7 @@ namespace android { struct TypeVariant { - TypeVariant(const ResTable_type* data); + explicit TypeVariant(const ResTable_type* data); class iterator { public: diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h index 10d088e02829..aa1466fde778 100644 --- a/libs/androidfw/include/androidfw/Util.h +++ b/libs/androidfw/include/androidfw/Util.h @@ -46,7 +46,7 @@ class unique_cptr { using pointer = typename std::add_pointer<T>::type; constexpr unique_cptr() : ptr_(nullptr) {} - constexpr unique_cptr(std::nullptr_t) : ptr_(nullptr) {} + constexpr explicit unique_cptr(std::nullptr_t) : ptr_(nullptr) {} explicit unique_cptr(pointer ptr) : ptr_(ptr) {} unique_cptr(unique_cptr&& o) noexcept : ptr_(o.ptr_) { o.ptr_ = nullptr; } diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index ed167e57158e..56b1885de820 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -79,8 +79,7 @@ static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut, switch (wcgDataspace) { case ui::Dataspace::DISPLAY_P3: *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut; - *colorSpace = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::Gamut::kDCIP3_D65_Gamut); + *colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); break; case ui::Dataspace::V0_SCRGB: *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut; diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 562a3b225e36..1661905eff57 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -23,6 +23,7 @@ #include "FileBlobCache.h" #include "Properties.h" #include "utils/TraceUtils.h" +#include <GrContext.h> namespace android { namespace uirenderer { @@ -168,6 +169,24 @@ void ShaderCache::store(const SkData& key, const SkData& data) { const void* value = data.data(); BlobCache* bc = getBlobCacheLocked(); + if (mInStoreVkPipelineInProgress) { + if (mOldPipelineCacheSize == -1) { + // Record the initial pipeline cache size stored in the file. + mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0); + } + if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) { + // There has not been change in pipeline cache size. Stop trying to save. + mTryToStorePipelineCache = false; + return; + } + mNewPipelineCacheSize = valueSize; + } else { + mCacheDirty = true; + // If there are new shaders compiled, we probably have new pipeline state too. + // Store pipeline cache on the next flush. + mNewPipelineCacheSize = -1; + mTryToStorePipelineCache = true; + } bc->set(key.data(), keySize, value, valueSize); if (!mSavePending && mDeferredSaveDelay > 0) { @@ -175,12 +194,31 @@ void ShaderCache::store(const SkData& key, const SkData& data) { std::thread deferredSaveThread([this]() { sleep(mDeferredSaveDelay); std::lock_guard<std::mutex> lock(mMutex); - saveToDiskLocked(); + // Store file on disk if there a new shader or Vulkan pipeline cache size changed. + if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) { + saveToDiskLocked(); + mOldPipelineCacheSize = mNewPipelineCacheSize; + mTryToStorePipelineCache = false; + mCacheDirty = false; + } }); deferredSaveThread.detach(); } } +void ShaderCache::onVkFrameFlushed(GrContext* context) { + { + std::lock_guard<std::mutex> lock(mMutex); + + if (!mInitialized || !mTryToStorePipelineCache) { + return; + } + } + mInStoreVkPipelineInProgress = true; + context->storeVkPipelineCacheData(); + mInStoreVkPipelineInProgress = false; +} + } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index d41aadb269dd..0898017d52a1 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -75,6 +75,13 @@ public: */ void store(const SkData& key, const SkData& data) override; + /** + * "onVkFrameFlushed" tries to store Vulkan pipeline cache state. + * Pipeline cache is saved on disk only if the size of the data has changed or there was + * a new shader compiled. + */ + void onVkFrameFlushed(GrContext* context); + private: // Creation and (the lack of) destruction is handled internally. ShaderCache(); @@ -167,6 +174,33 @@ private: mutable std::mutex mMutex; /** + * If set to "true", the next call to onVkFrameFlushed, will invoke + * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk. + */ + bool mTryToStorePipelineCache = true; + + /** + * This flag is used by "ShaderCache::store" to distinguish between shader data and + * Vulkan pipeline data. + */ + bool mInStoreVkPipelineInProgress = false; + + /** + * "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used + * to prevent unnecessary disk writes, if the pipeline cache size has not changed. + */ + size_t mNewPipelineCacheSize = -1; + /** + * "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk. + */ + size_t mOldPipelineCacheSize = -1; + + /** + * "mCacheDirty" is true when there is new shader cache data, which is not saved to disk. + */ + bool mCacheDirty = false; + + /** * "sCache" is the singleton ShaderCache object. */ static ShaderCache sCache; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 1d3a24463057..b9aae9839bdc 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -23,6 +23,7 @@ #include "VkInteropFunctorDrawable.h" #include "renderstate/RenderState.h" #include "renderthread/Frame.h" +#include "ShaderCache.h" #include <SkSurface.h> #include <SkTypes.h> @@ -73,6 +74,7 @@ bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, con } SkiaPipeline::updateLighting(lightGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer); + ShaderCache::get().onVkFrameFlushed(mRenderThread.getGrContext()); layerUpdateQueue->clear(); // Draw visual debugging features diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h index b2351fc026de..fd824bd957fd 100644 --- a/libs/hwui/private/hwui/DrawVkInfo.h +++ b/libs/hwui/private/hwui/DrawVkInfo.h @@ -52,17 +52,8 @@ struct DrawVkInfo { // Input: Format of the destination surface. VkFormat format; - // Input: Color space transfer params - float g; - float a; - float b; - float c; - float d; - float e; - float f; - - // Input: Color space transformation from linear RGB to D50-adapted XYZ - float colorSpaceTransform[9]; + // Input: Color space + const SkColorSpace* colorSpaceInfo; // Input: current clip rect int clipLeft; diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index f3a764874e2b..f6178aff0c2e 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -44,8 +44,8 @@ TEST(SkiaCanvas, drawShadowLayer) { } TEST(SkiaCanvas, colorSpaceXform) { - sk_sp<SkColorSpace> adobe = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::kAdobeRGB_Gamut); + sk_sp<SkColorSpace> adobe = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, + SkNamedGamut::kAdobeRGB); SkImageInfo adobeInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType, adobe); sk_sp<Bitmap> adobeBitmap = Bitmap::allocateHeapBitmap(adobeInfo); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index dc347f615d98..4415a593f6ee 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -25,38 +25,6 @@ namespace android { namespace uirenderer { -static inline bool almostEqual(float a, float b) { - return std::abs(a - b) < 1e-2f; -} - -bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace) { - if (colorSpace == nullptr) return true; - if (colorSpace->isSRGB()) return true; - - SkColorSpaceTransferFn transferFunction; - if (colorSpace->isNumericalTransferFn(&transferFunction)) { - // sRGB transfer function params: - const float sRGBParamA = 1 / 1.055f; - const float sRGBParamB = 0.055f / 1.055f; - const float sRGBParamC = 1 / 12.92f; - const float sRGBParamD = 0.04045f; - const float sRGBParamE = 0.0f; - const float sRGBParamF = 0.0f; - const float sRGBParamG = 2.4f; - - // This comparison will catch Display P3 - return almostEqual(sRGBParamA, transferFunction.fA) && - almostEqual(sRGBParamB, transferFunction.fB) && - almostEqual(sRGBParamC, transferFunction.fC) && - almostEqual(sRGBParamD, transferFunction.fD) && - almostEqual(sRGBParamE, transferFunction.fE) && - almostEqual(sRGBParamF, transferFunction.fF) && - almostEqual(sRGBParamG, transferFunction.fG); - } - - return false; -} - android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType) { switch (colorType) { case kRGBA_8888_SkColorType: @@ -79,19 +47,19 @@ android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType) { sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { - SkColorSpace::Gamut gamut; + skcms_Matrix3x3 gamut; switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { case HAL_DATASPACE_STANDARD_BT709: - gamut = SkColorSpace::kSRGB_Gamut; + gamut = SkNamedGamut::kSRGB; break; case HAL_DATASPACE_STANDARD_BT2020: - gamut = SkColorSpace::kRec2020_Gamut; + gamut = SkNamedGamut::kRec2020; break; case HAL_DATASPACE_STANDARD_DCI_P3: - gamut = SkColorSpace::kDCIP3_D65_Gamut; + gamut = SkNamedGamut::kDCIP3; break; case HAL_DATASPACE_STANDARD_ADOBE_RGB: - gamut = SkColorSpace::kAdobeRGB_Gamut; + gamut = SkNamedGamut::kAdobeRGB; break; case HAL_DATASPACE_STANDARD_UNSPECIFIED: return nullptr; @@ -109,9 +77,9 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_LINEAR: - return SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, gamut); + return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut); case HAL_DATASPACE_TRANSFER_SRGB: - return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, gamut); + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut); case HAL_DATASPACE_TRANSFER_GAMMA2_2: return SkColorSpace::MakeRGB({2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_GAMMA2_6: diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 4473ce632b1b..388025207ed6 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -111,11 +111,6 @@ static constexpr float EOCF(float srgb) { #endif } -// Returns whether the specified color space's transfer function can be -// approximated with the native sRGB transfer function. This method -// returns true for sRGB, gamma 2.2 and Display P3 for instance -bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace); - android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 0375de3cc496..bc9500ddb3b7 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -2289,6 +2289,30 @@ final public class MediaCodec { */ public static final int ERROR_UNSUPPORTED_OPERATION = 6; + /** + * This indicates that the security level of the device is not + * sufficient to meet the requirements set by the content owner + * in the license policy. + */ + public static final int ERROR_INSUFFICIENT_SECURITY = 7; + + /** + * This indicates that the video frame being decrypted exceeds + * the size of the device's protected output buffers. When + * encountering this error the app should try playing content + * of a lower resolution. + */ + public static final int ERROR_FRAME_TOO_LARGE = 8; + + /** + * This error indicates that session state has been + * invalidated. It can occur on devices that are not capable + * of retaining crypto session state across device + * suspend/resume. The session must be closed and a new + * session opened to resume operation. + */ + public static final int ERROR_LOST_STATE = 9; + /** @hide */ @IntDef({ ERROR_NO_KEY, @@ -2296,7 +2320,10 @@ final public class MediaCodec { ERROR_RESOURCE_BUSY, ERROR_INSUFFICIENT_OUTPUT_PROTECTION, ERROR_SESSION_NOT_OPENED, - ERROR_UNSUPPORTED_OPERATION + ERROR_UNSUPPORTED_OPERATION, + ERROR_INSUFFICIENT_SECURITY, + ERROR_FRAME_TOO_LARGE, + ERROR_LOST_STATE }) @Retention(RetentionPolicy.SOURCE) public @interface CryptoErrorCode {} diff --git a/media/java/android/media/MediaConstants.java b/media/java/android/media/MediaConstants.java index 275b0acd8ad6..5a5747ae4bab 100644 --- a/media/java/android/media/MediaConstants.java +++ b/media/java/android/media/MediaConstants.java @@ -24,7 +24,7 @@ class MediaConstants { static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME"; // Bundle key for Parcelable - static final String KEY_SESSION2_STUB = "android.media.key.SESSION2_STUB"; + static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK"; static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS"; private MediaConstants() { diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index b8381a7249be..774ea185f57c 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -19,7 +19,7 @@ package android.media; import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; -import static android.media.MediaConstants.KEY_SESSION2_STUB; +import static android.media.MediaConstants.KEY_SESSION2LINK; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; import static android.media.Session2Token.TYPE_SESSION; @@ -41,15 +41,15 @@ import java.util.concurrent.Executor; /** * Allows an app to interact with an active {@link MediaSession2} or a - * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other + * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other * commands can be sent to the session. * <p> * This API is not generally intended for third party application developers. * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. - * @hide */ +// TODO: use @link for MediaSession2Service public class MediaController2 implements AutoCloseable { static final String TAG = "MediaController2"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -130,8 +130,8 @@ public class MediaController2 implements AutoCloseable { synchronized (mLock) { if (mSessionBinder != null) { try { - mSessionBinder.unlinkToDeath(mDeathRecipient, 0); mSessionBinder.disconnect(mControllerStub, getNextSeqNumber()); + mSessionBinder.unlinkToDeath(mDeathRecipient, 0); } catch (RuntimeException e) { // No-op } @@ -153,6 +153,7 @@ public class MediaController2 implements AutoCloseable { * @return a token which will be sent together in {@link ControllerCallback#onCommandResult} * when its result is received. */ + @NonNull public Object sendSessionCommand(@NonNull Session2Command command, @Nullable Bundle args) { if (command == null) { throw new IllegalArgumentException("command shouldn't be null"); @@ -208,7 +209,7 @@ public class MediaController2 implements AutoCloseable { void onConnected(int seq, Bundle connectionResult) { final long token = Binder.clearCallingIdentity(); try { - Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2_STUB); + Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); Session2CommandGroup allowedCommands = connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); if (DEBUG) { @@ -349,7 +350,7 @@ public class MediaController2 implements AutoCloseable { * @return the result for the session command. A runtime exception will be thrown if null * is returned. */ - @NonNull + @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller, @NonNull Session2Command command, @Nullable Bundle args) { return null; diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index cdbc7b44f905..75b391534dd4 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -132,11 +132,19 @@ public final class MediaDrm implements AutoCloseable { private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES; private EventHandler mEventHandler; - private EventHandler mOnKeyStatusChangeEventHandler; - private EventHandler mOnExpirationUpdateEventHandler; + private EventHandler mKeyStatusChangeHandler; + private EventHandler mExpirationUpdateHandler; + private EventHandler mSessionLostStateHandler; + private OnEventListener mOnEventListener; private OnKeyStatusChangeListener mOnKeyStatusChangeListener; private OnExpirationUpdateListener mOnExpirationUpdateListener; + private OnSessionLostStateListener mOnSessionLostStateListener; + + private final Object mEventLock = new Object(); + private final Object mKeyStatusChangeLock = new Object(); + private final Object mExpirationUpdateLock = new Object(); + private final Object mSessionLostStateLock = new Object(); private long mNativeContext; @@ -200,6 +208,35 @@ public final class MediaDrm implements AutoCloseable { private static final native boolean isCryptoSchemeSupportedNative( @NonNull byte[] uuid, @Nullable String mimeType); + private EventHandler createHandler() { + Looper looper; + EventHandler handler; + if ((looper = Looper.myLooper()) != null) { + handler = new EventHandler(this, looper); + } else if ((looper = Looper.getMainLooper()) != null) { + handler = new EventHandler(this, looper); + } else { + handler = null; + } + return handler; + } + + private EventHandler updateHandler(Handler handler) { + Looper looper; + EventHandler newHandler = null; + if (handler != null) { + looper = handler.getLooper(); + } else { + looper = Looper.myLooper(); + } + if (looper != null) { + if (handler == null || handler.getLooper() != looper) { + newHandler = new EventHandler(this, looper); + } + } + return newHandler; + } + /** * Instantiate a MediaDrm object * @@ -209,14 +246,10 @@ public final class MediaDrm implements AutoCloseable { * specified scheme UUID */ public MediaDrm(@NonNull UUID uuid) throws UnsupportedSchemeException { - Looper looper; - if ((looper = Looper.myLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else if ((looper = Looper.getMainLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else { - mEventHandler = null; - } + mEventHandler = createHandler(); + mKeyStatusChangeHandler = createHandler(); + mExpirationUpdateHandler = createHandler(); + mSessionLostStateHandler = createHandler(); /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. @@ -272,6 +305,40 @@ public final class MediaDrm implements AutoCloseable { } /** + * Thrown when an error occurs in any method that has a session context. + */ + public static final class SessionException extends RuntimeException { + public SessionException(int errorCode, @Nullable String detailMessage) { + super(detailMessage); + mErrorCode = errorCode; + } + + /** + * This indicates that apps using MediaDrm sessions are + * temporarily exceeding the capacity of available crypto + * resources. The app should retry the operation later. + */ + public static final int ERROR_RESOURCE_CONTENTION = 1; + + /** @hide */ + @IntDef({ + ERROR_RESOURCE_CONTENTION, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SessionErrorCode {} + + /** + * Retrieve the error code associated with the SessionException + */ + @SessionErrorCode + public int getErrorCode() { + return mErrorCode; + } + + private final int mErrorCode; + } + + /** * Register a callback to be invoked when a session expiration update * occurs. The app's OnExpirationUpdateListener will be notified * when the expiration time of the keys in the session have changed. @@ -282,15 +349,12 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnExpirationUpdateListener( @Nullable OnExpirationUpdateListener listener, @Nullable Handler handler) { - if (listener != null) { - Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); - if (looper != null) { - if (mEventHandler == null || mEventHandler.getLooper() != looper) { - mEventHandler = new EventHandler(this, looper); - } + synchronized(mExpirationUpdateLock) { + if (listener != null) { + mExpirationUpdateHandler = updateHandler(handler); } + mOnExpirationUpdateListener = listener; } - mOnExpirationUpdateListener = listener; } /** @@ -324,15 +388,12 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnKeyStatusChangeListener( @Nullable OnKeyStatusChangeListener listener, @Nullable Handler handler) { - if (listener != null) { - Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); - if (looper != null) { - if (mEventHandler == null || mEventHandler.getLooper() != looper) { - mEventHandler = new EventHandler(this, looper); - } + synchronized(mKeyStatusChangeLock) { + if (listener != null) { + mKeyStatusChangeHandler = updateHandler(handler); } + mOnKeyStatusChangeListener = listener; } - mOnKeyStatusChangeListener = listener; } /** @@ -360,6 +421,46 @@ public final class MediaDrm implements AutoCloseable { } /** + * Register a callback to be invoked when session state has been + * lost. This event can occur on devices that are not capable of + * retaining crypto session state across device suspend/resume + * cycles. When this event occurs, the session must be closed and + * a new session opened to resume operation. + * + * @param listener the callback that will be run, or {@code null} to unregister the + * previously registered callback. + * @param handler the handler on which the listener should be invoked, or + * {@code null} if the listener should be invoked on the calling thread's looper. + */ + public void setOnSessionLostStateListener( + @Nullable OnSessionLostStateListener listener, @Nullable Handler handler) { + synchronized(mSessionLostStateLock) { + if (listener != null) { + mSessionLostStateHandler = updateHandler(handler); + } + mOnSessionLostStateListener = listener; + } + } + + /** + * Interface definition for a callback to be invoked when the + * session state has been lost and is now invalid + */ + public interface OnSessionLostStateListener + { + /** + * Called when session state has lost state, to inform the app + * about the condition so it can close the session and open a new + * one to resume operation. + * + * @param md the MediaDrm object on which the event occurred + * @param sessionId the DRM session ID on which the event occurred + */ + void onSessionLostState( + @NonNull MediaDrm md, @NonNull byte[] sessionId); + } + + /** * Defines the status of a key. * A KeyStatus for each key in a session is provided to the * {@link OnKeyStatusChangeListener#onKeyStatusChange} @@ -437,7 +538,9 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnEventListener(@Nullable OnEventListener listener) { - mOnEventListener = listener; + synchronized(mEventLock) { + mOnEventListener = listener; + } } /** @@ -513,6 +616,7 @@ public final class MediaDrm implements AutoCloseable { private static final int DRM_EVENT = 200; private static final int EXPIRATION_UPDATE = 201; private static final int KEY_STATUS_CHANGE = 202; + private static final int SESSION_LOST_STATE = 203; private class EventHandler extends Handler { @@ -532,52 +636,72 @@ public final class MediaDrm implements AutoCloseable { switch(msg.what) { case DRM_EVENT: - if (mOnEventListener != null) { - if (msg.obj != null && msg.obj instanceof Parcel) { - Parcel parcel = (Parcel)msg.obj; - byte[] sessionId = parcel.createByteArray(); - if (sessionId.length == 0) { - sessionId = null; - } - byte[] data = parcel.createByteArray(); - if (data.length == 0) { - data = null; + synchronized(mEventLock) { + if (mOnEventListener != null) { + if (msg.obj != null && msg.obj instanceof Parcel) { + Parcel parcel = (Parcel)msg.obj; + byte[] sessionId = parcel.createByteArray(); + if (sessionId.length == 0) { + sessionId = null; + } + byte[] data = parcel.createByteArray(); + if (data.length == 0) { + data = null; + } + + Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")"); + mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); } - - Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")"); - mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); } } return; case KEY_STATUS_CHANGE: - if (mOnKeyStatusChangeListener != null) { - if (msg.obj != null && msg.obj instanceof Parcel) { - Parcel parcel = (Parcel)msg.obj; - byte[] sessionId = parcel.createByteArray(); - if (sessionId.length > 0) { - List<KeyStatus> keyStatusList = keyStatusListFromParcel(parcel); - boolean hasNewUsableKey = (parcel.readInt() != 0); - - Log.i(TAG, "Drm key status changed"); - mOnKeyStatusChangeListener.onKeyStatusChange(mMediaDrm, sessionId, - keyStatusList, hasNewUsableKey); + synchronized(mKeyStatusChangeLock) { + if (mOnKeyStatusChangeListener != null) { + if (msg.obj != null && msg.obj instanceof Parcel) { + Parcel parcel = (Parcel)msg.obj; + byte[] sessionId = parcel.createByteArray(); + if (sessionId.length > 0) { + List<KeyStatus> keyStatusList = keyStatusListFromParcel(parcel); + boolean hasNewUsableKey = (parcel.readInt() != 0); + + Log.i(TAG, "Drm key status changed"); + mOnKeyStatusChangeListener.onKeyStatusChange(mMediaDrm, sessionId, + keyStatusList, hasNewUsableKey); + } } } } return; case EXPIRATION_UPDATE: - if (mOnExpirationUpdateListener != null) { - if (msg.obj != null && msg.obj instanceof Parcel) { - Parcel parcel = (Parcel)msg.obj; - byte[] sessionId = parcel.createByteArray(); - if (sessionId.length > 0) { - long expirationTime = parcel.readLong(); - - Log.i(TAG, "Drm key expiration update: " + expirationTime); - mOnExpirationUpdateListener.onExpirationUpdate(mMediaDrm, sessionId, - expirationTime); + synchronized(mExpirationUpdateLock) { + if (mOnExpirationUpdateListener != null) { + if (msg.obj != null && msg.obj instanceof Parcel) { + Parcel parcel = (Parcel)msg.obj; + byte[] sessionId = parcel.createByteArray(); + if (sessionId.length > 0) { + long expirationTime = parcel.readLong(); + + Log.i(TAG, "Drm key expiration update: " + expirationTime); + mOnExpirationUpdateListener.onExpirationUpdate(mMediaDrm, sessionId, + expirationTime); + } + } + } + } + return; + + case SESSION_LOST_STATE: + synchronized(mSessionLostStateLock) { + if (mOnSessionLostStateListener != null) { + if (msg.obj != null && msg.obj instanceof Parcel) { + Parcel parcel = (Parcel)msg.obj; + byte[] sessionId = parcel.createByteArray(); + Log.i(TAG, "Drm session lost state event: "); + mOnSessionLostStateListener.onSessionLostState(mMediaDrm, + sessionId); } } } @@ -619,9 +743,42 @@ public final class MediaDrm implements AutoCloseable { if (md == null) { return; } - if (md.mEventHandler != null) { - Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj); - md.mEventHandler.sendMessage(m); + switch (what) { + case DRM_EVENT: + synchronized(md.mEventLock) { + if (md.mEventHandler != null) { + Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj); + md.mEventHandler.sendMessage(m); + } + } + break; + case EXPIRATION_UPDATE: + synchronized(md.mExpirationUpdateLock) { + if (md.mExpirationUpdateHandler != null) { + Message m = md.mExpirationUpdateHandler.obtainMessage(what, obj); + md.mExpirationUpdateHandler.sendMessage(m); + } + } + break; + case KEY_STATUS_CHANGE: + synchronized(md.mKeyStatusChangeLock) { + if (md.mKeyStatusChangeHandler != null) { + Message m = md.mKeyStatusChangeHandler.obtainMessage(what, obj); + md.mKeyStatusChangeHandler.sendMessage(m); + } + } + break; + case SESSION_LOST_STATE: + synchronized(md.mSessionLostStateLock) { + if (md.mSessionLostStateHandler != null) { + Message m = md.mSessionLostStateHandler.obtainMessage(what, obj); + md.mSessionLostStateHandler.sendMessage(m); + } + } + break; + default: + Log.e(TAG, "Unknown message type " + what); + break; } } diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 7b20f7ae5954..e008adf120ab 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -19,7 +19,7 @@ package android.media; import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; -import static android.media.MediaConstants.KEY_SESSION2_STUB; +import static android.media.MediaConstants.KEY_SESSION2LINK; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; import static android.media.Session2Token.TYPE_SESSION; @@ -56,10 +56,9 @@ import java.util.concurrent.Executor; * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. - * @hide */ public class MediaSession2 implements AutoCloseable { - static final String TAG = "MediaSession"; + static final String TAG = "MediaSession2"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // Note: This checks the uniqueness of a session ID only in a single process. @@ -89,7 +88,6 @@ public class MediaSession2 implements AutoCloseable { private final Handler mResultHandler; //@GuardedBy("mLock") - @SuppressWarnings("WeakerAccess") /* synthetic access */ private boolean mClosed; MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity, @@ -113,6 +111,7 @@ public class MediaSession2 implements AutoCloseable { Context.MEDIA_SESSION_SERVICE); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. mResultHandler = new Handler(context.getMainLooper()); + mClosed = false; } @Override @@ -179,6 +178,7 @@ public class MediaSession2 implements AutoCloseable { * @return a token which will be sent together in {@link SessionCallback#onCommandResult} * when its result is received. */ + @NonNull public Object sendSessionCommand(@NonNull ControllerInfo controller, @NonNull Session2Command command, @Nullable Bundle args) { if (controller == null) { @@ -206,7 +206,10 @@ public class MediaSession2 implements AutoCloseable { * @param controller the controller to get the session command * @param token the token which is returned from {@link #sendSessionCommand}. */ - public void cancelSessionCommand(ControllerInfo controller, Object token) { + public void cancelSessionCommand(@NonNull ControllerInfo controller, @NonNull Object token) { + if (controller == null) { + throw new IllegalArgumentException("controller shouldn't be null"); + } if (token == null) { throw new IllegalArgumentException("token shouldn't be null"); } @@ -267,7 +270,7 @@ public class MediaSession2 implements AutoCloseable { // It's needed because we cannot call synchronous calls between // session/controller. Bundle connectionResult = new Bundle(); - connectionResult.putParcelable(KEY_SESSION2_STUB, mSessionStub); + connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); connectionResult.putParcelable(KEY_ALLOWED_COMMANDS, controllerInfo.mAllowedCommands); @@ -558,7 +561,7 @@ public class MediaSession2 implements AutoCloseable { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof ControllerInfo)) return false; if (this == obj) return true; @@ -570,6 +573,7 @@ public class MediaSession2 implements AutoCloseable { } @Override + @NonNull public String toString() { return "ControllerInfo {pkg=" + mRemoteUserInfo.getPackageName() + ", uid=" + mRemoteUserInfo.getUid() + ", allowedCommands=" + mAllowedCommands + "})"; @@ -693,7 +697,7 @@ public class MediaSession2 implements AutoCloseable { * @return the result for the session command. A runtime exception will be thrown if null * is returned. */ - @NonNull + @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session, @NonNull ControllerInfo controller, @NonNull Session2Command command, @Nullable Bundle args) { diff --git a/media/java/android/media/Session2CommandGroup.java b/media/java/android/media/Session2CommandGroup.java index 519888e86462..a189c264b029 100644 --- a/media/java/android/media/Session2CommandGroup.java +++ b/media/java/android/media/Session2CommandGroup.java @@ -59,6 +59,7 @@ public final class Session2CommandGroup implements Parcelable { * * @param commands The collection of commands to copy. */ + @SuppressWarnings("WeakerAccess") /* synthetic access */ Session2CommandGroup(@Nullable Collection<Session2Command> commands) { if (commands != null) { mCommands.addAll(commands); diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java index e1fff38c6a79..95ee4c039f4b 100644 --- a/media/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -34,7 +34,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. + * Represents an ongoing {@link MediaSession2} or a MediaSession2Service. * If it's representing a session service, it may not be ongoing. * <p> * This API is not generally intended for third party application developers. @@ -45,9 +45,8 @@ import java.util.Objects; * This may be passed to apps by the session owner to allow them to create a * {@link MediaController2} to communicate with the session. * <p> - * It can be also obtained by {@link MediaSessionManager}. + * It can be also obtained by {@link android.media.session.MediaSessionManager}. * - * @hide */ // New version of MediaSession2.Token for following reasons // - Stop implementing Parcelable for updatable support @@ -56,6 +55,7 @@ import java.util.Objects; // This helps controller apps to keep target of dispatching media key events in uniform way. // For details about the reason, see following. (Android O+) // android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged +// TODO: use @link for MediaSession2Service public final class Session2Token implements Parcelable { private static final String TAG = "Session2Token"; @@ -75,7 +75,7 @@ public final class Session2Token implements Parcelable { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = "TYPE_", value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE}) + @IntDef(prefix = "TYPE_", value = {TYPE_SESSION, TYPE_SESSION_SERVICE}) public @interface TokenType { } @@ -85,15 +85,10 @@ public final class Session2Token implements Parcelable { public static final int TYPE_SESSION = 0; /** - * Type for {@link MediaSession2Service}. + * Type for MediaSession2Service. */ public static final int TYPE_SESSION_SERVICE = 1; - /** - * Type for {@link MediaLibrary2Service}. - */ - public static final int TYPE_LIBRARY_SERVICE = 2; - private final int mUid; private final @TokenType int mType; private final String mPackageName; @@ -102,8 +97,7 @@ public final class Session2Token implements Parcelable { private final ComponentName mComponentName; /** - * Constructor for the token with type {@link #TYPE_SESSION_SERVICE} or - * {@link #TYPE_LIBRARY_SERVICE}. + * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}. * * @param context The context. * @param serviceComponent The component name of the service. @@ -239,7 +233,6 @@ public final class Session2Token implements Parcelable { * @return type of the token * @see #TYPE_SESSION * @see #TYPE_SESSION_SERVICE - * @see #TYPE_LIBRARY_SERVICE */ public @TokenType int getType() { return mType; diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 257860890437..7b07bea3cf1a 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -72,7 +72,10 @@ static struct CryptoErrorCodes { jint cryptoErrorResourceBusy; jint cryptoErrorInsufficientOutputProtection; jint cryptoErrorSessionNotOpened; + jint cryptoErrorInsufficientSecurity; jint cryptoErrorUnsupportedOperation; + jint cryptoErrorFrameTooLarge; + jint cryptoErrorLostState; } gCryptoErrorCodes; static struct CodecActionCodes { @@ -1005,10 +1008,22 @@ static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) { err = gCryptoErrorCodes.cryptoErrorSessionNotOpened; defaultMsg = "Attempted to use a closed session"; break; + case ERROR_DRM_INSUFFICIENT_SECURITY: + err = gCryptoErrorCodes.cryptoErrorInsufficientSecurity; + defaultMsg = "Required security level is not met"; + break; case ERROR_DRM_CANNOT_HANDLE: err = gCryptoErrorCodes.cryptoErrorUnsupportedOperation; defaultMsg = "Operation not supported in this configuration"; break; + case ERROR_DRM_FRAME_TOO_LARGE: + err = gCryptoErrorCodes.cryptoErrorFrameTooLarge; + defaultMsg = "Decrytped frame exceeds size of output buffer"; + break; + case ERROR_DRM_SESSION_LOST_STATE: + err = gCryptoErrorCodes.cryptoErrorLostState; + defaultMsg = "Session state was lost, open a new session and retry"; + break; default: /* Other negative DRM error codes go out as is. */ break; } @@ -1994,11 +2009,26 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { gCryptoErrorCodes.cryptoErrorSessionNotOpened = env->GetStaticIntField(clazz.get(), field); + field = env->GetStaticFieldID(clazz.get(), "ERROR_INSUFFICIENT_SECURITY", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorInsufficientSecurity = + env->GetStaticIntField(clazz.get(), field); + field = env->GetStaticFieldID(clazz.get(), "ERROR_UNSUPPORTED_OPERATION", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorUnsupportedOperation = env->GetStaticIntField(clazz.get(), field); + field = env->GetStaticFieldID(clazz.get(), "ERROR_FRAME_TOO_LARGE", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorFrameTooLarge = + env->GetStaticIntField(clazz.get(), field); + + field = env->GetStaticFieldID(clazz.get(), "ERROR_LOST_STATE", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorLostState = + env->GetStaticIntField(clazz.get(), field); + clazz.reset(env->FindClass("android/media/MediaCodec$CodecException")); CHECK(clazz.get() != NULL); field = env->GetStaticFieldID(clazz.get(), "ACTION_TRANSIENT", "I"); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index be71dad571bb..83364590039d 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -110,6 +110,7 @@ struct EventWhat { jint kWhatDrmEvent; jint kWhatExpirationUpdate; jint kWhatKeyStatusChange; + jint kWhatSessionLostState; } gEventWhat; struct KeyTypes { @@ -141,6 +142,16 @@ struct StateExceptionFields { jclass classId; }; +struct SessionExceptionFields { + jmethodID init; + jclass classId; + jfieldID errorCode; +}; + +struct SessionExceptionErrorCodes { + jint kResourceContention; +} gSessionExceptionErrorCodes; + struct HDCPLevels { jint kHdcpLevelUnknown; jint kHdcpNone; @@ -180,6 +191,7 @@ struct fields_t { EntryFields entry; CertificateFields certificate; StateExceptionFields stateException; + SessionExceptionFields sessionException; jclass certificateClassId; jclass hashmapClassId; jclass arraylistClassId; @@ -310,6 +322,9 @@ void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra, case DrmPlugin::kDrmPluginEventKeysChange: jwhat = gEventWhat.kWhatKeyStatusChange; break; + case DrmPlugin::kDrmPluginEventSessionLostState: + jwhat = gEventWhat.kWhatSessionLostState; + break; default: ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType); return; @@ -343,6 +358,30 @@ static void throwStateException(JNIEnv *env, const char *msg, status_t err) { env->Throw(static_cast<jthrowable>(exception)); } +static void throwSessionException(JNIEnv *env, const char *msg, status_t err) { + ALOGE("Session exception: %s (%d)", msg, err); + + jint jErrorCode = 0; + switch(err) { + case ERROR_DRM_RESOURCE_CONTENTION: + jErrorCode = gSessionExceptionErrorCodes.kResourceContention; + break; + default: + break; + } + + jobject exception = env->NewObject(gFields.sessionException.classId, + gFields.sessionException.init, static_cast<int>(err), + env->NewStringUTF(msg)); + + env->SetIntField(exception, gFields.sessionException.errorCode, jErrorCode); + env->Throw(static_cast<jthrowable>(exception)); +} + +static bool isSessionException(status_t err) { + return err == ERROR_DRM_RESOURCE_CONTENTION; +} + static bool throwExceptionAsNecessary( JNIEnv *env, status_t err, const char *msg = NULL) { @@ -370,7 +409,7 @@ static bool throwExceptionAsNecessary( case ERROR_DRM_CANNOT_HANDLE: drmMessage = "Invalid parameter or data format"; break; - case ERROR_DRM_TAMPER_DETECTED: + case ERROR_DRM_INVALID_STATE: drmMessage = "Invalid state"; break; default: @@ -399,6 +438,9 @@ static bool throwExceptionAsNecessary( jniThrowException(env, "android/media/MediaDrmResetException", "mediaserver died"); return true; + } else if (isSessionException(err)) { + throwSessionException(env, msg, err); + return true; } else if (err != OK) { String8 errbuf; if (drmMessage != NULL) { @@ -705,6 +747,8 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "KEY_STATUS_CHANGE", "I"); gEventWhat.kWhatKeyStatusChange = env->GetStaticIntField(clazz, field); + GET_STATIC_FIELD_ID(field, clazz, "SESSION_LOST_STATE", "I"); + gEventWhat.kWhatSessionLostState = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I"); gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field); @@ -831,6 +875,14 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException"); GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V"); gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); + + FIND_CLASS(clazz, "android/media/MediaDrm$SessionException"); + GET_METHOD_ID(gFields.sessionException.init, clazz, "<init>", "(ILjava/lang/String;)V"); + gFields.sessionException.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); + GET_FIELD_ID(gFields.sessionException.errorCode, clazz, "mErrorCode", "I"); + + GET_STATIC_FIELD_ID(field, clazz, "ERROR_RESOURCE_CONTENTION", "I"); + gSessionExceptionErrorCodes.kResourceContention = env->GetStaticIntField(clazz, field); } static void android_media_MediaDrm_native_setup( diff --git a/native/android/sharedmem.cpp b/native/android/sharedmem.cpp index 757aaecab40d..4410bd6fbeed 100644 --- a/native/android/sharedmem.cpp +++ b/native/android/sharedmem.cpp @@ -71,7 +71,7 @@ int ASharedMemory_dupFromJava(JNIEnv* env, jobject javaSharedMemory) { } int fd = env->CallIntMethod(javaSharedMemory, sSharedMemory.getFd); if (fd != -1) { - fd = dup(fd); + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); } return fd; } diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h index 6afd8837594c..bb2ee9b5da04 100644 --- a/native/webview/plat_support/draw_fn.h +++ b/native/webview/plat_support/draw_fn.h @@ -109,8 +109,15 @@ struct AwDrawFn_DrawVkParams { // Input: Format of the destination surface. VkFormat format; - // Input: Color space transformation from linear RGB to D50-adapted XYZ - float matrix[9]; + // Input: Color space parameters. + float transfer_function_g; + float transfer_function_a; + float transfer_function_b; + float transfer_function_c; + float transfer_function_d; + float transfer_function_e; + float transfer_function_f; + float color_space_toXYZD50[9]; // Input: current clip rect int clip_left; diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index 838b88b696c5..499d493ca6a2 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -286,6 +286,9 @@ public class Assistant extends NotificationAssistantService { if (!isForCurrentUser(sbn)) { return; } + + mAgingHelper.onNotificationRemoved(sbn.getKey()); + boolean updatedImpressions = false; String channelId = mLiveNotifications.remove(sbn.getKey()).getChannel().getId(); String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId); diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index a9d8f62d53f4..95df5f269467 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -27,6 +27,7 @@ import android.os.Process; import android.service.notification.NotificationAssistantService; import android.text.TextUtils; import android.util.LruCache; +import android.view.textclassifier.ConversationAction; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationContext; @@ -65,13 +66,13 @@ public class SmartActionsHelper { private static final int MAX_MESSAGES_TO_EXTRACT = 5; private static final int MAX_RESULT_ID_TO_CACHE = 20; - private static final ConversationActions.TypeConfig TYPE_CONFIG = - new ConversationActions.TypeConfig.Builder().setIncludedTypes( - Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY)) + private static final TextClassifier.EntityConfig TYPE_CONFIG = + new TextClassifier.EntityConfig.Builder().setIncludedTypes( + Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY)) .includeTypesFromTextClassifier(false) .build(); private static final List<String> HINTS = - Collections.singletonList(ConversationActions.HINT_FOR_NOTIFICATION); + Collections.singletonList(ConversationActions.Request.HINT_FOR_NOTIFICATION); private Context mContext; @Nullable @@ -137,7 +138,7 @@ public class SmartActionsHelper { ConversationActions conversationActionsResult = mTextClassifier.suggestConversationActions(request); - List<ConversationActions.ConversationAction> conversationActions = + List<ConversationAction> conversationActions = conversationActionsResult.getConversationActions(); ArrayList<CharSequence> replies = conversationActions.stream() .map(conversationAction -> conversationAction.getTextReply()) @@ -193,7 +194,7 @@ public class SmartActionsHelper { } TextClassifierEvent textClassifierEvent = createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId) - .setEntityType(ConversationActions.TYPE_TEXT_REPLY) + .setEntityType(ConversationAction.TYPE_TEXT_REPLY) .build(); mTextClassifier.onTextClassifierEvent(textClassifierEvent); } diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java index 7d74788e21c0..7b7ce3d87f31 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -32,6 +32,7 @@ import android.service.notification.NotificationAssistantService; import android.service.notification.StatusBarNotification; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import android.view.textclassifier.ConversationAction; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; @@ -65,9 +66,10 @@ public class SmartActionHelperTest { private static final String NOTIFICATION_KEY = "key"; private static final String RESULT_ID = "id"; - private static final ConversationActions.ConversationAction REPLY_ACTION = - new ConversationActions.ConversationAction.Builder( - ConversationActions.TYPE_TEXT_REPLY).setTextReply("Home").build(); + private static final ConversationAction REPLY_ACTION = + new ConversationAction.Builder(ConversationAction.TYPE_TEXT_REPLY) + .setTextReply("Home") + .build(); private SmartActionsHelper mSmartActionsHelper; private Context mContext; diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 0b0f1eca7aa5..7f8bb93ae023 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -20,6 +20,7 @@ package="com.android.mainline.networkstack" android:sharedUserId="android.uid.networkstack"> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index 94ea1b931348..4077d93d700b 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -545,7 +545,9 @@ public class NetworkMonitor extends StateMachine { return HANDLED; case CMD_FORCE_REEVALUATION: case CMD_CAPTIVE_PORTAL_RECHECK: - log("Forcing reevaluation for UID " + message.arg1); + final int dnsCount = mDnsStallDetector.getConsecutiveTimeoutCount(); + validationLog("Forcing reevaluation for UID " + message.arg1 + + ". Dns signal count: " + dnsCount); mUidResponsibleForReeval = message.arg1; transitionTo(mEvaluatingState); return HANDLED; diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java index aed02a268aea..8090169a4245 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java @@ -41,10 +41,6 @@ public class VisibilityLoggerMixin implements LifecycleObserver { private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN; private long mVisibleTimestamp; - private VisibilityLoggerMixin() { - mMetricsCategory = METRICS_CATEGORY_UNKNOWN; - } - public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) { mMetricsCategory = metricsCategory; mMetricsFeature = metricsFeature; @@ -81,12 +77,4 @@ public class VisibilityLoggerMixin implements LifecycleObserver { MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, MetricsProto.MetricsEvent.VIEW_UNKNOWN); } - - /** Returns elapsed time since onResume() */ - public long elapsedTimeSinceVisible() { - if (mVisibleTimestamp == 0) { - return 0; - } - return SystemClock.elapsedRealtime() - mVisibleTimestamp; - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 4f81dafe7305..af5a24f16222 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -332,9 +332,9 @@ public class AccessPoint implements Comparable<AccessPoint> { ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); bssid = config.BSSID; security = getSecurity(config); - updateKey(); networkId = config.networkId; mConfig = config; + updateKey(); } /** Updates {@link #mKey} and should only called upon object creation/initialization. */ @@ -343,7 +343,9 @@ public class AccessPoint implements Comparable<AccessPoint> { StringBuilder builder = new StringBuilder(); - if (TextUtils.isEmpty(getSsidStr())) { + if (isPasspoint()) { + builder.append(mConfig.FQDN); + } else if (TextUtils.isEmpty(getSsidStr())) { builder.append(getBssid()); } else { builder.append(getSsidStr()); @@ -606,7 +608,9 @@ public class AccessPoint implements Comparable<AccessPoint> { public static String getKey(WifiConfiguration config) { StringBuilder builder = new StringBuilder(); - if (TextUtils.isEmpty(config.SSID)) { + if (config.isPasspoint()) { + builder.append(config.FQDN); + } else if (TextUtils.isEmpty(config.SSID)) { builder.append(config.BSSID); } else { builder.append(removeDoubleQuotes(config.SSID)); @@ -621,9 +625,10 @@ public class AccessPoint implements Comparable<AccessPoint> { } public boolean matches(WifiConfiguration config) { - if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) { - return ssid.equals(removeDoubleQuotes(config.SSID)) && config.FQDN.equals(mConfig.FQDN); + if (config.isPasspoint()) { + return (isPasspoint() && config.FQDN.equals(mConfig.FQDN)); } else { + // Normal non-Passpoint network return ssid.equals(removeDoubleQuotes(config.SSID)) && security == getSecurity(config) && (mConfig == null || mConfig.shared == config.shared); @@ -828,6 +833,17 @@ public class AccessPoint implements Comparable<AccessPoint> { return ""; } + /** + * Returns the display title for the AccessPoint, such as for an AccessPointPreference's title. + */ + public String getTitle() { + if (isPasspoint()) { + return mConfig.providerFriendlyName; + } else { + return getSsidStr(); + } + } + public String getSummary() { return getSettingsSummary(mConfig); } @@ -1048,18 +1064,21 @@ public class AccessPoint implements Comparable<AccessPoint> { */ void setScanResults(Collection<ScanResult> scanResults) { - // Validate scan results are for current AP only - String key = getKey(); - for (ScanResult result : scanResults) { - String scanResultKey = AccessPoint.getKey(result); - if (!mKey.equals(scanResultKey)) { - throw new IllegalArgumentException( - String.format("ScanResult %s\nkey of %s did not match current AP key %s", - result, scanResultKey, key)); + // Validate scan results are for current AP only by matching SSID/BSSID + // Passpoint R1 networks are not bound to a specific SSID/BSSID, so skip this for passpoint. + if (!isPasspoint()) { + String key = getKey(); + for (ScanResult result : scanResults) { + String scanResultKey = AccessPoint.getKey(result); + if (!mKey.equals(scanResultKey)) { + throw new IllegalArgumentException( + String.format( + "ScanResult %s\nkey of %s did not match current AP key %s", + result, scanResultKey, key)); + } } } - int oldLevel = getLevel(); mScanResults.clear(); mScanResults.addAll(scanResults); @@ -1149,6 +1168,9 @@ public class AccessPoint implements Comparable<AccessPoint> { void update(@Nullable WifiConfiguration config) { mConfig = config; + if (mConfig != null) { + ssid = removeDoubleQuotes(mConfig.SSID); + } networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID; ThreadUtils.postOnMainThread(() -> { if (mAccessPointListener != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index db364a3b75e5..1fa7083498e1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -266,7 +266,7 @@ public class AccessPointPreference extends Preference { if (savedNetworks) { preference.setTitle(ap.getConfigName()); } else { - preference.setTitle(ap.getSsidStr()); + preference.setTitle(ap.getTitle()); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index e47ca3226cfb..79a72402e232 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -45,6 +45,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.widget.Toast; import androidx.annotation.GuardedBy; @@ -573,9 +574,52 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro accessPoints.add(accessPoint); } + List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values()); + + // Add a unique Passpoint R1 AccessPoint for each Passpoint profile's FQDN. + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans = + mWifiManager.getAllMatchingWifiConfigs(cachedScanResults); + Set<String> seenFQDNs = new ArraySet<>(); + for (Pair<WifiConfiguration, + Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) { + WifiConfiguration config = pairing.first; + + // TODO: Prioritize home networks before roaming networks + List<ScanResult> scanResults = new ArrayList<>(); + + List<ScanResult> homeScans = + pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); + List<ScanResult> roamingScans = + pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); + + if (homeScans == null) { + homeScans = new ArrayList<>(); + } + if (roamingScans == null) { + roamingScans = new ArrayList<>(); + } + + scanResults.addAll(homeScans); + scanResults.addAll(roamingScans); + + if (seenFQDNs.add(config.FQDN)) { + int bestRssi = Integer.MIN_VALUE; + for (ScanResult result : scanResults) { + if (result.level >= bestRssi) { + bestRssi = result.level; + config.SSID = AccessPoint.convertToQuotedString(result.SSID); + } + } + + AccessPoint accessPoint = new AccessPoint(mContext, config); + accessPoint.setScanResults(scanResults); + accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); + accessPoints.add(accessPoint); + } + } + // If there were no scan results, create an AP for the currently connected network (if // it exists). - // TODO(b/b/73076869): Add support for passpoint (ephemeral) networks if (accessPoints.isEmpty() && connectionConfig != null) { AccessPoint activeAp = new AccessPoint(mContext, connectionConfig); activeAp.update(connectionConfig, mLastInfo, mLastNetworkInfo); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index e853399133aa..ef90dc981870 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -28,8 +28,8 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; -import android.media.AudioSystem; import android.media.AudioManager; +import android.media.AudioSystem; import android.net.ConnectivityManager; import android.os.Build; import android.os.Environment; @@ -1104,9 +1104,7 @@ class DatabaseHelper extends SQLiteOpenHelper { } if (upgradeVersion == 77) { - // Introduce "vibrate when ringing" setting - loadVibrateWhenRingingSetting(db); - + // "vibrate when ringing" setting moved to SettingsProvider version 168 upgradeVersion = 78; } @@ -2223,8 +2221,6 @@ class DatabaseHelper extends SQLiteOpenHelper { } finally { if (stmt != null) stmt.close(); } - - loadVibrateWhenRingingSetting(db); } private void loadVibrateSetting(SQLiteDatabase db, boolean deleteOld) { @@ -2250,24 +2246,6 @@ class DatabaseHelper extends SQLiteOpenHelper { } } - private void loadVibrateWhenRingingSetting(SQLiteDatabase db) { - // The default should be off. VIBRATE_SETTING_ONLY_SILENT should also be ignored here. - // Phone app should separately check whether AudioManager#getRingerMode() returns - // RINGER_MODE_VIBRATE, with which the device should vibrate anyway. - int vibrateSetting = getIntValueFromSystem(db, Settings.System.VIBRATE_ON, - AudioManager.VIBRATE_SETTING_OFF); - boolean vibrateWhenRinging = ((vibrateSetting & 3) == AudioManager.VIBRATE_SETTING_ON); - - SQLiteStatement stmt = null; - try { - stmt = db.compileStatement("INSERT OR IGNORE INTO system(name,value)" - + " VALUES(?,?);"); - loadSetting(stmt, Settings.System.VIBRATE_WHEN_RINGING, vibrateWhenRinging ? 1 : 0); - } finally { - if (stmt != null) stmt.close(); - } - } - private void loadSettings(SQLiteDatabase db) { loadSystemSettings(db); loadSecureSettings(db); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index fa95bf2ee302..9b775e058ea8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -129,6 +129,7 @@ <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> + <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING" /> <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 2530abc765da..afb978174784 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -56,6 +56,7 @@ import com.google.android.collect.Lists; import android.accounts.Account; import android.accounts.AccountManager; import android.annotation.MainThread; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Notification; @@ -799,6 +800,18 @@ public class BugreportProgressService extends Service { Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent); return; } + final int max = intent.getIntExtra(EXTRA_MAX, -1); + final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT); + final String shareTitle = intent.getStringExtra(EXTRA_TITLE); + final String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION); + onBugreportFinished(id, bugreportFile, screenshotFile, shareTitle, shareDescription, max); + } + + /** + * Wraps up bugreport generation and triggers a notification to share the bugreport. + */ + private void onBugreportFinished(int id, File bugreportFile, @Nullable File screenshotFile, + String shareTitle, String shareDescription, int max) { mInfoDialog.onBugreportFinished(); BugreportInfo info = getInfo(id); if (info == null) { @@ -809,22 +822,17 @@ public class BugreportProgressService extends Service { } info.renameScreenshots(mScreenshotsDir); info.bugreportFile = bugreportFile; + if (screenshotFile != null) { + info.addScreenshot(screenshotFile); + } - final int max = intent.getIntExtra(EXTRA_MAX, -1); if (max != -1) { MetricsLogger.histogram(this, "dumpstate_duration", max); info.max = max; } - final File screenshot = getFileExtra(intent, EXTRA_SCREENSHOT); - if (screenshot != null) { - info.addScreenshot(screenshot); - } - - final String shareTitle = intent.getStringExtra(EXTRA_TITLE); if (!TextUtils.isEmpty(shareTitle)) { info.title = shareTitle; - final String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION); if (!TextUtils.isEmpty(shareDescription)) { info.shareDescription= shareDescription; } @@ -1944,6 +1952,23 @@ public class BugreportProgressService extends Service { } @Override + public void onProgress(int progress) throws RemoteException { + // TODO(b/111441001): change max argument? + updateProgressInfo(progress, CAPPED_MAX); + } + + @Override + public void onError(int errorCode) throws RemoteException { + // TODO(b/111441001): implement + } + + @Override + public void onFinished(long durationMs, String title, String description) + throws RemoteException { + // TODO(b/111441001): implement + } + + @Override public void onProgressUpdated(int progress) throws RemoteException { /* * Checks whether the progress changed in a way that should be displayed to the user: @@ -1964,21 +1989,7 @@ public class BugreportProgressService extends Service { } if (newPercentage > oldPercentage) { - if (DEBUG) { - if (progress != info.progress) { - Log.v(TAG, "Updating progress for PID " + info.pid + "(id: " + info.id - + ") from " + info.progress + " to " + progress); - } - if (max != info.max) { - Log.v(TAG, "Updating max progress for PID " + info.pid + "(id: " + info.id - + ") from " + info.max + " to " + max); - } - } - info.progress = progress; - info.max = max; - info.lastUpdate = System.currentTimeMillis(); - - updateProgress(info); + updateProgressInfo(progress, max); } } @@ -2000,5 +2011,23 @@ public class BugreportProgressService extends Service { public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("token: "); pw.println(token); } + + private void updateProgressInfo(int progress, int max) { + if (DEBUG) { + if (progress != info.progress) { + Log.v(TAG, "Updating progress for PID " + info.pid + "(id: " + info.id + + ") from " + info.progress + " to " + progress); + } + if (max != info.max) { + Log.v(TAG, "Updating max progress for PID " + info.pid + "(id: " + info.id + + ") from " + info.max + " to " + max); + } + } + info.progress = progress; + info.max = max; + info.lastUpdate = System.currentTimeMillis(); + + updateProgress(info); + } } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1c1a1404eacd..b4f2711ef9d2 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -87,6 +87,7 @@ <uses-permission android:name="android.permission.STOP_APP_SWITCHES" /> <uses-permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" /> <uses-permission android:name="android.permission.START_ANY_ACTIVITY" /> + <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" /> diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml new file mode 100644 index 000000000000..204408cda81f --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_view.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.systemui.bubbles.BubbleView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:id="@+id/bubble_view"> + + <com.android.systemui.bubbles.BadgedImageView + android:id="@+id/bubble_image" + android:layout_width="@dimen/bubble_size" + android:layout_height="@dimen/bubble_size" + android:padding="@dimen/bubble_view_padding" + android:clipToPadding="false"/> + + <TextView + android:id="@+id/message_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/bubble_message_min_width" + android:maxWidth="@dimen/bubble_message_max_width" + android:padding="@dimen/bubble_message_padding"/> + +</com.android.systemui.bubbles.BubbleView> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 10e5f74983a3..ef16bcaf0fd2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -981,14 +981,16 @@ <!-- How much a bubble is elevated --> <dimen name="bubble_elevation">8dp</dimen> + <!-- Padding around a collapsed bubble --> + <dimen name="bubble_view_padding">0dp</dimen> <!-- Padding between bubbles when displayed in expanded state --> <dimen name="bubble_padding">8dp</dimen> - <!-- Padding around the view displayed when the bubble is expanded --> - <dimen name="bubble_expanded_view_padding">8dp</dimen> <!-- Size of the collapsed bubble --> <dimen name="bubble_size">56dp</dimen> - <!-- Size of an icon displayed within the bubble --> - <dimen name="bubble_icon_size">24dp</dimen> + <!-- How much to inset the icon in the circle --> + <dimen name="bubble_icon_inset">16dp</dimen> + <!-- Padding around the view displayed when the bubble is expanded --> + <dimen name="bubble_expanded_view_padding">8dp</dimen> <!-- Default height of the expanded view shown when the bubble is expanded --> <dimen name="bubble_expanded_default_height">400dp</dimen> <!-- Height of the triangle that points to the expanded bubble --> @@ -1001,4 +1003,10 @@ <dimen name="bubble_expanded_header_height">48dp</dimen> <!-- Left and right padding applied to the header. --> <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen> + <!-- Max width of the message bubble--> + <dimen name="bubble_message_max_width">144dp</dimen> + <!-- Min width of the message bubble --> + <dimen name="bubble_message_min_width">32dp</dimen> + <!-- Interior padding of the message bubble --> + <dimen name="bubble_message_padding">4dp</dimen> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java new file mode 100644 index 000000000000..845b08483064 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.bubbles; + +import static android.graphics.Paint.ANTI_ALIAS_FLAG; +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Log; + +// XXX: Mostly opied from launcher code / can we share? +/** + * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge). + */ +public class BadgeRenderer { + + private static final String TAG = "BadgeRenderer"; + + // The badge sizes are defined as percentages of the app icon size. + private static final float SIZE_PERCENTAGE = 0.38f; + + // Extra scale down of the dot + private static final float DOT_SCALE = 0.6f; + + private final float mDotCenterOffset; + private final float mCircleRadius; + private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); + + public BadgeRenderer(int iconSizePx) { + mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx; + int size = (int) (DOT_SCALE * mDotCenterOffset); + mCircleRadius = size / 2f; + } + + /** + * Draw a circle in the top right corner of the given bounds. + * + * @param color The color (based on the icon) to use for the badge. + * @param iconBounds The bounds of the icon being badged. + * @param badgeScale The progress of the animation, from 0 to 1. + * @param spaceForOffset How much space to offset the badge up and to the left or right. + * @param onLeft Whether the badge should be draw on left or right side. + */ + public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale, + Point spaceForOffset, boolean onLeft) { + if (iconBounds == null) { + Log.e(TAG, "Invalid null argument(s) passed in call to draw."); + return; + } + canvas.save(); + // We draw the badge relative to its center. + int x = onLeft ? iconBounds.left : iconBounds.right; + float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2); + float badgeCenterX = x + offset; + float badgeCenterY = iconBounds.top + mDotCenterOffset / 2; + + canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y); + + canvas.scale(badgeScale, badgeScale); + mCirclePaint.setColor(color); + canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); + canvas.restore(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java new file mode 100644 index 000000000000..92d3cc1ae34f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.bubbles; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.android.systemui.R; + +/** + * View that circle crops its contents and supports displaying a coloured dot on a top corner. + */ +public class BadgedImageView extends ImageView { + + private BadgeRenderer mDotRenderer; + private int mIconSize; + private Rect mTempBounds = new Rect(); + private Point mTempPoint = new Point(); + private Path mClipPath = new Path(); + + private float mDotScale = 0f; + private int mUpdateDotColor; + private boolean mShowUpdateDot; + private boolean mOnLeft; + + public BadgedImageView(Context context) { + this(context, null); + } + + public BadgedImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setScaleType(ScaleType.CENTER_CROP); + mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); + mDotRenderer = new BadgeRenderer(mIconSize); + } + + // TODO: Clipping oval path isn't great: rerender image into a separate, rounded bitmap and + // then draw would be better + @Override + public void onDraw(Canvas canvas) { + canvas.save(); + // Circle crop + mClipPath.addOval(getPaddingStart(), getPaddingTop(), + getWidth() - getPaddingEnd(), getHeight() - getPaddingBottom(), Path.Direction.CW); + canvas.clipPath(mClipPath); + super.onDraw(canvas); + + // After we've circle cropped what we're showing, restore so we don't clip the badge + canvas.restore(); + + // Draw the badge + if (mShowUpdateDot) { + getDrawingRect(mTempBounds); + mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop()); + mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint, + mOnLeft); + } + } + + /** + * Set whether the dot should appear on left or right side of the view. + */ + public void setDotPosition(boolean onLeft) { + mOnLeft = onLeft; + invalidate(); + } + + /** + * Set whether the dot should show or not. + */ + public void setShowDot(boolean showBadge) { + mShowUpdateDot = showBadge; + invalidate(); + } + + /** + * @return whether the dot is being displayed. + */ + public boolean isShowingDot() { + return mShowUpdateDot; + } + + /** + * The colour to use for the dot. + */ + public void setDotColor(int color) { + mUpdateDotColor = color; + invalidate(); + } + + /** + * How big the dot should be, fraction from 0 to 1. + */ + public void setDotScale(float fraction) { + mDotScale = fraction; + invalidate(); + } + + public float getDotScale() { + return mDotScale; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 9f3ff782211d..d7bf77da1011 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -21,33 +21,42 @@ import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; +import static com.android.systemui.statusbar.StatusBarState.SHADE; +import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain; +import android.annotation.Nullable; +import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; +import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.Log; +import android.view.LayoutInflater; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; -import androidx.annotation.Nullable; - import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.phone.StatusBarWindowController; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -66,8 +75,6 @@ public class BubbleController { // Enables some subset of notifs to automatically become bubbles private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false; - // When a bubble is dismissed, recreate it as a notification - private static final boolean DEBUG_DEMOTE_TO_NOTIF = false; // Secure settings private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging"; @@ -80,6 +87,7 @@ public class BubbleController { private final NotificationEntryManager mNotificationEntryManager; private BubbleStateChangeListener mStateChangeListener; private BubbleExpandListener mExpandListener; + private LayoutInflater mInflater; private final Map<String, BubbleView> mBubbles = new HashMap<>(); private BubbleStackView mStackView; @@ -87,6 +95,12 @@ public class BubbleController { // Bubbles get added to the status bar view private final StatusBarWindowController mStatusBarWindowController; + private StatusBarStateListener mStatusBarStateListener; + + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); + + private INotificationManager mNotificationManagerService; // Used for determining view rect for touch interaction private Rect mTempRect = new Rect(); @@ -107,23 +121,53 @@ public class BubbleController { public interface BubbleExpandListener { /** * Called when the expansion state of the bubble stack changes. - * * @param isExpanding whether it's expanding or collapsing - * @param amount fraction of how expanded or collapsed it is, 1 being fully, 0 at the start + * @param key the notification key associated with bubble being expanded */ - void onBubbleExpandChanged(boolean isExpanding, float amount); + void onBubbleExpandChanged(boolean isExpanding, String key); + } + + /** + * Listens for the current state of the status bar and updates the visibility state + * of bubbles as needed. + */ + private class StatusBarStateListener implements StatusBarStateController.StateListener { + private int mState; + /** + * Returns the current status bar state. + */ + public int getCurrentState() { + return mState; + } + + @Override + public void onStateChanged(int newState) { + mState = newState; + updateVisibility(); + } } @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController) { mContext = context; - mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mDisplaySize = new Point(); wm.getDefaultDisplay().getSize(mDisplaySize); - mStatusBarWindowController = statusBarWindowController; + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); mNotificationEntryManager.addNotificationEntryListener(mEntryListener); + + try { + mNotificationManagerService = INotificationManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE)); + } catch (ServiceManager.ServiceNotFoundException e) { + e.printStackTrace(); + } + + mStatusBarWindowController = statusBarWindowController; + mStatusBarStateListener = new StatusBarStateListener(); + Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener); } /** @@ -148,7 +192,12 @@ public class BubbleController { * screen (e.g. if on AOD). */ public boolean hasBubbles() { - return mBubbles.size() > 0; + for (BubbleView bv : mBubbles.values()) { + if (!bv.getEntry().isBubbleDismissed()) { + return true; + } + } + return false; } /** @@ -163,7 +212,7 @@ public class BubbleController { */ public void collapseStack() { if (mStackView != null) { - mStackView.animateExpansion(false); + mStackView.collapseStack(); } } @@ -174,33 +223,32 @@ public class BubbleController { if (mStackView == null) { return; } - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); + Set<String> keys = mBubbles.keySet(); + for (String key: keys) { + mBubbles.get(key).getEntry().setBubbleDismissed(true); + } + mStackView.stackDismissed(); + // Reset the position of the stack (TODO - or should we save / respect last user position?) + Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); mStackView.setPosition(startPoint.x, startPoint.y); - for (String key: mBubbles.keySet()) { - removeBubble(key); - } + + updateVisibility(); mNotificationEntryManager.updateNotifications(); - updateBubblesShowing(); } /** - * Adds a bubble associated with the provided notification entry or updates it if it exists. + * Adds or updates a bubble associated with the provided notification entry. + * + * @param notif the notification associated with this bubble. + * @param updatePosition whether this update should promote the bubble to the top of the stack. */ - public void addBubble(NotificationEntry notif) { + public void updateBubble(NotificationEntry notif, boolean updatePosition) { if (mBubbles.containsKey(notif.key)) { // It's an update BubbleView bubble = mBubbles.get(notif.key); - mStackView.updateBubble(bubble, notif); + mStackView.updateBubble(bubble, notif, updatePosition); } else { - // It's new - BubbleView bubble = new BubbleView(mContext); - bubble.setNotif(notif); - if (shouldUseActivityView(mContext)) { - bubble.setAppOverlayIntent(getAppOverlayIntent(notif)); - } - mBubbles.put(bubble.getKey(), bubble); - boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; if (mStackView == null) { setPosition = true; @@ -215,15 +263,22 @@ public class BubbleController { mStackView.setExpandListener(mExpandListener); } } + // It's new + BubbleView bubble = (BubbleView) mInflater.inflate( + R.layout.bubble_view, mStackView, false /* attachToRoot */); + bubble.setNotif(notif); + if (shouldUseActivityView(mContext)) { + bubble.setAppOverlayIntent(getAppOverlayIntent(notif)); + } + mBubbles.put(bubble.getKey(), bubble); mStackView.addBubble(bubble); if (setPosition) { // Need to add the bubble to the stack before we can know the width Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); mStackView.setPosition(startPoint.x, startPoint.y); - mStackView.setVisibility(VISIBLE); } - updateBubblesShowing(); } + updateVisibility(); } @Nullable @@ -245,79 +300,96 @@ public class BubbleController { * Removes the bubble associated with the {@param uri}. */ void removeBubble(String key) { - BubbleView bv = mBubbles.get(key); + BubbleView bv = mBubbles.remove(key); if (mStackView != null && bv != null) { mStackView.removeBubble(bv); bv.destroyActivityView(mStackView); - bv.getEntry().setBubbleDismissed(true); } - NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key); + NotificationEntry entry = bv != null ? bv.getEntry() : null; if (entry != null) { entry.setBubbleDismissed(true); - if (!DEBUG_DEMOTE_TO_NOTIF) { - mNotificationEntryManager.performRemoveNotification(entry.notification); - } + mNotificationEntryManager.updateNotifications(); } - mNotificationEntryManager.updateNotifications(); - - updateBubblesShowing(); + updateVisibility(); } @SuppressWarnings("FieldCanBeLocal") private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { @Override public void onPendingEntryAdded(NotificationEntry entry) { - if (shouldAutoBubble(mContext, entry)) { + if (shouldAutoBubble(mContext, entry) || shouldBubble(entry)) { + // TODO: handle group summaries + // It's a new notif, it shows in the shade and as a bubble entry.setIsBubble(true); + entry.setShowInShadeWhenBubble(true); + } + } + + @Override + public void onEntryInflated(NotificationEntry entry, + @NotificationInflater.InflationFlag int inflatedFlags) { + if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { + updateBubble(entry, true /* updatePosition */); + } + } + + @Override + public void onPreEntryUpdated(NotificationEntry entry) { + if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) + && alertAgain(entry, entry.notification.getNotification())) { + entry.setShowInShadeWhenBubble(true); + entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed + if (mBubbles.containsKey(entry.key)) { + mBubbles.get(entry.key).updateDotVisibility(); + } + updateBubble(entry, true /* updatePosition */); + } + } + + @Override + public void onEntryRemoved(NotificationEntry entry, + @Nullable NotificationVisibility visibility, + boolean removedByUser) { + entry.setShowInShadeWhenBubble(false); + if (mBubbles.containsKey(entry.key)) { + mBubbles.get(entry.key).updateDotVisibility(); + } + if (!removedByUser) { + // This was a cancel so we should remove the bubble + removeBubble(entry.key); } } }; + /** + * Lets any listeners know if bubble state has changed. + */ private void updateBubblesShowing() { - boolean hasBubblesShowing = false; - for (BubbleView bv : mBubbles.values()) { - if (!bv.getEntry().isBubbleDismissed()) { - hasBubblesShowing = true; - break; - } + if (mStackView == null) { + return; } + boolean hadBubbles = mStatusBarWindowController.getBubblesShowing(); + boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE; mStatusBarWindowController.setBubblesShowing(hasBubblesShowing); - if (mStackView != null && !hasBubblesShowing) { - mStackView.setVisibility(INVISIBLE); - } if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) { mStateChangeListener.onHasBubblesChanged(hasBubblesShowing); } } /** - * Sets the visibility of the bubbles, doesn't un-bubble them, just changes visibility. + * Updates the visibility of the bubbles based on current state. + * Does not un-bubble, just hides or un-hides. Will notify any + * {@link BubbleStateChangeListener}s if visibility changes. */ - public void updateVisibility(boolean visible) { - if (mStackView == null) { - return; - } - ArrayList<BubbleView> viewsToRemove = new ArrayList<>(); - for (BubbleView bv : mBubbles.values()) { - NotificationEntry entry = bv.getEntry(); - if (entry != null) { - if (entry.isRowRemoved() || entry.isBubbleDismissed() || entry.isRowDismissed()) { - viewsToRemove.add(bv); - } - } - } - for (BubbleView bubbleView : viewsToRemove) { - mBubbles.remove(bubbleView.getKey()); - mStackView.removeBubble(bubbleView); - bubbleView.destroyActivityView(mStackView); - } - if (mStackView != null) { - mStackView.setVisibility(visible ? VISIBLE : INVISIBLE); - if (!visible) { - collapseStack(); - } + public void updateVisibility() { + if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) { + // Bubbles only appear in unlocked shade + mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE); + } else if (mStackView != null) { + mStackView.setVisibility(INVISIBLE); + collapseStack(); } updateBubblesShowing(); } @@ -368,18 +440,41 @@ public class BubbleController { } /** - * Whether the notification should bubble or not. + * Whether the notification has been developer configured to bubble and is allowed by the user. */ - private static boolean shouldAutoBubble(Context context, NotificationEntry entry) { + private boolean shouldBubble(NotificationEntry entry) { + StatusBarNotification n = entry.notification; + boolean canAppOverlay = false; + try { + canAppOverlay = mNotificationManagerService.areAppOverlaysAllowedForPackage( + n.getPackageName(), n.getUid()); + } catch (RemoteException e) { + Log.w(TAG, "Error calling NoMan to determine if app can overlay", e); + } + + boolean canChannelOverlay = mNotificationEntryManager.getNotificationData().getChannel( + entry.key).canOverlayApps(); + boolean hasOverlayIntent = n.getNotification().getAppOverlayIntent() != null; + return hasOverlayIntent && canChannelOverlay && canAppOverlay; + } + + /** + * Whether the notification should bubble or not. Gated by debug flag. + * <p> + * If a notification has been set to bubble via proper bubble APIs or if it is an important + * message-like notification. + * </p> + */ + private boolean shouldAutoBubble(Context context, NotificationEntry entry) { if (entry.isBubbleDismissed()) { return false; } + StatusBarNotification n = entry.notification; boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE; boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE; boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE; - StatusBarNotification n = entry.notification; boolean hasRemoteInput = false; if (n.getNotification().actions != null) { for (Notification.Action action : n.getNotification().actions) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java index badefe182bdd..71ae1f8620f6 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.ShapeDrawable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; @@ -88,6 +89,7 @@ public class BubbleExpandedViewContainer extends LinearLayout { */ public void setHeaderText(CharSequence text) { mHeaderView.setText(text); + mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 3280a331a5c7..9a11b965b319 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -64,9 +64,9 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private boolean mIsExpanded; private int mExpandedBubbleHeight; + private BubbleTouchHandler mTouchHandler; private BubbleView mExpandedBubble; private Point mCollapsedPosition; - private BubbleTouchHandler mTouchHandler; private BubbleController.BubbleExpandListener mExpandListener; private boolean mViewUpdatedRequested = false; @@ -211,13 +211,24 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F */ public void setExpandedBubble(BubbleView bubbleToExpand) { mExpandedBubble = bubbleToExpand; + boolean prevExpanded = mIsExpanded; mIsExpanded = true; - updateExpandedBubble(); - requestUpdate(); + if (!prevExpanded) { + // If we weren't previously expanded we should animate open. + animateExpansion(true /* expand */); + } else { + // If we were expanded just update the views + updateExpandedBubble(); + requestUpdate(); + } + mExpandedBubble.getEntry().setShowInShadeWhenBubble(false); + notifyExpansionChanged(mExpandedBubble, true /* expanded */); } /** - * Adds a bubble to the stack. + * Adds a bubble to the top of the stack. + * + * @param bubbleView the view to add to the stack. */ public void addBubble(BubbleView bubbleView) { mBubbleContainer.addView(bubbleView, 0, @@ -234,17 +245,26 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mBubbleContainer.removeView(bubbleView); boolean wasExpanded = mIsExpanded; int bubbleCount = mBubbleContainer.getChildCount(); - if (bubbleView.equals(mExpandedBubble) && bubbleCount > 0) { + if (mIsExpanded && bubbleView.equals(mExpandedBubble) && bubbleCount > 0) { // If we have other bubbles and are expanded go to the next one or previous // if the bubble removed was last int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1; - mExpandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex); + BubbleView expandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex); + setExpandedBubble(expandedBubble); } mIsExpanded = wasExpanded && mBubbleContainer.getChildCount() > 0; - requestUpdate(); - if (wasExpanded && !mIsExpanded && mExpandListener != null) { - mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */); + if (wasExpanded != mIsExpanded) { + notifyExpansionChanged(mExpandedBubble, mIsExpanded); } + requestUpdate(); + } + + /** + * Dismiss the stack of bubbles. + */ + public void stackDismissed() { + collapseStack(); + mBubbleContainer.removeAllViews(); } /** @@ -252,11 +272,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F * * @param bubbleView the view to update in the stack. * @param entry the entry to update it with. + * @param updatePosition whether this bubble should be moved to top of the stack. */ - public void updateBubble(BubbleView bubbleView, NotificationEntry entry) { - // TODO - move to top of bubble stack, make it show its update if it makes sense + public void updateBubble(BubbleView bubbleView, NotificationEntry entry, + boolean updatePosition) { bubbleView.update(entry); - if (bubbleView.equals(mExpandedBubble)) { + if (updatePosition && !mIsExpanded) { + // If alerting it gets promoted to top of the stack + mBubbleContainer.removeView(bubbleView); + mBubbleContainer.addView(bubbleView, 0); + requestUpdate(); + } + if (mIsExpanded && bubbleView.equals(mExpandedBubble)) { + entry.setShowInShadeWhenBubble(false); requestUpdate(); } } @@ -287,17 +315,36 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } /** + * Collapses the stack of bubbles. + */ + public void collapseStack() { + if (mIsExpanded) { + // TODO: Save opened bubble & move it to top of stack + animateExpansion(false /* shouldExpand */); + notifyExpansionChanged(mExpandedBubble, mIsExpanded); + } + } + + /** + * Expands the stack fo bubbles. + */ + public void expandStack() { + if (!mIsExpanded) { + mExpandedBubble = getTopBubble(); + mExpandedBubble.getEntry().setShowInShadeWhenBubble(false); + animateExpansion(true /* shouldExpand */); + notifyExpansionChanged(mExpandedBubble, true /* expanded */); + } + } + + /** * Tell the stack to animate to collapsed or expanded state. */ - public void animateExpansion(boolean shouldExpand) { + private void animateExpansion(boolean shouldExpand) { if (mIsExpanded != shouldExpand) { mIsExpanded = shouldExpand; - mExpandedBubble = shouldExpand ? getTopBubble() : null; updateExpandedBubble(); - if (mExpandListener != null) { - mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */); - } if (shouldExpand) { // Save current position so that we might return there savePosition(); @@ -347,6 +394,13 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mCollapsedPosition = getPosition(); } + private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) { + if (mExpandListener != null) { + NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null; + mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null); + } + } + private BubbleView getTopBubble() { return getBubbleAt(0); } @@ -400,6 +454,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } if (mExpandedBubble.hasAppOverlayIntent()) { + // Bubble with activity view expanded state ActivityView expandedView = mExpandedBubble.getActivityView(); expandedView.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight)); @@ -417,19 +472,27 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F @Override public void onActivityViewDestroyed(ActivityView view) { - NotificationEntry entry = mExpandedBubble.getEntry(); + NotificationEntry entry = mExpandedBubble != null + ? mExpandedBubble.getEntry() : null; Log.d(TAG, "onActivityViewDestroyed(key=" + ((entry != null) ? entry.key : "(none)") + "): " + view); } }); } else { + // Bubble with notification view expanded state ExpandableNotificationRow row = mExpandedBubble.getRowView(); - if (!row.equals(mExpandedViewContainer.getExpandedView())) { - // Different expanded view than what we have + if (row.getParent() != null) { + // Row might still be in the shade when we expand + ((ViewGroup) row.getParent()).removeView(row); + } + if (mIsExpanded) { + mExpandedViewContainer.setExpandedView(row); + } else { mExpandedViewContainer.setExpandedView(null); } - mExpandedViewContainer.setExpandedView(row); + // Bubble with notification as expanded state doesn't need a header / title mExpandedViewContainer.setHeaderText(null); + } int pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2); @@ -456,7 +519,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F int bubbsCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbsCount; i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); - bv.setZ(bubbsCount - 1); + bv.updateDotVisibility(); + bv.setZ(bubbsCount - i); int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i; ViewState viewState = new ViewState(); @@ -510,6 +574,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private void applyRowState(ExpandableNotificationRow view) { view.reset(); view.setHeadsUp(false); + view.resetTranslation(); view.setOnKeyguard(false); view.setOnAmbient(false); view.setClipBottomAmount(0); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 96b2dbab9bdf..97784b0f4f93 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -110,7 +110,7 @@ class BubbleTouchHandler implements View.OnTouchListener { : stack.getTargetView(event); boolean isFloating = targetView instanceof FloatingView; if (!isFloating || targetView == null || action == MotionEvent.ACTION_OUTSIDE) { - stack.animateExpansion(false /* shouldExpand */); + stack.collapseStack(); cleanUpDismissTarget(); resetTouches(); return false; @@ -196,9 +196,13 @@ class BubbleTouchHandler implements View.OnTouchListener { mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start(); } } else if (floatingView.equals(stack.getExpandedBubble())) { - stack.animateExpansion(false /* shouldExpand */); + stack.collapseStack(); } else if (isBubbleStack) { - stack.animateExpansion(!stack.isExpanded() /* shouldExpand */); + if (stack.isExpanded()) { + stack.collapseStack(); + } else { + stack.expandStack(); + } } else { stack.setExpandedBubble((BubbleView) floatingView); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index c1bbb9379e9c..91893ef3db00 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,47 @@ package com.android.systemui.bubbles; +import android.annotation.Nullable; import android.app.ActivityView; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; import android.graphics.Point; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; +import android.graphics.drawable.InsetDrawable; +import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; +import android.widget.FrameLayout; +import android.widget.TextView; -import com.android.internal.util.ContrastColorUtil; +import com.android.internal.graphics.ColorUtils; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** - * A floating object on the screen that has a collapsed and expanded state. + * A floating object on the screen that can post message updates. */ -class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView { +public class BubbleView extends FrameLayout implements BubbleTouchHandler.FloatingView { private static final String TAG = "BubbleView"; + // Same value as Launcher3 badge code + private static final float WHITE_SCRIM_ALPHA = 0.54f; private Context mContext; - private View mIconView; + + private BadgedImageView mBadgedImageView; + private TextView mMessageView; + private int mPadding; + private int mIconInset; private NotificationEntry mEntry; - private int mBubbleSize; - private int mIconSize; private PendingIntent mAppOverlayIntent; private ActivityView mActivityView; @@ -67,66 +74,156 @@ class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - setOrientation(LinearLayout.VERTICAL); mContext = context; - mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); - mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size); + // XXX: can this padding just be on the view and we look it up? + mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding); + mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image); + mMessageView = (TextView) findViewById(R.id.message_view); + mMessageView.setVisibility(GONE); + mMessageView.setPivotX(0); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updateViews(); + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + measureChild(mBadgedImageView, widthSpec, heightSpec); + measureChild(mMessageView, widthSpec, heightSpec); + boolean messageGone = mMessageView.getVisibility() == GONE; + int imageHeight = mBadgedImageView.getMeasuredHeight(); + int imageWidth = mBadgedImageView.getMeasuredWidth(); + int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight(); + int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth(); + setMeasuredDimension( + getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(), + getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom()); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + left = getPaddingStart(); + top = getPaddingTop(); + int imageWidth = mBadgedImageView.getMeasuredWidth(); + int imageHeight = mBadgedImageView.getMeasuredHeight(); + int messageWidth = mMessageView.getMeasuredWidth(); + int messageHeight = mMessageView.getMeasuredHeight(); + mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight); + mMessageView.layout(left + imageWidth + mPadding, top, + left + imageWidth + mPadding + messageWidth, top + messageHeight); } /** * Populates this view with a notification. + * <p> + * This should only be called when a new notification is being set on the view, updates to the + * current notification should use {@link #update(NotificationEntry)}. * * @param entry the notification to display as a bubble. */ public void setNotif(NotificationEntry entry) { - removeAllViews(); - // TODO: migrate to inflater - mIconView = new ImageView(mContext); - addView(mIconView); + mEntry = entry; + updateViews(); + } - LinearLayout.LayoutParams iconLp = (LinearLayout.LayoutParams) mIconView.getLayoutParams(); - iconLp.width = mBubbleSize; - iconLp.height = mBubbleSize; - mIconView.setLayoutParams(iconLp); + /** + * The {@link NotificationEntry} associated with this view, if one exists. + */ + @Nullable + public NotificationEntry getEntry() { + return mEntry; + } - update(entry); + /** + * The key for the {@link NotificationEntry} associated with this view, if one exists. + */ + @Nullable + public String getKey() { + return (mEntry != null) ? mEntry.key : null; } /** - * Updates the UI based on the entry. + * Updates the UI based on the entry, updates badge and animates messages as needed. */ public void update(NotificationEntry entry) { mEntry = entry; - Notification n = entry.notification.getNotification(); - Icon ic = n.getLargeIcon() != null ? n.getLargeIcon() : n.getSmallIcon(); - - if (n.getLargeIcon() == null) { - createCircledIcon(n.color, ic, ((ImageView) mIconView)); - } else { - ((ImageView) mIconView).setImageIcon(ic); - } + updateViews(); } + /** - * @return the key identifying this bubble / notification entry associated with this - * bubble, if it exists. + * @return the {@link ExpandableNotificationRow} view to display notification content when the + * bubble is expanded. */ - public String getKey() { - return mEntry == null ? null : mEntry.key; + @Nullable + public ExpandableNotificationRow getRowView() { + return (mEntry != null) ? mEntry.getRow() : null; } /** - * @return the notification entry associated with this bubble. + * Marks this bubble as "read", i.e. no badge should show. */ - public NotificationEntry getEntry() { - return mEntry; + public void updateDotVisibility() { + boolean showDot = getEntry().showInShadeWhenBubble(); + animateDot(showDot); } /** - * @return the view to display notification content when the bubble is expanded. + * Animates the badge to show or hide. */ - public ExpandableNotificationRow getRowView() { - return mEntry.getRow(); + private void animateDot(boolean showDot) { + if (mBadgedImageView.isShowingDot() != showDot) { + mBadgedImageView.setShowDot(showDot); + mBadgedImageView.clearAnimation(); + mBadgedImageView.animate().setDuration(200) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setUpdateListener((valueAnimator) -> { + float fraction = valueAnimator.getAnimatedFraction(); + fraction = showDot ? fraction : 1 - fraction; + mBadgedImageView.setDotScale(fraction); + }).withEndAction(() -> { + if (!showDot) { + mBadgedImageView.setShowDot(false); + } + }).start(); + } + } + + private void updateViews() { + if (mEntry == null) { + return; + } + Notification n = mEntry.notification.getNotification(); + boolean isLarge = n.getLargeIcon() != null; + Icon ic = isLarge ? n.getLargeIcon() : n.getSmallIcon(); + Drawable iconDrawable = ic.loadDrawable(mContext); + if (!isLarge) { + // Center icon on coloured background + iconDrawable.setTint(Color.WHITE); // TODO: dark mode + Drawable bg = new ColorDrawable(n.color); + InsetDrawable d = new InsetDrawable(iconDrawable, mIconInset); + Drawable[] layers = {bg, d}; + mBadgedImageView.setImageDrawable(new LayerDrawable(layers)); + } else { + mBadgedImageView.setImageDrawable(iconDrawable); + } + int badgeColor = determineDominateColor(iconDrawable, n.color); + mBadgedImageView.setDotColor(badgeColor); + animateDot(mEntry.showInShadeWhenBubble() /* showDot */); + } + + private int determineDominateColor(Drawable d, int defaultTint) { + // XXX: should we pull from the drawable, app icon, notif tint? + return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA); } /** @@ -170,8 +267,8 @@ class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView @Override public void setPosition(int x, int y) { - setTranslationX(x); - setTranslationY(y); + setPositionX(x); + setPositionY(y); } @Override @@ -189,25 +286,6 @@ class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView return new Point((int) getTranslationX(), (int) getTranslationY()); } - // Seems sub optimal - private void createCircledIcon(int tint, Icon icon, ImageView v) { - // TODO: dark mode - icon.setTint(Color.WHITE); - icon.scaleDownIfNecessary(mIconSize, mIconSize); - v.setImageDrawable(icon.loadDrawable(mContext)); - v.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams(); - int color = ContrastColorUtil.ensureContrast(tint, Color.WHITE, - false /* isBgDarker */, 3); - Drawable d = new ShapeDrawable(new OvalShape()); - d.setTint(color); - v.setBackgroundDrawable(d); - - lp.width = mBubbleSize; - lp.height = mBubbleSize; - v.setLayoutParams(lp); - } - /** * @return whether an ActivityView should be used to display the content of this Bubble */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index bf6caa010e9b..f2ff85bb226c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.StatusBarState.SHADE; - import android.content.Context; import android.content.res.Resources; import android.os.Trace; @@ -26,7 +24,6 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; -import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -66,7 +63,6 @@ public class NotificationViewHierarchyManager { protected final VisualStabilityManager mVisualStabilityManager; private final StatusBarStateController mStatusBarStateController; private final NotificationEntryManager mEntryManager; - private final BubbleController mBubbleController; // Lazy private final Lazy<ShadeController> mShadeController; @@ -80,41 +76,6 @@ public class NotificationViewHierarchyManager { private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; - private StatusBarStateListener mStatusBarStateListener; - - /** - * Listens for the current state of the status bar and updates the visibility state - * of bubbles as needed. - */ - public class StatusBarStateListener implements StatusBarStateController.StateListener { - private int mState; - private BubbleController mController; - - public StatusBarStateListener(BubbleController controller) { - mController = controller; - } - - /** - * Returns the current status bar state. - */ - public int getCurrentState() { - return mState; - } - - @Override - public void onStateChanged(int newState) { - mState = newState; - // Order here matters because we need to remove the expandable notification row - // from it's current parent (NSSL or bubble) before it can be added to the new parent - if (mState == SHADE) { - updateNotificationViews(); - mController.updateVisibility(true); - } else { - mController.updateVisibility(false); - updateNotificationViews(); - } - } - } @Inject public NotificationViewHierarchyManager(Context context, @@ -123,20 +84,16 @@ public class NotificationViewHierarchyManager { VisualStabilityManager visualStabilityManager, StatusBarStateController statusBarStateController, NotificationEntryManager notificationEntryManager, - BubbleController bubbleController, Lazy<ShadeController> shadeController) { mLockscreenUserManager = notificationLockscreenUserManager; mGroupManager = groupManager; mVisualStabilityManager = visualStabilityManager; mStatusBarStateController = statusBarStateController; mEntryManager = notificationEntryManager; - mBubbleController = bubbleController; mShadeController = shadeController; Resources res = context.getResources(); mAlwaysExpandNonGroupedNotification = res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); - mStatusBarStateListener = new StatusBarStateListener(mBubbleController); - mStatusBarStateController.addCallback(mStatusBarStateListener); } public void setUpWithPresenter(NotificationPresenter presenter, @@ -153,7 +110,6 @@ public class NotificationViewHierarchyManager { ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData() .getActiveNotifications(); ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); - ArrayList<NotificationEntry> toBubble = new ArrayList<>(); final int N = activeNotifications.size(); for (int i = 0; i < N; i++) { NotificationEntry ent = activeNotifications.get(i); @@ -162,13 +118,6 @@ public class NotificationViewHierarchyManager { // temporarily become children if they were isolated before. continue; } - ent.getRow().setStatusBarState(mStatusBarStateListener.getCurrentState()); - boolean showAsBubble = ent.isBubble() && !ent.isBubbleDismissed() - && mStatusBarStateListener.getCurrentState() == SHADE; - if (showAsBubble) { - toBubble.add(ent); - continue; - } int userId = ent.notification.getUserId(); @@ -269,12 +218,6 @@ public class NotificationViewHierarchyManager { } - for (int i = 0; i < toBubble.size(); i++) { - // TODO: might make sense to leave them in the shade and just reposition them - NotificationEntry ent = toBubble.get(i); - mBubbleController.addBubble(ent); - } - mVisualStabilityManager.onReorderingFinished(); // clear the map again for the next usage mTmpChildOrderMap.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java index 60d8cf460627..5605f3db90fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java @@ -150,7 +150,14 @@ public class NotificationAlertingManager { } } - private static boolean alertAgain( + /** + * Checks whether an update for a notification warrants an alert for the user. + * + * @param oldEntry the entry for this notification. + * @param newNotification the new notification for this entry. + * @return whether this notification should alert the user. + */ + public static boolean alertAgain( NotificationEntry oldEntry, Notification newNotification) { return oldEntry == null || !oldEntry.hasInterrupted() || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index e199ead18a68..154d7b356cd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -134,6 +134,10 @@ public class NotificationFilter { } } + if (entry.isBubble() && !entry.showInShadeWhenBubble()) { + return true; + } + return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java index fc7a2b37eca7..c50f10b55a71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java @@ -135,6 +135,29 @@ public class NotificationInterruptionStateProvider { } /** + * Whether the notification should appear as a bubble with a fly-out on top of the screen. + * + * @param entry the entry to check + * @return true if the entry should bubble up, false otherwise + */ + public boolean shouldBubbleUp(NotificationEntry entry) { + StatusBarNotification sbn = entry.notification; + if (!entry.isBubble()) { + if (DEBUG) { + Log.d(TAG, "No bubble up: notification " + sbn.getKey() + + " is bubble? " + entry.isBubble()); + } + return false; + } + + if (!canHeadsUpCommon(entry)) { + return false; + } + + return true; + } + + /** * Whether the notification should peek in from the top and alert the user. * * @param entry the entry to check @@ -150,10 +173,12 @@ public class NotificationInterruptionStateProvider { return false; } - // TODO: need to changes this, e.g. should still heads up in expanded shade, might want - // message bubble from the bubble to go through heads up path boolean inShade = mStatusBarStateController.getState() == SHADE; - if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) { + if (entry.isBubble() && inShade) { + if (DEBUG) { + Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a " + + "bubble: " + sbn.getKey()); + } return false; } @@ -164,9 +189,13 @@ public class NotificationInterruptionStateProvider { return false; } - if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (!canHeadsUpCommon(entry)) { + return false; + } + + if (entry.importance < NotificationManager.IMPORTANCE_HIGH) { if (DEBUG) { - Log.d(TAG, "No heads up: no huns or vr mode"); + Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey()); } return false; } @@ -186,34 +215,6 @@ public class NotificationInterruptionStateProvider { return false; } - if (entry.shouldSuppressPeek()) { - if (DEBUG) { - Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey()); - } - return false; - } - - if (isSnoozedPackage(sbn)) { - if (DEBUG) { - Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey()); - } - return false; - } - - if (entry.hasJustLaunchedFullScreenIntent()) { - if (DEBUG) { - Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey()); - } - return false; - } - - if (entry.importance < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey()); - } - return false; - } - if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) { return false; } @@ -302,6 +303,49 @@ public class NotificationInterruptionStateProvider { return true; } + /** + * Common checks between heads up alerting and bubble fly out alerting. See + * {@link #shouldHeadsUp(NotificationEntry)} and + * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these + * checks should not interrupt the user on screen. + * + * @param entry the entry to check + * @return true if these checks pass, false if the notification should not interrupt on screen + */ + public boolean canHeadsUpCommon(NotificationEntry entry) { + StatusBarNotification sbn = entry.notification; + + if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (DEBUG) { + Log.d(TAG, "No heads up: no huns or vr mode"); + } + return false; + } + + if (entry.shouldSuppressPeek()) { + if (DEBUG) { + Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey()); + } + return false; + } + + if (isSnoozedPackage(sbn)) { + if (DEBUG) { + Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey()); + } + return false; + } + + if (entry.hasJustLaunchedFullScreenIntent()) { + if (DEBUG) { + Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey()); + } + return false; + } + + return true; + } + private boolean isSnoozedPackage(StatusBarNotification sbn) { return mHeadsUpManager.isSnoozed(sbn.getPackageName()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 58aa02ccf440..ee551ee96e7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -141,6 +141,14 @@ public final class NotificationEntry { private boolean mIsBubble; /** + * Whether this notification should be shown in the shade when it is also displayed as a bubble. + * + * <p>When a notification is a bubble we don't show it in the shade once the bubble has been + * expanded</p> + */ + private boolean mShowInShadeWhenBubble; + + /** * Whether the user has dismissed this notification when it was in bubble form. */ private boolean mUserDismissedBubble; @@ -200,6 +208,23 @@ public final class NotificationEntry { } /** + * Sets whether this notification should be shown in the shade when it is also displayed as a + * bubble. + */ + public void setShowInShadeWhenBubble(boolean showInShade) { + mShowInShadeWhenBubble = showInShade; + } + + /** + * Whether this notification should be shown in the shade when it is also displayed as a + * bubble. + */ + public boolean showInShadeWhenBubble() { + // We always show it in the shade if non-clearable + return !isClearable() || mShowInShadeWhenBubble; + } + + /** * Resets the notification entry to be re-used. */ public void reset() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 95bd1ce1f9a1..df0189fc4c4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; @@ -2322,7 +2321,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private boolean isShownAsBubble() { - return mEntry.isBubble() && (mStatusBarState == SHADE || mStatusBarState == -1); + return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 3f931920081d..514bb228cd97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -461,13 +461,6 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationMediaManager mMediaManager; protected NotificationLockscreenUserManager mLockscreenUserManager; protected NotificationRemoteInputManager mRemoteInputManager; - protected BubbleController mBubbleController; - private final BubbleController.BubbleExpandListener mBubbleExpandListener = - (isExpanding, amount) -> { - if (amount == 1) { - updateScrimController(); - } - }; private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { @Override @@ -589,6 +582,12 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationActivityStarter mNotificationActivityStarter; private boolean mPulsing; private ContentObserver mFeatureFlagObserver; + protected BubbleController mBubbleController; + private final BubbleController.BubbleExpandListener mBubbleExpandListener = + (isExpanding, key) -> { + mEntryManager.updateNotifications(); + updateScrimController(); + }; @Override public void onActiveStateChanged(int code, int uid, String packageName, boolean active) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 4f61009095c7..04d24dc18f05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -346,7 +346,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } private void handleFullScreenIntent(NotificationEntry entry) { - boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry); + boolean isHeadsUped = mNotificationInterruptionStateProvider.canHeadsUpCommon(entry); if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) { if (shouldSuppressFullScreenIntent(entry)) { if (DEBUG) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 21d3652adca7..fa5cf04d56f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -19,7 +19,6 @@ package com.android.systemui.bubbles; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -74,7 +73,8 @@ public class BubbleControllerTest extends SysuiTestCase { private ExpandableNotificationRow mRow; private ExpandableNotificationRow mRow2; - private final NotificationData mNotificationData = new NotificationData(); + @Mock + private NotificationData mNotificationData; @Before public void setUp() throws Exception { @@ -93,6 +93,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Return non-null notification data from the NEM when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); + when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel); mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController); @@ -103,26 +104,21 @@ public class BubbleControllerTest extends SysuiTestCase { } @Test - public void testIsBubble() { - assertTrue(mRow.getEntry().isBubble()); - } - - @Test public void testAddBubble() { - mBubbleController.addBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); assertTrue(mBubbleController.hasBubbles()); } @Test public void testHasBubbles() { assertFalse(mBubbleController.hasBubbles()); - mBubbleController.addBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); assertTrue(mBubbleController.hasBubbles()); } @Test public void testRemoveBubble() { - mBubbleController.addBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); assertTrue(mBubbleController.hasBubbles()); mBubbleController.removeBubble(mRow.getEntry().key); @@ -133,35 +129,35 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testDismissStack() { - mBubbleController.addBubble(mRow.getEntry()); - mBubbleController.addBubble(mRow2.getEntry()); + mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); + mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */); assertTrue(mBubbleController.hasBubbles()); mBubbleController.dismissStack(); assertFalse(mStatusBarWindowController.getBubblesShowing()); - verify(mNotificationEntryManager, times(3)).updateNotifications(); + verify(mNotificationEntryManager).updateNotifications(); } @Test public void testIsStackExpanded() { assertFalse(mBubbleController.isStackExpanded()); - mBubbleController.addBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); BubbleStackView stackView = mBubbleController.getStackView(); - stackView.animateExpansion(true /* expanded */); + stackView.expandStack(); assertTrue(mBubbleController.isStackExpanded()); - stackView.animateExpansion(false /* expanded */); + stackView.collapseStack(); assertFalse(mBubbleController.isStackExpanded()); } @Test public void testCollapseStack() { - mBubbleController.addBubble(mRow.getEntry()); - mBubbleController.addBubble(mRow2.getEntry()); + mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); + mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */); BubbleStackView stackView = mBubbleController.getStackView(); - stackView.animateExpansion(true /* expanded */); + stackView.expandStack(); assertTrue(mBubbleController.isStackExpanded()); mBubbleController.collapseStack(); @@ -174,6 +170,12 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mRow.getEntry().isBubble()); } + @Test + public void testMarkNewNotificationAsShowInShade() { + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + assertTrue(mRow.getEntry().showInShadeWhenBubble()); + } + static class TestableBubbleController extends BubbleController { TestableBubbleController(Context context, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 529da8251c57..2b13f864b9a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -17,13 +17,16 @@ package com.android.systemui.statusbar; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Instrumentation; import android.app.Notification; import android.app.NotificationChannel; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.support.test.InstrumentationRegistry; @@ -86,8 +89,7 @@ public class NotificationTestHelper { * @throws Exception */ public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception { - return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */, - false /* isBubble */); + return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */); } /** @@ -98,8 +100,7 @@ public class NotificationTestHelper { * @throws Exception */ public ExpandableNotificationRow createRow(Notification notification) throws Exception { - return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */, - false /* isBubble */); + return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */); } /** @@ -112,8 +113,7 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags) throws Exception { - return generateRow(createNotification(), PKG, UID, extraInflationFlags, - false /* isBubble */); + return generateRow(createNotification(), PKG, UID, extraInflationFlags); } /** @@ -134,20 +134,21 @@ public class NotificationTestHelper { return createGroup(2); } - /** - * Retursn an {@link ExpandableNotificationRow} that should be a bubble. - */ - public ExpandableNotificationRow createBubble() throws Exception { - return createRow(PKG, UID, false /* isGroupSummary */, null /* groupKey */, - true /* isBubble */); - } - private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception { - return createRow(PKG, UID, true /* isGroupSummary */, groupkey, false); + return createRow(PKG, UID, true /* isGroupSummary */, groupkey); } private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception { - return createRow(PKG, UID, false /* isGroupSummary */, groupkey, false); + return createRow(PKG, UID, false /* isGroupSummary */, groupkey); + } + + /** + * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. + */ + public ExpandableNotificationRow createBubble() throws Exception { + Notification n = createNotification(false /* isGroupSummary */, + null /* groupKey */, true /* isBubble */); + return generateRow(n, PKG, UID, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); } /** @@ -157,7 +158,6 @@ public class NotificationTestHelper { * @param uid uid used for creating a {@link StatusBarNotification} * @param isGroupSummary whether the notification row is a group summary * @param groupKey the group key for the notification group used across notifications - * @param isBubble * @return a row with that's either a standalone notification or a group notification if the * groupKey is non-null * @throws Exception @@ -166,10 +166,10 @@ public class NotificationTestHelper { String pkg, int uid, boolean isGroupSummary, - @Nullable String groupKey, boolean isBubble) + @Nullable String groupKey) throws Exception { Notification notif = createNotification(isGroupSummary, groupKey); - return generateRow(notif, pkg, uid, 0 /* inflationFlags */, isBubble); + return generateRow(notif, pkg, uid, 0 /* inflationFlags */); } /** @@ -188,8 +188,20 @@ public class NotificationTestHelper { * @param groupKey the group key for the notification group used across notifications * @return a notification that is in the group specified or standalone if unspecified */ + private Notification createNotification(boolean isGroupSummary, @Nullable String groupKey) { + return createNotification(isGroupSummary, groupKey, false /* isBubble */); + } + + /** + * Creates a notification with the given parameters. + * + * @param isGroupSummary whether the notification is a group summary + * @param groupKey the group key for the notification group used across notifications + * @param isBubble whether this notification should bubble + * @return a notification that is in the group specified or standalone if unspecified + */ private Notification createNotification(boolean isGroupSummary, - @Nullable String groupKey) { + @Nullable String groupKey, boolean isBubble) { Notification publicVersion = new Notification.Builder(mContext).setSmallIcon( R.drawable.ic_person) .setCustomContentView(new RemoteViews(mContext.getPackageName(), @@ -207,6 +219,10 @@ public class NotificationTestHelper { if (!TextUtils.isEmpty(groupKey)) { notificationBuilder.setGroup(groupKey); } + if (isBubble) { + PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); + notificationBuilder.setAppOverlayIntent(bubbleIntent); + } return notificationBuilder.build(); } @@ -214,7 +230,17 @@ public class NotificationTestHelper { Notification notification, String pkg, int uid, - @InflationFlag int extraInflationFlags, boolean isBubble) + @InflationFlag int extraInflationFlags) + throws Exception { + return generateRow(notification, pkg, uid, extraInflationFlags, IMPORTANCE_DEFAULT); + } + + private ExpandableNotificationRow generateRow( + Notification notification, + String pkg, + int uid, + @InflationFlag int extraInflationFlags, + int importance) throws Exception { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( mContext.LAYOUT_INFLATER_SERVICE); @@ -242,9 +268,8 @@ public class NotificationTestHelper { entry.setRow(row); entry.createIcons(mContext, sbn); entry.channel = new NotificationChannel( - notification.getChannelId(), notification.getChannelId(), IMPORTANCE_DEFAULT); + notification.getChannelId(), notification.getChannelId(), importance); entry.channel.setBlockableSystem(true); - entry.setIsBubble(isBubble); row.setEntry(entry); row.getNotificationInflater().addInflationFlags(extraInflationFlags); NotificationInflaterTest.runThenWaitForInflation( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index bf91305137e2..56e1fc6b70de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -36,7 +36,6 @@ import android.widget.LinearLayout; import com.android.systemui.Dependency; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; -import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.VisualStabilityManager; @@ -96,7 +95,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mViewHierarchyManager = new NotificationViewHierarchyManager(mContext, mLockscreenUserManager, mGroupManager, mVisualStabilityManager, - mock(StatusBarStateController.class), mEntryManager, mock(BubbleController.class), + mock(StatusBarStateController.class), mEntryManager, () -> mShadeController); Dependency.get(InitController.class).executePostInitTasks(); mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer); diff --git a/packages/WallpaperCropper/Android.mk b/packages/WallpaperCropper/Android.mk index 848f2bd1a6fe..2fa1ddee0fd9 100644 --- a/packages/WallpaperCropper/Android.mk +++ b/packages/WallpaperCropper/Android.mk @@ -8,6 +8,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := WallpaperCropper LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_PRIVILEGED_MODULE := true LOCAL_PROGUARD_FLAG_FILES := proguard.flags diff --git a/packages/WallpaperCropper/CleanSpec.mk b/packages/WallpaperCropper/CleanSpec.mk new file mode 100644 index 000000000000..e6d8d5a774f1 --- /dev/null +++ b/packages/WallpaperCropper/CleanSpec.mk @@ -0,0 +1,50 @@ +# Copyright (C) 2019 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/WallpaperCropper) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 5b45a08af9b4..44edb568573c 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6794,6 +6794,13 @@ message MetricsEvent { // OS: Q NOTIFICATION_BLOCKING_HELPER = 1621; + // ACTION: Tap & Pay -> Default Application Setting -> Use Forground + // OS: Q + ACTION_NFC_PAYMENT_FOREGROUND_SETTING = 1622; + + // ACTION: Tap & Pay -> Default Application Setting -> Use Default + // OS: Q + ACTION_NFC_PAYMENT_ALWAYS_SETTING = 1623; // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 6cccd62d563f..24fd7b9ae71d 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -19,6 +19,7 @@ package com.android.server.autofill; import static android.Manifest.permission.MANAGE_AUTO_FILL; import static android.content.Context.AUTOFILL_MANAGER_SERVICE; import static android.util.DebugUtils.flagsToString; +import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sFullScreenMode; @@ -101,8 +102,6 @@ public final class AutofillManagerService private static final Object sLock = AutofillManagerService.class; - private static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes - /** * IME supports Smart Suggestions. */ diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 239a386aefcc..a8ff9b0d5a3f 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -168,7 +168,7 @@ final class RemoteAugmentedAutofillService } }; - // TODO(b/111330312): set cancellation signal, timeout (from both mClient and service), + // TODO(b/122728762): set cancellation signal, timeout (from both mClient and service), // cache IAugmentedAutofillManagerClient reference, etc... try { mClient.getAugmentedAutofillClient(receiver); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 0348f2bf1c2b..cf4963c1bebf 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2581,7 +2581,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final RemoteAugmentedAutofillService remoteService = mService .getRemoteAugmentedAutofillServiceLocked(); - if (remoteService == null) return null; + if (remoteService == null) { + if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user"); + return null; + } // Define which mode will be used final int mode; diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 12db4f37da58..ff378b373775 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -149,6 +149,9 @@ public class BackupManagerService { if (userBackupManagerService != null) { userBackupManagerService.tearDownService(); + + KeyValueBackupJob.cancel(userId, mContext); + FullBackupJob.cancel(userId, mContext); } } @@ -577,9 +580,9 @@ public class BackupManagerService { * @return Whether ongoing work will continue. The return value here will be passed along as the * return value to the callback {@link JobService#onStartJob(JobParameters)}. */ - public boolean beginFullBackup(FullBackupJob scheduledJob) { + public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) { UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "beginFullBackup()"); + getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()"); return userBackupManagerService != null && userBackupManagerService.beginFullBackup(scheduledJob); @@ -589,9 +592,9 @@ public class BackupManagerService { * Used by the {@link JobScheduler} to end the current full backup task when conditions are no * longer met for running the full backup job. */ - public void endFullBackup() { + public void endFullBackup(@UserIdInt int userId) { UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "endFullBackup()"); + getServiceForUserIfCallerHasPermission(userId, "endFullBackup()"); if (userBackupManagerService != null) { userBackupManagerService.endFullBackup(); diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java index 5708b1c36822..33d21cb0bf91 100644 --- a/services/backup/java/com/android/server/backup/FullBackupJob.java +++ b/services/backup/java/com/android/server/backup/FullBackupJob.java @@ -22,18 +22,30 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Bundle; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; public class FullBackupJob extends JobService { + private static final String USER_ID_EXTRA_KEY = "userId"; + + @VisibleForTesting + static final int MIN_JOB_ID = 52418896; + @VisibleForTesting + static final int MAX_JOB_ID = 52419896; + private static ComponentName sIdleService = new ComponentName("android", FullBackupJob.class.getName()); - private static final int JOB_ID = 0x5038; - - private JobParameters mParams; + @GuardedBy("mParamsForUser") + private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>(); - public static void schedule(Context ctx, long minDelay, BackupManagerConstants constants) { + public static void schedule(int userId, Context ctx, long minDelay, + BackupManagerConstants constants) { JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); - JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sIdleService); + JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService); synchronized (constants) { builder.setRequiresDeviceIdle(true) .setRequiredNetworkType(constants.getFullBackupRequiredNetworkType()) @@ -42,14 +54,28 @@ public class FullBackupJob extends JobService { if (minDelay > 0) { builder.setMinimumLatency(minDelay); } + + Bundle extraInfo = new Bundle(); + extraInfo.putInt(USER_ID_EXTRA_KEY, userId); + builder.setTransientExtras(extraInfo); + js.schedule(builder.build()); } + public static void cancel(int userId, Context ctx) { + JobScheduler js = (JobScheduler) ctx.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + js.cancel(getJobIdForUserId(userId)); + } + // callback from the Backup Manager Service: it's finished its work for this pass - public void finishBackupPass() { - if (mParams != null) { - jobFinished(mParams, false); - mParams = null; + public void finishBackupPass(int userId) { + synchronized (mParamsForUser) { + JobParameters jobParameters = mParamsForUser.get(userId); + if (jobParameters != null) { + jobFinished(jobParameters, false); + mParamsForUser.remove(userId); + } } } @@ -57,19 +83,33 @@ public class FullBackupJob extends JobService { @Override public boolean onStartJob(JobParameters params) { - mParams = params; + int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY); + + synchronized (mParamsForUser) { + mParamsForUser.put(userId, params); + } + Trampoline service = BackupManagerService.getInstance(); - return service.beginFullBackup(this); + return service.beginFullBackup(userId, this); } @Override public boolean onStopJob(JobParameters params) { - if (mParams != null) { - mParams = null; - Trampoline service = BackupManagerService.getInstance(); - service.endFullBackup(); + int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY); + + synchronized (mParamsForUser) { + if (mParamsForUser.removeReturnOld(userId) == null) { + return false; + } } + + Trampoline service = BackupManagerService.getInstance(); + service.endFullBackup(userId); + return false; } + private static int getJobIdForUserId(int userId) { + return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId); + } } diff --git a/services/backup/java/com/android/server/backup/JobIdManager.java b/services/backup/java/com/android/server/backup/JobIdManager.java new file mode 100644 index 000000000000..2e834dbfe245 --- /dev/null +++ b/services/backup/java/com/android/server/backup/JobIdManager.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +/** + * Allocates job IDs for {@link FullBackupJob} and {@link KeyValueBackupJob} + */ +public class JobIdManager { + public static int getJobIdForUserId(int minJobId, int maxJobId, int userId) { + if (minJobId + userId > maxJobId) { + throw new RuntimeException("No job IDs available in the given range"); + } + + return minJobId + userId; + } +} diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java index f2e74352b004..d43859ebeeda 100644 --- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java +++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java @@ -25,8 +25,14 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; +import android.util.SparseBooleanArray; +import android.util.SparseLongArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.Random; @@ -38,7 +44,8 @@ public class KeyValueBackupJob extends JobService { private static final String TAG = "KeyValueBackupJob"; private static ComponentName sKeyValueJobService = new ComponentName("android", KeyValueBackupJob.class.getName()); - private static final int JOB_ID = 0x5039; + + private static final String USER_ID_EXTRA_KEY = "userId"; // Once someone asks for a backup, this is how long we hold off until we find // an on-charging opportunity. If we hit this max latency we will run the operation @@ -46,16 +53,22 @@ public class KeyValueBackupJob extends JobService { // BackupManager.backupNow(). private static final long MAX_DEFERRAL = AlarmManager.INTERVAL_DAY; - private static boolean sScheduled = false; - private static long sNextScheduled = 0; + @GuardedBy("KeyValueBackupJob.class") + private static final SparseBooleanArray sScheduledForUserId = new SparseBooleanArray(); + @GuardedBy("KeyValueBackupJob.class") + private static final SparseLongArray sNextScheduledForUserId = new SparseLongArray(); + + private static final int MIN_JOB_ID = 52417896; + private static final int MAX_JOB_ID = 52418896; - public static void schedule(Context ctx, BackupManagerConstants constants) { - schedule(ctx, 0, constants); + public static void schedule(int userId, Context ctx, BackupManagerConstants constants) { + schedule(userId, ctx, 0, constants); } - public static void schedule(Context ctx, long delay, BackupManagerConstants constants) { + public static void schedule(int userId, Context ctx, long delay, + BackupManagerConstants constants) { synchronized (KeyValueBackupJob.class) { - if (sScheduled) { + if (sScheduledForUserId.get(userId)) { return; } @@ -76,51 +89,61 @@ public class KeyValueBackupJob extends JobService { if (DEBUG_SCHEDULING) { Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes"); } - JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService) + + JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), + sKeyValueJobService) .setMinimumLatency(delay) .setRequiredNetworkType(networkType) .setRequiresCharging(needsCharging) .setOverrideDeadline(MAX_DEFERRAL); + + Bundle extraInfo = new Bundle(); + extraInfo.putInt(USER_ID_EXTRA_KEY, userId); + builder.setTransientExtras(extraInfo); + JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); js.schedule(builder.build()); - sNextScheduled = System.currentTimeMillis() + delay; - sScheduled = true; + sScheduledForUserId.put(userId, true); + sNextScheduledForUserId.put(userId, System.currentTimeMillis() + delay); } } - public static void cancel(Context ctx) { + public static void cancel(int userId, Context ctx) { synchronized (KeyValueBackupJob.class) { - JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); - js.cancel(JOB_ID); - sNextScheduled = 0; - sScheduled = false; + JobScheduler js = (JobScheduler) ctx.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + js.cancel(getJobIdForUserId(userId)); + + clearScheduledForUserId(userId); } } - public static long nextScheduled() { + public static long nextScheduled(int userId) { synchronized (KeyValueBackupJob.class) { - return sNextScheduled; + return sNextScheduledForUserId.get(userId); } } - public static boolean isScheduled() { + @VisibleForTesting + public static boolean isScheduled(int userId) { synchronized (KeyValueBackupJob.class) { - return sScheduled; + return sScheduledForUserId.get(userId); } } @Override public boolean onStartJob(JobParameters params) { + int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY); + synchronized (KeyValueBackupJob.class) { - sNextScheduled = 0; - sScheduled = false; + clearScheduledForUserId(userId); } // Time to run a key/value backup! Trampoline service = BackupManagerService.getInstance(); try { - service.backupNow(); + service.backupNowForUser(userId); } catch (RemoteException e) {} // This was just a trigger; ongoing wakelock management is done by the @@ -134,4 +157,13 @@ public class KeyValueBackupJob extends JobService { return false; } + @GuardedBy("KeyValueBackupJob.class") + private static void clearScheduledForUserId(int userId) { + sScheduledForUserId.delete(userId); + sNextScheduledForUserId.delete(userId); + } + + private static int getJobIdForUserId(int userId) { + return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId); + } } diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java index e4ce62d0a5b5..4a1e5b9910bf 100644 --- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java +++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java @@ -23,9 +23,9 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.content.pm.SigningInfo; import android.os.Build; @@ -49,9 +49,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Set; - import java.util.Objects; +import java.util.Set; /** * We back up the signatures of each package so that during a system restore, @@ -95,6 +94,7 @@ public class PackageManagerBackupAgent extends BackupAgent { // is coming from pre-Android P device. private static final int UNDEFINED_ANCESTRAL_RECORD_VERSION = -1; + private int mUserId; private List<PackageInfo> mAllPackages; private PackageManager mPackageManager; // version & signature info of each app in a restore set @@ -129,17 +129,18 @@ public class PackageManagerBackupAgent extends BackupAgent { // We're constructed with the set of applications that are participating // in backup. This set changes as apps are installed & removed. - public PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) { - init(packageMgr, packages); + public PackageManagerBackupAgent( + PackageManager packageMgr, List<PackageInfo> packages, int userId) { + init(packageMgr, packages, userId); } - public PackageManagerBackupAgent(PackageManager packageMgr) { - init(packageMgr, null); + public PackageManagerBackupAgent(PackageManager packageMgr, int userId) { + init(packageMgr, null, userId); evaluateStorablePackages(); } - private void init(PackageManager packageMgr, List<PackageInfo> packages) { + private void init(PackageManager packageMgr, List<PackageInfo> packages, int userId) { mPackageManager = packageMgr; mAllPackages = packages; mRestoredSignatures = null; @@ -147,17 +148,19 @@ public class PackageManagerBackupAgent extends BackupAgent { mStoredSdkVersion = Build.VERSION.SDK_INT; mStoredIncrementalVersion = Build.VERSION.INCREMENTAL; + mUserId = userId; } // We will need to refresh our understanding of what is eligible for // backup periodically; this entry point serves that purpose. public void evaluateStorablePackages() { - mAllPackages = getStorableApplications(mPackageManager); + mAllPackages = getStorableApplications(mPackageManager, mUserId); } - public static List<PackageInfo> getStorableApplications(PackageManager pm) { - List<PackageInfo> pkgs; - pkgs = pm.getInstalledPackages(PackageManager.GET_SIGNING_CERTIFICATES); + /** Gets all packages installed on user {@code userId} eligible for backup. */ + public static List<PackageInfo> getStorableApplications(PackageManager pm, int userId) { + List<PackageInfo> pkgs = + pm.getInstalledPackagesAsUser(PackageManager.GET_SIGNING_CERTIFICATES, userId); int N = pkgs.size(); for (int a = N-1; a >= 0; a--) { PackageInfo pkg = pkgs.get(a); @@ -237,8 +240,8 @@ public class PackageManagerBackupAgent extends BackupAgent { ComponentName home = getPreferredHomeComponent(); if (home != null) { try { - homeInfo = mPackageManager.getPackageInfo(home.getPackageName(), - PackageManager.GET_SIGNING_CERTIFICATES); + homeInfo = mPackageManager.getPackageInfoAsUser(home.getPackageName(), + PackageManager.GET_SIGNING_CERTIFICATES, mUserId); homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName()); homeVersion = homeInfo.getLongVersionCode(); SigningInfo signingInfo = homeInfo.signingInfo; @@ -315,8 +318,8 @@ public class PackageManagerBackupAgent extends BackupAgent { } else { PackageInfo info = null; try { - info = mPackageManager.getPackageInfo(packName, - PackageManager.GET_SIGNING_CERTIFICATES); + info = mPackageManager.getPackageInfoAsUser(packName, + PackageManager.GET_SIGNING_CERTIFICATES, mUserId); } catch (NameNotFoundException e) { // Weird; we just found it, and now are told it doesn't exist. // Treat it as having been removed from the device. diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 79d4a2cb33de..4ca2545802c7 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -698,15 +698,15 @@ public class Trampoline extends IBackupManager.Stub { // Full backup/restore entry points - non-Binder; called directly // by the full-backup scheduled job - /* package */ boolean beginFullBackup(FullBackupJob scheduledJob) { + /* package */ boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) { BackupManagerService svc = mService; - return (svc != null) ? svc.beginFullBackup(scheduledJob) : false; + return (svc != null) ? svc.beginFullBackup(userId, scheduledJob) : false; } - /* package */ void endFullBackup() { + /* package */ void endFullBackup(@UserIdInt int userId) { BackupManagerService svc = mService; if (svc != null) { - svc.endFullBackup(); + svc.endFullBackup(userId); } } } diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index ddce6bbfd47d..529430ca4eaf 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -17,6 +17,7 @@ package com.android.server.backup; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.backup.BackupManager; import android.app.backup.BackupTransport; @@ -29,7 +30,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.RemoteException; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -61,7 +61,7 @@ public class TransportManager { public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); - private final Context mContext; + private final @UserIdInt int mUserId; private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; private final TransportClientManager mTransportClientManager; @@ -86,22 +86,24 @@ public class TransportManager { @Nullable private volatile String mCurrentTransportName; - TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) { - mContext = context; + TransportManager(@UserIdInt int userId, Context context, Set<ComponentName> whitelist, + String selectedTransport) { + mUserId = userId; mPackageManager = context.getPackageManager(); mTransportWhitelist = Preconditions.checkNotNull(whitelist); mCurrentTransportName = selectedTransport; mTransportStats = new TransportStats(); - mTransportClientManager = new TransportClientManager(context, mTransportStats); + mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats); } @VisibleForTesting TransportManager( + @UserIdInt int userId, Context context, Set<ComponentName> whitelist, String selectedTransport, TransportClientManager transportClientManager) { - mContext = context; + mUserId = userId; mPackageManager = context.getPackageManager(); mTransportWhitelist = Preconditions.checkNotNull(whitelist); mCurrentTransportName = selectedTransport; @@ -575,7 +577,7 @@ public class TransportManager { private void registerTransportsForIntent( Intent intent, Predicate<ComponentName> transportComponentFilter) { List<ResolveInfo> hosts = - mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM); + mPackageManager.queryIntentServicesAsUser(intent, 0, mUserId); if (hosts == null) { return; } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 6b0adfb44906..3a8966a04055 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -249,11 +249,11 @@ public class UserBackupManagerService { private final TransportManager mTransportManager; private final HandlerThread mUserBackupThread; - private Context mContext; - private PackageManager mPackageManager; - private IPackageManager mPackageManagerBinder; - private IActivityManager mActivityManager; - private ActivityManagerInternal mActivityManagerInternal; + private final Context mContext; + private final PackageManager mPackageManager; + private final IPackageManager mPackageManagerBinder; + private final IActivityManager mActivityManager; + private final ActivityManagerInternal mActivityManagerInternal; private PowerManager mPowerManager; private final AlarmManager mAlarmManager; private final IStorageManager mStorageManager; @@ -384,7 +384,7 @@ public class UserBackupManagerService { Slog.v(TAG, "Starting with transport " + currentTransport); } TransportManager transportManager = - new TransportManager(context, transportWhitelist, currentTransport); + new TransportManager(userId, context, transportWhitelist, currentTransport); File baseStateDir = UserBackupManagerFiles.getBaseStateDir(userId); File dataDir = UserBackupManagerFiles.getDataDir(userId); @@ -496,11 +496,18 @@ public class UserBackupManagerService { mBaseStateDir = checkNotNull(baseStateDir, "baseStateDir cannot be null"); mBaseStateDir.mkdirs(); if (!SELinux.restorecon(mBaseStateDir)) { - Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir); + Slog.w(TAG, "SELinux restorecon failed on " + mBaseStateDir); } mDataDir = checkNotNull(dataDir, "dataDir cannot be null"); - + // TODO(b/120424138): Remove when the system user moves out of the cache dir. The cache dir + // is managed by init.rc so we don't have to create it below. + if (userId != UserHandle.USER_SYSTEM) { + mDataDir.mkdirs(); + if (!SELinux.restorecon(mDataDir)) { + Slog.w(TAG, "SELinux restorecon failed on " + mDataDir); + } + } mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng); // Receivers for scheduled backups and transport initialization operations. @@ -797,7 +804,7 @@ public class UserBackupManagerService { * non-lifecycle agent instance, so we manually set up the context topology for it. */ public BackupAgent makeMetadataAgent() { - PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager); + PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId); pmAgent.attach(mContext); pmAgent.onCreate(); return pmAgent; @@ -808,7 +815,7 @@ public class UserBackupManagerService { */ public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) { PackageManagerBackupAgent pmAgent = - new PackageManagerBackupAgent(mPackageManager, packages); + new PackageManagerBackupAgent(mPackageManager, packages, mUserId); pmAgent.attach(mContext); pmAgent.onCreate(); return pmAgent; @@ -879,7 +886,7 @@ public class UserBackupManagerService { boolean changed = false; ArrayList<FullBackupEntry> schedule = null; List<PackageInfo> apps = - PackageManagerBackupAgent.getStorableApplications(mPackageManager); + PackageManagerBackupAgent.getStorableApplications(mPackageManager, mUserId); if (mFullBackupScheduleFile.exists()) { try (FileInputStream fstream = new FileInputStream(mFullBackupScheduleFile); @@ -1428,8 +1435,7 @@ public class UserBackupManagerService { mConnecting = true; mConnectedAgent = null; try { - if (mActivityManager.bindBackupAgent(app.packageName, mode, - UserHandle.USER_OWNER)) { + if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId)) { Slog.d(TAG, "awaiting agent for " + app); // success; wait for the agent to arrive @@ -1460,11 +1466,7 @@ public class UserBackupManagerService { } } if (agent == null) { - try { - mActivityManager.clearPendingBackup(); - } catch (RemoteException e) { - // can't happen - ActivityManager is local - } + mActivityManagerInternal.clearPendingBackup(mUserId); } return agent; } @@ -1488,7 +1490,7 @@ public class UserBackupManagerService { public void clearApplicationDataSynchronous(String packageName, boolean keepSystemState) { // Don't wipe packages marked allowClearUserData=false try { - PackageInfo info = mPackageManager.getPackageInfo(packageName, 0); + PackageInfo info = mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) == 0) { if (MORE_DEBUG) { Slog.i(TAG, "allowClearUserData=false so not wiping " @@ -1507,7 +1509,7 @@ public class UserBackupManagerService { mClearingData = true; try { mActivityManager.clearApplicationUserData( - packageName, keepSystemState, observer, 0); + packageName, keepSystemState, observer, mUserId); } catch (RemoteException e) { // can't happen because the activity manager is in this process } @@ -1616,8 +1618,8 @@ public class UserBackupManagerService { continue; } try { - PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, - PackageManager.GET_SIGNING_CERTIFICATES); + PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName, + PackageManager.GET_SIGNING_CERTIFICATES, mUserId); if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, mPackageManager)) { BackupObserverUtils.sendBackupOnPackageResult(observer, packageName, @@ -1674,8 +1676,8 @@ public class UserBackupManagerService { } // We don't want the backup jobs to kick in any time soon. // Reschedules them to run in the distant future. - KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants); - FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants); + KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants); + FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants); } finally { Binder.restoreCallingIdentity(oldToken); } @@ -1910,7 +1912,7 @@ public class UserBackupManagerService { Runnable r = new Runnable() { @Override public void run() { - FullBackupJob.schedule(mContext, latency, mConstants); + FullBackupJob.schedule(mUserId, mContext, latency, mConstants); } }; mBackupHandler.postDelayed(r, 2500); @@ -2033,7 +2035,7 @@ public class UserBackupManagerService { mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP); if (result.batterySaverEnabled) { if (DEBUG) Slog.i(TAG, "Deferring scheduled full backups in battery saver mode"); - FullBackupJob.schedule(mContext, keyValueBackupInterval, mConstants); + FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants); return false; } @@ -2147,7 +2149,7 @@ public class UserBackupManagerService { mBackupHandler.post(new Runnable() { @Override public void run() { - FullBackupJob.schedule(mContext, deferTime, mConstants); + FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants); } }); return false; @@ -2251,7 +2253,7 @@ public class UserBackupManagerService { } // ...and schedule a backup pass if necessary - KeyValueBackupJob.schedule(mContext, mConstants); + KeyValueBackupJob.schedule(mUserId, mContext, mConstants); } // Note: packageName is currently unused, but may be in the future @@ -2339,8 +2341,8 @@ public class UserBackupManagerService { if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName); PackageInfo info; try { - info = mPackageManager.getPackageInfo(packageName, - PackageManager.GET_SIGNING_CERTIFICATES); + info = mPackageManager.getPackageInfoAsUser(packageName, + PackageManager.GET_SIGNING_CERTIFICATES, mUserId); } catch (NameNotFoundException e) { Slog.d(TAG, "No such package '" + packageName + "' - not clearing backup data"); return; @@ -2401,7 +2403,8 @@ public class UserBackupManagerService { mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP); if (result.batterySaverEnabled) { if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode"); - KeyValueBackupJob.schedule(mContext, mConstants); // try again in several hours + // Try again in several hours. + KeyValueBackupJob.schedule(mUserId, mContext, mConstants); } else { if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { @@ -2414,7 +2417,7 @@ public class UserBackupManagerService { } // ...and cancel any pending scheduled job, because we've just superseded it - KeyValueBackupJob.cancel(mContext); + KeyValueBackupJob.cancel(mUserId, mContext); } } } finally { @@ -2737,13 +2740,13 @@ public class UserBackupManagerService { synchronized (mQueueLock) { if (enable && !wasEnabled && mSetupComplete) { // if we've just been enabled, start scheduling backup passes - KeyValueBackupJob.schedule(mContext, mConstants); + KeyValueBackupJob.schedule(mUserId, mContext, mConstants); scheduleNextFullBackupJob(0); } else if (!enable) { // No longer enabled, so stop running backups if (MORE_DEBUG) Slog.i(TAG, "Opting out of backup"); - KeyValueBackupJob.cancel(mContext); + KeyValueBackupJob.cancel(mUserId, mContext); // This also constitutes an opt-out, so we wipe any data for // this device from the backend. We start that process with @@ -3257,7 +3260,7 @@ public class UserBackupManagerService { if (packageName != null) { PackageInfo app = null; try { - app = mPackageManager.getPackageInfo(packageName, 0); + app = mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); } catch (NameNotFoundException nnf) { Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName); throw new IllegalArgumentException("Package " + packageName + " not found"); @@ -3361,7 +3364,7 @@ public class UserBackupManagerService { mTransportManager.getCurrentTransportClient(callerLogString); boolean eligible = AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport( - transportClient, packageName, mPackageManager); + transportClient, packageName, mPackageManager, mUserId); if (transportClient != null) { mTransportManager.disposeOfTransportClient(transportClient, callerLogString); } @@ -3385,7 +3388,7 @@ public class UserBackupManagerService { for (String packageName : packages) { if (AppBackupUtils .appIsRunningAndEligibleForBackupWithTransport( - transportClient, packageName, mPackageManager)) { + transportClient, packageName, mPackageManager, mUserId)) { eligibleApps.add(packageName); } } @@ -3451,7 +3454,7 @@ public class UserBackupManagerService { pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running"); pw.println("Last backup pass started: " + mLastBackupPass + " (now = " + System.currentTimeMillis() + ')'); - pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled()); + pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId)); pw.println("Transport whitelist:"); for (ComponentName transport : mTransportManager.getTransportWhitelist()) { diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index 2ee96d190f90..0fb4f93e542b 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -633,7 +633,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba unregisterTask(); if (mJob != null) { - mJob.finishBackupPass(); + mJob.finishBackupPass(backupManagerService.getUserId()); } synchronized (backupManagerService.getQueueLock()) { diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java index 62ae3eb8e192..c5e912e6d18b 100644 --- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java +++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java @@ -77,7 +77,8 @@ public class SetupObserver extends ContentObserver { if (MORE_DEBUG) { Slog.d(TAG, "Setup complete so starting backups"); } - KeyValueBackupJob.schedule(mContext, mUserBackupManagerService.getConstants()); + KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext, + mUserBackupManagerService.getConstants()); mUserBackupManagerService.scheduleNextFullBackupJob(0); } } diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index f39d795735f3..862ca711e694 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -45,7 +45,6 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.SELinux; -import android.os.UserHandle; import android.os.WorkSource; import com.android.internal.annotations.GuardedBy; @@ -241,6 +240,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { private final boolean mUserInitiated; private final boolean mNonIncremental; private final int mCurrentOpToken; + private final int mUserId; private final File mStateDirectory; private final File mDataDirectory; private final File mBlankStateFile; @@ -320,6 +320,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mCurrentOpToken = backupManagerService.generateRandomIntegerToken(); mQueueLock = mBackupManagerService.getQueueLock(); mBlankStateFile = new File(mStateDirectory, BLANK_STATE_FILE_NAME); + mUserId = backupManagerService.getUserId(); } private void registerTask() { @@ -480,8 +481,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { final PackageInfo packageInfo; try { packageInfo = - mPackageManager.getPackageInfo( - packageName, PackageManager.GET_SIGNING_CERTIFICATES); + mPackageManager.getPackageInfoAsUser( + packageName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); } catch (PackageManager.NameNotFoundException e) { mReporter.onAgentUnknown(packageName); throw AgentException.permanent(e); @@ -770,8 +771,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { private void writeWidgetPayloadIfAppropriate(FileDescriptor fd, String pkgName) throws IOException { - // TODO: http://b/22388012 - byte[] widgetState = AppWidgetBackupBridge.getWidgetState(pkgName, UserHandle.USER_SYSTEM); + byte[] widgetState = AppWidgetBackupBridge.getWidgetState(pkgName, mUserId); File widgetFile = new File(mStateDirectory, pkgName + "_widget"); boolean priorStateExists = widgetFile.exists(); if (!priorStateExists && widgetState == null) { @@ -1003,7 +1003,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { // Use the scheduler's default. delay = 0; } - KeyValueBackupJob.schedule( + KeyValueBackupJob.schedule(mBackupManagerService.getUserId(), mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants()); for (String packageName : mOriginalQueue) { diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java index e273b329d51a..0fa0f89e329e 100644 --- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java +++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java @@ -54,6 +54,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { private final TransportManager mTransportManager; private final String mTransportName; private final UserBackupManagerService mBackupManagerService; + private final int mUserId; @Nullable private final String mPackageName; public RestoreSet[] mRestoreSets = null; boolean mEnded = false; @@ -67,6 +68,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { mPackageName = packageName; mTransportManager = backupManagerService.getTransportManager(); mTransportName = transportName; + mUserId = backupManagerService.getUserId(); } public void markTimedOut() { @@ -304,7 +306,8 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { final PackageInfo app; try { - app = mBackupManagerService.getPackageManager().getPackageInfo(packageName, 0); + app = mBackupManagerService.getPackageManager().getPackageInfoAsUser( + packageName, 0, mUserId); } catch (NameNotFoundException nnf) { Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName); return -1; diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java index 45a398f80351..c7f3315493b4 100644 --- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java +++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java @@ -272,7 +272,7 @@ public class FullRestoreEngine extends RestoreEngine { instream, mBackupManagerService.getContext(), mDeleteObserver, mManifestSignatures, mPackagePolicies, info, installerPackageName, - bytesReadListener); + bytesReadListener, mBackupManagerService.getUserId()); // good to go; promote to ACCEPT mPackagePolicies.put(pkg, isSuccessfullyInstalled ? RestorePolicy.ACCEPT diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index f7efad604e35..5284d94c2aa7 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -48,7 +48,6 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; @@ -81,6 +80,7 @@ import java.util.List; public class PerformUnifiedRestoreTask implements BackupRestoreTask { private UserBackupManagerService backupManagerService; + private final int mUserId; private final TransportManager mTransportManager; // Transport client we're working with to do the restore private final TransportClient mTransportClient; @@ -175,6 +175,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { @Nullable String[] filterSet, OnTaskFinishedListener listener) { this.backupManagerService = backupManagerService; + mUserId = backupManagerService.getUserId(); mTransportManager = backupManagerService.getTransportManager(); mEphemeralOpToken = backupManagerService.generateRandomIntegerToken(); mState = UnifiedRestoreState.INITIAL; @@ -204,7 +205,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // We want everything and a pony List<PackageInfo> apps = PackageManagerBackupAgent.getStorableApplications( - backupManagerService.getPackageManager()); + backupManagerService.getPackageManager(), mUserId); filterSet = packagesToNames(apps); if (DEBUG) { Slog.i(TAG, "Full restore; asking about " + filterSet.length + " apps"); @@ -221,7 +222,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { for (int i = 0; i < filterSet.length; i++) { try { PackageManager pm = backupManagerService.getPackageManager(); - PackageInfo info = pm.getPackageInfo(filterSet[i], 0); + PackageInfo info = pm.getPackageInfoAsUser(filterSet[i], 0, mUserId); if ("android".equals(info.packageName)) { hasSystem = true; continue; @@ -240,16 +241,16 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } if (hasSystem) { try { - mAcceptSet.add(0, - backupManagerService.getPackageManager().getPackageInfo("android", 0)); + mAcceptSet.add(0, backupManagerService.getPackageManager().getPackageInfoAsUser( + "android", 0, mUserId)); } catch (NameNotFoundException e) { // won't happen; we know a priori that it's valid } } if (hasSettings) { try { - mAcceptSet.add(backupManagerService.getPackageManager().getPackageInfo( - SETTINGS_PACKAGE, 0)); + mAcceptSet.add(backupManagerService.getPackageManager().getPackageInfoAsUser( + SETTINGS_PACKAGE, 0, mUserId)); } catch (NameNotFoundException e) { // this one is always valid too } @@ -360,7 +361,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // If we're starting a full-system restore, set up to begin widget ID remapping if (mIsSystemRestore) { // TODO: http://b/22388012 - AppWidgetBackupBridge.restoreStarting(UserHandle.USER_SYSTEM); + AppWidgetBackupBridge.restoreStarting(mUserId); } try { @@ -508,8 +509,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } try { - mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo( - pkgName, PackageManager.GET_SIGNING_CERTIFICATES); + mCurrentPackage = backupManagerService.getPackageManager().getPackageInfoAsUser( + pkgName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId); } catch (NameNotFoundException e) { // Whoops, we thought we could restore this package but it // turns out not to be present. Skip it. @@ -1079,7 +1080,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // Kick off any work that may be needed regarding app widget restores // TODO: http://b/22388012 - AppWidgetBackupBridge.restoreFinished(UserHandle.USER_SYSTEM); + AppWidgetBackupBridge.restoreFinished(mUserId); // If this was a full-system restore, record the ancestral // dataset information diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java index e4dcb25c0623..7c5a57c004e4 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportClient.java +++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java @@ -20,6 +20,7 @@ import static com.android.server.backup.transport.TransportUtils.formatMessage; import android.annotation.IntDef; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.content.ComponentName; import android.content.Context; @@ -34,7 +35,6 @@ import android.os.UserHandle; import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.EventLog; -import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -79,6 +79,7 @@ public class TransportClient { @VisibleForTesting static final String TAG = "TransportClient"; private static final int LOG_BUFFER_SIZE = 5; + private final @UserIdInt int mUserId; private final Context mContext; private final TransportStats mTransportStats; private final Intent mBindIntent; @@ -106,6 +107,7 @@ public class TransportClient { private volatile IBackupTransport mTransport; TransportClient( + @UserIdInt int userId, Context context, TransportStats transportStats, Intent bindIntent, @@ -113,6 +115,7 @@ public class TransportClient { String identifier, String caller) { this( + userId, context, transportStats, bindIntent, @@ -124,6 +127,7 @@ public class TransportClient { @VisibleForTesting TransportClient( + @UserIdInt int userId, Context context, TransportStats transportStats, Intent bindIntent, @@ -131,6 +135,7 @@ public class TransportClient { String identifier, String caller, Handler listenerHandler) { + mUserId = userId; mContext = context; mTransportStats = transportStats; mTransportComponent = transportComponent; @@ -213,7 +218,7 @@ public class TransportClient { mBindIntent, mConnection, Context.BIND_AUTO_CREATE, - UserHandle.SYSTEM); + UserHandle.of(mUserId)); 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 diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java index f4e39287b277..a4e9b1091bed 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java +++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java @@ -19,12 +19,15 @@ package com.android.server.backup.transport; import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; import static com.android.server.backup.transport.TransportUtils.formatMessage; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; + import com.android.server.backup.TransportManager; import com.android.server.backup.transport.TransportUtils.Priority; + import java.io.PrintWriter; import java.util.Map; import java.util.WeakHashMap; @@ -36,13 +39,16 @@ import java.util.WeakHashMap; public class TransportClientManager { private static final String TAG = "TransportClientManager"; + private final @UserIdInt int mUserId; private final Context mContext; private final TransportStats mTransportStats; private final Object mTransportClientsLock = new Object(); private int mTransportClientsCreated = 0; private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>(); - public TransportClientManager(Context context, TransportStats transportStats) { + public TransportClientManager(@UserIdInt int userId, Context context, + TransportStats transportStats) { + mUserId = userId; mContext = context; mTransportStats = transportStats; } @@ -89,6 +95,7 @@ public class TransportClientManager { synchronized (mTransportClientsLock) { TransportClient transportClient = new TransportClient( + mUserId, mContext, mTransportStats, bindIntent, diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java index e465c7e5264f..054879b077ad 100644 --- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java +++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java @@ -91,10 +91,13 @@ public class AppBackupUtils { * </ol> */ public static boolean appIsRunningAndEligibleForBackupWithTransport( - @Nullable TransportClient transportClient, String packageName, PackageManager pm) { + @Nullable TransportClient transportClient, + String packageName, + PackageManager pm, + int userId) { try { - PackageInfo packageInfo = pm.getPackageInfo(packageName, - PackageManager.GET_SIGNING_CERTIFICATES); + PackageInfo packageInfo = pm.getPackageInfoAsUser(packageName, + PackageManager.GET_SIGNING_CERTIFICATES, userId); ApplicationInfo applicationInfo = packageInfo.applicationInfo; if (!appIsEligibleForBackup(applicationInfo, pm) || appIsStopped(applicationInfo) diff --git a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java index df7e6d45ba0f..cce5b3b4e238 100644 --- a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java +++ b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java @@ -72,7 +72,9 @@ public class RestoreUtils { HashMap<String, Signature[]> manifestSignatures, HashMap<String, RestorePolicy> packagePolicies, FileMetadata info, - String installerPackageName, BytesReadListener bytesReadListener) { + String installerPackageName, + BytesReadListener bytesReadListener, + int userId) { boolean okay = true; if (DEBUG) { @@ -144,8 +146,8 @@ public class RestoreUtils { uninstall = true; } else { try { - PackageInfo pkg = packageManager.getPackageInfo(info.packageName, - PackageManager.GET_SIGNING_CERTIFICATES); + PackageInfo pkg = packageManager.getPackageInfoAsUser(info.packageName, + PackageManager.GET_SIGNING_CERTIFICATES, userId); if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) { Slog.w(TAG, "Restore stream contains apk of package " diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0ff2a09bb17a..a96676e5fc5f 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -516,7 +516,7 @@ public final class ActiveServices { } // This app knows it is in the new model where this operation is not // allowed, so tell it what has happened. - UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid); + UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid); return new ComponentName("?", "app is in background uid " + uidRec); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 26141f7d7066..bd6fa498934c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -24,8 +24,6 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.REMOVE_TASKS; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; -import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; -import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; @@ -59,16 +57,11 @@ import static android.os.Process.PROC_PARENS; import static android.os.Process.PROC_SPACE_TERM; import static android.os.Process.ROOT_UID; import static android.os.Process.SCHED_FIFO; -import static android.os.Process.SCHED_OTHER; import static android.os.Process.SCHED_RESET_ON_FORK; import static android.os.Process.SE_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SIGNAL_USR1; import static android.os.Process.SYSTEM_UID; -import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE; -import static android.os.Process.THREAD_GROUP_DEFAULT; -import static android.os.Process.THREAD_GROUP_RESTRICTED; -import static android.os.Process.THREAD_GROUP_TOP_APP; import static android.os.Process.THREAD_PRIORITY_FOREGROUND; import static android.os.Process.getTotalMemory; import static android.os.Process.isThreadInProcess; @@ -79,7 +72,6 @@ import static android.os.Process.myUid; import static android.os.Process.readProcFile; import static android.os.Process.removeAllProcessGroups; import static android.os.Process.sendSignal; -import static android.os.Process.setProcessGroup; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; import static android.os.Process.zygoteProcess; @@ -96,11 +88,9 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ_REASON; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; @@ -109,7 +99,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; @@ -404,7 +393,7 @@ public class ActivityManagerService extends IActivityManager.Stub public static final int TOP_APP_PRIORITY_BOOST = -10; static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM; - private static final String TAG_BACKUP = TAG + POSTFIX_BACKUP; + static final String TAG_BACKUP = TAG + POSTFIX_BACKUP; private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST; private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP; private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; @@ -412,9 +401,9 @@ public class ActivityManagerService extends IActivityManager.Stub static final String TAG_LRU = TAG + POSTFIX_LRU; private static final String TAG_MU = TAG + POSTFIX_MU; private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK; - private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ; + static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ; private static final String TAG_POWER = TAG + POSTFIX_POWER; - private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS; + static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS; static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES; private static final String TAG_PROVIDER = TAG + POSTFIX_PROVIDER; static final String TAG_PSS = TAG + POSTFIX_PSS; @@ -541,6 +530,8 @@ public class ActivityManagerService extends IActivityManager.Stub private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds; + final OomAdjuster mOomAdjuster; + /** All system services */ SystemServiceManager mSystemServiceManager; @@ -555,7 +546,7 @@ public class ActivityManagerService extends IActivityManager.Stub public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler(); // Whether we should use SCHED_FIFO for UI and RenderThreads. - private boolean mUseFifoUiScheduling = false; + boolean mUseFifoUiScheduling = false; BroadcastQueue mFgBroadcastQueue; BroadcastQueue mBgBroadcastQueue; @@ -654,11 +645,6 @@ public class ActivityManagerService extends IActivityManager.Stub final ProcessStatsService mProcessStats; /** - * Service for compacting background apps. - */ - final AppCompactor mAppCompact; - - /** * Non-persistent appId whitelist for background restrictions */ int[] mBackgroundAppIdWhitelist = new int[] { @@ -821,8 +807,6 @@ public class ActivityManagerService extends IActivityManager.Stub */ boolean mFullPssPending = false; - /** Track all uids that have actively running processes. */ - final ActiveUids mActiveUids; /** * This is for verifying the UID report flow. @@ -936,8 +920,8 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Backup/restore process management */ - String mBackupAppName = null; - BackupRecord mBackupTarget = null; + @GuardedBy("this") + final SparseArray<BackupRecord> mBackupTargets = new SparseArray<>(); final ProviderMap mProviderMap; @@ -1104,32 +1088,7 @@ public class ActivityManagerService extends IActivityManager.Stub /** * State of external calls telling us if the device is awake or asleep. */ - private int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; - - /** - * Current sequence id for oom_adj computation traversal. - */ - int mAdjSeq = 0; - - /** - * Keep track of the non-cached/empty process we last found, to help - * determine how to distribute cached/empty processes next time. - */ - int mNumNonCachedProcs = 0; - - /** - * Keep track of the number of cached hidden procs, to balance oom adj - * distribution between those and empty procs. - */ - int mNumCachedHiddenProcs = 0; - - /** - * Keep track of the number of service processes we last found, to - * determine on the next iteration which should be B services. - */ - int mNumServiceProcs = 0; - int mNewNumAServiceProcs = 0; - int mNewNumServiceProcs = 0; + int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; /** * Allow the current computed overall memory level of the system to go down? @@ -1256,10 +1215,6 @@ public class ActivityManagerService extends IActivityManager.Stub String mTrackAllocationApp = null; String mNativeDebuggingApp = null; - final long[] mTmpLong = new long[3]; - - private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet(); - private final Injector mInjector; static final class ProcessChangeItem { @@ -1334,6 +1289,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } + // TODO: Move below 4 members and code to ProcessList final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>(); ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5]; @@ -2225,12 +2181,15 @@ public class ActivityManagerService extends IActivityManager.Stub mUiContext = null; mAppErrors = null; mPackageWatchdog = null; - mActiveUids = new ActiveUids(this, false /* postChangesToAtm */); mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */); mBatteryStatsService = null; mHandler = hasHandlerThread ? new MainHandler(handlerThread.getLooper()) : null; mHandlerThread = handlerThread; mConstants = hasHandlerThread ? new ActivityManagerConstants(this, mHandler) : null; + final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */); + mProcessList.init(this, activeUids); + mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); + mIntentFirewall = hasHandlerThread ? new IntentFirewall(new IntentFirewallInterface(), mHandler) : null; mProcessCpuThread = null; @@ -2246,7 +2205,6 @@ public class ActivityManagerService extends IActivityManager.Stub ? new PendingIntentController(handlerThread.getLooper(), mUserController) : null; mProcStartHandlerThread = null; mProcStartHandler = null; - mAppCompact = null; mHiddenApiBlacklist = null; mFactoryTest = FACTORY_TEST_OFF; } @@ -2276,8 +2234,9 @@ public class ActivityManagerService extends IActivityManager.Stub mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper()); mConstants = new ActivityManagerConstants(this, mHandler); - - mProcessList.init(this); + final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */); + mProcessList.init(this, activeUids); + mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); mFgBroadcastQueue = new BroadcastQueue(this, mHandler, "foreground", BROADCAST_FG_TIMEOUT, false); @@ -2293,7 +2252,6 @@ public class ActivityManagerService extends IActivityManager.Stub mProviderMap = new ProviderMap(this); mPackageWatchdog = PackageWatchdog.getInstance(mUiContext); mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog); - mActiveUids = new ActiveUids(this, true /* postChangesToAtm */); final File systemDir = SystemServiceManager.ensureSystemDir(); @@ -2330,8 +2288,6 @@ public class ActivityManagerService extends IActivityManager.Stub DisplayThread.get().getLooper()); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); - mAppCompact = new AppCompactor(this); - mProcessCpuThread = new Thread("CpuTracker") { @Override public void run() { @@ -2378,7 +2334,8 @@ public class ActivityManagerService extends IActivityManager.Stub try { Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(), Process.THREAD_GROUP_SYSTEM); - Process.setThreadGroupAndCpuset(mAppCompact.mCompactionThread.getThreadId(), + Process.setThreadGroupAndCpuset( + mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); } catch (Exception e) { Slog.w(TAG, "Setting background thread cpuset failed"); @@ -4408,6 +4365,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.removeProcessLocked(app, false, true, "timeout publishing content providers"); } + @GuardedBy("this") private final void processStartTimedOutLocked(ProcessRecord app) { final int pid = app.pid; boolean gone = mPidsSelfLocked.removeIfNoThread(pid); @@ -4428,7 +4386,8 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); } removeLruProcessLocked(app); - if (mBackupTarget != null && mBackupTarget.app.pid == pid) { + final BackupRecord backupTarget = mBackupTargets.get(app.userId); + if (backupTarget != null && backupTarget.app.pid == pid) { Slog.w(TAG, "Unattached app died before backup, skipping"); mHandler.post(new Runnable() { @Override @@ -4436,7 +4395,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { IBackupManager bm = IBackupManager.Stub.asInterface( ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentDisconnected(app.info.packageName); + bm.agentDisconnectedForUser(app.userId, app.info.packageName); } catch (RemoteException e) { // Can't happen; the backup manager is local } @@ -4559,6 +4518,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_ALL) Slog.v( TAG, "New app record " + app + " thread=" + thread.asBinder() + " pid=" + pid); + final BackupRecord backupTarget = mBackupTargets.get(app.userId); try { int testMode = ApplicationThreadConstants.DEBUG_OFF; if (mDebugApp != null && mDebugApp.equals(processName)) { @@ -4580,11 +4540,11 @@ public class ActivityManagerService extends IActivityManager.Stub // If the app is being launched for restore or full backup, set it up specially boolean isRestrictedBackupMode = false; - if (mBackupTarget != null && mBackupAppName.equals(processName)) { - isRestrictedBackupMode = mBackupTarget.appInfo.uid >= FIRST_APPLICATION_UID - && ((mBackupTarget.backupMode == BackupRecord.RESTORE) - || (mBackupTarget.backupMode == BackupRecord.RESTORE_FULL) - || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL)); + if (backupTarget != null && backupTarget.appInfo.packageName.equals(processName)) { + isRestrictedBackupMode = backupTarget.appInfo.uid >= FIRST_APPLICATION_UID + && ((backupTarget.backupMode == BackupRecord.RESTORE) + || (backupTarget.backupMode == BackupRecord.RESTORE_FULL) + || (backupTarget.backupMode == BackupRecord.BACKUP_FULL)); } final ActiveInstrumentation instr = app.getActiveInstrumentation(); @@ -4792,15 +4752,15 @@ public class ActivityManagerService extends IActivityManager.Stub } // Check whether the next backup agent is in this process... - if (!badApp && mBackupTarget != null && mBackupTarget.app == app) { + if (!badApp && backupTarget != null && backupTarget.app == app) { if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "New app is backup target, launching agent for " + app); - notifyPackageUse(mBackupTarget.appInfo.packageName, + notifyPackageUse(backupTarget.appInfo.packageName, PackageManager.NOTIFY_PACKAGE_USE_BACKUP); try { - thread.scheduleCreateBackupAgent(mBackupTarget.appInfo, - compatibilityInfoForPackage(mBackupTarget.appInfo), - mBackupTarget.backupMode); + thread.scheduleCreateBackupAgent(backupTarget.appInfo, + compatibilityInfoForPackage(backupTarget.appInfo), + backupTarget.backupMode); } catch (Exception e) { Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e); badApp = true; @@ -5347,7 +5307,7 @@ public class ActivityManagerService extends IActivityManager.Stub private boolean isAppForeground(int uid) { synchronized (this) { - UidRecord uidRec = mActiveUids.get(uid); + UidRecord uidRec = mProcessList.mActiveUids.get(uid); if (uidRec == null || uidRec.idle) { return false; } @@ -5359,15 +5319,10 @@ public class ActivityManagerService extends IActivityManager.Stub // be guarded by permission checking. int getUidState(int uid) { synchronized (this) { - return getUidStateLocked(uid); + return mProcessList.getUidProcStateLocked(uid); } } - int getUidStateLocked(int uid) { - UidRecord uidRec = mActiveUids.get(uid); - return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState(); - } - // ========================================================= // PROCESS INFO // ========================================================= @@ -5659,7 +5614,7 @@ public class ActivityManagerService extends IActivityManager.Stub int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk, int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) { - UidRecord uidRec = mActiveUids.get(uid); + UidRecord uidRec = mProcessList.getUidRecordLocked(uid); if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg=" + packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle=" + (uidRec != null ? uidRec.idle : false)); @@ -7880,8 +7835,7 @@ public class ActivityManagerService extends IActivityManager.Stub } synchronized (this) { - UidRecord uidRec = mActiveUids.get(uid); - return uidRec != null ? uidRec.getCurProcState() : PROCESS_STATE_NONEXISTENT; + return mProcessList.getUidProcStateLocked(uid); } } @@ -7917,7 +7871,7 @@ public class ActivityManagerService extends IActivityManager.Stub } boolean isUidActiveLocked(int uid) { - final UidRecord uidRecord = mActiveUids.get(uid); + final UidRecord uidRecord = mProcessList.getUidRecordLocked(uid); return uidRecord != null && !uidRecord.setIdle; } @@ -8709,7 +8663,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (mForceBackgroundCheck) { // Stop background services for idle UIDs. - doStopUidForIdleUidsLocked(); + mProcessList.doStopUidForIdleUidsLocked(); } } } @@ -9558,7 +9512,8 @@ public class ActivityManagerService extends IActivityManager.Stub proto.end(broadcastToken); long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES); - mServices.writeToProto(proto, ActivityManagerServiceDumpServicesProto.ACTIVE_SERVICES); + mServices.writeToProto(proto, + ActivityManagerServiceDumpServicesProto.ACTIVE_SERVICES); proto.end(serviceToken); long processToken = proto.start(ActivityManagerServiceProto.PROCESSES); @@ -10110,11 +10065,13 @@ public class ActivityManagerService extends IActivityManager.Stub } } - if (mActiveUids.size() > 0) { - if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) { + if (mProcessList.mActiveUids.size() > 0) { + if (dumpUids(pw, dumpPackage, dumpAppId, mProcessList.mActiveUids, + "UID states:", needSep)) { needSep = true; } } + if (dumpAll) { if (mValidateUids.size() > 0) { if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:", @@ -10377,12 +10334,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print(" mLastPowerCheckUptime="); TimeUtils.formatDuration(mLastPowerCheckUptime, pw); pw.println(""); - pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq); - pw.println(" mNumNonCachedProcs=" + mNumNonCachedProcs - + " (" + mProcessList.getLruSizeLocked() + " total)" - + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs - + " mNumServiceProcs=" + mNumServiceProcs - + " mNewNumServiceProcs=" + mNewNumServiceProcs); + mOomAdjuster.dumpSequenceNumbersLocked(pw); + mOomAdjuster.dumpProcCountsLocked(pw); pw.println(" mAllowLowerMemLevel=" + mAllowLowerMemLevel + " mLastMemoryLevel=" + mLastMemoryLevel + " mLastNumProcesses=" + mLastNumProcesses); @@ -10434,7 +10387,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { continue; } - r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.PROCS, mProcessList.mLruProcesses.indexOf(r) + r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.PROCS, + mProcessList.mLruProcesses.indexOf(r) ); if (r.isPersistent()) { numPers++; @@ -10458,19 +10412,20 @@ public class ActivityManagerService extends IActivityManager.Stub && !ai.mTargetInfo.packageName.equals(dumpPackage)) { continue; } - ai.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS); + ai.writeToProto(proto, + ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS); } int whichAppId = getAppId(dumpPackage); - for (int i=0; i<mActiveUids.size(); i++) { - UidRecord uidRec = mActiveUids.valueAt(i); + for (int i = 0; i < mProcessList.mActiveUids.size(); i++) { + UidRecord uidRec = mProcessList.mActiveUids.valueAt(i); if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) { continue; } uidRec.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS); } - for (int i=0; i<mValidateUids.size(); i++) { + for (int i = 0; i < mValidateUids.size(); i++) { UidRecord uidRec = mValidateUids.valueAt(i); if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) { continue; @@ -10545,12 +10500,15 @@ public class ActivityManagerService extends IActivityManager.Stub r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ON_HOLD_PROCS); } - writeProcessesToGcToProto(proto, ActivityManagerServiceDumpProcessesProto.GC_PROCS, dumpPackage); - mAppErrors.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS, dumpPackage); + writeProcessesToGcToProto(proto, ActivityManagerServiceDumpProcessesProto.GC_PROCS, + dumpPackage); + mAppErrors.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS, + dumpPackage); mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness, mTestPssMode); if (dumpPackage == null) { - mUserController.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER); + mUserController.writeToProto(proto, + ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER); } final int NI = mUidObservers.getRegisteredCallbackCount(); @@ -10662,11 +10620,7 @@ public class ActivityManagerService extends IActivityManager.Stub proto.write(ActivityManagerServiceDumpProcessesProto.CALL_FINISH_BOOTING, mCallFinishBooting); proto.write(ActivityManagerServiceDumpProcessesProto.BOOT_ANIMATION_COMPLETE, mBootAnimationComplete); proto.write(ActivityManagerServiceDumpProcessesProto.LAST_POWER_CHECK_UPTIME_MS, mLastPowerCheckUptime); - proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq); - proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq); - proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS, mNumNonCachedProcs); - proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs); - proto.write(ActivityManagerServiceDumpProcessesProto.NEW_NUM_SERVICE_PROCS, mNewNumServiceProcs); + mOomAdjuster.dumpProcessListVariablesLocked(proto); proto.write(ActivityManagerServiceDumpProcessesProto.ALLOW_LOWER_MEM_LEVEL, mAllowLowerMemLevel); proto.write(ActivityManagerServiceDumpProcessesProto.LAST_MEMORY_LEVEL, mLastMemoryLevel); proto.write(ActivityManagerServiceDumpProcessesProto.LAST_NUM_PROCESSES, mLastNumProcesses); @@ -13273,16 +13227,17 @@ public class ActivityManagerService extends IActivityManager.Stub app.receivers.clear(); // If the app is undergoing backup, tell the backup manager about it - if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) { + final BackupRecord backupTarget = mBackupTargets.get(app.userId); + if (backupTarget != null && app.pid == backupTarget.app.pid) { if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App " - + mBackupTarget.appInfo + " died during backup"); + + backupTarget.appInfo + " died during backup"); mHandler.post(new Runnable() { @Override public void run(){ try { IBackupManager bm = IBackupManager.Stub.asInterface( ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentDisconnected(app.info.packageName); + bm.agentDisconnectedForUser(app.userId, app.info.packageName); } catch (RemoteException e) { // can't happen; backup manager is local } @@ -13622,7 +13577,11 @@ public class ActivityManagerService extends IActivityManager.Stub // instantiated. The backup agent will invoke backupAgentCreated() on the // activity manager to announce its creation. public boolean bindBackupAgent(String packageName, int backupMode, int userId) { - if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode); + if (DEBUG_BACKUP) { + Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + + backupMode + " userId=" + userId + " callingUid = " + Binder.getCallingUid() + + " uid = " + Process.myUid()); + } enforceCallingPermission("android.permission.CONFIRM_FULL_BACKUP", "bindBackupAgent"); IPackageManager pm = AppGlobals.getPackageManager(); @@ -13674,10 +13633,10 @@ public class ActivityManagerService extends IActivityManager.Stub proc.inFullBackup = true; } r.app = proc; - oldBackupUid = mBackupTarget != null ? mBackupTarget.appInfo.uid : -1; + final BackupRecord backupTarget = mBackupTargets.get(userId); + oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1; newBackupUid = proc.inFullBackup ? r.appInfo.uid : -1; - mBackupTarget = r; - mBackupAppName = app.packageName; + mBackupTargets.put(userId, r); // Try not to kill the process during backup updateOomAdjLocked(proc, true); @@ -13712,14 +13671,14 @@ public class ActivityManagerService extends IActivityManager.Stub return true; } - @Override - public void clearPendingBackup() { - if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "clearPendingBackup"); - enforceCallingPermission("android.permission.BACKUP", "clearPendingBackup"); + private void clearPendingBackup(int userId) { + if (DEBUG_BACKUP) { + Slog.v(TAG_BACKUP, "clearPendingBackup: userId = " + userId + " callingUid = " + + Binder.getCallingUid() + " uid = " + Process.myUid()); + } synchronized (this) { - mBackupTarget = null; - mBackupAppName = null; + mBackupTargets.delete(userId); } JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class); @@ -13727,12 +13686,19 @@ public class ActivityManagerService extends IActivityManager.Stub } // A backup agent has just come up + @Override public void backupAgentCreated(String agentPackageName, IBinder agent) { - if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName - + " = " + agent); + final int callingUserId = UserHandle.getCallingUserId(); + if (DEBUG_BACKUP) { + Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent + + " callingUserId = " + callingUserId + " callingUid = " + + Binder.getCallingUid() + " uid = " + Process.myUid()); + } synchronized(this) { - if (!agentPackageName.equals(mBackupAppName)) { + final BackupRecord backupTarget = mBackupTargets.get(callingUserId); + String backupAppName = backupTarget == null ? null : backupTarget.appInfo.packageName; + if (!agentPackageName.equals(backupAppName)) { Slog.e(TAG, "Backup agent created for " + agentPackageName + " but not requested!"); return; } @@ -13742,7 +13708,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { IBackupManager bm = IBackupManager.Stub.asInterface( ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentConnected(agentPackageName, agent); + bm.agentConnectedForUser(callingUserId, agentPackageName, agent); } catch (RemoteException e) { // can't happen; the backup manager service is local } catch (Exception e) { @@ -13755,7 +13721,12 @@ public class ActivityManagerService extends IActivityManager.Stub // done with this agent public void unbindBackupAgent(ApplicationInfo appInfo) { - if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "unbindBackupAgent: " + appInfo); + if (DEBUG_BACKUP) { + Slog.v(TAG_BACKUP, "unbindBackupAgent: " + appInfo + " appInfo.uid = " + + appInfo.uid + " callingUid = " + Binder.getCallingUid() + " uid = " + + Process.myUid()); + } + enforceCallingPermission("android.permission.CONFIRM_FULL_BACKUP", "unbindBackupAgent"); if (appInfo == null) { Slog.w(TAG, "unbind backup agent for null app"); @@ -13764,24 +13735,27 @@ public class ActivityManagerService extends IActivityManager.Stub int oldBackupUid; + final int userId = UserHandle.getUserId(appInfo.uid); synchronized(this) { + final BackupRecord backupTarget = mBackupTargets.get(userId); + String backupAppName = backupTarget == null ? null : backupTarget.appInfo.packageName; try { - if (mBackupAppName == null) { + if (backupAppName == null) { Slog.w(TAG, "Unbinding backup agent with no active backup"); return; } - if (!mBackupAppName.equals(appInfo.packageName)) { + if (!backupAppName.equals(appInfo.packageName)) { Slog.e(TAG, "Unbind of " + appInfo + " but is not the current backup target"); return; } // Not backing this app up any more; reset its OOM adjustment - final ProcessRecord proc = mBackupTarget.app; + final ProcessRecord proc = backupTarget.app; updateOomAdjLocked(proc, true); proc.inFullBackup = false; - oldBackupUid = mBackupTarget != null ? mBackupTarget.appInfo.uid : -1; + oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1; // If the app crashed during backup, 'thread' will be null here if (proc.thread != null) { @@ -13794,8 +13768,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } } finally { - mBackupTarget = null; - mBackupAppName = null; + mBackupTargets.delete(userId); } } @@ -14640,7 +14613,7 @@ public class ActivityManagerService extends IActivityManager.Stub Intent.ACTION_PACKAGE_REPLACED.equals(action)) { final int uid = getUidFromIntent(intent); if (uid != -1) { - final UidRecord uidRec = mActiveUids.get(uid); + final UidRecord uidRec = mProcessList.getUidRecordLocked(uid); if (uidRec != null) { uidRec.updateHasInternetPermission(); } @@ -15418,7 +15391,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Returns whether the app is receiving broadcast. // If receiving, fetch all broadcast queues which the app is // the current [or imminent] receiver on. - private boolean isReceivingBroadcastLocked(ProcessRecord app, + boolean isReceivingBroadcastLocked(ProcessRecord app, ArraySet<BroadcastQueue> receivingQueues) { final int N = app.curReceivers.size(); if (N > 0) { @@ -15538,1087 +15511,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = - new ComputeOomAdjWindowCallback(); - - /** These methods are called inline during computeOomAdjLocked(), on the same thread */ - private final class ComputeOomAdjWindowCallback - implements WindowProcessController.ComputeOomAdjCallback { - - ProcessRecord app; - int adj; - boolean foregroundActivities; - int procState; - int schedGroup; - int appUid; - int logUid; - int processStateCurTop; - - void initialize(ProcessRecord app, int adj, boolean foregroundActivities, - int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) { - this.app = app; - this.adj = adj; - this.foregroundActivities = foregroundActivities; - this.procState = procState; - this.schedGroup = schedGroup; - this.appUid = appUid; - this.logUid = logUid; - this.processStateCurTop = processStateCurTop; - } - - @Override - public void onVisibleActivity() { - // App has a visible activity; only upgrade adjustment. - if (adj > ProcessList.VISIBLE_APP_ADJ) { - adj = ProcessList.VISIBLE_APP_ADJ; - app.adjType = "vis-activity"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app); - } - } - if (procState > processStateCurTop) { - procState = processStateCurTop; - app.adjType = "vis-activity"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to vis-activity (top): " + app); - } - } - if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) { - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - } - app.cached = false; - app.empty = false; - foregroundActivities = true; - } - - @Override - public void onPausedActivity() { - if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - app.adjType = "pause-activity"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app); - } - } - if (procState > processStateCurTop) { - procState = processStateCurTop; - app.adjType = "pause-activity"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to pause-activity (top): " + app); - } - } - if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) { - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - } - app.cached = false; - app.empty = false; - foregroundActivities = true; - } - - @Override - public void onStoppingActivity(boolean finishing) { - if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - app.adjType = "stop-activity"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise adj to stop-activity: " + app); - } - } - - // For the process state, we will at this point consider the process to be cached. It - // will be cached either as an activity or empty depending on whether the activity is - // finishing. We do this so that we can treat the process as cached for purposes of - // memory trimming (determining current memory level, trim command to send to process) - // since there can be an arbitrary number of stopping processes and they should soon all - // go into the cached state. - if (!finishing) { - if (procState > PROCESS_STATE_LAST_ACTIVITY) { - procState = PROCESS_STATE_LAST_ACTIVITY; - app.adjType = "stop-activity"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to stop-activity: " + app); - } - } - } - app.cached = false; - app.empty = false; - foregroundActivities = true; - } - - @Override - public void onOtherActivity() { - if (procState > PROCESS_STATE_CACHED_ACTIVITY) { - procState = PROCESS_STATE_CACHED_ACTIVITY; - app.adjType = "cch-act"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to cached activity: " + app); - } - } - } - } - - private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP, - boolean doingAll, long now, boolean cycleReEval) { - if (mAdjSeq == app.adjSeq) { - if (app.adjSeq == app.completedAdjSeq) { - // This adjustment has already been computed successfully. - return false; - } else { - // The process is being computed, so there is a cycle. We cannot - // rely on this process's state. - app.containsCycle = true; - - return false; - } - } - - if (app.thread == null) { - app.adjSeq = mAdjSeq; - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND); - app.setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY); - app.curAdj = ProcessList.CACHED_APP_MAX_ADJ; - app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ); - app.completedAdjSeq = app.adjSeq; - return false; - } - - app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; - app.adjSource = null; - app.adjTarget = null; - app.empty = false; - app.cached = false; - - final WindowProcessController wpc = app.getWindowProcessController(); - final int appUid = app.info.uid; - final int logUid = mCurOomAdjUid; - - int prevAppAdj = app.curAdj; - int prevProcState = app.getCurProcState(); - - if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) { - // The max adjustment doesn't allow this app to be anything - // below foreground, so it is not worth doing work for it. - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app); - } - app.adjType = "fixed"; - app.adjSeq = mAdjSeq; - app.setCurRawAdj(app.maxAdj); - app.setHasForegroundActivities(false); - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT); - app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT); - // System processes can do UI, and when they do we want to have - // them trim their memory after the user leaves the UI. To - // facilitate this, here we need to determine whether or not it - // is currently showing UI. - app.systemNoUi = true; - if (app == TOP_APP) { - app.systemNoUi = false; - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); - app.adjType = "pers-top-activity"; - } else if (app.hasTopUi()) { - // sched group/proc state adjustment is below - app.systemNoUi = false; - app.adjType = "pers-top-ui"; - } else if (wpc.hasVisibleActivities()) { - app.systemNoUi = false; - } - if (!app.systemNoUi) { - if (mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) { - // screen on, promote UI - app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI); - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); - } else { - // screen off, restrict UI scheduling - app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE); - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED); - } - } - app.setCurRawProcState(app.getCurProcState()); - app.curAdj = app.maxAdj; - app.completedAdjSeq = app.adjSeq; - // if curAdj is less than prevAppAdj, then this process was promoted - return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState; - } - - app.systemNoUi = false; - - final int PROCESS_STATE_CUR_TOP = mAtmInternal.getTopProcessState(); - - // Determine the importance of the process, starting with most - // important to least, and assign an appropriate OOM adjustment. - int adj; - int schedGroup; - int procState; - int cachedAdjSeq; - - boolean foregroundActivities = false; - mTmpBroadcastQueue.clear(); - if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) { - // The last app on the list is the foreground app. - adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_TOP_APP; - app.adjType = "top-activity"; - foregroundActivities = true; - procState = PROCESS_STATE_CUR_TOP; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app); - } - } else if (app.runningRemoteAnimation) { - adj = ProcessList.VISIBLE_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_TOP_APP; - app.adjType = "running-remote-anim"; - procState = PROCESS_STATE_CUR_TOP; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app); - } - } else if (app.getActiveInstrumentation() != null) { - // Don't want to kill running instrumentation. - adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - app.adjType = "instrumentation"; - procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app); - } - } else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) { - // An app that is currently receiving a broadcast also - // counts as being in the foreground for OOM killer purposes. - // It's placed in a sched group based on the nature of the - // broadcast as reflected by which queue it's active in. - adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = (mTmpBroadcastQueue.contains(mFgBroadcastQueue)) - ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; - app.adjType = "broadcast"; - procState = ActivityManager.PROCESS_STATE_RECEIVER; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app); - } - } else if (app.executingServices.size() > 0) { - // An app that is currently executing a service callback also - // counts as being in the foreground. - adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = app.execServicesFg ? - ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; - app.adjType = "exec-service"; - procState = ActivityManager.PROCESS_STATE_SERVICE; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app); - } - //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app); - } else if (app == TOP_APP) { - adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; - app.adjType = "top-sleeping"; - foregroundActivities = true; - procState = PROCESS_STATE_CUR_TOP; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app); - } - } else { - // As far as we know the process is empty. We may change our mind later. - schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; - // At this point we don't actually know the adjustment. Use the cached adj - // value that the caller wants us to. - adj = cachedAdj; - procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; - app.cached = true; - app.empty = true; - app.adjType = "cch-empty"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app); - } - } - - // Examine all activities if not already foreground. - if (!foregroundActivities && wpc.hasActivities()) { - mTmpComputeOomAdjWindowCallback.initialize(app, adj, foregroundActivities, procState, - schedGroup, appUid, logUid, PROCESS_STATE_CUR_TOP); - final int minLayer = wpc.computeOomAdjFromActivities( - ProcessList.VISIBLE_APP_LAYER_MAX, mTmpComputeOomAdjWindowCallback); - - adj = mTmpComputeOomAdjWindowCallback.adj; - foregroundActivities = mTmpComputeOomAdjWindowCallback.foregroundActivities; - procState = mTmpComputeOomAdjWindowCallback.procState; - schedGroup = mTmpComputeOomAdjWindowCallback.schedGroup; - - if (adj == ProcessList.VISIBLE_APP_ADJ) { - adj += minLayer; - } - } - - if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.hasRecentTasks()) { - procState = ActivityManager.PROCESS_STATE_CACHED_RECENT; - app.adjType = "cch-rec"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app); - } - } - - if (adj > ProcessList.PERCEPTIBLE_APP_ADJ - || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { - if (app.hasForegroundServices()) { - // The user is aware of this app, so make it visible. - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; - app.cached = false; - app.adjType = "fg-service"; - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to fg service: " + app); - } - } else if (app.hasOverlayUi()) { - // The process is display an overlay UI. - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; - app.cached = false; - app.adjType = "has-overlay-ui"; - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app); - } - } - } - - // If the app was recently in the foreground and moved to a foreground service status, - // allow it to get a higher rank in memory for some time, compared to other foreground - // services so that it can finish performing any persistence/processing of in-memory state. - if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ - && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now - || app.setProcState <= ActivityManager.PROCESS_STATE_TOP)) { - adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; - app.adjType = "fg-service-act"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app); - } - } - - if (adj > ProcessList.PERCEPTIBLE_APP_ADJ - || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { - if (app.forcingToImportant != null) { - // This is currently used for toasts... they are not interactive, and - // we don't want them to cause the app to become fully foreground (and - // thus out of background check), so we yes the best background level we can. - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; - app.cached = false; - app.adjType = "force-imp"; - app.adjSource = app.forcingToImportant; - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app); - } - } - } - - if (mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) { - if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) { - // We don't want to kill the current heavy-weight process. - adj = ProcessList.HEAVY_WEIGHT_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; - app.cached = false; - app.adjType = "heavy"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app); - } - } - if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { - procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; - app.adjType = "heavy"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app); - } - } - } - - if (wpc.isHomeProcess()) { - if (adj > ProcessList.HOME_APP_ADJ) { - // This process is hosting what we currently consider to be the - // home app, so we don't want to let it go into the background. - adj = ProcessList.HOME_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; - app.cached = false; - app.adjType = "home"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app); - } - } - if (procState > ActivityManager.PROCESS_STATE_HOME) { - procState = ActivityManager.PROCESS_STATE_HOME; - app.adjType = "home"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app); - } - } - } - - if (wpc.isPreviousProcess() && app.hasActivities()) { - if (adj > ProcessList.PREVIOUS_APP_ADJ) { - // This was the previous process that showed UI to the user. - // We want to try to keep it around more aggressively, to give - // a good experience around switching between two apps. - adj = ProcessList.PREVIOUS_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; - app.cached = false; - app.adjType = "previous"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app); - } - } - if (procState > PROCESS_STATE_LAST_ACTIVITY) { - procState = PROCESS_STATE_LAST_ACTIVITY; - app.adjType = "previous"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app); - } - } - } - - if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj - + " reason=" + app.adjType); - - // By default, we use the computed adjustment. It may be changed if - // there are applications dependent on our services or providers, but - // this gives us a baseline and makes sure we don't get into an - // infinite recursion. If we're re-evaluating due to cycles, use the previously computed - // values. - app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj())); - app.setCurRawProcState(!cycleReEval - ? procState - : Math.min(procState, app.getCurRawProcState())); - - app.hasStartedServices = false; - app.adjSeq = mAdjSeq; - - if (mBackupTarget != null && app == mBackupTarget.app) { - // If possible we want to avoid killing apps while they're being backed up - if (adj > ProcessList.BACKUP_APP_ADJ) { - if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app); - adj = ProcessList.BACKUP_APP_ADJ; - if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { - procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; - } - app.adjType = "backup"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app); - } - app.cached = false; - } - if (procState > ActivityManager.PROCESS_STATE_BACKUP) { - procState = ActivityManager.PROCESS_STATE_BACKUP; - app.adjType = "backup"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app); - } - } - } - - boolean mayBeTop = false; - String mayBeTopType = null; - Object mayBeTopSource = null; - Object mayBeTopTarget = null; - - for (int is = app.services.size()-1; - is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND - || procState > ActivityManager.PROCESS_STATE_TOP); - is--) { - ServiceRecord s = app.services.valueAt(is); - if (s.startRequested) { - app.hasStartedServices = true; - if (procState > ActivityManager.PROCESS_STATE_SERVICE) { - procState = ActivityManager.PROCESS_STATE_SERVICE; - app.adjType = "started-services"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to started service: " + app); - } - } - if (app.hasShownUi && !wpc.isHomeProcess()) { - // If this process has shown some UI, let it immediately - // go to the LRU list because it may be pretty heavy with - // UI stuff. We'll tag it with a label just to help - // debug and understand what is going on. - if (adj > ProcessList.SERVICE_ADJ) { - app.adjType = "cch-started-ui-services"; - } - } else { - if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { - // This service has seen some activity within - // recent memory, so we will keep its process ahead - // of the background processes. - if (adj > ProcessList.SERVICE_ADJ) { - adj = ProcessList.SERVICE_ADJ; - app.adjType = "started-services"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise adj to started service: " + app); - } - app.cached = false; - } - } - // If we have let the service slide into the background - // state, still have some text describing what it is doing - // even though the service no longer has an impact. - if (adj > ProcessList.SERVICE_ADJ) { - app.adjType = "cch-started-services"; - } - } - } - - for (int conni = s.connections.size()-1; - conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND - || procState > ActivityManager.PROCESS_STATE_TOP); - conni--) { - ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); - for (int i = 0; - i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND - || procState > ActivityManager.PROCESS_STATE_TOP); - i++) { - // XXX should compute this based on the max of - // all connected clients. - ConnectionRecord cr = clist.get(i); - if (cr.binding.client == app) { - // Binding to oneself is not interesting. - continue; - } - - boolean trackedProcState = false; - if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) { - ProcessRecord client = cr.binding.client; - computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval); - - if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) { - continue; - } - - int clientAdj = client.getCurRawAdj(); - int clientProcState = client.getCurRawProcState(); - - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. The specific cached state - // doesn't propagate except under certain conditions. - clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; - } - String adjType = null; - if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { - // Not doing bind OOM management, so treat - // this guy more like a started service. - if (app.hasShownUi && !wpc.isHomeProcess()) { - // If this process has shown some UI, let it immediately - // go to the LRU list because it may be pretty heavy with - // UI stuff. We'll tag it with a label just to help - // debug and understand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-ui-services"; - } - app.cached = false; - clientAdj = adj; - clientProcState = procState; - } else { - if (now >= (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { - // This service has not seen activity within - // recent memory, so allow it to drop to the - // LRU list if there is no other reason to keep - // it around. We'll also tag it with a label just - // to help debug and undertand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-services"; - } - clientAdj = adj; - } - } - } - if (adj > clientAdj) { - // If this process has recently shown UI, and - // the process that is binding to it is less - // important than being visible, then we don't - // care about the binding as much as we care - // about letting this process get into the LRU - // list to be killed and restarted if needed for - // memory. - if (app.hasShownUi && !wpc.isHomeProcess() - && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { - if (adj >= ProcessList.CACHED_APP_MIN_ADJ) { - adjType = "cch-bound-ui-services"; - } - } else { - int newAdj; - if ((cr.flags&(Context.BIND_ABOVE_CLIENT - |Context.BIND_IMPORTANT)) != 0) { - if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) { - newAdj = clientAdj; - } else { - // make this service persistent - newAdj = ProcessList.PERSISTENT_SERVICE_ADJ; - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - procState = ActivityManager.PROCESS_STATE_PERSISTENT; - cr.trackProcState(procState, mAdjSeq, now); - trackedProcState = true; - } - } else if ((cr.flags & Context.BIND_ADJUST_BELOW_PERCEPTIBLE) != 0 - && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ - && adj > ProcessList.PERCEPTIBLE_APP_ADJ + 1) { - newAdj = ProcessList.PERCEPTIBLE_APP_ADJ + 1; - } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0 - && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ - && adj > ProcessList.PERCEPTIBLE_APP_ADJ) { - newAdj = ProcessList.PERCEPTIBLE_APP_ADJ; - } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { - newAdj = clientAdj; - } else { - if (adj > ProcessList.VISIBLE_APP_ADJ) { - newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ); - } else { - newAdj = adj; - } - } - if (!client.cached) { - app.cached = false; - } - if (adj > newAdj) { - adj = newAdj; - app.setCurRawAdj(adj); - adjType = "service"; - } - } - } - if ((cr.flags & (Context.BIND_NOT_FOREGROUND - | Context.BIND_IMPORTANT_BACKGROUND)) == 0) { - // This will treat important bound services identically to - // the top app, which may behave differently than generic - // foreground work. - final int curSchedGroup = client.getCurrentSchedulingGroup(); - if (curSchedGroup > schedGroup) { - if ((cr.flags&Context.BIND_IMPORTANT) != 0) { - schedGroup = curSchedGroup; - } else { - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - } - } - if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) { - if (clientProcState == ActivityManager.PROCESS_STATE_TOP) { - // Special handling of clients who are in the top state. - // We *may* want to consider this process to be in the - // top state as well, but only if there is not another - // reason for it to be running. Being on the top is a - // special state, meaning you are specifically running - // for the current top app. If the process is already - // running in the background for some other reason, it - // is more important to continue considering it to be - // in the background state. - mayBeTop = true; - mayBeTopType = "service"; - mayBeTopSource = cr.binding.client; - mayBeTopTarget = s.instanceName; - clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; - } else { - // Special handling for above-top states (persistent - // processes). These should not bring the current process - // into the top state, since they are not on top. Instead - // give them the best state after that. - if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) { - clientProcState = - ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else if (mWakefulness - == PowerManagerInternal.WAKEFULNESS_AWAKE && - (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) - != 0) { - clientProcState = - ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else { - clientProcState = - ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; - } - } - } - } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) { - if (clientProcState < - ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { - clientProcState = - ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; - } - } else { - if (clientProcState < - ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) { - clientProcState = - ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; - } - } - if (!trackedProcState) { - cr.trackProcState(clientProcState, mAdjSeq, now); - } - if (procState > clientProcState) { - procState = clientProcState; - app.setCurRawProcState(procState); - if (adjType == null) { - adjType = "service"; - } - } - if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - && (cr.flags & Context.BIND_SHOWING_UI) != 0) { - app.setPendingUiClean(true); - } - if (adjType != null) { - app.adjType = adjType; - app.adjTypeCode = ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE; - app.adjSource = cr.binding.client; - app.adjSourceProcState = clientProcState; - app.adjTarget = s.instanceName; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + cr.binding.client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } - } - } - if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { - app.treatLikeActivity = true; - } - final ActivityServiceConnectionsHolder a = cr.activity; - if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { - if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ - && a.isActivityVisible()) { - adj = ProcessList.FOREGROUND_APP_ADJ; - app.setCurRawAdj(adj); - if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { - if ((cr.flags&Context.BIND_IMPORTANT) != 0) { - schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND; - } else { - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - } - } - app.cached = false; - app.adjType = "service"; - app.adjTypeCode = ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE; - app.adjSource = a; - app.adjSourceProcState = procState; - app.adjTarget = s.instanceName; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise to service w/activity: " + app); - } - } - } - } - } - } - - for (int provi = app.pubProviders.size()-1; - provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND - || procState > ActivityManager.PROCESS_STATE_TOP); - provi--) { - ContentProviderRecord cpr = app.pubProviders.valueAt(provi); - for (int i = cpr.connections.size()-1; - i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND - || procState > ActivityManager.PROCESS_STATE_TOP); - i--) { - ContentProviderConnection conn = cpr.connections.get(i); - ProcessRecord client = conn.client; - if (client == app) { - // Being our own client is not interesting. - continue; - } - computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval); - - if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) { - continue; - } - - int clientAdj = client.getCurRawAdj(); - int clientProcState = client.getCurRawProcState(); - - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. - clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; - } - String adjType = null; - if (adj > clientAdj) { - if (app.hasShownUi && !wpc.isHomeProcess() - && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { - adjType = "cch-ui-provider"; - } else { - adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ - ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; - app.setCurRawAdj(adj); - adjType = "provider"; - } - app.cached &= client.cached; - } - if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) { - if (clientProcState == ActivityManager.PROCESS_STATE_TOP) { - // Special handling of clients who are in the top state. - // We *may* want to consider this process to be in the - // top state as well, but only if there is not another - // reason for it to be running. Being on the top is a - // special state, meaning you are specifically running - // for the current top app. If the process is already - // running in the background for some other reason, it - // is more important to continue considering it to be - // in the background state. - mayBeTop = true; - clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; - mayBeTopType = adjType = "provider-top"; - mayBeTopSource = client; - mayBeTopTarget = cpr.name; - } else { - // Special handling for above-top states (persistent - // processes). These should not bring the current process - // into the top state, since they are not on top. Instead - // give them the best state after that. - clientProcState = - ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - if (adjType == null) { - adjType = "provider"; - } - } - } - conn.trackProcState(clientProcState, mAdjSeq, now); - if (procState > clientProcState) { - procState = clientProcState; - app.setCurRawProcState(procState); - } - if (client.getCurrentSchedulingGroup() > schedGroup) { - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - } - if (adjType != null) { - app.adjType = adjType; - app.adjTypeCode = ActivityManager.RunningAppProcessInfo - .REASON_PROVIDER_IN_USE; - app.adjSource = client; - app.adjSourceProcState = clientProcState; - app.adjTarget = cpr.name; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } - } - } - // If the provider has external (non-framework) process - // dependencies, ensure that its adjustment is at least - // FOREGROUND_APP_ADJ. - if (cpr.hasExternalProcessHandles()) { - if (adj > ProcessList.FOREGROUND_APP_ADJ) { - adj = ProcessList.FOREGROUND_APP_ADJ; - app.setCurRawAdj(adj); - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - app.cached = false; - app.adjType = "ext-provider"; - app.adjTarget = cpr.name; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise adj to external provider: " + app); - } - } - if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { - procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; - app.setCurRawProcState(procState); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to external provider: " + app); - } - } - } - } - - if (app.lastProviderTime > 0 && - (app.lastProviderTime+mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) { - if (adj > ProcessList.PREVIOUS_APP_ADJ) { - adj = ProcessList.PREVIOUS_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; - app.cached = false; - app.adjType = "recent-provider"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise adj to recent provider: " + app); - } - } - if (procState > PROCESS_STATE_LAST_ACTIVITY) { - procState = PROCESS_STATE_LAST_ACTIVITY; - app.adjType = "recent-provider"; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to recent provider: " + app); - } - } - } - - if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) { - // A client of one of our services or providers is in the top state. We - // *may* want to be in the top state, but not if we are already running in - // the background for some other reason. For the decision here, we are going - // to pick out a few specific states that we want to remain in when a client - // is top (states that tend to be longer-term) and otherwise allow it to go - // to the top state. - switch (procState) { - case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE: - case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE: - // Something else is keeping it at this level, just leave it. - break; - case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: - case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: - case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND: - case ActivityManager.PROCESS_STATE_SERVICE: - // These all are longer-term states, so pull them up to the top - // of the background states, but not all the way to the top state. - procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - app.adjType = mayBeTopType; - app.adjSource = mayBeTopSource; - app.adjTarget = mayBeTopTarget; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType - + ": " + app + ", due to " + mayBeTopSource - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } - break; - default: - // Otherwise, top is a better choice, so take it. - procState = ActivityManager.PROCESS_STATE_TOP; - app.adjType = mayBeTopType; - app.adjSource = mayBeTopSource; - app.adjTarget = mayBeTopTarget; - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType - + ": " + app + ", due to " + mayBeTopSource - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } - break; - } - } - - if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) { - if (app.hasClientActivities()) { - // This is a cached process, but with client activities. Mark it so. - procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; - app.adjType = "cch-client-act"; - } else if (app.treatLikeActivity) { - // This is a cached process, but somebody wants us to treat it like it has - // an activity, okay! - procState = PROCESS_STATE_CACHED_ACTIVITY; - app.adjType = "cch-as-act"; - } - } - - if (adj == ProcessList.SERVICE_ADJ) { - if (doingAll) { - app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); - mNewNumServiceProcs++; - //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb); - if (!app.serviceb) { - // This service isn't far enough down on the LRU list to - // normally be a B service, but if we are low on RAM and it - // is large we want to force it down since we would prefer to - // keep launcher over it. - if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL - && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { - app.serviceHighRam = true; - app.serviceb = true; - //Slog.i(TAG, "ADJ " + app + " high ram!"); - } else { - mNewNumAServiceProcs++; - //Slog.i(TAG, "ADJ " + app + " not high ram!"); - } - } else { - app.serviceHighRam = false; - } - } - if (app.serviceb) { - adj = ProcessList.SERVICE_B_ADJ; - } - } - - app.setCurRawAdj(adj); - - //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid + - // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj); - if (adj > app.maxAdj) { - adj = app.maxAdj; - if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { - schedGroup = ProcessList.SCHED_GROUP_DEFAULT; - } - } - - // Put bound foreground services in a special sched group for additional - // restrictions on screen off - if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE && - mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { - if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) { - schedGroup = ProcessList.SCHED_GROUP_RESTRICTED; - } - } - - // Do final modification to adj. Everything we do between here and applying - // the final setAdj must be done in this function, because we will also use - // it when computing the final cached adj later. Note that we don't need to - // worry about this for max adj above, since max adj will always be used to - // keep it out of the cached vaues. - app.curAdj = app.modifyRawOomAdj(adj); - app.setCurrentSchedulingGroup(schedGroup); - app.setCurProcState(procState); - app.setCurRawProcState(procState); - app.setHasForegroundActivities(foregroundActivities); - app.completedAdjSeq = mAdjSeq; - - // if curAdj or curProcState improved, then this process was promoted - return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState; - } - - /** - * Checks if for the given app and client, there's a cycle that should skip over the client - * for now or use partial values to evaluate the effect of the client binding. - * @param app - * @param client - * @param procState procstate evaluated so far for this app - * @param adj oom_adj evaluated so far for this app - * @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first - * evaluation. - * @return whether to skip using the client connection at this time - */ - private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client, - int procState, int adj, boolean cycleReEval) { - if (client.containsCycle) { - // We've detected a cycle. We should retry computeOomAdjLocked later in - // case a later-checked connection from a client would raise its - // priority legitimately. - app.containsCycle = true; - // If the client has not been completely evaluated, check if it's worth - // using the partial values. - if (client.completedAdjSeq < mAdjSeq) { - if (cycleReEval) { - // If the partial values are no better, skip until the next - // attempt - if (client.getCurRawProcState() >= procState - && client.getCurRawAdj() >= adj) { - return true; - } - // Else use the client's partial procstate and adj to adjust the - // effect of the binding - } else { - return true; - } - } - } - return false; - } - private static final class RecordPssRunnable implements Runnable { private final ActivityManagerService mService; private final ProcessRecord mProc; @@ -17016,287 +15908,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, - long nowElapsed) { - boolean success = true; - - if (app.getCurRawAdj() != app.setRawAdj) { - app.setRawAdj = app.getCurRawAdj(); - } - - int changes = 0; - - if (app.curAdj != app.setAdj) { - // don't compact during bootup - if (mConstants.USE_COMPACTION && mBooted) { - // Perform a minor compaction when a perceptible app becomes the prev/home app - // Perform a major compaction when any app enters cached - // reminder: here, setAdj is previous state, curAdj is upcoming state - if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ && - (app.curAdj == ProcessList.PREVIOUS_APP_ADJ || - app.curAdj == ProcessList.HOME_APP_ADJ)) { - mAppCompact.compactAppSome(app); - } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && - app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) { - mAppCompact.compactAppFull(app); - } - } - ProcessList.setOomAdj(app.pid, app.uid, app.curAdj); - if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) { - String msg = "Set " + app.pid + " " + app.processName + " adj " - + app.curAdj + ": " + app.adjType; - reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); - } - app.setAdj = app.curAdj; - app.verifiedAdj = ProcessList.INVALID_ADJ; - } - - final int curSchedGroup = app.getCurrentSchedulingGroup(); - if (app.setSchedGroup != curSchedGroup) { - int oldSchedGroup = app.setSchedGroup; - app.setSchedGroup = curSchedGroup; - if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) { - String msg = "Setting sched group of " + app.processName - + " to " + curSchedGroup + ": " + app.adjType; - reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); - } - if (app.waitingToKill != null && app.curReceivers.isEmpty() - && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) { - app.kill(app.waitingToKill, true); - success = false; - } else { - int processGroup; - switch (curSchedGroup) { - case ProcessList.SCHED_GROUP_BACKGROUND: - processGroup = THREAD_GROUP_BG_NONINTERACTIVE; - break; - case ProcessList.SCHED_GROUP_TOP_APP: - case ProcessList.SCHED_GROUP_TOP_APP_BOUND: - processGroup = THREAD_GROUP_TOP_APP; - break; - case ProcessList.SCHED_GROUP_RESTRICTED: - processGroup = THREAD_GROUP_RESTRICTED; - break; - default: - processGroup = THREAD_GROUP_DEFAULT; - break; - } - long oldId = Binder.clearCallingIdentity(); - try { - setProcessGroup(app.pid, processGroup); - if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) { - // do nothing if we already switched to RT - if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) { - app.getWindowProcessController().onTopProcChanged(); - if (mUseFifoUiScheduling) { - // Switch UI pipeline for app to SCHED_FIFO - app.savedPriority = Process.getThreadPriority(app.pid); - scheduleAsFifoPriority(app.pid, /* suppressLogs */true); - if (app.renderThreadTid != 0) { - scheduleAsFifoPriority(app.renderThreadTid, - /* suppressLogs */true); - if (DEBUG_OOM_ADJ) { - Slog.d("UI_FIFO", "Set RenderThread (TID " + - app.renderThreadTid + ") to FIFO"); - } - } else { - if (DEBUG_OOM_ADJ) { - Slog.d("UI_FIFO", "Not setting RenderThread TID"); - } - } - } else { - // Boost priority for top app UI and render threads - setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST); - if (app.renderThreadTid != 0) { - try { - setThreadPriority(app.renderThreadTid, - TOP_APP_PRIORITY_BOOST); - } catch (IllegalArgumentException e) { - // thread died, ignore - } - } - } - } - } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP && - curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) { - app.getWindowProcessController().onTopProcChanged(); - if (mUseFifoUiScheduling) { - try { - // Reset UI pipeline to SCHED_OTHER - setThreadScheduler(app.pid, SCHED_OTHER, 0); - setThreadPriority(app.pid, app.savedPriority); - if (app.renderThreadTid != 0) { - setThreadScheduler(app.renderThreadTid, - SCHED_OTHER, 0); - setThreadPriority(app.renderThreadTid, -4); - } - } catch (IllegalArgumentException e) { - Slog.w(TAG, - "Failed to set scheduling policy, thread does not exist:\n" - + e); - } catch (SecurityException e) { - Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e); - } - } else { - // Reset priority for top app UI and render threads - setThreadPriority(app.pid, 0); - if (app.renderThreadTid != 0) { - setThreadPriority(app.renderThreadTid, 0); - } - } - } - } catch (Exception e) { - if (false) { - Slog.w(TAG, "Failed setting process group of " + app.pid - + " to " + app.getCurrentSchedulingGroup()); - Slog.w(TAG, "at location", e); - } - } finally { - Binder.restoreCallingIdentity(oldId); - } - } - } - if (app.repForegroundActivities != app.hasForegroundActivities()) { - app.repForegroundActivities = app.hasForegroundActivities(); - changes |= ProcessChangeItem.CHANGE_ACTIVITIES; - } - if (app.getReportedProcState() != app.getCurProcState()) { - app.setReportedProcState(app.getCurProcState()); - if (app.thread != null) { - try { - if (false) { - //RuntimeException h = new RuntimeException("here"); - Slog.i(TAG, "Sending new process state " + app.getReportedProcState() - + " to " + app /*, h*/); - } - app.thread.setProcessState(app.getReportedProcState()); - } catch (RemoteException e) { - } - } - } - if (app.setProcState == PROCESS_STATE_NONEXISTENT - || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) { - if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) { - // Experimental code to more aggressively collect pss while - // running test... the problem is that this tends to collect - // the data right when a process is transitioning between process - // states, which will tend to give noisy data. - long start = SystemClock.uptimeMillis(); - long startTime = SystemClock.currentThreadTimeMillis(); - long pss = Debug.getPss(app.pid, mTmpLong, null); - long endTime = SystemClock.currentThreadTimeMillis(); - recordPssSampleLocked(app, app.getCurProcState(), pss, mTmpLong[0], mTmpLong[1], - mTmpLong[2], ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now); - mPendingPssProcesses.remove(app); - Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState - + " to " + app.getCurProcState() + ": " - + (SystemClock.uptimeMillis()-start) + "ms"); - } - app.lastStateTime = now; - app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(), - app.procStateMemTracker, mTestPssMode, mAtmInternal.isSleeping(), now); - if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from " - + ProcessList.makeProcStateString(app.setProcState) + " to " - + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in " - + (app.nextPssTime-now) + ": " + app); - } else { - if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL) - && now > (app.lastStateTime+ProcessList.minTimeFromStateChange( - mTestPssMode)))) { - if (requestPssLocked(app, app.setProcState)) { - app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(), - app.procStateMemTracker, mTestPssMode, mAtmInternal.isSleeping(), now); - } - } else if (false && DEBUG_PSS) Slog.d(TAG_PSS, - "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now)); - } - if (app.setProcState != app.getCurProcState()) { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) { - String msg = "Proc state change of " + app.processName - + " to " + ProcessList.makeProcStateString(app.getCurProcState()) - + " (" + app.getCurProcState() + ")" + ": " + app.adjType; - reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); - } - boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE; - boolean curImportant = app.getCurProcState() < ActivityManager.PROCESS_STATE_SERVICE; - if (setImportant && !curImportant) { - // This app is no longer something we consider important enough to allow to use - // arbitrary amounts of battery power. Note its current CPU time to later know to - // kill it if it is not behaving well. - app.setWhenUnimportant(now); - app.lastCpuTime = 0; - } - // Inform UsageStats of important process state change - // Must be called before updating setProcState - maybeUpdateUsageStatsLocked(app, nowElapsed); - - maybeUpdateLastTopTime(app, now); - - app.setProcState = app.getCurProcState(); - if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) { - app.notCachedSinceIdle = false; - } - if (!doingAll) { - setProcessTrackerStateLocked(app, mProcessStats.getMemFactorLocked(), now); - } else { - app.procStateChanged = true; - } - } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime()) - > mConstants.USAGE_STATS_INTERACTION_INTERVAL) { - // For apps that sit around for a long time in the interactive state, we need - // to report this at least once a day so they don't go idle. - maybeUpdateUsageStatsLocked(app, nowElapsed); - } - - if (changes != 0) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "Changes in " + app + ": " + changes); - int i = mPendingProcessChanges.size()-1; - ProcessChangeItem item = null; - while (i >= 0) { - item = mPendingProcessChanges.get(i); - if (item.pid == app.pid) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "Re-using existing item: " + item); - break; - } - i--; - } - if (i < 0) { - // No existing item in pending changes; need a new one. - final int NA = mAvailProcessChanges.size(); - if (NA > 0) { - item = mAvailProcessChanges.remove(NA-1); - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "Retrieving available item: " + item); - } else { - item = new ProcessChangeItem(); - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "Allocating new item: " + item); - } - item.changes = 0; - item.pid = app.pid; - item.uid = app.info.uid; - if (mPendingProcessChanges.size() == 0) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "*** Enqueueing dispatch processes changed!"); - mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG).sendToTarget(); - } - mPendingProcessChanges.add(item); - } - item.changes |= changes; - item.foregroundActivities = app.repForegroundActivities; - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "Item " + Integer.toHexString(System.identityHashCode(item)) - + " " + app.toShortString() + ": changes=" + item.changes - + " foreground=" + item.foregroundActivities - + " type=" + app.adjType + " source=" + app.adjSource - + " target=" + app.adjTarget); - } - - return success; - } - private boolean isEphemeralLocked(int uid) { String packages[] = mContext.getPackageManager().getPackagesForUid(uid); if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid @@ -17410,78 +16021,13 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) { - if (DEBUG_USAGE_STATS) { - Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList()) - + "] state changes: old = " + app.setProcState + ", new = " - + app.getCurProcState()); - } - if (mUsageStatsService == null) { - return; - } - boolean isInteraction; - // To avoid some abuse patterns, we are going to be careful about what we consider - // to be an app interaction. Being the top activity doesn't count while the display - // is sleeping, nor do short foreground services. - if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) { - isInteraction = true; - app.setFgInteractionTime(0); - } else if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { - if (app.getFgInteractionTime() == 0) { - app.setFgInteractionTime(nowElapsed); - isInteraction = false; - } else { - isInteraction = nowElapsed > app.getFgInteractionTime() - + mConstants.SERVICE_USAGE_INTERACTION_TIME; - } - } else { - isInteraction = - app.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; - app.setFgInteractionTime(0); - } - if (isInteraction - && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime()) - > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) { - app.setInteractionEventTime(nowElapsed); - String[] packages = app.getPackageList(); - if (packages != null) { - for (int i = 0; i < packages.length; i++) { - mUsageStatsService.reportEvent(packages[i], app.userId, - UsageEvents.Event.SYSTEM_INTERACTION); - } - } - } - app.reportedInteraction = isInteraction; - if (!isInteraction) { - app.setInteractionEventTime(0); - } - } - - private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) { - if (app.setProcState <= ActivityManager.PROCESS_STATE_TOP - && app.getCurProcState() > ActivityManager.PROCESS_STATE_TOP) { - app.lastTopTime = nowUptime; - } - } - - private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) { + final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) { if (proc.thread != null && proc.baseProcessTracker != null) { proc.baseProcessTracker.setState( proc.getReportedProcState(), memFactor, now, proc.pkgList.mPkgList); } } - private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj, - ProcessRecord TOP_APP, boolean doingAll, long now) { - if (app.thread == null) { - return false; - } - - computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false); - - return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime()); - } - @GuardedBy("this") final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground, boolean oomAdj) { @@ -17564,29 +16110,10 @@ public class ActivityManagerService extends IActivityManager.Stub */ @GuardedBy("this") final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll) { - final ProcessRecord TOP_APP = getTopAppLocked(); - final boolean wasCached = app.cached; - - mAdjSeq++; - - // This is the desired cached adjusment we want to tell it to use. - // If our app is currently cached, we know it, and that is it. Otherwise, - // we don't know it yet, and it needs to now be cached we will then - // need to do a complete oom adj. - final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ - ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ; - boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false, - SystemClock.uptimeMillis()); - if (oomAdjAll - && (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) { - // Changed to/from cached state, so apps after it in the LRU - // list may also be changed. - updateOomAdjLocked(); - } - return success; + return mOomAdjuster.updateOomAdjLocked(app, oomAdjAll); } - private static final class ProcStatsRunnable implements Runnable { + static final class ProcStatsRunnable implements Runnable { private final ActivityManagerService mService; private final ProcessStatsService mProcessStats; @@ -17777,387 +16304,7 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") final void updateOomAdjLocked() { - mOomAdjProfiler.oomAdjStarted(); - final ProcessRecord TOP_APP = getTopAppLocked(); - final long now = SystemClock.uptimeMillis(); - final long nowElapsed = SystemClock.elapsedRealtime(); - final long oldTime = now - ProcessList.MAX_EMPTY_TIME; - final int N = mProcessList.getLruSizeLocked(); - - // Reset state in all uid records. - for (int i=mActiveUids.size()-1; i>=0; i--) { - final UidRecord uidRec = mActiveUids.valueAt(i); - if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "Starting update of " + uidRec); - uidRec.reset(); - } - - if (mAtmInternal != null) { - mAtmInternal.rankTaskLayersIfNeeded(); - } - - mAdjSeq++; - mNewNumServiceProcs = 0; - mNewNumAServiceProcs = 0; - - final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES; - final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit; - - // Let's determine how many processes we have running vs. - // how many slots we have for background processes; we may want - // to put multiple processes in a slot of there are enough of - // them. - final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ - - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2 - / ProcessList.CACHED_APP_IMPORTANCE_LEVELS; - int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs; - if (numEmptyProcs > cachedProcessLimit) { - // If there are more empty processes than our limit on cached - // processes, then use the cached process limit for the factor. - // This ensures that the really old empty processes get pushed - // down to the bottom, so if we are running low on memory we will - // have a better chance at keeping around more cached processes - // instead of a gazillion empty processes. - numEmptyProcs = cachedProcessLimit; - } - int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots; - if (emptyFactor < 1) emptyFactor = 1; - int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1) - / numSlots; - if (cachedFactor < 1) cachedFactor = 1; - int stepCached = -1; - int stepEmpty = -1; - int numCached = 0; - int numCachedExtraGroup = 0; - int numEmpty = 0; - int numTrimming = 0; - int lastCachedGroup = 0; - int lastCachedGroupImportance = 0; - int lastCachedGroupUid = 0; - - mNumNonCachedProcs = 0; - mNumCachedHiddenProcs = 0; - - // First update the OOM adjustment for each of the - // application processes based on their current state. - int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; - int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2); - int curCachedImpAdj = 0; - int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; - int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2); - - boolean retryCycles = false; - - // need to reset cycle state before calling computeOomAdjLocked because of service connections - for (int i=N-1; i>=0; i--) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - app.containsCycle = false; - app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY); - app.setCurRawAdj(ProcessList.UNKNOWN_ADJ); - } - for (int i=N-1; i>=0; i--) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - if (!app.killedByAm && app.thread != null) { - app.procStateChanged = false; - computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false); - - // if any app encountered a cycle, we need to perform an additional loop later - retryCycles |= app.containsCycle; - - // If we haven't yet assigned the final cached adj - // to the process, do that now. - if (app.curAdj >= ProcessList.UNKNOWN_ADJ) { - switch (app.getCurProcState()) { - case PROCESS_STATE_CACHED_ACTIVITY: - case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: - case ActivityManager.PROCESS_STATE_CACHED_RECENT: - // Figure out the next cached level, taking into account groups. - boolean inGroup = false; - if (app.connectionGroup != 0) { - if (lastCachedGroupUid == app.uid - && lastCachedGroup == app.connectionGroup) { - // This is in the same group as the last process, just tweak - // adjustment by importance. - if (app.connectionImportance > lastCachedGroupImportance) { - lastCachedGroupImportance = app.connectionImportance; - if (curCachedAdj < nextCachedAdj - && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) { - curCachedImpAdj++; - } - } - inGroup = true; - } else { - lastCachedGroupUid = app.uid; - lastCachedGroup = app.connectionGroup; - lastCachedGroupImportance = app.connectionImportance; - } - } - if (!inGroup && curCachedAdj != nextCachedAdj) { - stepCached++; - curCachedImpAdj = 0; - if (stepCached >= cachedFactor) { - stepCached = 0; - curCachedAdj = nextCachedAdj; - nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; - if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { - nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; - } - } - } - // This process is a cached process holding activities... - // assign it the next cached value for that type, and then - // step that cached level. - app.setCurRawAdj(curCachedAdj + curCachedImpAdj); - app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj); - if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i - + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj - + " curCachedImpAdj=" + curCachedImpAdj + ")"); - break; - default: - // Figure out the next cached level. - if (curEmptyAdj != nextEmptyAdj) { - stepEmpty++; - if (stepEmpty >= emptyFactor) { - stepEmpty = 0; - curEmptyAdj = nextEmptyAdj; - nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; - if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) { - nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ; - } - } - } - // For everything else, assign next empty cached process - // level and bump that up. Note that this means that - // long-running services that have dropped down to the - // cached level will be treated as empty (since their process - // state is still as a service), which is what we want. - app.setCurRawAdj(curEmptyAdj); - app.curAdj = app.modifyRawOomAdj(curEmptyAdj); - if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i - + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj - + ")"); - break; - } - } - } - } - - // Cycle strategy: - // - Retry computing any process that has encountered a cycle. - // - Continue retrying until no process was promoted. - // - Iterate from least important to most important. - int cycleCount = 0; - while (retryCycles && cycleCount < 10) { - cycleCount++; - retryCycles = false; - - for (int i=0; i<N; i++) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - if (!app.killedByAm && app.thread != null && app.containsCycle == true) { - app.adjSeq--; - app.completedAdjSeq--; - } - } - - for (int i=0; i<N; i++) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - if (!app.killedByAm && app.thread != null && app.containsCycle == true) { - if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, - true)) { - retryCycles = true; - } - } - } - } - - lastCachedGroup = lastCachedGroupUid = 0; - - for (int i=N-1; i>=0; i--) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - if (!app.killedByAm && app.thread != null) { - applyOomAdjLocked(app, true, now, nowElapsed); - - // Count the number of process types. - switch (app.getCurProcState()) { - case PROCESS_STATE_CACHED_ACTIVITY: - case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: - mNumCachedHiddenProcs++; - numCached++; - if (app.connectionGroup != 0) { - if (lastCachedGroupUid == app.uid - && lastCachedGroup == app.connectionGroup) { - // If this process is the next in the same group, we don't - // want it to count against our limit of the number of cached - // processes, so bump up the group count to account for it. - numCachedExtraGroup++; - } else { - lastCachedGroupUid = app.uid; - lastCachedGroup = app.connectionGroup; - } - } else { - lastCachedGroupUid = lastCachedGroup = 0; - } - if ((numCached - numCachedExtraGroup) > cachedProcessLimit) { - app.kill("cached #" + numCached, true); - } - break; - case ActivityManager.PROCESS_STATE_CACHED_EMPTY: - if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES - && app.lastActivityTime < oldTime) { - app.kill("empty for " - + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) - / 1000) + "s", true); - } else { - numEmpty++; - if (numEmpty > emptyProcessLimit) { - app.kill("empty #" + numEmpty, true); - } - } - break; - default: - mNumNonCachedProcs++; - break; - } - - if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) { - // If this is an isolated process, there are no services - // running in it, and it's not a special process with a - // custom entry point, then the process is no longer - // needed. We agressively kill these because we can by - // definition not re-use the same process again, and it is - // good to avoid having whatever code was running in them - // left sitting around after no longer needed. - app.kill("isolated not needed", true); - } else { - // Keeping this process, update its uid. - final UidRecord uidRec = app.uidRecord; - if (uidRec != null) { - uidRec.ephemeral = app.info.isInstantApp(); - if (uidRec.getCurProcState() > app.getCurProcState()) { - uidRec.setCurProcState(app.getCurProcState()); - } - if (app.hasForegroundServices()) { - uidRec.foregroundServices = true; - } - } - } - - if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME - && !app.killedByAm) { - numTrimming++; - } - } - } - - incrementProcStateSeqAndNotifyAppsLocked(); - - mNumServiceProcs = mNewNumServiceProcs; - - boolean allChanged = updateLowMemStateLocked(numCached, numEmpty, numTrimming); - - if (mAlwaysFinishActivities) { - // Need to do this on its own message because the stack may not - // be in a consistent state at this point. - mAtmInternal.scheduleDestroyAllActivities("always-finish"); - } - - if (allChanged) { - requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered()); - } - - ArrayList<UidRecord> becameIdle = null; - - // Update from any uid changes. - if (mLocalPowerManager != null) { - mLocalPowerManager.startUidChanges(); - } - for (int i=mActiveUids.size()-1; i>=0; i--) { - final UidRecord uidRec = mActiveUids.valueAt(i); - int uidChange = UidRecord.CHANGE_PROCSTATE; - if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT - && (uidRec.setProcState != uidRec.getCurProcState() - || uidRec.setWhitelist != uidRec.curWhitelist)) { - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec - + ": proc state from " + uidRec.setProcState + " to " - + uidRec.getCurProcState() + ", whitelist from " + uidRec.setWhitelist - + " to " + uidRec.curWhitelist); - if (ActivityManager.isProcStateBackground(uidRec.getCurProcState()) - && !uidRec.curWhitelist) { - // UID is now in the background (and not on the temp whitelist). Was it - // previously in the foreground (or on the temp whitelist)? - if (!ActivityManager.isProcStateBackground(uidRec.setProcState) - || uidRec.setWhitelist) { - uidRec.lastBackgroundTime = nowElapsed; - if (!mHandler.hasMessages(IDLE_UIDS_MSG)) { - // Note: the background settle time is in elapsed realtime, while - // the handler time base is uptime. All this means is that we may - // stop background uids later than we had intended, but that only - // happens because the device was sleeping so we are okay anyway. - mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, - mConstants.BACKGROUND_SETTLE_TIME); - } - } - if (uidRec.idle && !uidRec.setIdle) { - uidChange = UidRecord.CHANGE_IDLE; - if (becameIdle == null) { - becameIdle = new ArrayList<>(); - } - becameIdle.add(uidRec); - } - } else { - if (uidRec.idle) { - uidChange = UidRecord.CHANGE_ACTIVE; - EventLogTags.writeAmUidActive(uidRec.uid); - uidRec.idle = false; - } - uidRec.lastBackgroundTime = 0; - } - final boolean wasCached = uidRec.setProcState - > ActivityManager.PROCESS_STATE_RECEIVER; - final boolean isCached = uidRec.getCurProcState() - > ActivityManager.PROCESS_STATE_RECEIVER; - if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) { - uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED; - } - uidRec.setProcState = uidRec.getCurProcState(); - uidRec.setWhitelist = uidRec.curWhitelist; - uidRec.setIdle = uidRec.idle; - enqueueUidChangeLocked(uidRec, -1, uidChange); - noteUidProcessState(uidRec.uid, uidRec.getCurProcState()); - if (uidRec.foregroundServices) { - mServices.foregroundServiceProcStateChangedLocked(uidRec); - } - } - } - if (mLocalPowerManager != null) { - mLocalPowerManager.finishUidChanges(); - } - - if (becameIdle != null) { - // If we have any new uids that became idle this time, we need to make sure - // they aren't left with running services. - for (int i = becameIdle.size() - 1; i >= 0; i--) { - mServices.stopInBackgroundLocked(becameIdle.get(i).uid); - } - } - - if (mProcessStats.shouldWriteNowLocked(now)) { - mHandler.post(new ProcStatsRunnable(ActivityManagerService.this, mProcessStats)); - } - - // Run this after making sure all procstates are updated. - mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now); - - if (DEBUG_OOM_ADJ) { - final long duration = SystemClock.uptimeMillis() - now; - if (false) { - Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms", - new RuntimeException("here").fillInStackTrace()); - } else { - Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms"); - } - } - mOomAdjProfiler.oomAdjEnded(); + mOomAdjuster.updateOomAdjLocked(); } @Override @@ -18192,9 +16339,9 @@ public class ActivityManagerService extends IActivityManager.Stub mLocalPowerManager.startUidChanges(); } final int appId = UserHandle.getAppId(pkgUid); - final int N = mActiveUids.size(); - for (int i=N-1; i>=0; i--) { - final UidRecord uidRec = mActiveUids.valueAt(i); + final int N = mProcessList.mActiveUids.size(); + for (int i = N - 1; i >= 0; i--) { + final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i); final long bgTime = uidRec.lastBackgroundTime; if (bgTime > 0 && !uidRec.idle) { if (UserHandle.getAppId(uidRec.uid) == appId) { @@ -18219,49 +16366,17 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** Make the currently active UIDs idle after a certain grace period. */ final void idleUids() { synchronized (this) { - final int N = mActiveUids.size(); - if (N <= 0) { - return; - } - final long nowElapsed = SystemClock.elapsedRealtime(); - final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME; - long nextTime = 0; - if (mLocalPowerManager != null) { - mLocalPowerManager.startUidChanges(); - } - for (int i=N-1; i>=0; i--) { - final UidRecord uidRec = mActiveUids.valueAt(i); - final long bgTime = uidRec.lastBackgroundTime; - if (bgTime > 0 && !uidRec.idle) { - if (bgTime <= maxBgTime) { - EventLogTags.writeAmUidIdle(uidRec.uid); - uidRec.idle = true; - uidRec.setIdle = true; - doStopUidLocked(uidRec.uid, uidRec); - } else { - if (nextTime == 0 || nextTime > bgTime) { - nextTime = bgTime; - } - } - } - } - if (mLocalPowerManager != null) { - mLocalPowerManager.finishUidChanges(); - } - if (nextTime > 0) { - mHandler.removeMessages(IDLE_UIDS_MSG); - mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, - nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed); - } + mOomAdjuster.idleUidsLocked(); } } /** * Checks if any uid is coming from background to foreground or vice versa and if so, increments * the {@link UidRecord#curProcStateSeq} corresponding to that uid using global seq counter - * {@link #mProcStateSeqCounter} and notifies the app if it needs to block. + * {@link ProcessList#mProcStateSeqCounter} and notifies the app if it needs to block. */ @VisibleForTesting @GuardedBy("this") @@ -18271,8 +16386,8 @@ public class ActivityManagerService extends IActivityManager.Stub } // Used for identifying which uids need to block for network. ArrayList<Integer> blockingUids = null; - for (int i = mActiveUids.size() - 1; i >= 0; --i) { - final UidRecord uidRec = mActiveUids.valueAt(i); + for (int i = mProcessList.mActiveUids.size() - 1; i >= 0; --i) { + final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i); // If the network is not restricted for uid, then nothing to do here. if (!mInjector.isNetworkRestrictedForUid(uidRec.uid)) { continue; @@ -18320,13 +16435,15 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } if (!app.killedByAm && app.thread != null) { - final UidRecord uidRec = mActiveUids.get(app.uid); + final UidRecord uidRec = mProcessList.getUidRecordLocked(app.uid); try { if (DEBUG_NETWORK) { Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: " + uidRec); } - app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq); + if (uidRec != null) { + app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq); + } } catch (RemoteException ignored) { } } @@ -18367,7 +16484,7 @@ public class ActivityManagerService extends IActivityManager.Stub final void runInBackgroundDisabled(int uid) { synchronized (this) { - UidRecord uidRec = mActiveUids.get(uid); + UidRecord uidRec = mProcessList.getUidRecordLocked(uid); if (uidRec != null) { // This uid is actually running... should it be considered background now? if (uidRec.idle) { @@ -18380,24 +16497,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } - /** - * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs. - */ - void doStopUidForIdleUidsLocked() { - final int size = mActiveUids.size(); - for (int i = 0; i < size; i++) { - final int uid = mActiveUids.keyAt(i); - if (UserHandle.isCore(uid)) { - continue; - } - final UidRecord uidRec = mActiveUids.valueAt(i); - if (!uidRec.idle) { - continue; - } - doStopUidLocked(uidRec.uid, uidRec); - } - } - + @GuardedBy("this") final void doStopUidLocked(int uid, final UidRecord uidRec) { mServices.stopInBackgroundLocked(uid); enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE); @@ -18481,27 +16581,12 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) { - boolean changed = false; - for (int i=mActiveUids.size()-1; i>=0; i--) { - final UidRecord uidRec = mActiveUids.valueAt(i); - if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) { - uidRec.curWhitelist = onWhitelist; - changed = true; - } - } - if (changed) { - updateOomAdjLocked(); - } + mOomAdjuster.setAppIdTempWhitelistStateLocked(appId, onWhitelist); } @GuardedBy("this") final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) { - boolean changed = false; - final UidRecord uidRec = mActiveUids.get(uid); - if (uidRec != null && uidRec.curWhitelist != onWhitelist) { - uidRec.curWhitelist = onWhitelist; - updateOomAdjLocked(); - } + mOomAdjuster.setUidTempWhitelistStateLocked(uid, onWhitelist); } final void trimApplications() { @@ -19164,7 +17249,7 @@ public class ActivityManagerService extends IActivityManager.Stub } UidRecord record; synchronized (ActivityManagerService.this) { - record = mActiveUids.get(uid); + record = mProcessList.getUidRecordLocked(uid); if (record == null) { if (DEBUG_NETWORK) { Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid @@ -19708,6 +17793,11 @@ public class ActivityManagerService extends IActivityManager.Stub public boolean isAppForeground(int uid) { return ActivityManagerService.this.isAppForeground(uid); } + + @Override + public void clearPendingBackup(int userId) { + ActivityManagerService.this.clearPendingBackup(userId); + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { @@ -19783,7 +17873,7 @@ public class ActivityManagerService extends IActivityManager.Stub } UidRecord record; synchronized (this) { - record = mActiveUids.get(callingUid); + record = mProcessList.getUidRecordLocked(callingUid); if (record == null) { return; } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java new file mode 100644 index 000000000000..1f9362e246b7 --- /dev/null +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -0,0 +1,2101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; +import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.os.Process.SCHED_OTHER; +import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE; +import static android.os.Process.THREAD_GROUP_DEFAULT; +import static android.os.Process.THREAD_GROUP_RESTRICTED; +import static android.os.Process.THREAD_GROUP_TOP_APP; +import static android.os.Process.setProcessGroup; +import static android.os.Process.setThreadPriority; +import static android.os.Process.setThreadScheduler; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ_REASON; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS; +import static com.android.server.am.ActivityManagerService.DISPATCH_OOM_ADJ_OBSERVER_MSG; +import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESSES_CHANGED_UI_MSG; +import static com.android.server.am.ActivityManagerService.IDLE_UIDS_MSG; +import static com.android.server.am.ActivityManagerService.TAG_BACKUP; +import static com.android.server.am.ActivityManagerService.TAG_LRU; +import static com.android.server.am.ActivityManagerService.TAG_OOM_ADJ; +import static com.android.server.am.ActivityManagerService.TAG_PROCESS_OBSERVERS; +import static com.android.server.am.ActivityManagerService.TAG_PSS; +import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; + +import android.app.ActivityManager; +import android.app.usage.UsageEvents; +import android.content.Context; +import android.os.Binder; +import android.os.Debug; +import android.os.PowerManagerInternal; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.procstats.ProcessStats; +import com.android.server.LocalServices; +import com.android.server.wm.ActivityServiceConnectionsHolder; +import com.android.server.wm.WindowProcessController; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * All of the code required to compute proc states and oom_adj values. + */ +public final class OomAdjuster { + private static final String TAG = "OomAdjuster"; + + /** + * For some direct access we need to power manager. + */ + PowerManagerInternal mLocalPowerManager; + + /** + * Service for compacting background apps. + */ + AppCompactor mAppCompact; + + ActivityManagerConstants mConstants; + + final long[] mTmpLong = new long[3]; + + /** + * Current sequence id for oom_adj computation traversal. + */ + int mAdjSeq = 0; + + /** + * Keep track of the number of service processes we last found, to + * determine on the next iteration which should be B services. + */ + int mNumServiceProcs = 0; + int mNewNumAServiceProcs = 0; + int mNewNumServiceProcs = 0; + + /** + * Keep track of the non-cached/empty process we last found, to help + * determine how to distribute cached/empty processes next time. + */ + int mNumNonCachedProcs = 0; + + /** + * Keep track of the number of cached hidden procs, to balance oom adj + * distribution between those and empty procs. + */ + int mNumCachedHiddenProcs = 0; + + /** Track all uids that have actively running processes. */ + ActiveUids mActiveUids; + + private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet(); + + private final ActivityManagerService mService; + private final ProcessList mProcessList; + + OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) { + mService = service; + mProcessList = processList; + mActiveUids = activeUids; + + mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class); + mAppCompact = new AppCompactor(mService); + mConstants = mService.mConstants; + } + + /** + * Update OomAdj for a specific process. + * @param app The process to update + * @param oomAdjAll If it's ok to call updateOomAdjLocked() for all running apps + * if necessary, or skip. + * @return whether updateOomAdjLocked(app) was successful. + */ + @GuardedBy("mService") + final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll) { + final ProcessRecord TOP_APP = mService.getTopAppLocked(); + final boolean wasCached = app.cached; + + mAdjSeq++; + + // This is the desired cached adjusment we want to tell it to use. + // If our app is currently cached, we know it, and that is it. Otherwise, + // we don't know it yet, and it needs to now be cached we will then + // need to do a complete oom adj. + final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ + ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ; + boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false, + SystemClock.uptimeMillis()); + if (oomAdjAll + && (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) { + // Changed to/from cached state, so apps after it in the LRU + // list may also be changed. + updateOomAdjLocked(); + } + return success; + } + + @GuardedBy("mService") + private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj, + ProcessRecord TOP_APP, boolean doingAll, long now) { + if (app.thread == null) { + return false; + } + + computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false); + + return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime()); + } + + @GuardedBy("mService") + final void updateOomAdjLocked() { + mService.mOomAdjProfiler.oomAdjStarted(); + final ProcessRecord TOP_APP = mService.getTopAppLocked(); + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long oldTime = now - ProcessList.MAX_EMPTY_TIME; + final int N = mProcessList.getLruSizeLocked(); + + // Reset state in all uid records. + for (int i = mActiveUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = mActiveUids.valueAt(i); + if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, + "Starting update of " + uidRec); + uidRec.reset(); + } + + if (mService.mAtmInternal != null) { + mService.mAtmInternal.rankTaskLayersIfNeeded(); + } + + mAdjSeq++; + mNewNumServiceProcs = 0; + mNewNumAServiceProcs = 0; + + final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES; + final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES + - emptyProcessLimit; + + // Let's determine how many processes we have running vs. + // how many slots we have for background processes; we may want + // to put multiple processes in a slot of there are enough of + // them. + final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ + - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2 + / ProcessList.CACHED_APP_IMPORTANCE_LEVELS; + int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs; + if (numEmptyProcs > cachedProcessLimit) { + // If there are more empty processes than our limit on cached + // processes, then use the cached process limit for the factor. + // This ensures that the really old empty processes get pushed + // down to the bottom, so if we are running low on memory we will + // have a better chance at keeping around more cached processes + // instead of a gazillion empty processes. + numEmptyProcs = cachedProcessLimit; + } + int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots; + if (emptyFactor < 1) emptyFactor = 1; + int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1) + / numSlots; + if (cachedFactor < 1) cachedFactor = 1; + int stepCached = -1; + int stepEmpty = -1; + int numCached = 0; + int numCachedExtraGroup = 0; + int numEmpty = 0; + int numTrimming = 0; + int lastCachedGroup = 0; + int lastCachedGroupImportance = 0; + int lastCachedGroupUid = 0; + + mNumNonCachedProcs = 0; + mNumCachedHiddenProcs = 0; + + // First update the OOM adjustment for each of the + // application processes based on their current state. + int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; + int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2); + int curCachedImpAdj = 0; + int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; + int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2); + + boolean retryCycles = false; + + // need to reset cycle state before calling computeOomAdjLocked because of service conns + for (int i = N - 1; i >= 0; i--) { + ProcessRecord app = mProcessList.mLruProcesses.get(i); + app.containsCycle = false; + app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY); + app.setCurRawAdj(ProcessList.UNKNOWN_ADJ); + } + for (int i = N - 1; i >= 0; i--) { + ProcessRecord app = mProcessList.mLruProcesses.get(i); + if (!app.killedByAm && app.thread != null) { + app.procStateChanged = false; + computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false); + + // if any app encountered a cycle, we need to perform an additional loop later + retryCycles |= app.containsCycle; + + // If we haven't yet assigned the final cached adj + // to the process, do that now. + if (app.curAdj >= ProcessList.UNKNOWN_ADJ) { + switch (app.getCurProcState()) { + case PROCESS_STATE_CACHED_ACTIVITY: + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + case ActivityManager.PROCESS_STATE_CACHED_RECENT: + // Figure out the next cached level, taking into account groups. + boolean inGroup = false; + if (app.connectionGroup != 0) { + if (lastCachedGroupUid == app.uid + && lastCachedGroup == app.connectionGroup) { + // This is in the same group as the last process, just tweak + // adjustment by importance. + if (app.connectionImportance > lastCachedGroupImportance) { + lastCachedGroupImportance = app.connectionImportance; + if (curCachedAdj < nextCachedAdj + && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) { + curCachedImpAdj++; + } + } + inGroup = true; + } else { + lastCachedGroupUid = app.uid; + lastCachedGroup = app.connectionGroup; + lastCachedGroupImportance = app.connectionImportance; + } + } + if (!inGroup && curCachedAdj != nextCachedAdj) { + stepCached++; + curCachedImpAdj = 0; + if (stepCached >= cachedFactor) { + stepCached = 0; + curCachedAdj = nextCachedAdj; + nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; + if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { + nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; + } + } + } + // This process is a cached process holding activities... + // assign it the next cached value for that type, and then + // step that cached level. + app.setCurRawAdj(curCachedAdj + curCachedImpAdj); + app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj); + if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i + + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj + + " curCachedImpAdj=" + curCachedImpAdj + ")"); + break; + default: + // Figure out the next cached level. + if (curEmptyAdj != nextEmptyAdj) { + stepEmpty++; + if (stepEmpty >= emptyFactor) { + stepEmpty = 0; + curEmptyAdj = nextEmptyAdj; + nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; + if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) { + nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ; + } + } + } + // For everything else, assign next empty cached process + // level and bump that up. Note that this means that + // long-running services that have dropped down to the + // cached level will be treated as empty (since their process + // state is still as a service), which is what we want. + app.setCurRawAdj(curEmptyAdj); + app.curAdj = app.modifyRawOomAdj(curEmptyAdj); + if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i + + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj + + ")"); + break; + } + } + } + } + + // Cycle strategy: + // - Retry computing any process that has encountered a cycle. + // - Continue retrying until no process was promoted. + // - Iterate from least important to most important. + int cycleCount = 0; + while (retryCycles && cycleCount < 10) { + cycleCount++; + retryCycles = false; + + for (int i = 0; i < N; i++) { + ProcessRecord app = mProcessList.mLruProcesses.get(i); + if (!app.killedByAm && app.thread != null && app.containsCycle == true) { + app.adjSeq--; + app.completedAdjSeq--; + } + } + + for (int i = 0; i < N; i++) { + ProcessRecord app = mProcessList.mLruProcesses.get(i); + if (!app.killedByAm && app.thread != null && app.containsCycle == true) { + if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, + true)) { + retryCycles = true; + } + } + } + } + + lastCachedGroup = lastCachedGroupUid = 0; + + for (int i = N - 1; i >= 0; i--) { + ProcessRecord app = mProcessList.mLruProcesses.get(i); + if (!app.killedByAm && app.thread != null) { + applyOomAdjLocked(app, true, now, nowElapsed); + + // Count the number of process types. + switch (app.getCurProcState()) { + case PROCESS_STATE_CACHED_ACTIVITY: + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + mNumCachedHiddenProcs++; + numCached++; + if (app.connectionGroup != 0) { + if (lastCachedGroupUid == app.uid + && lastCachedGroup == app.connectionGroup) { + // If this process is the next in the same group, we don't + // want it to count against our limit of the number of cached + // processes, so bump up the group count to account for it. + numCachedExtraGroup++; + } else { + lastCachedGroupUid = app.uid; + lastCachedGroup = app.connectionGroup; + } + } else { + lastCachedGroupUid = lastCachedGroup = 0; + } + if ((numCached - numCachedExtraGroup) > cachedProcessLimit) { + app.kill("cached #" + numCached, true); + } + break; + case ActivityManager.PROCESS_STATE_CACHED_EMPTY: + if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES + && app.lastActivityTime < oldTime) { + app.kill("empty for " + + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) + / 1000) + "s", true); + } else { + numEmpty++; + if (numEmpty > emptyProcessLimit) { + app.kill("empty #" + numEmpty, true); + } + } + break; + default: + mNumNonCachedProcs++; + break; + } + + if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) { + // If this is an isolated process, there are no services + // running in it, and it's not a special process with a + // custom entry point, then the process is no longer + // needed. We agressively kill these because we can by + // definition not re-use the same process again, and it is + // good to avoid having whatever code was running in them + // left sitting around after no longer needed. + app.kill("isolated not needed", true); + } else { + // Keeping this process, update its uid. + final UidRecord uidRec = app.uidRecord; + if (uidRec != null) { + uidRec.ephemeral = app.info.isInstantApp(); + if (uidRec.getCurProcState() > app.getCurProcState()) { + uidRec.setCurProcState(app.getCurProcState()); + } + if (app.hasForegroundServices()) { + uidRec.foregroundServices = true; + } + } + } + + if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME + && !app.killedByAm) { + numTrimming++; + } + } + } + + mService.incrementProcStateSeqAndNotifyAppsLocked(); + + mNumServiceProcs = mNewNumServiceProcs; + + boolean allChanged = mService.updateLowMemStateLocked(numCached, numEmpty, numTrimming); + + if (mService.mAlwaysFinishActivities) { + // Need to do this on its own message because the stack may not + // be in a consistent state at this point. + mService.mAtmInternal.scheduleDestroyAllActivities("always-finish"); + } + + if (allChanged) { + mService.requestPssAllProcsLocked(now, false, + mService.mProcessStats.isMemFactorLowered()); + } + + ArrayList<UidRecord> becameIdle = null; + + // Update from any uid changes. + if (mLocalPowerManager != null) { + mLocalPowerManager.startUidChanges(); + } + for (int i = mActiveUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = mActiveUids.valueAt(i); + int uidChange = UidRecord.CHANGE_PROCSTATE; + if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT + && (uidRec.setProcState != uidRec.getCurProcState() + || uidRec.setWhitelist != uidRec.curWhitelist)) { + if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec + + ": proc state from " + uidRec.setProcState + " to " + + uidRec.getCurProcState() + ", whitelist from " + uidRec.setWhitelist + + " to " + uidRec.curWhitelist); + if (ActivityManager.isProcStateBackground(uidRec.getCurProcState()) + && !uidRec.curWhitelist) { + // UID is now in the background (and not on the temp whitelist). Was it + // previously in the foreground (or on the temp whitelist)? + if (!ActivityManager.isProcStateBackground(uidRec.setProcState) + || uidRec.setWhitelist) { + uidRec.lastBackgroundTime = nowElapsed; + if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { + // Note: the background settle time is in elapsed realtime, while + // the handler time base is uptime. All this means is that we may + // stop background uids later than we had intended, but that only + // happens because the device was sleeping so we are okay anyway. + mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, + mConstants.BACKGROUND_SETTLE_TIME); + } + } + if (uidRec.idle && !uidRec.setIdle) { + uidChange = UidRecord.CHANGE_IDLE; + if (becameIdle == null) { + becameIdle = new ArrayList<>(); + } + becameIdle.add(uidRec); + } + } else { + if (uidRec.idle) { + uidChange = UidRecord.CHANGE_ACTIVE; + EventLogTags.writeAmUidActive(uidRec.uid); + uidRec.idle = false; + } + uidRec.lastBackgroundTime = 0; + } + final boolean wasCached = uidRec.setProcState + > ActivityManager.PROCESS_STATE_RECEIVER; + final boolean isCached = uidRec.getCurProcState() + > ActivityManager.PROCESS_STATE_RECEIVER; + if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) { + uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED; + } + uidRec.setProcState = uidRec.getCurProcState(); + uidRec.setWhitelist = uidRec.curWhitelist; + uidRec.setIdle = uidRec.idle; + mService.enqueueUidChangeLocked(uidRec, -1, uidChange); + mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState()); + if (uidRec.foregroundServices) { + mService.mServices.foregroundServiceProcStateChangedLocked(uidRec); + } + } + } + if (mLocalPowerManager != null) { + mLocalPowerManager.finishUidChanges(); + } + + if (becameIdle != null) { + // If we have any new uids that became idle this time, we need to make sure + // they aren't left with running services. + for (int i = becameIdle.size() - 1; i >= 0; i--) { + mService.mServices.stopInBackgroundLocked(becameIdle.get(i).uid); + } + } + + if (mService.mProcessStats.shouldWriteNowLocked(now)) { + mService.mHandler.post(new ActivityManagerService.ProcStatsRunnable(mService, + mService.mProcessStats)); + } + + // Run this after making sure all procstates are updated. + mService.mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now); + + if (DEBUG_OOM_ADJ) { + final long duration = SystemClock.uptimeMillis() - now; + if (false) { + Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms", + new RuntimeException("here").fillInStackTrace()); + } else { + Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms"); + } + } + mService.mOomAdjProfiler.oomAdjEnded(); + } + + private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = + new ComputeOomAdjWindowCallback(); + + /** These methods are called inline during computeOomAdjLocked(), on the same thread */ + private final class ComputeOomAdjWindowCallback + implements WindowProcessController.ComputeOomAdjCallback { + + ProcessRecord app; + int adj; + boolean foregroundActivities; + int procState; + int schedGroup; + int appUid; + int logUid; + int processStateCurTop; + + void initialize(ProcessRecord app, int adj, boolean foregroundActivities, + int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) { + this.app = app; + this.adj = adj; + this.foregroundActivities = foregroundActivities; + this.procState = procState; + this.schedGroup = schedGroup; + this.appUid = appUid; + this.logUid = logUid; + this.processStateCurTop = processStateCurTop; + } + + @Override + public void onVisibleActivity() { + // App has a visible activity; only upgrade adjustment. + if (adj > ProcessList.VISIBLE_APP_ADJ) { + adj = ProcessList.VISIBLE_APP_ADJ; + app.adjType = "vis-activity"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app); + } + } + if (procState > processStateCurTop) { + procState = processStateCurTop; + app.adjType = "vis-activity"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to vis-activity (top): " + app); + } + } + if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) { + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + } + app.cached = false; + app.empty = false; + foregroundActivities = true; + } + + @Override + public void onPausedActivity() { + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + app.adjType = "pause-activity"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app); + } + } + if (procState > processStateCurTop) { + procState = processStateCurTop; + app.adjType = "pause-activity"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to pause-activity (top): " + app); + } + } + if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) { + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + } + app.cached = false; + app.empty = false; + foregroundActivities = true; + } + + @Override + public void onStoppingActivity(boolean finishing) { + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + app.adjType = "stop-activity"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise adj to stop-activity: " + app); + } + } + + // For the process state, we will at this point consider the process to be cached. It + // will be cached either as an activity or empty depending on whether the activity is + // finishing. We do this so that we can treat the process as cached for purposes of + // memory trimming (determining current memory level, trim command to send to process) + // since there can be an arbitrary number of stopping processes and they should soon all + // go into the cached state. + if (!finishing) { + if (procState > PROCESS_STATE_LAST_ACTIVITY) { + procState = PROCESS_STATE_LAST_ACTIVITY; + app.adjType = "stop-activity"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to stop-activity: " + app); + } + } + } + app.cached = false; + app.empty = false; + foregroundActivities = true; + } + + @Override + public void onOtherActivity() { + if (procState > PROCESS_STATE_CACHED_ACTIVITY) { + procState = PROCESS_STATE_CACHED_ACTIVITY; + app.adjType = "cch-act"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to cached activity: " + app); + } + } + } + } + + private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj, + ProcessRecord TOP_APP, boolean doingAll, long now, boolean cycleReEval) { + if (mAdjSeq == app.adjSeq) { + if (app.adjSeq == app.completedAdjSeq) { + // This adjustment has already been computed successfully. + return false; + } else { + // The process is being computed, so there is a cycle. We cannot + // rely on this process's state. + app.containsCycle = true; + + return false; + } + } + + if (app.thread == null) { + app.adjSeq = mAdjSeq; + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND); + app.setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY); + app.curAdj = ProcessList.CACHED_APP_MAX_ADJ; + app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ); + app.completedAdjSeq = app.adjSeq; + return false; + } + + app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; + app.adjSource = null; + app.adjTarget = null; + app.empty = false; + app.cached = false; + + final WindowProcessController wpc = app.getWindowProcessController(); + final int appUid = app.info.uid; + final int logUid = mService.mCurOomAdjUid; + + int prevAppAdj = app.curAdj; + int prevProcState = app.getCurProcState(); + + if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) { + // The max adjustment doesn't allow this app to be anything + // below foreground, so it is not worth doing work for it. + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + mService.reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app); + } + app.adjType = "fixed"; + app.adjSeq = mAdjSeq; + app.setCurRawAdj(app.maxAdj); + app.setHasForegroundActivities(false); + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT); + app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT); + // System processes can do UI, and when they do we want to have + // them trim their memory after the user leaves the UI. To + // facilitate this, here we need to determine whether or not it + // is currently showing UI. + app.systemNoUi = true; + if (app == TOP_APP) { + app.systemNoUi = false; + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); + app.adjType = "pers-top-activity"; + } else if (app.hasTopUi()) { + // sched group/proc state adjustment is below + app.systemNoUi = false; + app.adjType = "pers-top-ui"; + } else if (wpc.hasVisibleActivities()) { + app.systemNoUi = false; + } + if (!app.systemNoUi) { + if (mService.mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) { + // screen on, promote UI + app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI); + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); + } else { + // screen off, restrict UI scheduling + app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE); + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED); + } + } + app.setCurRawProcState(app.getCurProcState()); + app.curAdj = app.maxAdj; + app.completedAdjSeq = app.adjSeq; + // if curAdj is less than prevAppAdj, then this process was promoted + return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState; + } + + app.systemNoUi = false; + + final int PROCESS_STATE_CUR_TOP = mService.mAtmInternal.getTopProcessState(); + + // Determine the importance of the process, starting with most + // important to least, and assign an appropriate OOM adjustment. + int adj; + int schedGroup; + int procState; + int cachedAdjSeq; + + boolean foregroundActivities = false; + mTmpBroadcastQueue.clear(); + if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) { + // The last app on the list is the foreground app. + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_TOP_APP; + app.adjType = "top-activity"; + foregroundActivities = true; + procState = PROCESS_STATE_CUR_TOP; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app); + } + } else if (app.runningRemoteAnimation) { + adj = ProcessList.VISIBLE_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_TOP_APP; + app.adjType = "running-remote-anim"; + procState = PROCESS_STATE_CUR_TOP; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app); + } + } else if (app.getActiveInstrumentation() != null) { + // Don't want to kill running instrumentation. + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + app.adjType = "instrumentation"; + procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app); + } + } else if (mService.isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) { + // An app that is currently receiving a broadcast also + // counts as being in the foreground for OOM killer purposes. + // It's placed in a sched group based on the nature of the + // broadcast as reflected by which queue it's active in. + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = (mTmpBroadcastQueue.contains(mService.mFgBroadcastQueue)) + ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; + app.adjType = "broadcast"; + procState = ActivityManager.PROCESS_STATE_RECEIVER; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app); + } + } else if (app.executingServices.size() > 0) { + // An app that is currently executing a service callback also + // counts as being in the foreground. + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = app.execServicesFg ? + ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; + app.adjType = "exec-service"; + procState = ActivityManager.PROCESS_STATE_SERVICE; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app); + } + //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app); + } else if (app == TOP_APP) { + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + app.adjType = "top-sleeping"; + foregroundActivities = true; + procState = PROCESS_STATE_CUR_TOP; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app); + } + } else { + // As far as we know the process is empty. We may change our mind later. + schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + // At this point we don't actually know the adjustment. Use the cached adj + // value that the caller wants us to. + adj = cachedAdj; + procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + app.cached = true; + app.empty = true; + app.adjType = "cch-empty"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app); + } + } + + // Examine all activities if not already foreground. + if (!foregroundActivities && wpc.hasActivities()) { + mTmpComputeOomAdjWindowCallback.initialize(app, adj, foregroundActivities, procState, + schedGroup, appUid, logUid, PROCESS_STATE_CUR_TOP); + final int minLayer = wpc.computeOomAdjFromActivities( + ProcessList.VISIBLE_APP_LAYER_MAX, mTmpComputeOomAdjWindowCallback); + + adj = mTmpComputeOomAdjWindowCallback.adj; + foregroundActivities = mTmpComputeOomAdjWindowCallback.foregroundActivities; + procState = mTmpComputeOomAdjWindowCallback.procState; + schedGroup = mTmpComputeOomAdjWindowCallback.schedGroup; + + if (adj == ProcessList.VISIBLE_APP_ADJ) { + adj += minLayer; + } + } + + if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.hasRecentTasks()) { + procState = ActivityManager.PROCESS_STATE_CACHED_RECENT; + app.adjType = "cch-rec"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app); + } + } + + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ + || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + if (app.hasForegroundServices()) { + // The user is aware of this app, so make it visible. + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + app.cached = false; + app.adjType = "fg-service"; + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to fg service: " + app); + } + } else if (app.hasOverlayUi()) { + // The process is display an overlay UI. + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + app.cached = false; + app.adjType = "has-overlay-ui"; + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app); + } + } + } + + // If the app was recently in the foreground and moved to a foreground service status, + // allow it to get a higher rank in memory for some time, compared to other foreground + // services so that it can finish performing any persistence/processing of in-memory state. + if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now + || app.setProcState <= ActivityManager.PROCESS_STATE_TOP)) { + adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; + app.adjType = "fg-service-act"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app); + } + } + + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ + || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { + if (app.forcingToImportant != null) { + // This is currently used for toasts... they are not interactive, and + // we don't want them to cause the app to become fully foreground (and + // thus out of background check), so we yes the best background level we can. + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; + app.cached = false; + app.adjType = "force-imp"; + app.adjSource = app.forcingToImportant; + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app); + } + } + } + + if (mService.mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) { + if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) { + // We don't want to kill the current heavy-weight process. + adj = ProcessList.HEAVY_WEIGHT_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + app.cached = false; + app.adjType = "heavy"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app); + } + } + if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { + procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; + app.adjType = "heavy"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app); + } + } + } + + if (wpc.isHomeProcess()) { + if (adj > ProcessList.HOME_APP_ADJ) { + // This process is hosting what we currently consider to be the + // home app, so we don't want to let it go into the background. + adj = ProcessList.HOME_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + app.cached = false; + app.adjType = "home"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app); + } + } + if (procState > ActivityManager.PROCESS_STATE_HOME) { + procState = ActivityManager.PROCESS_STATE_HOME; + app.adjType = "home"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app); + } + } + } + + if (wpc.isPreviousProcess() && app.hasActivities()) { + if (adj > ProcessList.PREVIOUS_APP_ADJ) { + // This was the previous process that showed UI to the user. + // We want to try to keep it around more aggressively, to give + // a good experience around switching between two apps. + adj = ProcessList.PREVIOUS_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + app.cached = false; + app.adjType = "previous"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app); + } + } + if (procState > PROCESS_STATE_LAST_ACTIVITY) { + procState = PROCESS_STATE_LAST_ACTIVITY; + app.adjType = "previous"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app); + } + } + } + + if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj + + " reason=" + app.adjType); + + // By default, we use the computed adjustment. It may be changed if + // there are applications dependent on our services or providers, but + // this gives us a baseline and makes sure we don't get into an + // infinite recursion. If we're re-evaluating due to cycles, use the previously computed + // values. + app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj())); + app.setCurRawProcState(!cycleReEval + ? procState + : Math.min(procState, app.getCurRawProcState())); + + app.hasStartedServices = false; + app.adjSeq = mAdjSeq; + + final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId); + if (backupTarget != null && app == backupTarget.app) { + // If possible we want to avoid killing apps while they're being backed up + if (adj > ProcessList.BACKUP_APP_ADJ) { + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app); + adj = ProcessList.BACKUP_APP_ADJ; + if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { + procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; + } + app.adjType = "backup"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app); + } + app.cached = false; + } + if (procState > ActivityManager.PROCESS_STATE_BACKUP) { + procState = ActivityManager.PROCESS_STATE_BACKUP; + app.adjType = "backup"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app); + } + } + } + + boolean mayBeTop = false; + String mayBeTopType = null; + Object mayBeTopSource = null; + Object mayBeTopTarget = null; + + for (int is = app.services.size() - 1; + is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND + || procState > ActivityManager.PROCESS_STATE_TOP); + is--) { + ServiceRecord s = app.services.valueAt(is); + if (s.startRequested) { + app.hasStartedServices = true; + if (procState > ActivityManager.PROCESS_STATE_SERVICE) { + procState = ActivityManager.PROCESS_STATE_SERVICE; + app.adjType = "started-services"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to started service: " + app); + } + } + if (app.hasShownUi && !wpc.isHomeProcess()) { + // If this process has shown some UI, let it immediately + // go to the LRU list because it may be pretty heavy with + // UI stuff. We'll tag it with a label just to help + // debug and understand what is going on. + if (adj > ProcessList.SERVICE_ADJ) { + app.adjType = "cch-started-ui-services"; + } + } else { + if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { + // This service has seen some activity within + // recent memory, so we will keep its process ahead + // of the background processes. + if (adj > ProcessList.SERVICE_ADJ) { + adj = ProcessList.SERVICE_ADJ; + app.adjType = "started-services"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise adj to started service: " + app); + } + app.cached = false; + } + } + // If we have let the service slide into the background + // state, still have some text describing what it is doing + // even though the service no longer has an impact. + if (adj > ProcessList.SERVICE_ADJ) { + app.adjType = "cch-started-services"; + } + } + } + + for (int conni = s.connections.size() - 1; + conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND + || procState > ActivityManager.PROCESS_STATE_TOP); + conni--) { + ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); + for (int i = 0; + i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND + || procState > ActivityManager.PROCESS_STATE_TOP); + i++) { + // XXX should compute this based on the max of + // all connected clients. + ConnectionRecord cr = clist.get(i); + if (cr.binding.client == app) { + // Binding to oneself is not interesting. + continue; + } + + boolean trackedProcState = false; + if ((cr.flags& Context.BIND_WAIVE_PRIORITY) == 0) { + ProcessRecord client = cr.binding.client; + computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval); + + if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) { + continue; + } + + int clientAdj = client.getCurRawAdj(); + int clientProcState = client.getCurRawProcState(); + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. The specific cached state + // doesn't propagate except under certain conditions. + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + } + String adjType = null; + if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { + // Not doing bind OOM management, so treat + // this guy more like a started service. + if (app.hasShownUi && !wpc.isHomeProcess()) { + // If this process has shown some UI, let it immediately + // go to the LRU list because it may be pretty heavy with + // UI stuff. We'll tag it with a label just to help + // debug and understand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-ui-services"; + } + app.cached = false; + clientAdj = adj; + clientProcState = procState; + } else { + if (now >= (s.lastActivity + + mConstants.MAX_SERVICE_INACTIVITY)) { + // This service has not seen activity within + // recent memory, so allow it to drop to the + // LRU list if there is no other reason to keep + // it around. We'll also tag it with a label just + // to help debug and undertand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-services"; + } + clientAdj = adj; + } + } + } + if (adj > clientAdj) { + // If this process has recently shown UI, and + // the process that is binding to it is less + // important than being visible, then we don't + // care about the binding as much as we care + // about letting this process get into the LRU + // list to be killed and restarted if needed for + // memory. + if (app.hasShownUi && !wpc.isHomeProcess() + && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { + if (adj >= ProcessList.CACHED_APP_MIN_ADJ) { + adjType = "cch-bound-ui-services"; + } + } else { + int newAdj; + if ((cr.flags&(Context.BIND_ABOVE_CLIENT + |Context.BIND_IMPORTANT)) != 0) { + if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) { + newAdj = clientAdj; + } else { + // make this service persistent + newAdj = ProcessList.PERSISTENT_SERVICE_ADJ; + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + procState = ActivityManager.PROCESS_STATE_PERSISTENT; + cr.trackProcState(procState, mAdjSeq, now); + trackedProcState = true; + } + } else if ((cr.flags & Context.BIND_ADJUST_BELOW_PERCEPTIBLE) != 0 + && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ + && adj > ProcessList.PERCEPTIBLE_APP_ADJ + 1) { + newAdj = ProcessList.PERCEPTIBLE_APP_ADJ + 1; + } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0 + && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ + && adj > ProcessList.PERCEPTIBLE_APP_ADJ) { + newAdj = ProcessList.PERCEPTIBLE_APP_ADJ; + } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { + newAdj = clientAdj; + } else { + if (adj > ProcessList.VISIBLE_APP_ADJ) { + newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ); + } else { + newAdj = adj; + } + } + if (!client.cached) { + app.cached = false; + } + if (adj > newAdj) { + adj = newAdj; + app.setCurRawAdj(adj); + adjType = "service"; + } + } + } + if ((cr.flags & (Context.BIND_NOT_FOREGROUND + | Context.BIND_IMPORTANT_BACKGROUND)) == 0) { + // This will treat important bound services identically to + // the top app, which may behave differently than generic + // foreground work. + final int curSchedGroup = client.getCurrentSchedulingGroup(); + if (curSchedGroup > schedGroup) { + if ((cr.flags&Context.BIND_IMPORTANT) != 0) { + schedGroup = curSchedGroup; + } else { + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + } + } + if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) { + if (clientProcState == ActivityManager.PROCESS_STATE_TOP) { + // Special handling of clients who are in the top state. + // We *may* want to consider this process to be in the + // top state as well, but only if there is not another + // reason for it to be running. Being on the top is a + // special state, meaning you are specifically running + // for the current top app. If the process is already + // running in the background for some other reason, it + // is more important to continue considering it to be + // in the background state. + mayBeTop = true; + mayBeTopType = "service"; + mayBeTopSource = cr.binding.client; + mayBeTopTarget = s.instanceName; + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + } else { + // Special handling for above-top states (persistent + // processes). These should not bring the current process + // into the top state, since they are not on top. Instead + // give them the best state after that. + if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) { + clientProcState = + ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else if (mService.mWakefulness + == PowerManagerInternal.WAKEFULNESS_AWAKE && + (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) + != 0) { + clientProcState = + ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else { + clientProcState = + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + } + } + } + } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) { + if (clientProcState < + ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { + clientProcState = + ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; + } + } else { + if (clientProcState < + ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) { + clientProcState = + ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; + } + } + if (!trackedProcState) { + cr.trackProcState(clientProcState, mAdjSeq, now); + } + if (procState > clientProcState) { + procState = clientProcState; + app.setCurRawProcState(procState); + if (adjType == null) { + adjType = "service"; + } + } + if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + && (cr.flags & Context.BIND_SHOWING_UI) != 0) { + app.setPendingUiClean(true); + } + if (adjType != null) { + app.adjType = adjType; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; + app.adjSource = cr.binding.client; + app.adjSourceProcState = clientProcState; + app.adjTarget = s.instanceName; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + cr.binding.client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + } + if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { + app.treatLikeActivity = true; + } + final ActivityServiceConnectionsHolder a = cr.activity; + if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { + if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ + && a.isActivityVisible()) { + adj = ProcessList.FOREGROUND_APP_ADJ; + app.setCurRawAdj(adj); + if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { + if ((cr.flags&Context.BIND_IMPORTANT) != 0) { + schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND; + } else { + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + } + } + app.cached = false; + app.adjType = "service"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; + app.adjSource = a; + app.adjSourceProcState = procState; + app.adjTarget = s.instanceName; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise to service w/activity: " + app); + } + } + } + } + } + } + + for (int provi = app.pubProviders.size() - 1; + provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND + || procState > ActivityManager.PROCESS_STATE_TOP); + provi--) { + ContentProviderRecord cpr = app.pubProviders.valueAt(provi); + for (int i = cpr.connections.size() - 1; + i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND + || procState > ActivityManager.PROCESS_STATE_TOP); + i--) { + ContentProviderConnection conn = cpr.connections.get(i); + ProcessRecord client = conn.client; + if (client == app) { + // Being our own client is not interesting. + continue; + } + computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval); + + if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) { + continue; + } + + int clientAdj = client.getCurRawAdj(); + int clientProcState = client.getCurRawProcState(); + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + } + String adjType = null; + if (adj > clientAdj) { + if (app.hasShownUi && !wpc.isHomeProcess() + && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { + adjType = "cch-ui-provider"; + } else { + adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ + ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; + app.setCurRawAdj(adj); + adjType = "provider"; + } + app.cached &= client.cached; + } + if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) { + if (clientProcState == ActivityManager.PROCESS_STATE_TOP) { + // Special handling of clients who are in the top state. + // We *may* want to consider this process to be in the + // top state as well, but only if there is not another + // reason for it to be running. Being on the top is a + // special state, meaning you are specifically running + // for the current top app. If the process is already + // running in the background for some other reason, it + // is more important to continue considering it to be + // in the background state. + mayBeTop = true; + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + mayBeTopType = adjType = "provider-top"; + mayBeTopSource = client; + mayBeTopTarget = cpr.name; + } else { + // Special handling for above-top states (persistent + // processes). These should not bring the current process + // into the top state, since they are not on top. Instead + // give them the best state after that. + clientProcState = + ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + if (adjType == null) { + adjType = "provider"; + } + } + } + conn.trackProcState(clientProcState, mAdjSeq, now); + if (procState > clientProcState) { + procState = clientProcState; + app.setCurRawProcState(procState); + } + if (client.getCurrentSchedulingGroup() > schedGroup) { + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + } + if (adjType != null) { + app.adjType = adjType; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_PROVIDER_IN_USE; + app.adjSource = client; + app.adjSourceProcState = clientProcState; + app.adjTarget = cpr.name; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + } + // If the provider has external (non-framework) process + // dependencies, ensure that its adjustment is at least + // FOREGROUND_APP_ADJ. + if (cpr.hasExternalProcessHandles()) { + if (adj > ProcessList.FOREGROUND_APP_ADJ) { + adj = ProcessList.FOREGROUND_APP_ADJ; + app.setCurRawAdj(adj); + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + app.cached = false; + app.adjType = "ext-provider"; + app.adjTarget = cpr.name; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise adj to external provider: " + app); + } + } + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + app.setCurRawProcState(procState); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to external provider: " + app); + } + } + } + } + + if (app.lastProviderTime > 0 && + (app.lastProviderTime + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) { + if (adj > ProcessList.PREVIOUS_APP_ADJ) { + adj = ProcessList.PREVIOUS_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + app.cached = false; + app.adjType = "recent-provider"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise adj to recent provider: " + app); + } + } + if (procState > PROCESS_STATE_LAST_ACTIVITY) { + procState = PROCESS_STATE_LAST_ACTIVITY; + app.adjType = "recent-provider"; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to recent provider: " + app); + } + } + } + + if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) { + // A client of one of our services or providers is in the top state. We + // *may* want to be in the top state, but not if we are already running in + // the background for some other reason. For the decision here, we are going + // to pick out a few specific states that we want to remain in when a client + // is top (states that tend to be longer-term) and otherwise allow it to go + // to the top state. + switch (procState) { + case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE: + case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE: + // Something else is keeping it at this level, just leave it. + break; + case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: + case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: + case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND: + case ActivityManager.PROCESS_STATE_SERVICE: + // These all are longer-term states, so pull them up to the top + // of the background states, but not all the way to the top state. + procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + app.adjType = mayBeTopType; + app.adjSource = mayBeTopSource; + app.adjTarget = mayBeTopTarget; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType + + ": " + app + ", due to " + mayBeTopSource + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + break; + default: + // Otherwise, top is a better choice, so take it. + procState = ActivityManager.PROCESS_STATE_TOP; + app.adjType = mayBeTopType; + app.adjSource = mayBeTopSource; + app.adjTarget = mayBeTopTarget; + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType + + ": " + app + ", due to " + mayBeTopSource + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + break; + } + } + + if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) { + if (app.hasClientActivities()) { + // This is a cached process, but with client activities. Mark it so. + procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; + app.adjType = "cch-client-act"; + } else if (app.treatLikeActivity) { + // This is a cached process, but somebody wants us to treat it like it has + // an activity, okay! + procState = PROCESS_STATE_CACHED_ACTIVITY; + app.adjType = "cch-as-act"; + } + } + + if (adj == ProcessList.SERVICE_ADJ) { + if (doingAll) { + app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); + mNewNumServiceProcs++; + //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb); + if (!app.serviceb) { + // This service isn't far enough down on the LRU list to + // normally be a B service, but if we are low on RAM and it + // is large we want to force it down since we would prefer to + // keep launcher over it. + if (mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL + && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { + app.serviceHighRam = true; + app.serviceb = true; + //Slog.i(TAG, "ADJ " + app + " high ram!"); + } else { + mNewNumAServiceProcs++; + //Slog.i(TAG, "ADJ " + app + " not high ram!"); + } + } else { + app.serviceHighRam = false; + } + } + if (app.serviceb) { + adj = ProcessList.SERVICE_B_ADJ; + } + } + + app.setCurRawAdj(adj); + + //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid + + // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj); + if (adj > app.maxAdj) { + adj = app.maxAdj; + if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + } + } + + // Put bound foreground services in a special sched group for additional + // restrictions on screen off + if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE && + mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) { + if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) { + schedGroup = ProcessList.SCHED_GROUP_RESTRICTED; + } + } + + // Do final modification to adj. Everything we do between here and applying + // the final setAdj must be done in this function, because we will also use + // it when computing the final cached adj later. Note that we don't need to + // worry about this for max adj above, since max adj will always be used to + // keep it out of the cached vaues. + app.curAdj = app.modifyRawOomAdj(adj); + app.setCurrentSchedulingGroup(schedGroup); + app.setCurProcState(procState); + app.setCurRawProcState(procState); + app.setHasForegroundActivities(foregroundActivities); + app.completedAdjSeq = mAdjSeq; + + // if curAdj or curProcState improved, then this process was promoted + return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState; + } + + /** + * Checks if for the given app and client, there's a cycle that should skip over the client + * for now or use partial values to evaluate the effect of the client binding. + * @param app + * @param client + * @param procState procstate evaluated so far for this app + * @param adj oom_adj evaluated so far for this app + * @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first + * evaluation. + * @return whether to skip using the client connection at this time + */ + private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client, + int procState, int adj, boolean cycleReEval) { + if (client.containsCycle) { + // We've detected a cycle. We should retry computeOomAdjLocked later in + // case a later-checked connection from a client would raise its + // priority legitimately. + app.containsCycle = true; + // If the client has not been completely evaluated, check if it's worth + // using the partial values. + if (client.completedAdjSeq < mAdjSeq) { + if (cycleReEval) { + // If the partial values are no better, skip until the next + // attempt + if (client.getCurRawProcState() >= procState + && client.getCurRawAdj() >= adj) { + return true; + } + // Else use the client's partial procstate and adj to adjust the + // effect of the binding + } else { + return true; + } + } + } + return false; + } + + /** Inform the oomadj observer of changes to oomadj. Used by tests. */ + @GuardedBy("mService") + void reportOomAdjMessageLocked(String tag, String msg) { + Slog.d(tag, msg); + if (mService.mCurOomAdjObserver != null) { + mService.mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget(); + } + } + + /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */ + @GuardedBy("mService") + private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, + long nowElapsed) { + boolean success = true; + + if (app.getCurRawAdj() != app.setRawAdj) { + app.setRawAdj = app.getCurRawAdj(); + } + + int changes = 0; + + if (app.curAdj != app.setAdj) { + // don't compact during bootup + if (mConstants.USE_COMPACTION && mService.mBooted) { + // Perform a minor compaction when a perceptible app becomes the prev/home app + // Perform a major compaction when any app enters cached + // reminder: here, setAdj is previous state, curAdj is upcoming state + if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ && + (app.curAdj == ProcessList.PREVIOUS_APP_ADJ || + app.curAdj == ProcessList.HOME_APP_ADJ)) { + mAppCompact.compactAppSome(app); + } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && + app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + mAppCompact.compactAppFull(app); + } + } + ProcessList.setOomAdj(app.pid, app.uid, app.curAdj); + if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) { + String msg = "Set " + app.pid + " " + app.processName + " adj " + + app.curAdj + ": " + app.adjType; + reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); + } + app.setAdj = app.curAdj; + app.verifiedAdj = ProcessList.INVALID_ADJ; + } + + final int curSchedGroup = app.getCurrentSchedulingGroup(); + if (app.setSchedGroup != curSchedGroup) { + int oldSchedGroup = app.setSchedGroup; + app.setSchedGroup = curSchedGroup; + if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) { + String msg = "Setting sched group of " + app.processName + + " to " + curSchedGroup + ": " + app.adjType; + reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); + } + if (app.waitingToKill != null && app.curReceivers.isEmpty() + && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) { + app.kill(app.waitingToKill, true); + success = false; + } else { + int processGroup; + switch (curSchedGroup) { + case ProcessList.SCHED_GROUP_BACKGROUND: + processGroup = THREAD_GROUP_BG_NONINTERACTIVE; + break; + case ProcessList.SCHED_GROUP_TOP_APP: + case ProcessList.SCHED_GROUP_TOP_APP_BOUND: + processGroup = THREAD_GROUP_TOP_APP; + break; + case ProcessList.SCHED_GROUP_RESTRICTED: + processGroup = THREAD_GROUP_RESTRICTED; + break; + default: + processGroup = THREAD_GROUP_DEFAULT; + break; + } + long oldId = Binder.clearCallingIdentity(); + try { + setProcessGroup(app.pid, processGroup); + if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) { + // do nothing if we already switched to RT + if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) { + app.getWindowProcessController().onTopProcChanged(); + if (mService.mUseFifoUiScheduling) { + // Switch UI pipeline for app to SCHED_FIFO + app.savedPriority = Process.getThreadPriority(app.pid); + mService.scheduleAsFifoPriority(app.pid, /* suppressLogs */true); + if (app.renderThreadTid != 0) { + mService.scheduleAsFifoPriority(app.renderThreadTid, + /* suppressLogs */true); + if (DEBUG_OOM_ADJ) { + Slog.d("UI_FIFO", "Set RenderThread (TID " + + app.renderThreadTid + ") to FIFO"); + } + } else { + if (DEBUG_OOM_ADJ) { + Slog.d("UI_FIFO", "Not setting RenderThread TID"); + } + } + } else { + // Boost priority for top app UI and render threads + setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST); + if (app.renderThreadTid != 0) { + try { + setThreadPriority(app.renderThreadTid, + TOP_APP_PRIORITY_BOOST); + } catch (IllegalArgumentException e) { + // thread died, ignore + } + } + } + } + } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP && + curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) { + app.getWindowProcessController().onTopProcChanged(); + if (mService.mUseFifoUiScheduling) { + try { + // Reset UI pipeline to SCHED_OTHER + setThreadScheduler(app.pid, SCHED_OTHER, 0); + setThreadPriority(app.pid, app.savedPriority); + if (app.renderThreadTid != 0) { + setThreadScheduler(app.renderThreadTid, + SCHED_OTHER, 0); + setThreadPriority(app.renderThreadTid, -4); + } + } catch (IllegalArgumentException e) { + Slog.w(TAG, + "Failed to set scheduling policy, thread does not exist:\n" + + e); + } catch (SecurityException e) { + Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e); + } + } else { + // Reset priority for top app UI and render threads + setThreadPriority(app.pid, 0); + if (app.renderThreadTid != 0) { + setThreadPriority(app.renderThreadTid, 0); + } + } + } + } catch (Exception e) { + if (false) { + Slog.w(TAG, "Failed setting process group of " + app.pid + + " to " + app.getCurrentSchedulingGroup()); + Slog.w(TAG, "at location", e); + } + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + } + if (app.repForegroundActivities != app.hasForegroundActivities()) { + app.repForegroundActivities = app.hasForegroundActivities(); + changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES; + } + if (app.getReportedProcState() != app.getCurProcState()) { + app.setReportedProcState(app.getCurProcState()); + if (app.thread != null) { + try { + if (false) { + //RuntimeException h = new RuntimeException("here"); + Slog.i(TAG, "Sending new process state " + app.getReportedProcState() + + " to " + app /*, h*/); + } + app.thread.setProcessState(app.getReportedProcState()); + } catch (RemoteException e) { + } + } + } + if (app.setProcState == PROCESS_STATE_NONEXISTENT + || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) { + if (false && mService.mTestPssMode + && app.setProcState >= 0 && app.lastStateTime <= (now-200)) { + // Experimental code to more aggressively collect pss while + // running test... the problem is that this tends to collect + // the data right when a process is transitioning between process + // states, which will tend to give noisy data. + long start = SystemClock.uptimeMillis(); + long startTime = SystemClock.currentThreadTimeMillis(); + long pss = Debug.getPss(app.pid, mTmpLong, null); + long endTime = SystemClock.currentThreadTimeMillis(); + mService.recordPssSampleLocked(app, app.getCurProcState(), pss, + mTmpLong[0], mTmpLong[1], mTmpLong[2], + ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now); + mService.mPendingPssProcesses.remove(app); + Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState + + " to " + app.getCurProcState() + ": " + + (SystemClock.uptimeMillis()-start) + "ms"); + } + app.lastStateTime = now; + app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(), + app.procStateMemTracker, mService.mTestPssMode, + mService.mAtmInternal.isSleeping(), now); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from " + + ProcessList.makeProcStateString(app.setProcState) + " to " + + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in " + + (app.nextPssTime-now) + ": " + app); + } else { + if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL) + && now > (app.lastStateTime+ProcessList.minTimeFromStateChange( + mService.mTestPssMode)))) { + if (mService.requestPssLocked(app, app.setProcState)) { + app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(), + app.procStateMemTracker, mService.mTestPssMode, + mService.mAtmInternal.isSleeping(), now); + } + } else if (false && DEBUG_PSS) { + Slog.d(TAG_PSS, + "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now)); + } + } + if (app.setProcState != app.getCurProcState()) { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) { + String msg = "Proc state change of " + app.processName + + " to " + ProcessList.makeProcStateString(app.getCurProcState()) + + " (" + app.getCurProcState() + ")" + ": " + app.adjType; + reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); + } + boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE; + boolean curImportant = app.getCurProcState() < ActivityManager.PROCESS_STATE_SERVICE; + if (setImportant && !curImportant) { + // This app is no longer something we consider important enough to allow to use + // arbitrary amounts of battery power. Note its current CPU time to later know to + // kill it if it is not behaving well. + app.setWhenUnimportant(now); + app.lastCpuTime = 0; + } + // Inform UsageStats of important process state change + // Must be called before updating setProcState + maybeUpdateUsageStatsLocked(app, nowElapsed); + + maybeUpdateLastTopTime(app, now); + + app.setProcState = app.getCurProcState(); + if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) { + app.notCachedSinceIdle = false; + } + if (!doingAll) { + mService.setProcessTrackerStateLocked(app, + mService.mProcessStats.getMemFactorLocked(), now); + } else { + app.procStateChanged = true; + } + } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime()) + > mConstants.USAGE_STATS_INTERACTION_INTERVAL) { + // For apps that sit around for a long time in the interactive state, we need + // to report this at least once a day so they don't go idle. + maybeUpdateUsageStatsLocked(app, nowElapsed); + } + + if (changes != 0) { + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Changes in " + app + ": " + changes); + int i = mService.mPendingProcessChanges.size()-1; + ActivityManagerService.ProcessChangeItem item = null; + while (i >= 0) { + item = mService.mPendingProcessChanges.get(i); + if (item.pid == app.pid) { + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Re-using existing item: " + item); + break; + } + i--; + } + if (i < 0) { + // No existing item in pending changes; need a new one. + final int NA = mService.mAvailProcessChanges.size(); + if (NA > 0) { + item = mService.mAvailProcessChanges.remove(NA-1); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Retrieving available item: " + item); + } else { + item = new ActivityManagerService.ProcessChangeItem(); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Allocating new item: " + item); + } + item.changes = 0; + item.pid = app.pid; + item.uid = app.info.uid; + if (mService.mPendingProcessChanges.size() == 0) { + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "*** Enqueueing dispatch processes changed!"); + mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG) + .sendToTarget(); + } + mService.mPendingProcessChanges.add(item); + } + item.changes |= changes; + item.foregroundActivities = app.repForegroundActivities; + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Item " + Integer.toHexString(System.identityHashCode(item)) + + " " + app.toShortString() + ": changes=" + item.changes + + " foreground=" + item.foregroundActivities + + " type=" + app.adjType + " source=" + app.adjSource + + " target=" + app.adjTarget); + } + + return success; + } + + @GuardedBy("mService") + private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) { + if (DEBUG_USAGE_STATS) { + Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList()) + + "] state changes: old = " + app.setProcState + ", new = " + + app.getCurProcState()); + } + if (mService.mUsageStatsService == null) { + return; + } + boolean isInteraction; + // To avoid some abuse patterns, we are going to be careful about what we consider + // to be an app interaction. Being the top activity doesn't count while the display + // is sleeping, nor do short foreground services. + if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) { + isInteraction = true; + app.setFgInteractionTime(0); + } else if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + if (app.getFgInteractionTime() == 0) { + app.setFgInteractionTime(nowElapsed); + isInteraction = false; + } else { + isInteraction = nowElapsed > app.getFgInteractionTime() + + mConstants.SERVICE_USAGE_INTERACTION_TIME; + } + } else { + isInteraction = + app.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + app.setFgInteractionTime(0); + } + if (isInteraction + && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime()) + > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) { + app.setInteractionEventTime(nowElapsed); + String[] packages = app.getPackageList(); + if (packages != null) { + for (int i = 0; i < packages.length; i++) { + mService.mUsageStatsService.reportEvent(packages[i], app.userId, + UsageEvents.Event.SYSTEM_INTERACTION); + } + } + } + app.reportedInteraction = isInteraction; + if (!isInteraction) { + app.setInteractionEventTime(0); + } + } + + private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) { + if (app.setProcState <= ActivityManager.PROCESS_STATE_TOP + && app.getCurProcState() > ActivityManager.PROCESS_STATE_TOP) { + app.lastTopTime = nowUptime; + } + } + + /** + * Look for recently inactive apps and mark them idle after a grace period. If idled, stop + * any background services and inform listeners. + */ + @GuardedBy("mService") + void idleUidsLocked() { + final int N = mActiveUids.size(); + if (N <= 0) { + return; + } + final long nowElapsed = SystemClock.elapsedRealtime(); + final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME; + long nextTime = 0; + if (mLocalPowerManager != null) { + mLocalPowerManager.startUidChanges(); + } + for (int i = N - 1; i >= 0; i--) { + final UidRecord uidRec = mActiveUids.valueAt(i); + final long bgTime = uidRec.lastBackgroundTime; + if (bgTime > 0 && !uidRec.idle) { + if (bgTime <= maxBgTime) { + EventLogTags.writeAmUidIdle(uidRec.uid); + uidRec.idle = true; + uidRec.setIdle = true; + mService.doStopUidLocked(uidRec.uid, uidRec); + } else { + if (nextTime == 0 || nextTime > bgTime) { + nextTime = bgTime; + } + } + } + } + if (mLocalPowerManager != null) { + mLocalPowerManager.finishUidChanges(); + } + if (nextTime > 0) { + mService.mHandler.removeMessages(IDLE_UIDS_MSG); + mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, + nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed); + } + } + + @GuardedBy("mService") + final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) { + boolean changed = false; + for (int i = mActiveUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = mActiveUids.valueAt(i); + if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) { + uidRec.curWhitelist = onWhitelist; + changed = true; + } + } + if (changed) { + updateOomAdjLocked(); + } + } + + @GuardedBy("mService") + final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) { + boolean changed = false; + final UidRecord uidRec = mActiveUids.get(uid); + if (uidRec != null && uidRec.curWhitelist != onWhitelist) { + uidRec.curWhitelist = onWhitelist; + updateOomAdjLocked(); + } + } + + @GuardedBy("mService") + void dumpProcessListVariablesLocked(ProtoOutputStream proto) { + proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq); + proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq); + proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS, + mNumNonCachedProcs); + proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs); + proto.write(ActivityManagerServiceDumpProcessesProto.NEW_NUM_SERVICE_PROCS, + mNewNumServiceProcs); + + } + + @GuardedBy("mService") + void dumpSequenceNumbersLocked(PrintWriter pw) { + pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq); + } + + @GuardedBy("mService") + void dumpProcCountsLocked(PrintWriter pw) { + pw.println(" mNumNonCachedProcs=" + mNumNonCachedProcs + + " (" + mProcessList.getLruSizeLocked() + " total)" + + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs + + " mNumServiceProcs=" + mNumServiceProcs + + " mNewNumServiceProcs=" + mNewNumServiceProcs); + } + +} diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 9898d06837be..5bc88450c376 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; import static android.os.Process.SYSTEM_UID; @@ -123,7 +124,7 @@ import java.util.List; * </ul> */ public final class ProcessList { - private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; + static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; // The minimum time we allow between crashes, for us to consider this // application to be bad and stop and its services and reject broadcasts. @@ -352,6 +353,8 @@ public final class ProcessList { */ int mLruSeq = 0; + ActiveUids mActiveUids; + /** * The currently running isolated processes. */ @@ -549,8 +552,10 @@ public final class ProcessList { updateOomLevels(0, 0, false); } - void init(ActivityManagerService service) { + void init(ActivityManagerService service, ActiveUids activeUids) { mService = service; + mActiveUids = activeUids; + if (sKillHandler == null) { sKillThread = new ServiceThread(TAG + ":kill", THREAD_PRIORITY_BACKGROUND, true /* allowIo */); @@ -2176,7 +2181,7 @@ public final class ProcessList { } else if (old != null) { Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc); } - UidRecord uidRec = mService.mActiveUids.get(proc.uid); + UidRecord uidRec = mActiveUids.get(proc.uid); if (uidRec == null) { uidRec = new UidRecord(proc.uid, mService.mAtmInternal); // This is the first appearance of the uid, report it now! @@ -2188,7 +2193,7 @@ public final class ProcessList { uidRec.setWhitelist = uidRec.curWhitelist = true; } uidRec.updateHasInternetPermission(); - mService.mActiveUids.put(proc.uid, uidRec); + mActiveUids.put(proc.uid, uidRec); EventLogTags.writeAmUidRunning(uidRec.uid); mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState()); } @@ -2290,7 +2295,7 @@ public final class ProcessList { "No more processes in " + old.uidRecord); mService.enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE); EventLogTags.writeAmUidStopped(uid); - mService.mActiveUids.remove(uid); + mActiveUids.remove(uid); mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT); } old.uidRecord = null; @@ -3050,4 +3055,37 @@ public final class ProcessList { } } } + + /** Returns the uid's process state or PROCESS_STATE_NONEXISTENT if not running */ + @GuardedBy("mService") + int getUidProcStateLocked(int uid) { + UidRecord uidRec = mActiveUids.get(uid); + return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState(); + } + + /** Returns the UidRecord for the given uid, if it exists. */ + @GuardedBy("mService") + UidRecord getUidRecordLocked(int uid) { + return mActiveUids.get(uid); + } + + /** + * Call {@link ActivityManagerService#doStopUidLocked} + * (which will also stop background services) for all idle UIDs. + */ + @GuardedBy("mService") + void doStopUidForIdleUidsLocked() { + final int size = mActiveUids.size(); + for (int i = 0; i < size; i++) { + final int uid = mActiveUids.keyAt(i); + if (UserHandle.isCore(uid)) { + continue; + } + final UidRecord uidRec = mActiveUids.valueAt(i); + if (!uidRec.idle) { + continue; + } + mService.doStopUidLocked(uidRec.uid, uidRec); + } + } } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index d5ede5b63edc..c2117a7cc72e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -77,6 +77,7 @@ class SettingsToPropertiesMapper { // permission in the corresponding .te file your feature belongs to. @VisibleForTesting static final String[] sDeviceConfigScopes = new String[] { + DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, }; private final String[] mGlobalSettings; diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 8d7811fc8e78..714a807da1b0 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -1454,8 +1454,8 @@ final class HistoricalRegistry { if (accessCount > 0) { if (!printedUidState) { mWriter.print(mUidStatePrefix); - mWriter.print(AppOpsManager.uidStateToString(uidState)); - mWriter.print("["); + mWriter.print(AppOpsService.UID_STATE_NAMES[uidState]); + mWriter.print(" = "); printedUidState = true; } mWriter.print("access="); @@ -1465,11 +1465,11 @@ final class HistoricalRegistry { if (rejectCount > 0) { if (!printedUidState) { mWriter.print(mUidStatePrefix); - mWriter.print(AppOpsManager.uidStateToString(uidState)); - mWriter.print("["); + mWriter.print(AppOpsService.UID_STATE_NAMES[uidState]); + mWriter.print(" = "); printedUidState = true; } else { - mWriter.print(","); + mWriter.print(", "); } mWriter.print("reject="); mWriter.print(rejectCount); @@ -1478,16 +1478,17 @@ final class HistoricalRegistry { if (accessDuration > 0) { if (!printedUidState) { mWriter.print(mUidStatePrefix); - mWriter.print(AppOpsManager.uidStateToString(uidState)); + mWriter.print(AppOpsService.UID_STATE_NAMES[uidState]); + mWriter.print(" = "); printedUidState = true; } else { - mWriter.print(","); + mWriter.print(", "); } mWriter.print("duration="); - mWriter.print(accessDuration); + TimeUtils.formatDuration(accessDuration, mWriter); } if (printedUidState) { - mWriter.println("]"); + mWriter.println(""); } } } diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 6f5a19612895..ff029c194f12 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -343,14 +343,6 @@ final class Constants { static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES = "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses"; - /** - * Property to indicate if the current device is a cec switch device. - * - * <p> Default is false. - */ - static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH = - "ro.hdmi.property_is_device_hdmi_cec_switch"; - // Set to false to allow playback device to go to suspend mode even // when it's an active source. True by default. static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake"; diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index b75e75feabb3..ba21b7882afd 100755 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -28,7 +28,7 @@ import java.util.List; /** * Feature action that handles device discovery sequences. - * Device discovery is launched when TV device is woken from "Standby" state + * Device discovery is launched when device is woken from "Standby" state * or enabled "Control for Hdmi" from disabled state. * * <p>Device discovery goes through the following steps. @@ -89,6 +89,7 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { private final DeviceDiscoveryCallback mCallback; private int mProcessedDeviceCount = 0; private int mTimeoutRetry = 0; + private boolean mIsTvDevice = source().mService.isTvDevice(); /** * Constructor. @@ -266,15 +267,19 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { current.mPortId = getPortId(current.mPhysicalAddress); current.mDeviceType = params[2] & 0xFF; - tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, + // TODO(amyjojo): check if non-TV device needs to update cec switch info. + // This is to manager CEC device separately in case they don't have address. + if (mIsTvDevice) { + tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, current.mPhysicalAddress); - + } increaseProcessedDeviceCount(); checkAndProceedStage(); } private int getPortId(int physicalAddress) { - return tv().getPortId(physicalAddress); + return mIsTvDevice ? tv().getPortId(physicalAddress) + : source().getPortId(physicalAddress); } private void handleSetOsdName(HdmiCecMessage cmd) { @@ -345,7 +350,9 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { mCallback.onDeviceDiscoveryDone(result); finish(); // Process any commands buffered while device discovery action was in progress. - tv().processAllDelayedMessages(); + if (mIsTvDevice) { + tv().processAllDelayedMessages(); + } } private void checkAndProceedStage() { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 6e1b0181adbf..32dc02613512 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -916,6 +916,11 @@ abstract class HdmiCecLocalDevice { setActivePath(mService.portIdToPath(portId)); } + // Returns the id of the port that the target device is connected to. + int getPortId(int physicalAddress) { + return mService.pathToPortId(physicalAddress); + } + @ServiceThreadOnly HdmiCecMessageCache getCecMessageCache() { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 8cfe47f3d75e..0e4e3342451c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -23,19 +23,25 @@ import android.annotation.Nullable; import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.media.tv.TvContract; import android.os.SystemProperties; +import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.Constants.AudioCodec; +import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; +import java.io.UnsupportedEncodingException; import java.util.HashMap; +import java.util.List; /** * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android @@ -71,6 +77,10 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // processing. private final HashMap<Integer, String> mTvInputs = new HashMap<>(); + // Map-like container of all cec devices. + // device id is used as key of container. + private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); + protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); mSystemAudioControlFeatureEnabled = true; @@ -86,6 +96,132 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7"); } + /** + * Called when a device is newly added or a new device is detected or + * an existing device is updated. + * + * @param info device info of a new device. + */ + @ServiceThreadOnly + final void addCecDevice(HdmiDeviceInfo info) { + assertRunOnServiceThread(); + HdmiDeviceInfo old = addDeviceInfo(info); + if (info.getPhysicalAddress() == mService.getPhysicalAddress()) { + // The addition of the device itself should not be notified. + // Note that different logical address could still be the same local device. + return; + } + if (old == null) { + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } else if (!old.equals(info)) { + invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + } + + /** + * Called when a device is removed or removal of device is detected. + * + * @param address a logical address of a device to be removed + */ + @ServiceThreadOnly + final void removeCecDevice(int address) { + assertRunOnServiceThread(); + HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); + + mCecMessageCache.flushMessagesFrom(address); + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + + /** + * Called when a device is updated. + * + * @param info device info of the updating device. + */ + @ServiceThreadOnly + final void updateCecDevice(HdmiDeviceInfo info) { + assertRunOnServiceThread(); + HdmiDeviceInfo old = addDeviceInfo(info); + + if (old == null) { + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } else if (!old.equals(info)) { + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + } + + /** + * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same + * logical address as new device info's. + * + * @param deviceInfo a new {@link HdmiDeviceInfo} to be added. + * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} + * that has the same logical address as new one has. + */ + @ServiceThreadOnly + @VisibleForTesting + protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { + assertRunOnServiceThread(); + HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); + if (oldDeviceInfo != null) { + removeDeviceInfo(deviceInfo.getId()); + } + mDeviceInfos.append(deviceInfo.getId(), deviceInfo); + return oldDeviceInfo; + } + + /** + * Remove a device info corresponding to the given {@code logicalAddress}. + * It returns removed {@link HdmiDeviceInfo} if exists. + * + * @param id id of device to be removed + * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} + */ + @ServiceThreadOnly + private HdmiDeviceInfo removeDeviceInfo(int id) { + assertRunOnServiceThread(); + HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); + if (deviceInfo != null) { + mDeviceInfos.remove(id); + } + return deviceInfo; + } + + /** + * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. + * + * @param logicalAddress logical address of the device to be retrieved + * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. + * Returns null if no logical address matched + */ + @ServiceThreadOnly + HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { + assertRunOnServiceThread(); + return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); + } + + private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { + mService.invokeDeviceEventListeners(info, status); + } + + @Override + @ServiceThreadOnly + void onHotplug(int portId, boolean connected) { + assertRunOnServiceThread(); + if (connected) { + mService.wakeUp(); + } + if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { + mCecMessageCache.flushAll(); + } else { + if (connected) { + launchDeviceDiscovery(); + } else { + // TODO(amyjojo): remove device from mDeviceInfo + } + } + } + @Override @ServiceThreadOnly protected void onStandby(boolean initiatedByCec, int standbyAction) { @@ -116,6 +252,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { boolean lastSystemAudioControlStatus = SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); + clearDeviceInfoList(); + launchDeviceDiscovery(); startQueuedActions(); } @@ -152,6 +290,78 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly + protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { + assertRunOnServiceThread(); + int path = HdmiUtils.twoBytesToInt(message.getParams()); + int address = message.getSource(); + int type = message.getParams()[2]; + + // Ignore if [Device Discovery Action] is going on. + if (hasAction(DeviceDiscoveryAction.class)) { + Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); + return true; + } + + // Update the device info with TIF, note that the same device info could have added in + // device discovery and we do not want to override it with default OSD name. Therefore we + // need the following check to skip redundant device info updating. + HdmiDeviceInfo oldDevice = getCecDeviceInfo(address); + if (oldDevice == null || oldDevice.getPhysicalAddress() != path) { + addCecDevice(new HdmiDeviceInfo( + address, path, mService.pathToPortId(path), type, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address))); + // if we are adding a new device info, send out a give osd name command + // to update the name of the device in TIF + mService.sendCecCommand( + HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); + return true; + } + + Slog.w(TAG, "Device info exists. Not updating on Physical Address."); + return true; + } + + @Override + protected boolean handleReportPowerStatus(HdmiCecMessage command) { + int newStatus = command.getParams()[0] & 0xFF; + updateDevicePowerStatus(command.getSource(), newStatus); + return true; + } + + @Override + @ServiceThreadOnly + protected boolean handleSetOsdName(HdmiCecMessage message) { + int source = message.getSource(); + String osdName; + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source); + // If the device is not in device list, ignore it. + if (deviceInfo == null) { + Slog.i(TAG, "No source device info for <Set Osd Name>." + message); + return true; + } + try { + osdName = new String(message.getParams(), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); + return true; + } + + if (deviceInfo.getDisplayName().equals(osdName)) { + Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); + return true; + } + + Slog.d(TAG, "Updating device OSD name from " + + deviceInfo.getDisplayName() + + " to " + osdName); + updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), + deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), + deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); + return true; + } + + @Override + @ServiceThreadOnly protected boolean handleReportAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement report audio status handler @@ -407,8 +617,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { HdmiLogger.debug( "System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, newSystemAudioMode); - // Wake up device if System Audio Control is turned on but device is still on standby - if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) { + // Wake up device if System Audio Control is turned on + if (newSystemAudioMode) { mService.wakeUp(); } setSystemAudioMode(newSystemAudioMode); @@ -494,19 +704,20 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); return; } - getActiveSource().invalidate(); if (!mService.isControlEnabled()) { + setRoutingPort(portId); setLocalActivePort(portId); invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } - int oldPath = getLocalActivePort() != Constants.CEC_SWITCH_HOME - ? getActivePathOnSwitchFromActivePortId(getLocalActivePort()) + int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME + ? mService.portIdToPath(getRoutingPort()) : getDeviceInfo().getPhysicalAddress(); - int newPath = getActivePathOnSwitchFromActivePortId(portId); + int newPath = mService.portIdToPath(portId); if (oldPath == newPath) { return; } + setRoutingPort(portId); setLocalActivePort(portId); HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); @@ -575,10 +786,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); return; } - // Wake up device if it is still on standby - if (mService.isPowerStandbyOrTransient()) { - mService.wakeUp(); - } + // Wake up device + mService.wakeUp(); // Check if TV supports System Audio Control. // Handle broadcasting setSystemAudioMode on or aborting message on callback. queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() { @@ -635,9 +844,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { return; } // Wake up if the current device if ready to route. - if (mService.isPowerStandbyOrTransient()) { - mService.wakeUp(); - } + mService.wakeUp(); if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) { switchToHomeTvInput(); } else if (portId == Constants.CEC_SWITCH_ARC) { @@ -725,4 +932,50 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mAddress, routingInformationPath)); routeToInputFromPortId(getRoutingPort()); } + + protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { + HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + if (info == null) { + Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); + return; + } + + if (info.getDevicePowerStatus() == newPowerStatus) { + return; + } + + HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus); + // addDeviceInfo replaces old device info with new one if exists. + addDeviceInfo(newInfo); + + invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + + @ServiceThreadOnly + private void launchDeviceDiscovery() { + assertRunOnServiceThread(); + DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, + new DeviceDiscoveryCallback() { + @Override + public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { + for (HdmiDeviceInfo info : deviceInfos) { + addCecDevice(info); + } + } + }); + addAndStartAction(action); + } + + // Clear all device info. + @ServiceThreadOnly + private void clearDeviceInfoList() { + assertRunOnServiceThread(); + for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { + if (info.getPhysicalAddress() == mService.getPhysicalAddress()) { + continue; + } + invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + mDeviceInfos.clear(); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 6532e16cffe5..a95f7f1f3f3d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -16,7 +16,10 @@ package com.android.server.hdmi; +import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; + import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.SystemProperties; import android.util.Slog; @@ -42,7 +45,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { // Device has cec switch functionality or not. // Default is false. protected boolean mIsSwitchDevice = SystemProperties.getBoolean( - Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); + PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); // Routing port number used for Routing Control. // This records the default routing port or the previous valid routing port. @@ -71,9 +74,11 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { @ServiceThreadOnly void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); - mCecMessageCache.flushAll(); + if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { + mCecMessageCache.flushAll(); + } // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. - if (mService.isPowerStandbyOrTransient()) { + if (connected) { mService.wakeUp(); } } @@ -117,6 +122,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(activeSource); } setIsActiveSource(physicalAddress == mService.getPhysicalAddress()); + updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON); switchInputOnReceivingNewActivePath(physicalAddress); return true; } @@ -185,6 +191,13 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { // do nothing } + // Update the power status of the devices connected to the current device. + // This only works if the current device is a switch and keeps tracking the device info + // of the device connected to it. + protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { + // do nothing + } + // Active source claiming needs to be handled in Service // since service can decide who will be the active source when the device supports // multiple device types in this method. @@ -204,10 +217,8 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { if (!mIsActiveSource) { return; } - // Wake up the device if the power is in standby mode - if (mService.isPowerStandbyOrTransient()) { - mService.wakeUp(); - } + // Wake up the device + mService.wakeUp(); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index b91d8c637c79..a8c435086e8e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -346,10 +346,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - int getPortId(int physicalAddress) { - return mService.pathToPortId(physicalAddress); - } - /** * Returns the previous port id kept to handle input switching on <Inactive Source>. */ diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 833091df5f1c..2d6e76294e4a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -19,6 +19,7 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE; +import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; import static com.android.server.hdmi.Constants.DISABLED; import static com.android.server.hdmi.Constants.ENABLED; import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE; @@ -754,6 +755,9 @@ public class HdmiControlService extends SystemService { mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); + if (mMhlController == null) { + return; + } HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); for (HdmiPortInfo info : mhlPortInfo) { @@ -808,13 +812,31 @@ public class HdmiControlService extends SystemService { } /** - * Returns the id of HDMI port located at the top of the hierarchy of - * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, - * the port id to be returned is the ID associated with the port address - * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. + * Returns the id of HDMI port located at the current device that runs this method. + * + * For TV with physical address 0x0000, target device 0x1120, we want port physical address + * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address + * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id. + * + * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to. + * + * @param path the target device's physical address. + * @return the id of the port that the target device eventually connects to + * on the current device. */ int pathToPortId(int path) { - int portAddress = path & Constants.ROUTING_PATH_TOP_MASK; + int mask = 0xF000; + int finalMask = 0xF000; + int physicalAddress = getPhysicalAddress(); + int maskedAddress = physicalAddress; + + while (maskedAddress != 0) { + maskedAddress = physicalAddress & mask; + finalMask |= mask; + mask >>= 4; + } + + int portAddress = path & finalMask; return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); } @@ -1007,8 +1029,9 @@ public class HdmiControlService extends SystemService { void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); - if (connected && !isTvDevice()) { - if (getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT && isSwitchDevice()) { + if (connected && !isTvDevice() + && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { + if (isSwitchDevice()) { initPortInfo(); HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx."); } @@ -2130,7 +2153,7 @@ public class HdmiControlService extends SystemService { boolean isSwitchDevice() { return SystemProperties.getBoolean( - Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); + PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); } boolean isTvDeviceEnabled() { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index df28f30d9127..979de66f1dc8 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1761,12 +1761,15 @@ public class InputManagerService extends IInputManager.Stub // Native callback private void notifyFocusChanged(IBinder oldToken, IBinder newToken) { - if (mFocusedWindow.asBinder() == newToken) { - Log.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + mFocusedWindow); - return; + if (mFocusedWindow != null) { + if (mFocusedWindow.asBinder() == newToken) { + Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + + mFocusedWindow); + return; + } + setPointerCapture(false); } - setPointerCapture(false); mFocusedWindow = IWindow.Stub.asInterface(newToken); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7b7a3ec9809d..20eebe7bf2fc 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2317,8 +2317,8 @@ public class NotificationManagerService extends SystemService { @Override public boolean areAppOverlaysAllowedForPackage(String pkg, int uid) { - checkCallerIsSystemOrSameApp(pkg); - + enforceSystemOrSystemUIOrSamePackage("Caller not system or systemui or same package", + pkg); return mPreferencesHelper.areAppOverlaysAllowed(pkg, uid); } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index b1e914442871..b145e1eee3a3 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -106,9 +106,7 @@ public class RankingHelper { synchronized (mProxyByGroupTmp) { // record individual ranking result and nominate proxies for each group - // Note: iteration is done backwards such that the index can be used as a sort key - // in a string compare below - for (int i = N - 1; i >= 0; i--) { + for (int i = 0; i < N; i++) { final NotificationRecord record = notificationList.get(i); record.setAuthoritativeRank(i); final String groupKey = record.getGroupKey(); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index aa9bc2637fcc..b6dae1977262 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -24,6 +24,7 @@ import android.app.AppDetailsActivity; import android.app.AppGlobals; import android.app.IApplicationThread; import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -115,6 +116,7 @@ public class LauncherAppsService extends SystemService { private final ShortcutServiceInternal mShortcutServiceInternal; private final PackageCallbackList<IOnAppsChangedListener> mListeners = new PackageCallbackList<IOnAppsChangedListener>(); + private final DevicePolicyManager mDpm; private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); @@ -133,6 +135,7 @@ public class LauncherAppsService extends SystemService { LocalServices.getService(ShortcutServiceInternal.class)); mShortcutServiceInternal.addListener(mPackageMonitor); mCallbackHandler = BackgroundThread.getHandler(); + mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); } @VisibleForTesting @@ -301,6 +304,17 @@ public class LauncherAppsService extends SystemService { } @Override + public boolean shouldHideFromSuggestions(String packageName, UserHandle user) { + if (!canAccessProfile(user.getIdentifier(), "cannot get shouldHideFromSuggestions")) { + return false; + } + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + int flags = pmi.getDistractingPackageRestrictions(packageName, user.getIdentifier()); + return (flags & PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS) != 0; + } + + @Override public ParceledListSlice<ResolveInfo> getLauncherActivities(String callingPackage, String packageName, UserHandle user) throws RemoteException { ParceledListSlice<ResolveInfo> launcherActivities = queryActivitiesForUser( @@ -319,28 +333,36 @@ public class LauncherAppsService extends SystemService { } final int callingUid = injectBinderCallingUid(); - final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList()); - final PackageManagerInternal pmInt = - LocalServices.getService(PackageManagerInternal.class); - if (packageName != null) { - // If this hidden app should not be shown, return the original list. - // Otherwise, inject hidden activity that forwards user to app details page. - if (result.size() > 0) { + final long ident = injectClearCallingIdentity(); + try { + if (mUm.getUserInfo(user.getIdentifier()).isManagedProfile()) { + // Managed profile should not show hidden apps return launcherActivities; } - ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName, /*flags*/ 0, - callingUid, user.getIdentifier()); - if (shouldShowHiddenApp(appInfo)) { - ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user); - if (info != null) { - result.add(info); - } + if (mDpm.getDeviceOwnerComponentOnAnyUser() != null) { + // Device owner devices should not show hidden apps + return launcherActivities; } - return new ParceledListSlice<>(result); - } - long ident = injectClearCallingIdentity(); - try { + final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList()); + final PackageManagerInternal pmInt = + LocalServices.getService(PackageManagerInternal.class); + if (packageName != null) { + // If this hidden app should not be shown, return the original list. + // Otherwise, inject hidden activity that forwards user to app details page. + if (result.size() > 0) { + return launcherActivities; + } + ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName, /*flags*/ 0, + callingUid, user.getIdentifier()); + if (shouldShowHiddenApp(appInfo)) { + ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user); + if (info != null) { + result.add(info); + } + } + return new ParceledListSlice<>(result); + } final HashSet<String> visiblePackages = new HashSet<>(); for (ResolveInfo info : result) { visiblePackages.add(info.activityInfo.packageName); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index d0b20e815a42..bf4e272d4004 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -225,6 +225,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements Slog.w(TAG, "Deleting orphan icon " + icon); icon.delete(); } + + // Invalid sessions might have been marked while parsing. Re-write the database with + // the updated information. + writeSessionsLocked(); + + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + if (session.isStaged()) { + mStagingManager.restoreSession(session); + } + } } } @@ -329,9 +340,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if (valid) { mSessions.put(session.sessionId, session); - if (session.isStaged()) { - mStagingManager.restoreSession(session); - } } else { // Since this is early during boot we don't send // any observer events about the session, but we @@ -1122,6 +1130,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); } + public void onStagedSessionChanged(PackageInstallerSession session) { + writeSessionsAsync(); + mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId); + } + public void onSessionFinished(final PackageInstallerSession session, boolean success) { mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 516927fa7c49..12d335dd87ab 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2009,6 +2009,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mCallback.onSessionFinished(this, success); } + /** {@hide} */ void setStagedSessionReady() { synchronized (mLock) { mStagedSessionReady = true; @@ -2016,6 +2017,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionFailed = false; mStagedSessionErrorCode = SessionInfo.NO_ERROR; } + mCallback.onStagedSessionChanged(this); } /** {@hide} */ @@ -2026,6 +2028,33 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionFailed = true; mStagedSessionErrorCode = errorCode; } + mCallback.onStagedSessionChanged(this); + } + + /** {@hide} */ + void setStagedSessionApplied() { + synchronized (mLock) { + mStagedSessionReady = false; + mStagedSessionApplied = true; + mStagedSessionFailed = false; + mStagedSessionErrorCode = SessionInfo.NO_ERROR; + } + mCallback.onStagedSessionChanged(this); + } + + /** {@hide} */ + boolean isStagedSessionReady() { + return mStagedSessionReady; + } + + /** {@hide} */ + boolean isStagedSessionApplied() { + return mStagedSessionApplied; + } + + /** {@hide} */ + boolean isStagedSessionFailed() { + return mStagedSessionFailed; } private void destroyInternal() { @@ -2221,6 +2250,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return permissionsArray; } + // Sanity check to be performed when the session is restored from an external file. Only one + // of the session states should be true, or none of them. + private static boolean isStagedSessionStateValid(boolean isReady, boolean isApplied, + boolean isFailed) { + return (!isReady && !isApplied && !isFailed) + || (isReady && !isApplied && !isFailed) + || (!isReady && isApplied && !isFailed) + || (!isReady && !isApplied && isFailed); + } + /** * Read new session from a {@link XmlPullParser xml description} and create it. * @@ -2287,6 +2326,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED); final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE); + if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { + throw new IllegalArgumentException("Can't restore staged session with invalid state."); + } + return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerPackageName, installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 597f5b3f4e05..945d7ad008d4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -84,6 +84,7 @@ import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING; import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.content.pm.PackageManager.RESTRICTION_NONE; import static android.content.pm.PackageParser.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; @@ -12657,22 +12658,30 @@ public class PackageManagerService extends IPackageManager.Stub info.sendPackageRemovedBroadcasts(true /*killApp*/); } + private void sendDistractingPackagesChanged(String[] pkgList, int[] uidList, int userId, + int distractionFlags) { + final Bundle extras = new Bundle(3); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); + extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags); + sendPackageBroadcast(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null, extras, + Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, new int[]{userId}, null); + } + private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId, boolean suspended, PersistableBundle launcherExtras) { - if (pkgList.length > 0) { - Bundle extras = new Bundle(1); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); - if (launcherExtras != null) { - extras.putBundle(Intent.EXTRA_LAUNCHER_EXTRAS, - new Bundle(launcherExtras.deepCopy())); - } - sendPackageBroadcast( - suspended ? Intent.ACTION_PACKAGES_SUSPENDED - : Intent.ACTION_PACKAGES_UNSUSPENDED, - null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, - new int[] {userId}, null); + final Bundle extras = new Bundle(3); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); + if (launcherExtras != null) { + extras.putBundle(Intent.EXTRA_LAUNCHER_EXTRAS, + new Bundle(launcherExtras.deepCopy())); } + sendPackageBroadcast( + suspended ? Intent.ACTION_PACKAGES_SUSPENDED + : Intent.ACTION_PACKAGES_UNSUSPENDED, + null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, + new int[] {userId}, null); } /** @@ -12822,6 +12831,62 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames, + int restrictionFlags, int userId) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, + "setPackagesSuspendedAsUser"); + + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID + && UserHandle.getUserId(callingUid) != userId) { + throw new SecurityException("Calling uid " + callingUid + " cannot call for user " + + userId); + } + Preconditions.checkNotNull(packageNames, "packageNames cannot be null"); + + final List<String> changedPackagesList = new ArrayList<>(packageNames.length); + final IntArray changedUids = new IntArray(packageNames.length); + final List<String> unactionedPackages = new ArrayList<>(packageNames.length); + + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + final PackageSetting pkgSetting; + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null || filterAppAccessLPr(pkgSetting, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageName + + ". Skipping..."); + unactionedPackages.add(packageName); + continue; + } + } + if (restrictionFlags != 0 && !canSuspendPackageForUserInternal(packageName, userId)) { + unactionedPackages.add(packageName); + continue; + } + synchronized (mPackages) { + final int oldDistractionFlags = pkgSetting.getDistractionFlags(userId); + if (restrictionFlags != oldDistractionFlags) { + pkgSetting.setDistractionFlags(restrictionFlags, userId); + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, pkgSetting.appId)); + } + } + } + + if (!changedPackagesList.isEmpty()) { + final String[] changedPackages = changedPackagesList.toArray( + new String[changedPackagesList.size()]); + sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId, + restrictionFlags); + synchronized (mPackages) { + scheduleWritePackageRestrictionsLocked(userId); + } + } + return unactionedPackages.toArray(new String[0]); + } + + @Override public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, PersistableBundle appExtras, PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo, String callingPackage, int userId) { @@ -12846,44 +12911,37 @@ public class PackageManagerService extends IPackageManager.Stub final List<String> changedPackagesList = new ArrayList<>(packageNames.length); final IntArray changedUids = new IntArray(packageNames.length); final List<String> unactionedPackages = new ArrayList<>(packageNames.length); - final long callingId = Binder.clearCallingIdentity(); - try { - for (int i = 0; i < packageNames.length; i++) { - final String packageName = packageNames[i]; - if (callingPackage.equals(packageName)) { - Slog.w(TAG, "Calling package: " + callingPackage + " trying to " - + (suspended ? "" : "un") + "suspend itself. Ignoring"); - unactionedPackages.add(packageName); - continue; - } - PackageSetting pkgSetting; - synchronized (mPackages) { - pkgSetting = mSettings.mPackages.get(packageName); - if (pkgSetting == null - || filterAppAccessLPr(pkgSetting, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageName - + ". Skipping suspending/un-suspending."); - unactionedPackages.add(packageName); - continue; - } - } - if (suspended && !canSuspendPackageForUserInternal(packageName, userId)) { + + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + if (callingPackage.equals(packageName)) { + Slog.w(TAG, "Calling package: " + callingPackage + " trying to " + + (suspended ? "" : "un") + "suspend itself. Ignoring"); + unactionedPackages.add(packageName); + continue; + } + final PackageSetting pkgSetting; + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null || filterAppAccessLPr(pkgSetting, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageName + + ". Skipping suspending/un-suspending."); unactionedPackages.add(packageName); continue; } - synchronized (mPackages) { - pkgSetting = mSettings.mPackages.get(packageName); - if (pkgSetting != null) { - pkgSetting.setSuspended(suspended, callingPackage, dialogInfo, appExtras, - launcherExtras, userId); - } - } - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, pkgSetting.appId)); } - } finally { - Binder.restoreCallingIdentity(callingId); + if (suspended && !canSuspendPackageForUserInternal(packageName, userId)) { + unactionedPackages.add(packageName); + continue; + } + synchronized (mPackages) { + pkgSetting.setSuspended(suspended, callingPackage, dialogInfo, appExtras, + launcherExtras, userId); + } + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, pkgSetting.appId)); } + if (!changedPackagesList.isEmpty()) { final String[] changedPackages = changedPackagesList.toArray( new String[changedPackagesList.size()]); @@ -13035,88 +13093,87 @@ public class PackageManagerService extends IPackageManager.Stub + " cannot query getUnsuspendablePackagesForUser for user " + userId); } final ArraySet<String> unactionablePackages = new ArraySet<>(); - final long identity = Binder.clearCallingIdentity(); - try { - for (String packageName : packageNames) { - if (!canSuspendPackageForUserInternal(packageName, userId)) { - unactionablePackages.add(packageName); - } + for (String packageName : packageNames) { + if (!canSuspendPackageForUserInternal(packageName, userId)) { + unactionablePackages.add(packageName); } - } finally { - Binder.restoreCallingIdentity(identity); } return unactionablePackages.toArray(new String[unactionablePackages.size()]); } private boolean canSuspendPackageForUserInternal(String packageName, int userId) { - if (isPackageDeviceAdmin(packageName, userId)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": has an active device admin"); - return false; - } - - String activeLauncherPackageName = getActiveLauncherPackageName(userId); - if (packageName.equals(activeLauncherPackageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": contains the active launcher"); - return false; - } - - if (packageName.equals(mRequiredInstallerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package installation"); - return false; - } + final long callingId = Binder.clearCallingIdentity(); + try { + if (isPackageDeviceAdmin(packageName, userId)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": has an active device admin"); + return false; + } - if (packageName.equals(mRequiredUninstallerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package uninstallation"); - return false; - } + String activeLauncherPackageName = getActiveLauncherPackageName(userId); + if (packageName.equals(activeLauncherPackageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": contains the active launcher"); + return false; + } - if (packageName.equals(mRequiredVerifierPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package verification"); - return false; - } + if (packageName.equals(mRequiredInstallerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package installation"); + return false; + } - if (packageName.equals(getDefaultDialerPackageName(userId))) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": is the default dialer"); - return false; - } + if (packageName.equals(mRequiredUninstallerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package uninstallation"); + return false; + } - if (packageName.equals(mRequiredPermissionControllerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for permissions management"); - return false; - } + if (packageName.equals(mRequiredVerifierPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package verification"); + return false; + } - synchronized (mPackages) { - if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + if (packageName.equals(getDefaultDialerPackageName(userId))) { Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": protected package"); + + "\": is the default dialer"); return false; } - // Cannot suspend static shared libs as they are considered - // a part of the using app (emulating static linking). Also - // static libs are installed always on internal storage. - PackageParser.Package pkg = mPackages.get(packageName); - if (pkg != null && pkg.applicationInfo.isStaticSharedLibrary()) { - Slog.w(TAG, "Cannot suspend package: " + packageName - + " providing static shared library: " - + pkg.staticSharedLibName); + if (packageName.equals(mRequiredPermissionControllerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for permissions management"); return false; } - } - if (PLATFORM_PACKAGE_NAME.equals(packageName)) { - Slog.w(TAG, "Cannot suspend the platform package: " + packageName); - return false; - } + synchronized (mPackages) { + if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": protected package"); + return false; + } - return true; + // Cannot suspend static shared libs as they are considered + // a part of the using app (emulating static linking). Also + // static libs are installed always on internal storage. + PackageParser.Package pkg = mPackages.get(packageName); + if (pkg != null && pkg.applicationInfo.isStaticSharedLibrary()) { + Slog.w(TAG, "Cannot suspend package: " + packageName + + " providing static shared library: " + + pkg.staticSharedLibName); + return false; + } + } + + if (PLATFORM_PACKAGE_NAME.equals(packageName)) { + Slog.w(TAG, "Cannot suspend the platform package: " + packageName); + return false; + } + return true; + } finally { + Binder.restoreCallingIdentity(callingId); + } } private String getActiveLauncherPackageName(int userId) { @@ -13717,14 +13774,15 @@ public class PackageManagerService extends IPackageManager.Stub IBackupManager bm = IBackupManager.Stub.asInterface( ServiceManager.getService(Context.BACKUP_SERVICE)); if (bm != null) { + int userId = args.user.getIdentifier(); if (DEBUG_INSTALL) { - Log.v(TAG, "token " + token + " to BM for possible restore"); + Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId); } Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token); try { - // TODO: http://b/22388012 - if (bm.isBackupServiceActive(UserHandle.USER_SYSTEM)) { - bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token); + if (bm.isBackupServiceActive(userId)) { + bm.restoreAtInstallForUser( + userId, res.pkg.applicationInfo.packageName, token); } else { doRestore = false; } @@ -18425,6 +18483,7 @@ public class PackageManagerService extends IPackageManager.Stub true /*stopped*/, true /*notLaunched*/, false /*hidden*/, + 0 /*distractionFlags*/, false /*suspended*/, null /*suspendingPackage*/, null /*dialogInfo*/, @@ -23240,6 +23299,14 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public int getDistractingPackageRestrictions(String packageName, int userId) { + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + return (ps != null) ? ps.getDistractionFlags(userId) : RESTRICTION_NONE; + } + } + + @Override public int getPackageUid(String packageName, int flags, int userId) { return PackageManagerService.this .getPackageUid(packageName, flags, userId); diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 3c22f07ad108..58f262c4c889 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -392,6 +392,14 @@ public abstract class PackageSettingBase extends SettingBase { modifyUserState(userId).hidden = hidden; } + int getDistractionFlags(int userId) { + return readUserState(userId).distractionFlags; + } + + void setDistractionFlags(int distractionFlags, int userId) { + modifyUserState(userId).distractionFlags = distractionFlags; + } + boolean getSuspended(int userId) { return readUserState(userId).suspended; } @@ -423,7 +431,8 @@ public abstract class PackageSettingBase extends SettingBase { } void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped, - boolean notLaunched, boolean hidden, boolean suspended, String suspendingPackage, + boolean notLaunched, boolean hidden, int distractionFlags, boolean suspended, + String suspendingPackage, SuspendDialogInfo dialogInfo, PersistableBundle suspendedAppExtras, PersistableBundle suspendedLauncherExtras, boolean instantApp, boolean virtualPreload, String lastDisableAppCaller, @@ -437,6 +446,7 @@ public abstract class PackageSettingBase extends SettingBase { state.stopped = stopped; state.notLaunched = notLaunched; state.hidden = hidden; + state.distractionFlags = distractionFlags; state.suspended = suspended; state.suspendingPackage = suspendingPackage; state.dialogInfo = dialogInfo; @@ -607,6 +617,7 @@ public abstract class PackageSettingBase extends SettingBase { } proto.write(PackageProto.UserInfoProto.INSTALL_TYPE, installType); proto.write(PackageProto.UserInfoProto.IS_HIDDEN, state.hidden); + proto.write(PackageProto.UserInfoProto.DISTRACTION_FLAGS, state.distractionFlags); proto.write(PackageProto.UserInfoProto.IS_SUSPENDED, state.suspended); if (state.suspended) { proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE, state.suspendingPackage); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index c524dba01ba6..95da2091828d 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -223,6 +223,7 @@ public final class Settings { private static final String ATTR_BLOCKED = "blocked"; // New name for the above attribute. private static final String ATTR_HIDDEN = "hidden"; + private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags"; private static final String ATTR_SUSPENDED = "suspended"; private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package"; /** @@ -734,6 +735,7 @@ public final class Settings { true /*stopped*/, true /*notLaunched*/, false /*hidden*/, + 0 /*distractionFlags*/, false /*suspended*/, null /*suspendingPackage*/, null /*dialogInfo*/, @@ -1628,6 +1630,7 @@ public final class Settings { false /*stopped*/, false /*notLaunched*/, false /*hidden*/, + 0 /*distractionFlags*/, false /*suspended*/, null /*suspendingPackage*/, null /*dialogInfo*/, @@ -1703,6 +1706,8 @@ public final class Settings { hidden = hiddenStr == null ? hidden : Boolean.parseBoolean(hiddenStr); + final int distractionFlags = XmlUtils.readIntAttribute(parser, + ATTR_DISTRACTION_FLAGS, 0); final boolean suspended = XmlUtils.readBooleanAttribute(parser, ATTR_SUSPENDED, false); String suspendingPackage = parser.getAttributeValue(null, @@ -1781,7 +1786,8 @@ public final class Settings { setBlockUninstallLPw(userId, name, true); } ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched, - hidden, suspended, suspendingPackage, suspendDialogInfo, + hidden, distractionFlags, suspended, suspendingPackage, + suspendDialogInfo, suspendedAppExtras, suspendedLauncherExtras, instantApp, virtualPreload, enabledCaller, enabledComponents, disabledComponents, verifState, linkGeneration, installReason, harmfulAppWarning); @@ -2089,6 +2095,10 @@ public final class Settings { if (ustate.hidden) { serializer.attribute(null, ATTR_HIDDEN, "true"); } + if (ustate.distractionFlags != 0) { + serializer.attribute(null, ATTR_DISTRACTION_FLAGS, + Integer.toString(ustate.distractionFlags)); + } if (ustate.suspended) { serializer.attribute(null, ATTR_SUSPENDED, "true"); if (ustate.suspendingPackage != null) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index c297c62dfece..ac05ee8a9779 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -168,7 +168,6 @@ public class StagingManager { } else { session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); } - mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId); } void commitSession(@NonNull PackageInstallerSession session) { diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index b4883371b468..c0ec3672c665 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -406,7 +406,6 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @Nullable private ArraySet<String> getRoleHoldersInternal(@NonNull String roleName, @UserIdInt int userId) { - migrateRoleIfNecessary(roleName, userId); RoleUserState userState = getOrCreateUserState(userId); return userState.getRoleHolders(roleName); } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 80212655d616..d12f7eda9a1b 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -977,6 +977,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { ensureRollbackDataLoadedLocked(); mAvailableRollbacks.add(data); } + + scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); removeFile(data.backupDir); diff --git a/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java b/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java index 438c303de6cc..d77cf900a0a9 100644 --- a/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java +++ b/services/core/java/com/android/server/signedconfig/GlobalSettingsConfigApplicator.java @@ -23,6 +23,7 @@ import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.util.StatsLog; import java.security.GeneralSecurityException; import java.util.Arrays; @@ -66,12 +67,14 @@ class GlobalSettingsConfigApplicator { private final Context mContext; private final String mSourcePackage; + private final SignedConfigEvent mEvent; private final SignatureVerifier mVerifier; - GlobalSettingsConfigApplicator(Context context, String sourcePackage) { + GlobalSettingsConfigApplicator(Context context, String sourcePackage, SignedConfigEvent event) { mContext = context; mSourcePackage = sourcePackage; - mVerifier = new SignatureVerifier(); + mEvent = event; + mVerifier = new SignatureVerifier(mEvent); } private boolean checkSignature(String data, String signature) { @@ -79,6 +82,7 @@ class GlobalSettingsConfigApplicator { return mVerifier.verifySignature(data, signature); } catch (GeneralSecurityException e) { Slog.e(TAG, "Failed to verify signature", e); + mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__SECURITY_EXCEPTION; return false; } } @@ -109,14 +113,17 @@ class GlobalSettingsConfigApplicator { SignedConfig config; try { config = SignedConfig.parse(configStr, ALLOWED_KEYS, KEY_VALUE_MAPPERS); + mEvent.version = config.version; } catch (InvalidConfigException e) { Slog.e(TAG, "Failed to parse global settings from package " + mSourcePackage, e); + mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__INVALID_CONFIG; return; } int currentVersion = getCurrentConfigVersion(); if (currentVersion >= config.version) { Slog.i(TAG, "Global settings from package " + mSourcePackage + " is older than existing: " + config.version + "<=" + currentVersion); + mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__OLD_CONFIG; return; } // We have new config! @@ -126,10 +133,12 @@ class GlobalSettingsConfigApplicator { config.getMatchingConfig(Build.VERSION.SDK_INT); if (matchedConfig == null) { Slog.i(TAG, "Settings is not applicable to current SDK version; ignoring"); + mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__NOT_APPLICABLE; return; } Slog.i(TAG, "Updating global settings to version " + config.version); updateCurrentConfig(config.version, matchedConfig.values); + mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__APPLIED; } } diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java index 944db84acc71..fcf40cf3601b 100644 --- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java +++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java @@ -18,6 +18,7 @@ package com.android.server.signedconfig; import android.os.Build; import android.util.Slog; +import android.util.StatsLog; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; @@ -42,11 +43,18 @@ public class SignatureVerifier { private static final String DEBUG_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60" + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ=="; + private static final String PROD_KEY = + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp" + + "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g=="; + private final SignedConfigEvent mEvent; private final PublicKey mDebugKey; + private final PublicKey mProdKey; - public SignatureVerifier() { - mDebugKey = createKey(DEBUG_KEY); + public SignatureVerifier(SignedConfigEvent event) { + mEvent = event; + mDebugKey = Build.IS_DEBUGGABLE ? createKey(DEBUG_KEY) : null; + mProdKey = createKey(PROD_KEY); } private static PublicKey createKey(String base64) { @@ -67,6 +75,14 @@ public class SignatureVerifier { } } + private boolean verifyWithPublicKey(PublicKey key, byte[] data, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature verifier = Signature.getInstance("SHA256withECDSA"); + verifier.initVerify(key); + verifier.update(data); + return verifier.verify(signature); + } + /** * Verify a signature for signed config. * @@ -80,6 +96,7 @@ public class SignatureVerifier { try { signature = Base64.getDecoder().decode(base64Signature); } catch (IllegalArgumentException e) { + mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__BASE64_FAILURE_SIGNATURE; Slog.e(TAG, "Failed to base64 decode signature"); return false; } @@ -89,11 +106,9 @@ public class SignatureVerifier { if (Build.IS_DEBUGGABLE) { if (mDebugKey != null) { if (DBG) Slog.w(TAG, "Trying to verify signature using debug key"); - Signature verifier = Signature.getInstance("SHA256withECDSA"); - verifier.initVerify(mDebugKey); - verifier.update(data); - if (verifier.verify(signature)) { + if (verifyWithPublicKey(mDebugKey, data, signature)) { Slog.i(TAG, "Verified config using debug key"); + mEvent.verifiedWith = StatsLog.SIGNED_CONFIG_REPORTED__VERIFIED_WITH__DEBUG; return true; } else { if (DBG) Slog.i(TAG, "Config verification failed using debug key"); @@ -102,8 +117,18 @@ public class SignatureVerifier { Slog.w(TAG, "Debuggable build, but have no debug key"); } } - // TODO verify production key. - Slog.w(TAG, "NO PRODUCTION KEY YET, FAILING VERIFICATION"); - return false; + if (mProdKey == null) { + Slog.e(TAG, "No prod key; construction failed?"); + return false; + } + if (verifyWithPublicKey(mProdKey, data, signature)) { + Slog.i(TAG, "Verified config using production key"); + mEvent.verifiedWith = StatsLog.SIGNED_CONFIG_REPORTED__VERIFIED_WITH__PRODUCTION; + return true; + } else { + if (DBG) Slog.i(TAG, "Verification failed using production key"); + mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__SIGNATURE_CHECK_FAILED; + return false; + } } } diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigEvent.java b/services/core/java/com/android/server/signedconfig/SignedConfigEvent.java new file mode 100644 index 000000000000..2f2062c6f2ee --- /dev/null +++ b/services/core/java/com/android/server/signedconfig/SignedConfigEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.signedconfig; + +import android.util.StatsLog; + +/** + * Helper class to allow a SignedConfigReported event to be built up in stages. + */ +public class SignedConfigEvent { + + public int type = StatsLog.SIGNED_CONFIG_REPORTED__TYPE__UNKNOWN_TYPE; + public int status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__UNKNOWN_STATUS; + public int version = 0; + public String fromPackage = null; + public int verifiedWith = StatsLog.SIGNED_CONFIG_REPORTED__VERIFIED_WITH__NO_KEY; + + /** + * Write this event to statslog. + */ + public void send() { + StatsLog.write(StatsLog.SIGNED_CONFIG_REPORTED, + type, status, version, fromPackage, verifiedWith); + } + +} diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigService.java b/services/core/java/com/android/server/signedconfig/SignedConfigService.java index 6bcee1413fa8..dc39542dc29f 100644 --- a/services/core/java/com/android/server/signedconfig/SignedConfigService.java +++ b/services/core/java/com/android/server/signedconfig/SignedConfigService.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.Bundle; import android.util.Slog; +import android.util.StatsLog; import com.android.server.LocalServices; @@ -82,21 +83,30 @@ public class SignedConfigService { } if (metaData.containsKey(KEY_GLOBAL_SETTINGS) && metaData.containsKey(KEY_GLOBAL_SETTINGS_SIGNATURE)) { - String config = metaData.getString(KEY_GLOBAL_SETTINGS); - String signature = metaData.getString(KEY_GLOBAL_SETTINGS_SIGNATURE); + SignedConfigEvent event = new SignedConfigEvent(); try { - // Base64 encoding is standard (not URL safe) encoding: RFC4648 - config = new String(Base64.getDecoder().decode(config), StandardCharsets.UTF_8); - } catch (IllegalArgumentException iae) { - Slog.e(TAG, "Failed to base64 decode global settings config from " + packageName); - return; + event.type = StatsLog.SIGNED_CONFIG_REPORTED__TYPE__GLOBAL_SETTINGS; + event.fromPackage = packageName; + String config = metaData.getString(KEY_GLOBAL_SETTINGS); + String signature = metaData.getString(KEY_GLOBAL_SETTINGS_SIGNATURE); + try { + // Base64 encoding is standard (not URL safe) encoding: RFC4648 + config = new String(Base64.getDecoder().decode(config), StandardCharsets.UTF_8); + } catch (IllegalArgumentException iae) { + Slog.e(TAG, "Failed to base64 decode global settings config from " + + packageName); + event.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__BASE64_FAILURE_CONFIG; + return; + } + if (DBG) { + Slog.d(TAG, "Got global settings config: " + config); + Slog.d(TAG, "Got global settings signature: " + signature); + } + new GlobalSettingsConfigApplicator(mContext, packageName, event).applyConfig( + config, signature); + } finally { + event.send(); } - if (DBG) { - Slog.d(TAG, "Got global settings config: " + config); - Slog.d(TAG, "Got global settings signature: " + signature); - } - new GlobalSettingsConfigApplicator(mContext, packageName).applyConfig( - config, signature); } else { if (DBG) Slog.d(TAG, "Package has no global settings config/signature."); } diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java index 315d5e39c94b..1d1c28f5f9b7 100644 --- a/services/core/java/com/android/server/slice/SlicePermissionManager.java +++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java @@ -175,18 +175,24 @@ public class SlicePermissionManager implements DirtyTracker { handlePersist(); } for (String file : new File(mSliceDir.getAbsolutePath()).list()) { - if (file.isEmpty()) continue; try (ParserHolder parser = getParser(file)) { - Persistable p; - while (parser.parser.getEventType() != XmlPullParser.START_TAG) { + Persistable p = null; + while (parser.parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if (parser.parser.getEventType() == XmlPullParser.START_TAG) { + if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) { + p = SliceClientPermissions.createFrom(parser.parser, tracker); + } else { + p = SliceProviderPermissions.createFrom(parser.parser, tracker); + } + break; + } parser.parser.next(); } - if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) { - p = SliceClientPermissions.createFrom(parser.parser, tracker); + if (p != null) { + p.writeTo(out); } else { - p = SliceProviderPermissions.createFrom(parser.parser, tracker); + Slog.w(TAG, "Invalid or empty slice permissions file: " + file); } - p.writeTo(out); } } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index b100ecdb0e73..ef3c8d5257fb 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.app.Activity.RESULT_CANCELED; import static android.app.ActivityManager.START_ABORTED; import static android.app.ActivityManager.START_CANCELED; @@ -50,6 +51,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -745,8 +747,9 @@ class ActivityStarter { // not sure if we need to create START_ABORTED_BACKGROUND so for now piggybacking // on START_ABORTED if (!abort) { - abort |= shouldAbortBackgroundActivityStart(callingUid, callingPackage, realCallingUid, - callerApp, originatingPendingIntent, allowBackgroundActivityStart); + abort |= shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, + realCallingUid, callerApp, originatingPendingIntent, + allowBackgroundActivityStart); } // Merge the two options bundles, while realCallerOptions takes precedence. @@ -893,8 +896,8 @@ class ActivityStarter { true /* doResume */, checkedOptions, inTask, outActivity); } - private boolean shouldAbortBackgroundActivityStart(int callingUid, final String callingPackage, - int realCallingUid, WindowProcessController callerApp, + private boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, + final String callingPackage, int realCallingUid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { if (mService.isBackgroundActivityStartsEnabled()) { return false; @@ -928,6 +931,11 @@ class ActivityStarter { if (callerApp != null && callerApp.areBackgroundActivityStartsAllowed()) { return false; } + // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission + if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) + == PERMISSION_GRANTED) { + return false; + } // don't abort if the caller has the same uid as the recents component if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { return false; @@ -945,7 +953,7 @@ class ActivityStarter { /** Returns true if uid has a visible window or its process is in a top state. */ private boolean isUidForeground(int uid) { return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP) - || mService.mWindowManager.isAnyWindowVisibleForUid(uid); + || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid); } /** Returns true if uid is in a persistent state. */ @@ -968,18 +976,19 @@ class ActivityStarter { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "logActivityStart"); final int callingUidProcState = mService.getUidStateLocked(callingUid); final boolean callingUidHasAnyVisibleWindow = - mService.mWindowManager.isAnyWindowVisibleForUid(callingUid); + mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid); final int realCallingUidProcState = (callingUid == realCallingUid) ? callingUidProcState : mService.getUidStateLocked(realCallingUid); final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid) ? callingUidHasAnyVisibleWindow - : mService.mWindowManager.isAnyWindowVisibleForUid(realCallingUid); + : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid( + realCallingUid); final String targetPackage = (r != null) ? r.packageName : null; final int targetUid = (r!= null) ? ((r.appInfo != null) ? r.appInfo.uid : -1) : -1; final int targetUidProcState = mService.getUidStateLocked(targetUid); final boolean targetUidHasAnyVisibleWindow = (targetUid != -1) - ? mService.mWindowManager.isAnyWindowVisibleForUid(targetUid) + ? mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(targetUid) : false; final String targetWhitelistTag = (targetUid != -1) ? mService.getPendingTempWhitelistTagForUidLocked(targetUid) diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 4e70bbc277d8..8fb79477eb2f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -280,6 +281,15 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } /** + * Returns true if the callingUid has any non-toast window currently visible to the user. + */ + boolean isAnyNonToastWindowVisibleForUid(int callingUid) { + return forAllWindows(w -> { + return w.getOwningUid() == callingUid && w.isVisible() && w.mAttrs.type != TYPE_TOAST; + }, true /* traverseTopToBottom */); + } + + /** * Returns the app window token for the input binder if it exist in the system. * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since * AppWindowToken represents an activity which can only exist on one display. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9b82f5c66a1d..90506e744250 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5716,17 +5716,6 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Returns true if the callingUid has any window currently visible to the user. - */ - public boolean isAnyWindowVisibleForUid(int callingUid) { - synchronized (mGlobalLock) { - return mRoot.forAllWindows(w -> { - return w.getOwningUid() == callingUid && w.isVisible(); - }, true /* traverseTopToBottom */); - } - } - - /** * Called when a task has been removed from the recent tasks list. * <p> * Note: This doesn't go through {@link TaskWindowContainerController} yet as the window diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk index 9159f0d47621..0cf0d3402dea 100644 --- a/services/robotests/Android.mk +++ b/services/robotests/Android.mk @@ -26,9 +26,6 @@ LOCAL_MODULE_TAGS := optional LOCAL_PRIVILEGED_MODULE := true LOCAL_STATIC_JAVA_LIBRARIES := \ - bmgrlib \ - bu \ - services.backup \ services.core \ services.net @@ -41,8 +38,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := FrameworksServicesRoboTests -LOCAL_SRC_FILES := $(call all-java-files-under, src) \ - $(call all-java-files-under, backup/src) +LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_RESOURCE_DIR := \ $(LOCAL_PATH)/res @@ -82,8 +78,7 @@ LOCAL_JAVA_LIBRARIES := \ LOCAL_TEST_PACKAGE := FrameworksServicesLib -LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.) \ - $(call find-files-in-subdirs,$(LOCAL_PATH)/backup/src,*Test.java,.) +LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.) include external/robolectric-shadows/run_robotests.mk diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java index 769a9d4a2bd4..b8db3f39eb62 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java @@ -46,6 +46,7 @@ import android.platform.test.annotations.Presubmit; import android.util.SparseArray; import com.android.server.backup.testing.TransportData; +import com.android.server.testing.shadows.ShadowApplicationPackageManager; import com.android.server.testing.shadows.ShadowBinder; import org.junit.After; @@ -65,7 +66,7 @@ import java.io.PrintWriter; /** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */ @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBinder.class}) +@Config(shadows = {ShadowApplicationPackageManager.class, ShadowBinder.class}) @Presubmit public class BackupManagerServiceTest { private static final String TEST_PACKAGE = "package"; @@ -1075,7 +1076,7 @@ public class BackupManagerServiceTest { createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); FullBackupJob job = new FullBackupJob(); - backupManagerService.beginFullBackup(job); + backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job); verify(mUserOneService).beginFullBackup(job); } @@ -1086,7 +1087,7 @@ public class BackupManagerServiceTest { BackupManagerService backupManagerService = createService(); FullBackupJob job = new FullBackupJob(); - backupManagerService.beginFullBackup(job); + backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job); verify(mUserOneService, never()).beginFullBackup(job); } @@ -1097,7 +1098,7 @@ public class BackupManagerServiceTest { BackupManagerService backupManagerService = createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); - backupManagerService.endFullBackup(); + backupManagerService.endFullBackup(UserHandle.USER_SYSTEM); verify(mUserOneService).endFullBackup(); } @@ -1107,7 +1108,7 @@ public class BackupManagerServiceTest { public void testEndFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception { BackupManagerService backupManagerService = createService(); - backupManagerService.endFullBackup(); + backupManagerService.endFullBackup(UserHandle.USER_SYSTEM); verify(mUserOneService, never()).endFullBackup(); } diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java new file mode 100644 index 000000000000..9a78d0b3f456 --- /dev/null +++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.UserIdInt; +import android.app.job.JobScheduler; +import android.content.Context; +import android.os.Handler; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowJobScheduler; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowJobScheduler.class}) +@Presubmit +public class FullBackupJobTest { + private Context mContext; + private BackupManagerConstants mConstants; + private ShadowJobScheduler mShadowJobScheduler; + + @UserIdInt private int mUserOneId; + @UserIdInt private int mUserTwoId; + + @Before + public void setUp() throws Exception { + mContext = RuntimeEnvironment.application; + mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver()); + mConstants.start(); + + mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class)); + + mUserOneId = UserHandle.USER_SYSTEM; + mUserTwoId = mUserOneId + 1; + } + + @After + public void tearDown() throws Exception { + mConstants.stop(); + FullBackupJob.cancel(mUserOneId, mContext); + FullBackupJob.cancel(mUserTwoId, mContext); + } + + @Test + public void testSchedule_afterScheduling_jobExists() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull(); + } + + @Test + public void testCancel_afterCancelling_jobDoesntExist() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants); + FullBackupJob.cancel(mUserOneId, mContext); + FullBackupJob.cancel(mUserTwoId, mContext); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull(); + } +// + @Test + public void testSchedule_onlySchedulesForRequestedUser() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull(); + } +// + @Test + public void testCancel_onlyCancelsForRequestedUser() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants); + FullBackupJob.cancel(mUserOneId, mContext); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull(); + } + + private static int getJobIdForUserId(int userId) { + return JobIdManager.getJobIdForUserId(FullBackupJob.MIN_JOB_ID, FullBackupJob.MAX_JOB_ID, + userId); + } +} diff --git a/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java new file mode 100644 index 000000000000..f8bb1ee60b80 --- /dev/null +++ b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.UserIdInt; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import static org.testng.Assert.expectThrows; + +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class JobIdManagerTest { + private static final int MIN_JOB_ID = 10; + private static final int MAX_JOB_ID = 20; + + @UserIdInt private int mUserOneId; + @UserIdInt private int mUserTwoId; + + @Before + public void setUp() { + mUserOneId = UserHandle.USER_SYSTEM; + mUserTwoId = mUserOneId + 1; + } + + @Test + public void testGetJobIdForUserId_returnsDifferentJobIdsForDifferentUsers() { + int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId); + int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserTwoId); + + assertThat(jobIdOne).isNotEqualTo(jobIdTwo); + } + + @Test + public void testGetJobIdForUserId_returnsSameJobIdForSameUser() { + int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId); + int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId); + + assertThat(jobIdOne).isEqualTo(jobIdTwo); + } + + @Test + public void testGetJobIdForUserId_throwsExceptionIfRangeIsExceeded() { + expectThrows( + RuntimeException.class, + () -> JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, + MAX_JOB_ID + 1)); + } +} diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java index 8e17209e1f50..8d9e44fbf1ad 100644 --- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java +++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java @@ -18,8 +18,10 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; +import android.annotation.UserIdInt; import android.content.Context; import android.os.Handler; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import org.junit.After; @@ -35,32 +37,67 @@ public class KeyValueBackupJobTest { private Context mContext; private BackupManagerConstants mConstants; + @UserIdInt private int mUserOneId; + @UserIdInt private int mUserTwoId; + @Before public void setUp() throws Exception { mContext = RuntimeEnvironment.application; mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver()); mConstants.start(); + + mUserOneId = UserHandle.USER_SYSTEM; + mUserTwoId = mUserOneId + 1; } @After public void tearDown() throws Exception { mConstants.stop(); - KeyValueBackupJob.cancel(mContext); + KeyValueBackupJob.cancel(mUserOneId, mContext); + KeyValueBackupJob.cancel(mUserTwoId, mContext); } @Test public void testIsScheduled_beforeScheduling_returnsFalse() { - boolean isScheduled = KeyValueBackupJob.isScheduled(); - - assertThat(isScheduled).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse(); } @Test public void testIsScheduled_afterScheduling_returnsTrue() { - KeyValueBackupJob.schedule(mContext, mConstants); + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants); + + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue(); + } + + @Test + public void testIsScheduled_afterCancelling_returnsFalse() { + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants); + KeyValueBackupJob.cancel(mUserOneId, mContext); + KeyValueBackupJob.cancel(mUserTwoId, mContext); - boolean isScheduled = KeyValueBackupJob.isScheduled(); + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse(); + } + + @Test + public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() { + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse(); + } + + @Test + public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() { + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants); + KeyValueBackupJob.cancel(mUserOneId, mContext); - assertThat(isScheduled).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue(); } } diff --git a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java index 693092da0334..7559560f4f6d 100644 --- a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java @@ -41,6 +41,7 @@ import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.app.backup.BackupTransport; import android.content.ComponentName; @@ -48,6 +49,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import com.android.server.backup.testing.TransportData; @@ -84,12 +86,14 @@ public class TransportManagerTest { private TransportData mTransportA2; private TransportData mTransportB1; private ShadowPackageManager mShadowPackageManager; + private @UserIdInt int mUserId; private Context mContext; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mUserId = UserHandle.USER_SYSTEM; mContext = RuntimeEnvironment.application; mShadowPackageManager = shadowOf(mContext.getPackageManager()); @@ -684,6 +688,7 @@ public class TransportManagerTest { @Nullable TransportData selectedTransport, TransportData... transports) { TransportManager transportManager = new TransportManager( + mUserId, mContext, merge(selectedTransport, transports) .stream() diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index 8d5c301d93e7..3b7fa3d6ed05 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -42,6 +42,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.os.Binder; import android.os.HandlerThread; import android.os.PowerManager; @@ -54,6 +56,7 @@ import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.shadows.ShadowAppBackupUtils; +import com.android.server.testing.shadows.ShadowApplicationPackageManager; import com.android.server.testing.shadows.ShadowBinder; import com.android.server.testing.shadows.ShadowKeyValueBackupJob; import com.android.server.testing.shadows.ShadowKeyValueBackupTask; @@ -80,7 +83,7 @@ import java.util.List; * UserBackupManagerService} that performs operations for its target user. */ @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowAppBackupUtils.class}) +@Config(shadows = {ShadowAppBackupUtils.class, ShadowApplicationPackageManager.class}) @Presubmit public class UserBackupManagerServiceTest { private static final String TAG = "BMSTest"; @@ -137,6 +140,7 @@ public class UserBackupManagerServiceTest { public void tearDown() throws Exception { mBackupThread.quit(); ShadowAppBackupUtils.reset(); + ShadowApplicationPackageManager.reset(); } /** @@ -195,6 +199,7 @@ public class UserBackupManagerServiceTest { public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); setUpCurrentTransport(mTransportManager, mTransport); + registerPackages(PACKAGE_1); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); boolean result = backupManagerService.isAppEligibleForBackup(PACKAGE_1); @@ -210,6 +215,7 @@ public class UserBackupManagerServiceTest { public void testIsAppEligibleForBackup_whenAppEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport()); + registerPackages(PACKAGE_1); ShadowAppBackupUtils.setAppRunningAndEligibleForBackupWithTransport(PACKAGE_1); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); @@ -228,6 +234,7 @@ public class UserBackupManagerServiceTest { public void testIsAppEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); setUpCurrentTransport(mTransportManager, mTransport); + registerPackages(PACKAGE_1); ShadowAppBackupUtils.setAppRunningAndEligibleForBackupWithTransport(PACKAGE_1); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); @@ -245,6 +252,7 @@ public class UserBackupManagerServiceTest { public void testFilterAppsEligibleForBackup() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport); + registerPackages(PACKAGE_1, PACKAGE_2); ShadowAppBackupUtils.setAppRunningAndEligibleForBackupWithTransport(PACKAGE_1); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); @@ -264,6 +272,7 @@ public class UserBackupManagerServiceTest { @Test public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + registerPackages(PACKAGE_1, PACKAGE_2); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); String[] filtered = @@ -281,6 +290,7 @@ public class UserBackupManagerServiceTest { public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); setUpCurrentTransport(mTransportManager, mTransport); + registerPackages(PACKAGE_1, PACKAGE_2); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( @@ -749,7 +759,7 @@ public class UserBackupManagerServiceTest { private void setUpForRequestBackup(String... packages) throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); for (String packageName : packages) { - mShadowPackageManager.addPackage(packageName); + registerPackages(packageName); ShadowAppBackupUtils.setAppRunningAndEligibleForBackupWithTransport(packageName); } setUpCurrentTransport(mTransportManager, mTransport); @@ -864,7 +874,7 @@ public class UserBackupManagerServiceTest { @Test public void testRequestBackup_whenAppNotEligibleForBackup() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - mShadowPackageManager.addPackage(PACKAGE_1); + registerPackages(PACKAGE_1); setUpCurrentTransport(mTransportManager, mTransport); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(true); @@ -1127,6 +1137,22 @@ public class UserBackupManagerServiceTest { backupManagerService.setPowerManager(powerManagerMock); } + private PackageInfo getPackageInfo(String packageName) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + packageInfo.applicationInfo = new ApplicationInfo(); + packageInfo.applicationInfo.packageName = packageName; + return packageInfo; + } + + private void registerPackages(String... packages) { + for (String packageName : packages) { + PackageInfo packageInfo = getPackageInfo(packageName); + mShadowPackageManager.installPackage(packageInfo); + ShadowApplicationPackageManager.addInstalledPackage(packageName, packageInfo); + } + } + /** * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method. @@ -1137,8 +1163,9 @@ public class UserBackupManagerServiceTest { * Implementation of {@link ShadowKeyValueBackupJob#schedule(Context, long, * BackupManagerConstants)} that throws an {@link IllegalArgumentException}. */ - public static void schedule(Context ctx, long delay, BackupManagerConstants constants) { - ShadowKeyValueBackupJob.schedule(ctx, delay, constants); + public static void schedule(int userId, Context ctx, long delay, + BackupManagerConstants constants) { + ShadowKeyValueBackupJob.schedule(userId, ctx, delay, constants); throw new IllegalArgumentException(); } } diff --git a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java index b754356ca0cb..bfb2b1479c74 100644 --- a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java +++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java @@ -29,6 +29,7 @@ import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.testing.BackupManagerServiceTestUtils; import com.android.server.backup.testing.TestUtils; +import com.android.server.testing.shadows.ShadowApplicationPackageManager; import org.junit.Before; import org.junit.Test; @@ -37,6 +38,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; import java.io.File; @@ -45,6 +47,7 @@ import java.io.File; * UserBackupManagerService}. */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowApplicationPackageManager.class}) @Presubmit public class SetupObserverTest { private static final String TAG = "SetupObserverTest"; @@ -125,7 +128,7 @@ public class SetupObserverTest { setupObserver.onChange(true); - assertThat(KeyValueBackupJob.isScheduled()).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserBackupManagerService.getUserId())).isTrue(); // Verifies that the full backup job is scheduled. The job is scheduled via a posted message // on the backup handler so we verify that a message exists. assertThat(mUserBackupManagerService.getBackupHandler().hasMessagesOrCallbacks()).isTrue(); diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 7dac79599ac6..4811523b22f9 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -71,7 +71,6 @@ import static java.util.stream.Collectors.toList; import android.annotation.Nullable; import android.app.Application; -import android.app.ApplicationPackageManager; import android.app.IBackupAgent; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; @@ -116,6 +115,7 @@ import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.testing.shadows.FrameworkShadowLooper; +import com.android.server.testing.shadows.ShadowApplicationPackageManager; import com.android.server.testing.shadows.ShadowBackupDataInput; import com.android.server.testing.shadows.ShadowBackupDataOutput; import com.android.server.testing.shadows.ShadowEventLog; @@ -135,8 +135,6 @@ import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowQueuedWork; @@ -160,7 +158,7 @@ import java.util.stream.Stream; @Config( shadows = { FrameworkShadowLooper.class, - KeyValueBackupTaskTest.ShadowApplicationPackageManager.class, + ShadowApplicationPackageManager.class, ShadowBackupDataInput.class, ShadowBackupDataOutput.class, ShadowEventLog.class, @@ -2437,11 +2435,12 @@ public class KeyValueBackupTaskTest { private AgentMock setUpAgent(PackageData packageData) { try { + String packageName = packageData.packageName; mPackageManager.setApplicationEnabledSetting( - packageData.packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); PackageInfo packageInfo = getPackageInfo(packageData); mShadowPackageManager.installPackage(packageInfo); - ShadowApplicationPackageManager.setPackageInfo(packageInfo); + ShadowApplicationPackageManager.addInstalledPackage(packageName, packageInfo); mContext.sendBroadcast(getPackageAddedIntent(packageData)); // Run the backup looper because on the receiver we post MSG_SCHEDULE_BACKUP_PACKAGE mShadowBackupLooper.runToEndOfTasks(); @@ -2537,7 +2536,7 @@ public class KeyValueBackupTaskTest { private PackageManagerBackupAgent createPmAgent() { PackageManagerBackupAgent pmAgent = - new PackageManagerBackupAgent(mApplication.getPackageManager()); + new PackageManagerBackupAgent(mApplication.getPackageManager(), USER_ID); pmAgent.attach(mApplication); pmAgent.onCreate(); return pmAgent; @@ -2727,7 +2726,7 @@ public class KeyValueBackupTaskTest { throws RemoteException, IOException { verify(transportMock.transport).requestBackupTime(); assertBackupPendingFor(packages); - assertThat(KeyValueBackupJob.isScheduled()).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mBackupManagerService.getUserId())).isTrue(); } private void assertBackupPendingFor(PackageData... packages) throws IOException { @@ -2842,7 +2841,7 @@ public class KeyValueBackupTaskTest { ThrowingPackageManagerBackupAgent( PackageManager packageManager, RuntimeException exception) { - super(packageManager); + super(packageManager, USER_ID); mException = exception; } @@ -2854,29 +2853,4 @@ public class KeyValueBackupTaskTest { throw mException; } } - - /** - * Extends {@link org.robolectric.shadows.ShadowApplicationPackageManager} to return the correct - * package in user-specific invocations. - */ - @Implements(value = ApplicationPackageManager.class) - public static class ShadowApplicationPackageManager - extends org.robolectric.shadows.ShadowApplicationPackageManager { - private static PackageInfo sPackageInfo; - - static void setPackageInfo(PackageInfo packageInfo) { - sPackageInfo = packageInfo; - } - - @Override - protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) { - return sPackageInfo; - } - - /** Clear {@link #sPackageInfo}. */ - @Resetter - public static void reset() { - sPackageInfo = null; - } - } } diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java index 4009876314e8..f17a9fe48c61 100644 --- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java @@ -57,6 +57,7 @@ import com.android.server.backup.internal.BackupHandler; import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils; import com.android.server.backup.testing.TransportTestUtils.TransportMock; +import com.android.server.testing.shadows.ShadowApplicationPackageManager; import com.android.server.testing.shadows.ShadowEventLog; import com.android.server.testing.shadows.ShadowPerformUnifiedRestoreTask; @@ -77,7 +78,13 @@ import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayDeque; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowEventLog.class, ShadowPerformUnifiedRestoreTask.class, ShadowBinder.class}) +@Config( + shadows = { + ShadowApplicationPackageManager.class, + ShadowBinder.class, + ShadowEventLog.class, + ShadowPerformUnifiedRestoreTask.class + }) @Presubmit public class ActiveRestoreSessionTest { private static final String PACKAGE_1 = "com.example.package1"; @@ -140,6 +147,7 @@ public class ActiveRestoreSessionTest { @After public void tearDown() throws Exception { + ShadowApplicationPackageManager.reset(); ShadowPerformUnifiedRestoreTask.reset(); } @@ -561,7 +569,8 @@ public class ActiveRestoreSessionTest { packageInfo.packageName = packageName; packageInfo.applicationInfo = new ApplicationInfo(); packageInfo.applicationInfo.uid = uid; - mShadowPackageManager.addPackage(packageInfo); + mShadowPackageManager.installPackage(packageInfo); + ShadowApplicationPackageManager.addInstalledPackage(packageName, packageInfo); } private IRestoreSession createActiveRestoreSession( diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java index 7dd0d927edd0..f033af8a9b63 100644 --- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -49,6 +50,7 @@ public class TransportClientManagerTest { @Mock private Context mContext; @Mock private TransportConnectionListener mTransportConnectionListener; + private @UserIdInt int mUserId; private TransportClientManager mTransportClientManager; private ComponentName mTransportComponent; private Intent mBindIntent; @@ -57,7 +59,9 @@ public class TransportClientManagerTest { public void setUp() { MockitoAnnotations.initMocks(this); - mTransportClientManager = new TransportClientManager(mContext, new TransportStats()); + mUserId = UserHandle.USER_SYSTEM; + mTransportClientManager = + new TransportClientManager(mUserId, mContext, new TransportStats()); mTransportComponent = new ComponentName(PACKAGE_NAME, CLASS_NAME); mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent); diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java index 7281a3c87a29..392f2ca9490e 100644 --- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java @@ -36,6 +36,7 @@ import static org.robolectric.shadow.api.Shadow.extract; import static org.testng.Assert.expectThrows; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -83,6 +84,7 @@ public class TransportClientTest { @Mock private TransportConnectionListener mTransportConnectionListener; @Mock private TransportConnectionListener mTransportConnectionListener2; @Mock private IBackupTransport.Stub mTransportBinder; + @UserIdInt private int mUserId; private TransportStats mTransportStats; private TransportClient mTransportClient; private ComponentName mTransportComponent; @@ -96,6 +98,7 @@ public class TransportClientTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mUserId = UserHandle.USER_SYSTEM; Looper mainLooper = Looper.getMainLooper(); mShadowMainLooper = extract(mainLooper); mTransportComponent = @@ -105,6 +108,7 @@ public class TransportClientTest { mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent); mTransportClient = new TransportClient( + mUserId, mContext, mTransportStats, mBindIntent, diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java index 5fffb149fd96..aefc871d2639 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java @@ -54,7 +54,10 @@ public class ShadowAppBackupUtils { @Implementation protected static boolean appIsRunningAndEligibleForBackupWithTransport( - @Nullable TransportClient transportClient, String packageName, PackageManager pm) { + @Nullable TransportClient transportClient, + String packageName, + PackageManager pm, + int userId) { return sAppsRunningAndEligibleForBackupWithTransport.contains(packageName); } diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java new file mode 100644 index 000000000000..dc322094add8 --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.testing.shadows; + +import static android.content.pm.PackageManager.NameNotFoundException; + +import android.app.ApplicationPackageManager; +import android.content.pm.PackageInfo; +import android.util.ArrayMap; + +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Extends {@link org.robolectric.shadows.ShadowApplicationPackageManager} to return the correct + * package in user-specific invocations. + */ +@Implements(value = ApplicationPackageManager.class) +public class ShadowApplicationPackageManager + extends org.robolectric.shadows.ShadowApplicationPackageManager { + private static final Map<String, PackageInfo> sPackageInfos = new ArrayMap<>(); + private static final List<PackageInfo> sInstalledPackages = new ArrayList<>(); + + /** + * Registers the package {@code packageName} to be returned when invoking {@link + * ApplicationPackageManager#getPackageInfoAsUser(String, int, int)} and {@link + * ApplicationPackageManager#getInstalledPackagesAsUser(int, int)}. + */ + public static void addInstalledPackage(String packageName, PackageInfo packageInfo) { + sPackageInfos.put(packageName, packageInfo); + sInstalledPackages.add(packageInfo); + } + + @Override + protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) + throws NameNotFoundException { + if (!sPackageInfos.containsKey(packageName)) { + throw new NameNotFoundException(packageName); + } + return sPackageInfos.get(packageName); + } + + @Override + protected List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) { + return sInstalledPackages; + } + + /** Clear package state. */ + @Resetter + public static void reset() { + sPackageInfos.clear(); + sInstalledPackages.clear(); + org.robolectric.shadows.ShadowApplicationPackageManager.reset(); + } +} diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java index 23c44b0e8c83..f90ea6aff41b 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java @@ -34,7 +34,8 @@ public class ShadowKeyValueBackupJob { } @Implementation - protected static void schedule(Context ctx, long delay, BackupManagerConstants constants) { + protected static void schedule(int userId, Context ctx, long delay, + BackupManagerConstants constants) { callingUid = Binder.getCallingUid(); } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java index e4fe5995ce2f..00a60b91ec5c 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java @@ -117,7 +117,7 @@ public class ActivityManagerInternalTest { thread2.assertWaiting("Unexpected state for " + record2); thread2.interrupt(); - mAms.mActiveUids.clear(); + mAms.mProcessList.mActiveUids.clear(); } private UidRecord addActiveUidRecord(int uid, long curProcStateSeq, @@ -126,7 +126,7 @@ public class ActivityManagerInternalTest { record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq; record.curProcStateSeq = curProcStateSeq; record.waitingForNetwork = true; - mAms.mActiveUids.put(uid, record); + mAms.mProcessList.mActiveUids.put(uid, record); return record; } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 2cc338cb8674..419c73611cb9 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -253,7 +253,7 @@ public class ActivityManagerServiceTest { final UidRecord uidRec = new UidRecord(uid, null /* atmInternal */); uidRec.waitingForNetwork = true; uidRec.hasInternetPermission = true; - mAms.mActiveUids.put(uid, uidRec); + mAms.mProcessList.mActiveUids.put(uid, uidRec); final ProcessRecord appRec = new ProcessRecord(mAms, new ApplicationInfo(), TAG, uid); appRec.thread = Mockito.mock(IApplicationThread.class); @@ -715,7 +715,8 @@ public class ActivityManagerServiceTest { // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it // will be removed from validateUids. - assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size()); + assertNotEquals("validateUids should not be empty", 0, + mAms.mValidateUids.size()); for (int i = 0; i < pendingItemsForUids.size(); ++i) { final UidRecord.ChangeItem item = pendingItemsForUids.get(i); // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd @@ -853,7 +854,7 @@ public class ActivityManagerServiceTest { record.curProcStateSeq = curProcStateSeq; record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq; record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq; - mAms.mActiveUids.put(Process.myUid(), record); + mAms.mProcessList.mActiveUids.put(Process.myUid(), record); CustomThread thread = new CustomThread(record.networkStateLock, new Runnable() { @Override @@ -876,7 +877,7 @@ public class ActivityManagerServiceTest { thread.assertTerminated(errMsg); } - mAms.mActiveUids.clear(); + mAms.mProcessList.mActiveUids.clear(); } private static class TestHandler extends Handler { diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java index 8109b1c56207..1dfce51725f1 100644 --- a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java +++ b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java @@ -26,6 +26,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.server.appop.AppOpsService; +import com.android.server.wm.ActivityTaskManagerService; import org.junit.Before; import org.junit.Test; @@ -62,13 +63,15 @@ public class AppErrorDialogTest { return false; } }); + mService.mActivityTaskManager = new ActivityTaskManagerService(mContext); + mService.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); } @Test @UiThreadTest public void testCreateWorks() { AppErrorDialog.Data data = new AppErrorDialog.Data(); - data.proc = new ProcessRecord(null, mContext.getApplicationInfo(), "name", 12345); + data.proc = new ProcessRecord(mService, mContext.getApplicationInfo(), "name", 12345); data.result = new AppErrorResult(); AppErrorDialog dialog = new AppErrorDialog(mContext, mService, data); diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java index db83505900c5..6a9a12133e70 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -1149,31 +1149,31 @@ public class TrampolineTest { @Test public void beginFullBackup_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.beginFullBackup(new FullBackupJob()); + mTrampoline.beginFullBackup(mUserId, new FullBackupJob()); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test public void beginFullBackup_forwarded() throws RemoteException { FullBackupJob fullBackupJob = new FullBackupJob(); - when(mBackupManagerServiceMock.beginFullBackup(fullBackupJob)).thenReturn(true); + when(mBackupManagerServiceMock.beginFullBackup(mUserId, fullBackupJob)).thenReturn(true); mTrampoline.initializeService(); - assertTrue(mTrampoline.beginFullBackup(fullBackupJob)); - verify(mBackupManagerServiceMock).beginFullBackup(fullBackupJob); + assertTrue(mTrampoline.beginFullBackup(mUserId, fullBackupJob)); + verify(mBackupManagerServiceMock).beginFullBackup(mUserId, fullBackupJob); } @Test public void endFullBackup_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.endFullBackup(); + mTrampoline.endFullBackup(mUserId); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test public void endFullBackup_forwarded() throws RemoteException { mTrampoline.initializeService(); - mTrampoline.endFullBackup(); - verify(mBackupManagerServiceMock).endFullBackup(); + mTrampoline.endFullBackup(mUserId); + verify(mBackupManagerServiceMock).endFullBackup(mUserId); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index 4255e37aac2d..8607ec66a5ba 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -50,6 +50,7 @@ final class FakeNativeWrapper implements NativeWrapper { private final List<HdmiCecMessage> mResultMessages = new ArrayList<>(); private int mMyPhysicalAddress = 0; + private HdmiPortInfo[] mHdmiPortInfo = null; @Override public long nativeInit(HdmiCecController handler, MessageQueue messageQueue) { @@ -92,9 +93,11 @@ final class FakeNativeWrapper implements NativeWrapper { @Override public HdmiPortInfo[] nativeGetPortInfos(long controllerPtr) { - HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[1]; - hdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true); - return hdmiPortInfo; + if (mHdmiPortInfo == null) { + mHdmiPortInfo = new HdmiPortInfo[1]; + mHdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true); + } + return mHdmiPortInfo; } @Override @@ -131,4 +134,10 @@ final class FakeNativeWrapper implements NativeWrapper { protected void setPhysicalAddress(int physicalAddress) { mMyPhysicalAddress = physicalAddress; } + + @VisibleForTesting + protected void setPortInfo(HdmiPortInfo[] hdmiPortInfo) { + mHdmiPortInfo = new HdmiPortInfo[hdmiPortInfo.length]; + System.arraycopy(hdmiPortInfo, 0, mHdmiPortInfo, 0, hdmiPortInfo.length); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index b47f269bc721..3b51a2a70f83 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -15,6 +15,9 @@ */ package com.android.server.hdmi; +import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; +import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE; + import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; @@ -25,6 +28,7 @@ import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; import static com.google.common.truth.Truth.assertThat; +import android.hardware.hdmi.HdmiDeviceInfo; import android.media.AudioManager; import android.os.Looper; import android.os.SystemProperties; @@ -64,6 +68,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { private int mMusicMaxVolume; private boolean mMusicMute; private int mAvrPhysicalAddress; + private int mInvokeDeviceEventState; + private HdmiDeviceInfo mDeviceInfo; @Before public void setUp() { @@ -127,6 +133,18 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Override void wakeUp() {} + + @Override + void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { + mDeviceInfo = device; + mInvokeDeviceEventState = status; + } + + @Override + int pathToPortId(int path) { + // port id is not useful for the test right now + return 1; + } }; mMyLooper = mTestLooper.getLooper(); @@ -157,6 +175,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { mNativeWrapper.setPhysicalAddress(mAvrPhysicalAddress); SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "true"); SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "true"); + mInvokeDeviceEventState = 0; + mDeviceInfo = null; } @Test @@ -611,4 +631,73 @@ public class HdmiCecLocalDeviceAudioSystemTest { mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } + + @Test + public void updateCecDevice_deviceNotExists_addDevice() { + assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE); + HdmiDeviceInfo newDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + + mHdmiCecLocalDeviceAudioSystem.updateCecDevice(newDevice); + assertThat(mDeviceInfo).isEqualTo(newDevice); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getCecDeviceInfo(newDevice.getLogicalAddress())).isEqualTo(newDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE); + } + + @Test + public void updateCecDevice_deviceExists_doNothing() { + mInvokeDeviceEventState = 0; + HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + + mHdmiCecLocalDeviceAudioSystem.updateCecDevice(oldDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(0); + } + + @Test + public void updateCecDevice_deviceInfoDifferent_updateDevice() { + assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_UPDATE_DEVICE); + HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + + HdmiDeviceInfo differentDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 4, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + + mHdmiCecLocalDeviceAudioSystem.updateCecDevice(differentDevice); + assertThat(mDeviceInfo).isEqualTo(differentDevice); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_UPDATE_DEVICE); + } + + @Test + public void handleReportPhysicalAddress_differentPath_addDevice() { + assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE); + HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + + HdmiDeviceInfo differentDevice = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK, + Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); + HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder + .buildReportPhysicalAddressCommand( + ADDR_PLAYBACK_1, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK); + mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress); + + mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); + mTestLooper.dispatchAll(); + assertThat(mDeviceInfo).isEqualTo(differentDevice); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice); + assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 18c9a653dc7d..67ce13fdef72 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -20,13 +20,18 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import android.hardware.hdmi.HdmiPortInfo; import android.os.Looper; import android.os.test.TestLooper; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -102,6 +107,7 @@ public class HdmiControlServiceTest { private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); private boolean mStandbyMessageReceived; + private HdmiPortInfo[] mHdmiPortInfo; @Before public void SetUp() { @@ -131,6 +137,16 @@ public class HdmiControlServiceTest { mLocalDevices.add(mMyAudioSystemDevice); mLocalDevices.add(mMyPlaybackDevice); + mHdmiPortInfo = new HdmiPortInfo[4]; + mHdmiPortInfo[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); + mHdmiPortInfo[1] = + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false); + mHdmiPortInfo[2] = + new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false); + mHdmiPortInfo[3] = + new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); + mNativeWrapper.setPortInfo(mHdmiPortInfo); mHdmiControlService.initPortInfo(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); @@ -159,4 +175,24 @@ public class HdmiControlServiceTest { assertTrue(mMyPlaybackDevice.isDisabled()); assertTrue(mMyAudioSystemDevice.isDisabled()); } + + @Test + public void pathToPort_pathExists_weAreNonTv() { + mNativeWrapper.setPhysicalAddress(0x2000); + assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1); + assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2); + } + + @Test + public void pathToPort_pathExists_weAreTv() { + mNativeWrapper.setPhysicalAddress(0x0000); + assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3); + assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4); + } + + @Test + public void pathToPort_pathInvalid() { + mNativeWrapper.setPhysicalAddress(0x2000); + assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 517b5ade44b8..6d28ed19af4f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -35,6 +35,7 @@ import static org.junit.Assert.fail; import android.annotation.NonNull; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.SuspendDialogInfo; @@ -226,8 +227,8 @@ public class PackageManagerSettingsTests { settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3)); // now read and verify settingsUnderTest.readPackageRestrictionsLPr(0); - final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1). - readUserState(0); + final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1) + .readUserState(0); assertThat(readPus1.suspended, is(true)); assertThat(readPus1.suspendingPackage, equalTo("suspendingPackage1")); assertThat(readPus1.dialogInfo, equalTo(dialogInfo1)); @@ -235,16 +236,16 @@ public class PackageManagerSettingsTests { assertThat(BaseBundle.kindofEquals(readPus1.suspendedLauncherExtras, launcherExtras1), is(true)); - final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2). - readUserState(0); + final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2) + .readUserState(0); assertThat(readPus2.suspended, is(true)); assertThat(readPus2.suspendingPackage, equalTo("suspendingPackage2")); assertThat(readPus2.dialogInfo, is(nullValue())); assertThat(readPus2.suspendedAppExtras, is(nullValue())); assertThat(readPus2.suspendedLauncherExtras, is(nullValue())); - final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3). - readUserState(0); + final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3) + .readUserState(0); assertThat(readPus3.suspended, is(false)); assertThat(readPus3.suspendingPackage, is(nullValue())); assertThat(readPus3.dialogInfo, is(nullValue())); @@ -254,11 +255,59 @@ public class PackageManagerSettingsTests { @Test public void testPackageRestrictionsSuspendedDefault() { - final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1); + final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1); assertThat(defaultSetting.getSuspended(0), is(false)); } @Test + public void testReadWritePackageRestrictions_distractionFlags() { + final Context context = InstrumentationRegistry.getTargetContext(); + final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, new Object()); + final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1); + final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2); + final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3); + + final int distractionFlags1 = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS; + ps1.setDistractionFlags(distractionFlags1, 0); + settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1); + + final int distractionFlags2 = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS + | PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS; + ps2.setDistractionFlags(distractionFlags2, 0); + settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2); + + final int distractionFlags3 = PackageManager.RESTRICTION_NONE; + ps3.setDistractionFlags(distractionFlags3, 0); + settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3); + + settingsUnderTest.writePackageRestrictionsLPr(0); + + settingsUnderTest.mPackages.clear(); + settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1)); + settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2)); + settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3)); + // now read and verify + settingsUnderTest.readPackageRestrictionsLPr(0); + final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1) + .readUserState(0); + assertThat(readPus1.distractionFlags, is(distractionFlags1)); + + final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2) + .readUserState(0); + assertThat(readPus2.distractionFlags, is(distractionFlags2)); + + final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3) + .readUserState(0); + assertThat(readPus3.distractionFlags, is(distractionFlags3)); + } + + @Test + public void testPackageRestrictionsDistractionFlagsDefault() { + final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1); + assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE)); + } + + @Test public void testEnableDisable() { // Write the package files and make sure they're parsed properly the first time writeOldFiles(); @@ -692,6 +741,7 @@ public class PackageManagerSettingsTests { assertThat(userState.notLaunched, is(notLaunched)); assertThat(userState.stopped, is(stopped)); assertThat(userState.suspended, is(false)); + assertThat(userState.distractionFlags, is(0)); if (oldUserState != null) { assertThat(userState.equals(oldUserState), is(not(userStateChanged))); } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java index f0ed612400ed..8eaf35f6432f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java @@ -22,6 +22,7 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; +import android.content.pm.PackageManager; import android.content.pm.PackageUserState; import android.content.pm.SuspendDialogInfo; import android.os.PersistableBundle; @@ -227,4 +228,19 @@ public class PackageUserStateTest { assertThat(testUserState1.equals(testUserState2), is(true)); } + @Test + public void testPackageUserState06() { + final PackageUserState userState1 = new PackageUserState(); + assertThat(userState1.distractionFlags, is(PackageManager.RESTRICTION_NONE)); + userState1.distractionFlags = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS; + + final PackageUserState copyOfUserState1 = new PackageUserState(userState1); + assertThat(userState1.distractionFlags, is(copyOfUserState1.distractionFlags)); + assertThat(userState1.equals(copyOfUserState1), is(true)); + + final PackageUserState userState2 = new PackageUserState(userState1); + userState2.distractionFlags = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS; + assertThat(userState1.equals(userState2), is(false)); + } + } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index 5638cb360c78..c79e1db0a745 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -15,8 +15,11 @@ */ package com.android.server.notification; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; +import static junit.framework.TestCase.assertEquals; + import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.anyInt; @@ -153,7 +156,7 @@ public class RankingHelperTest extends UiServiceTestCase { .build(); mRecordGroupGSortA = new NotificationRecord(mContext, new StatusBarNotification( PKG, PKG, 1, null, 0, 0, mNotiGroupGSortA, user, - null, System.currentTimeMillis()), getDefaultChannel()); + null, System.currentTimeMillis()), getLowChannel()); mNotiGroupGSortB = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("B") @@ -163,7 +166,7 @@ public class RankingHelperTest extends UiServiceTestCase { .build(); mRecordGroupGSortB = new NotificationRecord(mContext, new StatusBarNotification( PKG, PKG, 1, null, 0, 0, mNotiGroupGSortB, user, - null, System.currentTimeMillis()), getDefaultChannel()); + null, System.currentTimeMillis()), getLowChannel()); mNotiNoGroup = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("C") @@ -171,7 +174,7 @@ public class RankingHelperTest extends UiServiceTestCase { .build(); mRecordNoGroup = new NotificationRecord(mContext, new StatusBarNotification( PKG, PKG, 1, null, 0, 0, mNotiNoGroup, user, - null, System.currentTimeMillis()), getDefaultChannel()); + null, System.currentTimeMillis()), getLowChannel()); mNotiNoGroup2 = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("D") @@ -179,7 +182,7 @@ public class RankingHelperTest extends UiServiceTestCase { .build(); mRecordNoGroup2 = new NotificationRecord(mContext, new StatusBarNotification( PKG, PKG, 1, null, 0, 0, mNotiNoGroup2, user, - null, System.currentTimeMillis()), getDefaultChannel()); + null, System.currentTimeMillis()), getLowChannel()); mNotiNoGroupSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("E") @@ -188,7 +191,7 @@ public class RankingHelperTest extends UiServiceTestCase { .build(); mRecordNoGroupSortA = new NotificationRecord(mContext, new StatusBarNotification( PKG, PKG, 1, null, 0, 0, mNotiNoGroupSortA, user, - null, System.currentTimeMillis()), getDefaultChannel()); + null, System.currentTimeMillis()), getLowChannel()); mAudioAttributes = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) @@ -197,11 +200,16 @@ public class RankingHelperTest extends UiServiceTestCase { .build(); } - private NotificationChannel getDefaultChannel() { + private NotificationChannel getLowChannel() { return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name", IMPORTANCE_LOW); } + private NotificationChannel getDefaultChannel() { + return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name", + IMPORTANCE_DEFAULT); + } + @Test public void testSortShouldRespectCritical() throws Exception { ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(7); @@ -285,4 +293,40 @@ public class RankingHelperTest extends UiServiceTestCase { ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(); mHelper.sort(notificationList); } + + @Test + public void testGroupNotifications_highestIsProxy() { + ArrayList<NotificationRecord> notificationList = new ArrayList<>(); + // this should be the last in the list, except it's in a group with a high child + Notification lowSummaryN = new Notification.Builder(mContext, "") + .setGroup("group") + .setGroupSummary(true) + .build(); + NotificationRecord lowSummary = new NotificationRecord(mContext, new StatusBarNotification( + PKG, PKG, 1, "summary", 0, 0, lowSummaryN, USER, + null, System.currentTimeMillis()), getLowChannel()); + notificationList.add(lowSummary); + + Notification lowN = new Notification.Builder(mContext, "").build(); + NotificationRecord low = new NotificationRecord(mContext, new StatusBarNotification( + PKG, PKG, 1, "low", 0, 0, lowN, USER, + null, System.currentTimeMillis()), getLowChannel()); + low.setContactAffinity(0.5f); + notificationList.add(low); + + Notification highChildN = new Notification.Builder(mContext, "") + .setGroup("group") + .setGroupSummary(false) + .build(); + NotificationRecord highChild = new NotificationRecord(mContext, new StatusBarNotification( + PKG, PKG, 1, "child", 0, 0, highChildN, USER, + null, System.currentTimeMillis()), getDefaultChannel()); + notificationList.add(highChild); + + mHelper.sort(notificationList); + + assertEquals(lowSummary, notificationList.get(0)); + assertEquals(highChild, notificationList.get(1)); + assertEquals(low, notificationList.get(2)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java index b315e514d6a6..efefee119e5f 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java @@ -16,6 +16,7 @@ package com.android.server.slice; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import android.content.ContentProvider; import android.content.ContentResolver; @@ -26,6 +27,7 @@ import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import android.util.Log; import android.util.Xml.Encoding; import com.android.server.UiServiceTestCase; @@ -46,10 +48,12 @@ import java.io.IOException; @RunWith(AndroidTestingRunner.class) @RunWithLooper public class SlicePermissionManagerTest extends UiServiceTestCase { + private static final String TAG = "SlicePerManTest"; @Test public void testGrant() { - File sliceDir = new File(mContext.getDataDir(), "system/slices"); + File sliceDir = new File(mContext.getCacheDir(), "testGrantSlices"); + Log.v(TAG, "testGrant: slice permissions stored in " + sliceDir.getAbsolutePath()); SlicePermissionManager permissions = new SlicePermissionManager(mContext, TestableLooper.get(this).getLooper(), sliceDir); Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT) @@ -59,11 +63,15 @@ public class SlicePermissionManagerTest extends UiServiceTestCase { permissions.grantSliceAccess("my.pkg", 0, "provider.pkg", 0, uri); assertTrue(permissions.hasPermission("my.pkg", 0, uri)); + + // Cleanup. + assertTrue(FileUtils.deleteContentsAndDir(sliceDir)); } @Test public void testBackup() throws XmlPullParserException, IOException { - File sliceDir = new File(mContext.getDataDir(), "system/slices"); + File sliceDir = new File(mContext.getCacheDir(), "testBackupSlices"); + Log.v(TAG, "testBackup: slice permissions stored in " + sliceDir.getAbsolutePath()); Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority("authority") .path("something").build(); @@ -90,7 +98,10 @@ public class SlicePermissionManagerTest extends UiServiceTestCase { TestableLooper.get(this).getLooper()); permissions.readRestore(parser); - assertTrue(permissions.hasFullAccess("com.android.mypkg", 10)); + if (!permissions.hasFullAccess("com.android.mypkg", 10)) { + fail("com.android.mypkg@10 did not have full access. backup file: " + + output.toString()); + } assertTrue(permissions.hasPermission("com.android.otherpkg", 0, ContentProvider.maybeAddUserId(uri, 1))); permissions.removePkg("com.android.lastpkg", 1); @@ -102,8 +113,9 @@ public class SlicePermissionManagerTest extends UiServiceTestCase { } @Test - public void testInvalid() throws Exception { - File sliceDir = new File(mContext.getCacheDir(), "slices-test"); + public void testInvalid() { + File sliceDir = new File(mContext.getCacheDir(), "testInvalidSlices"); + Log.v(TAG, "testInvalid: slice permissions stored in " + sliceDir.getAbsolutePath()); if (!sliceDir.exists()) { sliceDir.mkdir(); } @@ -118,7 +130,8 @@ public class SlicePermissionManagerTest extends UiServiceTestCase { @Override public void writeTo(XmlSerializer out) throws IOException { - throw new RuntimeException("this doesn't work"); + throw new RuntimeException("this RuntimeException inside junk.writeTo() " + + "should be caught and suppressed by surrounding code"); } }; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 7a6b2b50d1e1..ec88718dab5d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -643,10 +643,10 @@ public class ActivityStarterTests extends ActivityTestsBase { boolean hasForegroundActivities, boolean callerIsRecents, boolean callerIsTempWhitelisted) { // window visibility - doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager).isAnyWindowVisibleForUid( - callingUid); - doReturn(realCallingUidHasVisibleWindow).when(mService.mWindowManager) - .isAnyWindowVisibleForUid(realCallingUid); + doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager.mRoot) + .isAnyNonToastWindowVisibleForUid(callingUid); + doReturn(realCallingUidHasVisibleWindow).when(mService.mWindowManager.mRoot) + .isAnyNonToastWindowVisibleForUid(realCallingUid); // process importance doReturn(callingUidProcState).when(mService).getUidStateLocked(callingUid); doReturn(realCallingUidProcState).when(mService).getUidStateLocked(realCallingUid); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java new file mode 100644 index 000000000000..45fe5d24adf5 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_TOAST; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Tests for RootWindowContainer. + * + * Build/Install/Run: + * atest WmTests:RootWindowContainerTests + */ +@SmallTest +@Presubmit +public class RootWindowContainerTests extends WindowTestsBase { + + private static final int FAKE_CALLING_UID = 667; + + @Test + public void testIsAnyNonToastWindowVisibleForUid_oneToastOneNonToastBothVisible() { + final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID); + toastyToast.mHasSurface = true; + app.mHasSurface = true; + + assertTrue(toastyToast.isVisible()); + assertTrue(app.isVisible()); + assertTrue(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID)); + } + + @Test + public void testIsAnyNonToastWindowVisibleForUid_onlyToastVisible() { + final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID); + toastyToast.mHasSurface = true; + + assertTrue(toastyToast.isVisible()); + assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID)); + } + + @Test + public void testIsAnyNonToastWindowVisibleForUid_aFewNonToastButNoneVisible() { + final WindowState topBar = createWindow(null, TYPE_STATUS_BAR, "topBar", FAKE_CALLING_UID); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID); + + assertFalse(topBar.isVisible()); + assertFalse(app.isVisible()); + assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID)); + } +} + diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 638cb03f4707..cdc0a477b9c6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -257,6 +257,14 @@ class WindowTestsBase { } } + WindowState createWindow(WindowState parent, int type, String name, int ownerId) { + synchronized (mWm.mGlobalLock) { + return (parent == null) + ? createWindow(parent, type, mDisplayContent, name, ownerId) + : createWindow(parent, type, parent.mToken, name, ownerId); + } + } + WindowState createWindowOnStack(WindowState parent, int windowingMode, int activityType, int type, DisplayContent dc, String name) { synchronized (mWm.mGlobalLock) { @@ -277,7 +285,16 @@ class WindowTestsBase { synchronized (mWm.mGlobalLock) { final WindowToken token = createWindowToken( dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); - return createWindow(parent, type, token, name); + return createWindow(parent, type, token, name, 0 /* ownerId */); + } + } + + WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name, + int ownerId) { + synchronized (mWm.mGlobalLock) { + final WindowToken token = createWindowToken( + dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); + return createWindow(parent, type, token, name, ownerId); } } @@ -299,6 +316,14 @@ class WindowTestsBase { } WindowState createWindow(WindowState parent, int type, WindowToken token, String name, + int ownerId) { + synchronized (mWm.mGlobalLock) { + return createWindow(parent, type, token, name, ownerId, + false /* ownerCanAddInternalSystemWindow */); + } + } + + WindowState createWindow(WindowState parent, int type, WindowToken token, String name, int ownerId, boolean ownerCanAddInternalSystemWindow) { return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow, mWm, mMockSession, mIWindow); diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp index 2fc3a0dd874e..7dc83c3db868 100644 --- a/startop/view_compiler/Android.bp +++ b/startop/view_compiler/Android.bp @@ -46,7 +46,7 @@ cc_defaults { }, } -cc_library_host_static { +cc_library_static { name: "libviewcompiler", defaults: ["viewcompiler_defaults"], srcs: [ @@ -58,9 +58,10 @@ cc_library_host_static { "util.cc", "layout_validation.cc", ], + host_supported: true, } -cc_binary_host { +cc_binary { name: "viewcompiler", defaults: ["viewcompiler_defaults"], srcs: [ @@ -70,6 +71,7 @@ cc_binary_host { "libgflags", "libviewcompiler", ], + host_supported: true } cc_test_host { diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 84256037b379..05d5a13092f1 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -505,6 +505,14 @@ public abstract class Connection extends Conferenceable { "android.telecom.extra.ORIGINAL_CONNECTION_ID"; /** + * Boolean connection extra key set on the extras passed to + * {@link Connection#sendConnectionEvent} which indicates that audio is present + * on the RTT call when the extra value is true. + */ + public static final String EXTRA_IS_RTT_AUDIO_PRESENT = + "android.telecom.extra.IS_RTT_AUDIO_PRESENT"; + + /** * Connection event used to inform Telecom that it should play the on hold tone. This is used * to play a tone when the peer puts the current call on hold. Sent to Telecom via * {@link #sendConnectionEvent(String, Bundle)}. @@ -619,6 +627,13 @@ public abstract class Connection extends Conferenceable { */ public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE"; + /** + * Connection event used to inform an {@link InCallService} that the RTT audio indication + * has changed. + */ + public static final String EVENT_RTT_AUDIO_INDICATION_CHANGED = + "android.telecom.event.RTT_AUDIO_INDICATION_CHANGED"; + // Flag controlling whether PII is emitted into the logs private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); diff --git a/telephony/java/android/telephony/CallAttributes.aidl b/telephony/java/android/telephony/CallAttributes.aidl new file mode 100644 index 000000000000..69127df19714 --- /dev/null +++ b/telephony/java/android/telephony/CallAttributes.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable CallAttributes; + diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java new file mode 100644 index 000000000000..2b99ce1d8252 --- /dev/null +++ b/telephony/java/android/telephony/CallAttributes.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.TelephonyManager.NetworkType; + +import java.util.Objects; + +/** + * Contains information about a call's attributes as passed up from the HAL. If there are multiple + * ongoing calls, the CallAttributes will pertain to the call in the foreground. + * @hide + */ +@SystemApi +public class CallAttributes implements Parcelable { + private PreciseCallState mPreciseCallState; + @NetworkType + private int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints + private CallQuality mCallQuality; + + + public CallAttributes(PreciseCallState state, @NetworkType int networkType, + CallQuality callQuality) { + this.mPreciseCallState = state; + this.mNetworkType = networkType; + this.mCallQuality = callQuality; + } + + @Override + public String toString() { + return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType + + " mCallQuality=" + mCallQuality; + } + + private CallAttributes(Parcel in) { + mPreciseCallState = (PreciseCallState) in.readValue(mPreciseCallState.getClass() + .getClassLoader()); + mNetworkType = in.readInt(); + mCallQuality = (CallQuality) in.readValue(mCallQuality.getClass().getClassLoader()); + } + + // getters + /** + * Returns the {@link PreciseCallState} of the call. + */ + public PreciseCallState getPreciseCallState() { + return mPreciseCallState; + } + + /** + * Returns the {@link TelephonyManager#NetworkType} of the call. + * + * @see TelephonyManager#NETWORK_TYPE_UNKNOWN + * @see TelephonyManager#NETWORK_TYPE_GPRS + * @see TelephonyManager#NETWORK_TYPE_EDGE + * @see TelephonyManager#NETWORK_TYPE_UMTS + * @see TelephonyManager#NETWORK_TYPE_CDMA + * @see TelephonyManager#NETWORK_TYPE_EVDO_0 + * @see TelephonyManager#NETWORK_TYPE_EVDO_A + * @see TelephonyManager#NETWORK_TYPE_1xRTT + * @see TelephonyManager#NETWORK_TYPE_HSDPA + * @see TelephonyManager#NETWORK_TYPE_HSUPA + * @see TelephonyManager#NETWORK_TYPE_HSPA + * @see TelephonyManager#NETWORK_TYPE_IDEN + * @see TelephonyManager#NETWORK_TYPE_EVDO_B + * @see TelephonyManager#NETWORK_TYPE_LTE + * @see TelephonyManager#NETWORK_TYPE_EHRPD + * @see TelephonyManager#NETWORK_TYPE_HSPAP + * @see TelephonyManager#NETWORK_TYPE_GSM + * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA + * @see TelephonyManager#NETWORK_TYPE_IWLAN + * @see TelephonyManager#NETWORK_TYPE_LTE_CA + * @see TelephonyManager#NETWORK_TYPE_NR + */ + @NetworkType + public int getNetworkType() { + return mNetworkType; + } + + /** + * Returns the {#link CallQuality} of the call. + */ + public CallQuality getCallQuality() { + return mCallQuality; + } + + @Override + public int hashCode() { + return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof CallAttributes) || hashCode() != o.hashCode()) { + return false; + } + + if (this == o) { + return true; + } + + CallAttributes s = (CallAttributes) o; + + return (mPreciseCallState == s.mPreciseCallState + && mNetworkType == s.mNetworkType + && mCallQuality == s.mCallQuality); + } + + /** + * {@link Parcelable#describeContents} + */ + public @Parcelable.ContentsFlags int describeContents() { + return 0; + } + + /** + * {@link Parcelable#writeToParcel} + */ + public void writeToParcel(Parcel dest, @Parcelable.WriteFlags int flags) { + mPreciseCallState.writeToParcel(dest, flags); + dest.writeInt(mNetworkType); + mCallQuality.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<CallAttributes> CREATOR = new Parcelable.Creator() { + public CallAttributes createFromParcel(Parcel in) { + return new CallAttributes(in); + } + + public CallAttributes[] newArray(int size) { + return new CallAttributes[size]; + } + }; +} diff --git a/telephony/java/android/telephony/CallQuality.aidl b/telephony/java/android/telephony/CallQuality.aidl new file mode 100644 index 000000000000..f54355f48ad5 --- /dev/null +++ b/telephony/java/android/telephony/CallQuality.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable CallQuality; + diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java new file mode 100644 index 000000000000..b27f6b44370f --- /dev/null +++ b/telephony/java/android/telephony/CallQuality.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Parcelable object to handle call quality. + * <p> + * Currently this supports IMS calls. + * <p> + * It provides the call quality level, duration, and additional information related to RTP packets, + * jitter and delay. + * <p> + * If there are multiple active calls, the CallQuality will pertain to the call in the foreground. + * + * @hide + */ +@SystemApi +public final class CallQuality implements Parcelable { + + // Constants representing the call quality level (see #CallQuality); + public static final int CALL_QUALITY_EXCELLENT = 0; + public static final int CALL_QUALITY_GOOD = 1; + public static final int CALL_QUALITY_FAIR = 2; + public static final int CALL_QUALITY_POOR = 3; + public static final int CALL_QUALITY_BAD = 4; + public static final int CALL_QUALITY_NOT_AVAILABLE = 5; + + /** + * Call quality + * @hide + */ + @IntDef(prefix = { "CALL_QUALITY_" }, value = { + CALL_QUALITY_EXCELLENT, + CALL_QUALITY_GOOD, + CALL_QUALITY_FAIR, + CALL_QUALITY_POOR, + CALL_QUALITY_BAD, + CALL_QUALITY_NOT_AVAILABLE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CallQualityLevel {} + + @CallQualityLevel + private int mDownlinkCallQualityLevel; + @CallQualityLevel + private int mUplinkCallQualityLevel; + private int mCallDuration; + private int mNumRtpPacketsTransmitted; + private int mNumRtpPacketsReceived; + private int mNumRtpPacketsTransmittedLost; + private int mNumRtpPacketsNotReceived; + private int mAverageRelativeJitter; + private int mMaxRelativeJitter; + private int mAverageRoundTripTime; + private int mCodecType; + + /** @hide **/ + public CallQuality(Parcel in) { + mDownlinkCallQualityLevel = in.readInt(); + mUplinkCallQualityLevel = in.readInt(); + mCallDuration = in.readInt(); + mNumRtpPacketsTransmitted = in.readInt(); + mNumRtpPacketsReceived = in.readInt(); + mNumRtpPacketsTransmittedLost = in.readInt(); + mNumRtpPacketsNotReceived = in.readInt(); + mAverageRelativeJitter = in.readInt(); + mMaxRelativeJitter = in.readInt(); + mAverageRoundTripTime = in.readInt(); + mCodecType = in.readInt(); + } + + /** + * Constructor. + * + * @param callQualityLevel the call quality level (see #CallQualityLevel) + * @param callDuration the call duration in milliseconds + * @param numRtpPacketsTransmitted RTP packets sent to network + * @param numRtpPacketsReceived RTP packets received from network + * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never + * transmitted + * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved + * @param averageRelativeJitter average relative jitter in milliseconds + * @param maxRelativeJitter maximum relative jitter in milliseconds + * @param averageRoundTripTime average round trip delay in milliseconds + * @param codecType the codec type + */ + public CallQuality( + @CallQualityLevel int downlinkCallQualityLevel, + @CallQualityLevel int uplinkCallQualityLevel, + int callDuration, + int numRtpPacketsTransmitted, + int numRtpPacketsReceived, + int numRtpPacketsTransmittedLost, + int numRtpPacketsNotReceived, + int averageRelativeJitter, + int maxRelativeJitter, + int averageRoundTripTime, + int codecType) { + this.mDownlinkCallQualityLevel = downlinkCallQualityLevel; + this.mUplinkCallQualityLevel = uplinkCallQualityLevel; + this.mCallDuration = callDuration; + this.mNumRtpPacketsTransmitted = numRtpPacketsTransmitted; + this.mNumRtpPacketsReceived = numRtpPacketsReceived; + this.mNumRtpPacketsTransmittedLost = numRtpPacketsTransmittedLost; + this.mNumRtpPacketsNotReceived = numRtpPacketsNotReceived; + this.mAverageRelativeJitter = averageRelativeJitter; + this.mMaxRelativeJitter = maxRelativeJitter; + this.mAverageRoundTripTime = averageRoundTripTime; + this.mCodecType = codecType; + } + + // getters + /** + * Returns the downlink CallQualityLevel for a given ongoing call. + */ + @CallQualityLevel + public int getDownlinkCallQualityLevel() { + return mDownlinkCallQualityLevel; + } + + /** + * Returns the uplink CallQualityLevel for a given ongoing call. + */ + @CallQualityLevel + public int getUplinkCallQualityLevel() { + return mUplinkCallQualityLevel; + } + + /** + * Returns the duration of the call, in milliseconds. + */ + public int getCallDuration() { + return mCallDuration; + } + + /** + * Returns the total number of RTP packets transmitted by this device for a given ongoing call. + */ + public int getNumRtpPacketsTransmitted() { + return mNumRtpPacketsTransmitted; + } + + /** + * Returns the total number of RTP packets received by this device for a given ongoing call. + */ + public int getNumRtpPacketsReceived() { + return mNumRtpPacketsReceived; + } + + /** + * Returns the number of RTP packets which were sent by this device but were lost in the + * network before reaching the other party. + */ + public int getNumRtpPacketsTransmittedLost() { + return mNumRtpPacketsTransmittedLost; + } + + /** + * Returns the number of RTP packets which were sent by the other party but were lost in the + * network before reaching this device. + */ + public int getNumRtpPacketsNotReceived() { + return mNumRtpPacketsNotReceived; + } + + /** + * Returns the average relative jitter in milliseconds. Jitter represents the amount of variance + * in interarrival time of packets, for example, if two packets are sent 2 milliseconds apart + * but received 3 milliseconds apart, the relative jitter between those packets is 1 + * millisecond. + * + * <p>See RFC 3550 for more information on jitter calculations. + */ + public int getAverageRelativeJitter() { + return mAverageRelativeJitter; + } + + /** + * Returns the maximum relative jitter for a given ongoing call. Jitter represents the amount of + * variance in interarrival time of packets, for example, if two packets are sent 2 milliseconds + * apart but received 3 milliseconds apart, the relative jitter between those packets is 1 + * millisecond. + * + * <p>See RFC 3550 for more information on jitter calculations. + */ + public int getMaxRelativeJitter() { + return mMaxRelativeJitter; + } + + /** + * Returns the average round trip time in milliseconds. + */ + public int getAverageRoundTripTime() { + return mAverageRoundTripTime; + } + + /** + * Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in + * {@link ImsStreamMediaProfile}. + * + * @see ImsStreamMediaProfile#AUDIO_QUALITY_NONE + * @see ImsStreamMediaProfile#AUDIO_QUALITY_AMR + * @see ImsStreamMediaProfile#AUDIO_QUALITY_AMR_WB + * @see ImsStreamMediaProfile#AUDIO_QUALITY_QCELP13K + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVRC + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVRC_B + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVRC_WB + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVRC_NW + * @see ImsStreamMediaProfile#AUDIO_QUALITY_GSM_EFR + * @see ImsStreamMediaProfile#AUDIO_QUALITY_GSM_FR + * @see ImsStreamMediaProfile#AUDIO_QUALITY_GSM_HR + * @see ImsStreamMediaProfile#AUDIO_QUALITY_G711U + * @see ImsStreamMediaProfile#AUDIO_QUALITY_G723 + * @see ImsStreamMediaProfile#AUDIO_QUALITY_G711A + * @see ImsStreamMediaProfile#AUDIO_QUALITY_G722 + * @see ImsStreamMediaProfile#AUDIO_QUALITY_G711AB + * @see ImsStreamMediaProfile#AUDIO_QUALITY_G729 + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVS_NB + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVS_WB + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVS_SWB + * @see ImsStreamMediaProfile#AUDIO_QUALITY_EVS_FB + */ + public int getCodecType() { + return mCodecType; + } + + // Parcelable things + @Override + public String toString() { + return "CallQuality: {downlinkCallQualityLevel=" + mDownlinkCallQualityLevel + + " uplinkCallQualityLevel=" + mUplinkCallQualityLevel + + " callDuration=" + mCallDuration + + " numRtpPacketsTransmitted=" + mNumRtpPacketsTransmitted + + " numRtpPacketsReceived=" + mNumRtpPacketsReceived + + " numRtpPacketsTransmittedLost=" + mNumRtpPacketsTransmittedLost + + " numRtpPacketsNotReceived=" + mNumRtpPacketsNotReceived + + " averageRelativeJitter=" + mAverageRelativeJitter + + " maxRelativeJitter=" + mMaxRelativeJitter + + " averageRoundTripTime=" + mAverageRoundTripTime + + " codecType=" + mCodecType + + "}"; + } + + @Override + public int hashCode() { + return Objects.hash( + mDownlinkCallQualityLevel, + mUplinkCallQualityLevel, + mCallDuration, + mNumRtpPacketsTransmitted, + mNumRtpPacketsReceived, + mNumRtpPacketsTransmittedLost, + mNumRtpPacketsNotReceived, + mAverageRelativeJitter, + mMaxRelativeJitter, + mAverageRoundTripTime, + mCodecType); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof CallQuality) || hashCode() != o.hashCode()) { + return false; + } + + if (this == o) { + return true; + } + + CallQuality s = (CallQuality) o; + + return (mDownlinkCallQualityLevel == s.mDownlinkCallQualityLevel + && mUplinkCallQualityLevel == s.mUplinkCallQualityLevel + && mCallDuration == s.mCallDuration + && mNumRtpPacketsTransmitted == s.mNumRtpPacketsTransmitted + && mNumRtpPacketsReceived == s.mNumRtpPacketsReceived + && mNumRtpPacketsTransmittedLost == s.mNumRtpPacketsTransmittedLost + && mNumRtpPacketsNotReceived == s.mNumRtpPacketsNotReceived + && mAverageRelativeJitter == s.mAverageRelativeJitter + && mMaxRelativeJitter == s.mMaxRelativeJitter + && mAverageRoundTripTime == s.mAverageRoundTripTime + && mCodecType == s.mCodecType); + } + + /** + * {@link Parcelable#describeContents} + */ + public @Parcelable.ContentsFlags int describeContents() { + return 0; + } + + /** + * {@link Parcelable#writeToParcel} + */ + public void writeToParcel(Parcel dest, @Parcelable.WriteFlags int flags) { + dest.writeInt(mDownlinkCallQualityLevel); + dest.writeInt(mUplinkCallQualityLevel); + dest.writeInt(mCallDuration); + dest.writeInt(mNumRtpPacketsTransmitted); + dest.writeInt(mNumRtpPacketsReceived); + dest.writeInt(mNumRtpPacketsTransmittedLost); + dest.writeInt(mNumRtpPacketsNotReceived); + dest.writeInt(mAverageRelativeJitter); + dest.writeInt(mMaxRelativeJitter); + dest.writeInt(mAverageRoundTripTime); + dest.writeInt(mCodecType); + } + + public static final Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() { + public CallQuality createFromParcel(Parcel in) { + return new CallQuality(in); + } + + public CallQuality[] newArray(int size) { + return new CallQuality[size]; + } + }; +} diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl index 7fb62682703a..ee3c1b1be6d9 100644 --- a/telephony/java/android/telephony/ICellInfoCallback.aidl +++ b/telephony/java/android/telephony/ICellInfoCallback.aidl @@ -16,6 +16,7 @@ package android.telephony; +import android.os.ParcelableException; import android.telephony.CellInfo; import java.util.List; @@ -27,4 +28,5 @@ import java.util.List; oneway interface ICellInfoCallback { void onCellInfo(in List<CellInfo> state); + void onError(in int errorCode, in ParcelableException detail); } diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index e27b38596324..c81670139eae 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -296,7 +296,7 @@ public class PhoneStateListener { /** * Listen for changes to preferred data subId. - * See {@link SubscriptionManager#setPreferredData(int)} + * See {@link SubscriptionManager#setPreferredDataSubId(int)} * for more details. * * @see #onPreferredDataSubIdChanged @@ -335,6 +335,18 @@ public class PhoneStateListener { @SystemApi public static final int LISTEN_CALL_DISCONNECT_CAUSES = 0x02000000; + /** + * Listen for changes to the call attributes of a currently active call. + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} + * + * @see #onCallAttributesChanged + * @hide + */ + @SystemApi + public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 0x04000000; + /* * Subscription used to listen to the phone state changes * @hide @@ -683,6 +695,17 @@ public class PhoneStateListener { } /** + * Callback invoked when the call attributes changes. Requires + * the READ_PRIVILEGED_PHONE_STATE permission. + * @param callAttributes the call attributes + * @hide + */ + @SystemApi + public void onCallAttributesChanged(CallAttributes callAttributes) { + // default implementation empty + } + + /** * Callback invoked when modem radio power state changes. Requires * the READ_PRIVILEGED_PHONE_STATE permission. * @param state the modem radio power state @@ -941,6 +964,14 @@ public class PhoneStateListener { () -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state))); } + public void onCallAttributesChanged(CallAttributes callAttributes) { + PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); + if (psl == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes))); + } + public void onPreferredDataSubIdChanged(int subId) { PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); if (psl == null) return; diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index 2271069dddb7..e77042d1930f 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -178,6 +180,35 @@ public class SignalStrength implements Parcelable { return mLte; } + /** + * Returns a List of CellSignalStrength Components of this SignalStrength Report. + * + * Use this API to access underlying + * {@link android.telephony#CellSignalStrength CellSignalStrength} objects that provide more + * granular information about the SignalStrength report. Only valid (non-empty) + * CellSignalStrengths will be returned. The order of any returned elements is not guaranteed, + * and the list may contain more than one instance of a CellSignalStrength type. + * + * @return a List of CellSignalStrength or an empty List if there are no valid measurements. + * + * @see android.telephony#CellSignalStrength + * @see android.telephony#CellSignalStrengthNr + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony#CellSignalStrengthTdscdma + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony#CellSignalStrengthGsm + */ + public @NonNull List<CellSignalStrength> getCellSignalStrengths() { + List<CellSignalStrength> cssList = new ArrayList<>(2); // Usually have 2 or fewer elems + if (mLte.isValid()) cssList.add(mLte); + if (mCdma.isValid()) cssList.add(mCdma); + if (mTdscdma.isValid()) cssList.add(mTdscdma); + if (mWcdma.isValid()) cssList.add(mWcdma); + if (mGsm.isValid()) cssList.add(mGsm); + return cssList; + } + /** @hide */ public void updateLevel(PersistableBundle cc, ServiceState ss) { mLteRsrpBoost = ss.getLteEarfcnRsrpBoost(); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 34f7abd79a70..9fa4c3ce899f 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2453,10 +2453,39 @@ public class SubscriptionManager { * */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void setPreferredData(int subId) { - if (VDBG) logd("[setPreferredData]+ subId:" + subId); - setSubscriptionPropertyHelper(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, - "setPreferredData", (iSub)-> iSub.setPreferredData(subId)); + public void setPreferredDataSubscriptionId(int subId) { + if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId); + setSubscriptionPropertyHelper(DEFAULT_SUBSCRIPTION_ID, "setPreferredDataSubscriptionId", + (iSub)-> iSub.setPreferredDataSubscriptionId(subId)); + } + + /** + * Get which subscription is preferred for cellular data. + * It's also usually the subscription we set up internet connection on. + * + * PreferredData overwrites user setting of default data subscription. And it's used + * by AlternativeNetworkService or carrier apps to switch primary and CBRS + * subscription dynamically in multi-SIM devices. + * + * @return preferred subscription id for cellular data. {@link DEFAULT_SUBSCRIPTION_ID} if + * there's no prefered subscription. + * + * @hide + * + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public int getPreferredDataSubscriptionId() { + int preferredSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + preferredSubId = iSub.getPreferredDataSubscriptionId(); + } + } catch (RemoteException ex) { + // ignore it + } + + return preferredSubId; } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index b48a1ced61a4..babeb7b1b61c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4898,19 +4898,53 @@ public class TelephonyManager { /** Callback for providing asynchronous {@link CellInfo} on request */ public abstract static class CellInfoCallback { /** - * Response to + * Success response to * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}. * - * <p>Invoked when there is a response to + * Invoked when there is a response to * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()} * to provide a list of {@link CellInfo}. If no {@link CellInfo} is available then an empty - * list will be provided. If an error occurs, null will be provided. + * list will be provided. If an error occurs, null will be provided unless the onError + * callback is overridden. * * @param cellInfo a list of {@link CellInfo}, an empty list, or null. * * {@see android.telephony.TelephonyManager#getAllCellInfo getAllCellInfo()} */ - public abstract void onCellInfo(List<CellInfo> cellInfo); + public abstract void onCellInfo(@NonNull List<CellInfo> cellInfo); + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ERROR_"}, value = {ERROR_TIMEOUT, ERROR_MODEM_ERROR}) + public @interface CellInfoCallbackError {} + + /** + * The system timed out waiting for a response from the Radio. + */ + public static final int ERROR_TIMEOUT = 1; + + /** + * The modem returned a failure. + */ + public static final int ERROR_MODEM_ERROR = 2; + + /** + * Error response to + * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}. + * + * Invoked when an error condition prevents updated {@link CellInfo} from being fetched + * and returned from the modem. Callers of requestCellInfoUpdate() should override this + * function to receive detailed status information in the event of an error. By default, + * this function will invoke onCellInfo() with null. + * + * @param errorCode an error code indicating the type of failure. + * @param detail a Throwable object with additional detail regarding the failure if + * available, otherwise null. + */ + public void onError(@CellInfoCallbackError int errorCode, @Nullable Throwable detail) { + // By default, simply invoke the success callback with an empty list. + onCellInfo(new ArrayList<CellInfo>()); + } }; /** @@ -4937,6 +4971,12 @@ public class TelephonyManager { Binder.withCleanCallingIdentity(() -> executor.execute(() -> callback.onCellInfo(cellInfo))); } + + public void onError(int errorCode, android.os.ParcelableException detail) { + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> callback.onError( + errorCode, detail.getCause()))); + } }, getOpPackageName()); } catch (RemoteException ex) { @@ -4971,6 +5011,12 @@ public class TelephonyManager { Binder.withCleanCallingIdentity(() -> executor.execute(() -> callback.onCellInfo(cellInfo))); } + + public void onError(int errorCode, android.os.ParcelableException detail) { + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> callback.onError( + errorCode, detail.getCause()))); + } }, getOpPackageName(), workSource); } catch (RemoteException ex) { } @@ -9864,10 +9910,11 @@ public class TelephonyManager { try { IOns iOpportunisticNetworkService = getIOns(); if (iOpportunisticNetworkService != null) { - return iOpportunisticNetworkService.setPreferredData(subId, pkgForDebug); + return iOpportunisticNetworkService + .setPreferredDataSubscriptionId(subId, pkgForDebug); } } catch (RemoteException ex) { - Rlog.e(TAG, "setPreferredData RemoteException", ex); + Rlog.e(TAG, "setPreferredDataSubscriptionId RemoteException", ex); } return false; } @@ -9888,10 +9935,10 @@ public class TelephonyManager { try { IOns iOpportunisticNetworkService = getIOns(); if (iOpportunisticNetworkService != null) { - subId = iOpportunisticNetworkService.getPreferredData(pkgForDebug); + subId = iOpportunisticNetworkService.getPreferredDataSubscriptionId(pkgForDebug); } } catch (RemoteException ex) { - Rlog.e(TAG, "getPreferredData RemoteException", ex); + Rlog.e(TAG, "getPreferredDataSubscriptionId RemoteException", ex); } return subId; } diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index df903cc270a2..397d5d9b4eb5 100644 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -443,6 +443,13 @@ public class ImsCallSession { public void callSessionRttMessageReceived(String rttMessage) { // no-op } + + /** + * While in call, there has been a change in RTT audio indicator. + */ + public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { + // no-op + } } private final IImsCallSession miSession; @@ -1397,6 +1404,16 @@ public class ImsCallSession { mListener.callSessionRttMessageReceived(rttMessage); } } + + /** + * While in call, there has been a change in RTT audio indicator. + */ + @Override + public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { + if (mListener != null) { + mListener.callSessionRttAudioIndicatorChanged(profile); + } + } } /** diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java index a7f124a5b791..a4696a3f93aa 100644 --- a/telephony/java/android/telephony/ims/ImsCallSessionListener.java +++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java @@ -599,5 +599,18 @@ public class ImsCallSessionListener { throw new RuntimeException(e); } } + + /** + * While in call, there has been a change in RTT audio indicator. + * + * @param profile updated ImsStreamMediaProfile + */ + public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { + try { + mListener.callSessionRttAudioIndicatorChanged(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } } diff --git a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java index 52d72b5847b5..837ef54a2f24 100644 --- a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java +++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java @@ -97,6 +97,9 @@ public final class ImsStreamMediaProfile implements Parcelable { // Rtt related information /** @hide */ public int mRttMode; + // RTT Audio Speech Indicator + /** @hide */ + public boolean mHasRttAudioSpeech = false; /** @hide */ public ImsStreamMediaProfile(Parcel in) { @@ -197,7 +200,8 @@ public final class ImsStreamMediaProfile implements Parcelable { ", audioDirection=" + mAudioDirection + ", videoQuality=" + mVideoQuality + ", videoDirection=" + mVideoDirection + - ", rttMode=" + mRttMode + " }"; + ", rttMode=" + mRttMode + + ", hasRttAudioSpeech=" + mHasRttAudioSpeech + " }"; } @Override @@ -212,6 +216,7 @@ public final class ImsStreamMediaProfile implements Parcelable { out.writeInt(mVideoQuality); out.writeInt(mVideoDirection); out.writeInt(mRttMode); + out.writeBoolean(mHasRttAudioSpeech); } private void readFromParcel(Parcel in) { @@ -220,6 +225,7 @@ public final class ImsStreamMediaProfile implements Parcelable { mVideoQuality = in.readInt(); mVideoDirection = in.readInt(); mRttMode = in.readInt(); + mHasRttAudioSpeech = in.readBoolean(); } public static final Creator<ImsStreamMediaProfile> CREATOR = @@ -250,6 +256,10 @@ public final class ImsStreamMediaProfile implements Parcelable { mRttMode = rttMode; } + public void setRttAudioSpeech(boolean audioOn) { + mHasRttAudioSpeech = audioOn; + } + public int getAudioQuality() { return mAudioQuality; } @@ -269,4 +279,8 @@ public final class ImsStreamMediaProfile implements Parcelable { public int getRttMode() { return mRttMode; } + + public boolean getRttAudioSpeech() { + return mHasRttAudioSpeech; + } } diff --git a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl index f25b4b148605..d0b31e16a4f0 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl @@ -138,4 +138,10 @@ oneway interface IImsCallSessionListener { * @param rttMessage Received RTT message */ void callSessionRttMessageReceived(in String rttMessage); + + /* + * While in call, there has been a change in RTT audio indicator. + * @param profile updated ImsStreamMediaProfile + */ + void callSessionRttAudioIndicatorChanged(in ImsStreamMediaProfile profile); } diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java index 23de2fd3c32c..bc58e46806c2 100644 --- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java +++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java @@ -591,5 +591,11 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { public void callSessionRttMessageReceived(String rttMessage) throws RemoteException { mNewListener.callSessionRttMessageReceived(rttMessage); } + + @Override + public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) + throws RemoteException { + mNewListener.callSessionRttAudioIndicatorChanged(profile); + } } } diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl index a8e8b7dd03aa..bbb27af1ba36 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl @@ -152,4 +152,10 @@ oneway interface IImsCallSessionListener { * @param rttMessage Received RTT message */ void callSessionRttMessageReceived(in String rttMessage); + + /* + * While in call, there has been a change in RTT audio indicator. + * @param profile updated ImsStreamMediaProfile + */ + void callSessionRttAudioIndicatorChanged(in ImsStreamMediaProfile profile); } diff --git a/telephony/java/com/android/internal/telephony/IOns.aidl b/telephony/java/com/android/internal/telephony/IOns.aidl index d6779f1fb334..0e3d12b7f838 100755 --- a/telephony/java/com/android/internal/telephony/IOns.aidl +++ b/telephony/java/com/android/internal/telephony/IOns.aidl @@ -66,7 +66,7 @@ interface IOns { * @return true if request is accepted, else false. * */ - boolean setPreferredData(int subId, String callingPackage); + boolean setPreferredDataSubscriptionId(int subId, String callingPackage); /** * Get preferred opportunistic data subscription Id @@ -78,7 +78,7 @@ interface IOns { * subscription id * */ - int getPreferredData(String callingPackage); + int getPreferredDataSubscriptionId(String callingPackage); /** * Update availability of a list of networks in the current location. diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index 00cf9c3577ec..3dbebe832fac 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -17,14 +17,15 @@ package com.android.internal.telephony; import android.os.Bundle; -import android.telephony.ServiceState; -import android.telephony.SignalStrength; +import android.telephony.CallAttributes; import android.telephony.CellInfo; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.PhoneCapability; import android.telephony.PhysicalChannelConfig; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; oneway interface IPhoneStateListener { @@ -54,6 +55,7 @@ oneway interface IPhoneStateListener { void onPhoneCapabilityChanged(in PhoneCapability capability); void onPreferredDataSubIdChanged(in int subId); void onRadioPowerStateChanged(in int state); + void onCallAttributesChanged(in CallAttributes callAttributes); void onEmergencyNumberListChanged(in Map emergencyNumberList); void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause); } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index d169b7d04f5c..577ddbda50fa 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -200,7 +200,15 @@ interface ISub { * @hide * */ - int setPreferredData(int subId); + int setPreferredDataSubscriptionId(int subId); + + /** + * Get which subscription is preferred for cellular data. + * + * @hide + * + */ + int getPreferredDataSubscriptionId(); /** * Get User downloaded Profiles. diff --git a/tests/PackageWatchdog/Android.mk b/tests/PackageWatchdog/Android.mk index b53f27d28740..1c1c2a426d15 100644 --- a/tests/PackageWatchdog/Android.mk +++ b/tests/PackageWatchdog/Android.mk @@ -23,7 +23,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ junit \ frameworks-base-testutils \ android-support-test \ - services + services.core LOCAL_JAVA_LIBRARIES := \ android.test.runner diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java index f82b380f5d43..932fee0c1956 100644 --- a/tests/net/java/android/net/LinkPropertiesTest.java +++ b/tests/net/java/android/net/LinkPropertiesTest.java @@ -18,6 +18,7 @@ package android.net; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -33,6 +34,9 @@ import android.support.test.runner.AndroidJUnit4; import android.system.OsConstants; import android.util.ArraySet; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; @@ -41,9 +45,6 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import org.junit.Test; -import org.junit.runner.RunWith; - @RunWith(AndroidJUnit4.class) @SmallTest public class LinkPropertiesTest { @@ -504,6 +505,40 @@ public class LinkPropertiesTest { } @Test + public void testNat64Prefix() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.addLinkAddress(LINKADDRV4); + lp.addLinkAddress(LINKADDRV6); + + assertNull(lp.getNat64Prefix()); + + IpPrefix p = new IpPrefix("64:ff9b::/96"); + lp.setNat64Prefix(p); + assertEquals(p, lp.getNat64Prefix()); + + p = new IpPrefix("2001:db8:a:b:1:2:3::/96"); + lp.setNat64Prefix(p); + assertEquals(p, lp.getNat64Prefix()); + + p = new IpPrefix("2001:db8:a:b:1:2::/80"); + try { + lp.setNat64Prefix(p); + } catch (IllegalArgumentException expected) { + } + + p = new IpPrefix("64:ff9b::/64"); + try { + lp.setNat64Prefix(p); + } catch (IllegalArgumentException expected) { + } + + assertEquals(new IpPrefix("2001:db8:a:b:1:2:3::/96"), lp.getNat64Prefix()); + + lp.setNat64Prefix(null); + assertNull(lp.getNat64Prefix()); + } + + @Test public void testIsProvisioned() { LinkProperties lp4 = new LinkProperties(); assertFalse("v4only:empty", lp4.isProvisioned()); @@ -815,7 +850,7 @@ public class LinkPropertiesTest { } @Test - public void testLinkPropertiesParcelable() { + public void testLinkPropertiesParcelable() throws Exception { LinkProperties source = new LinkProperties(); source.setInterfaceName(NAME); // set 2 link addresses @@ -833,6 +868,8 @@ public class LinkPropertiesTest { source.setMtu(MTU); + source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96")); + Parcel p = Parcel.obtain(); source.writeToParcel(p, /* flags */ 0); p.setDataPosition(0); diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 0580df60f32a..7783e108f674 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -115,6 +115,7 @@ cc_library_host_static { "optimize/MultiApkGenerator.cpp", "optimize/ResourceDeduper.cpp", "optimize/ResourceFilter.cpp", + "optimize/ResourcePathShortener.cpp", "optimize/VersionCollapser.cpp", "process/SymbolTable.cpp", "split/TableSplitter.cpp", diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index b353ff00a23f..45719ef474cd 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -223,8 +223,17 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table io::IFile* file = iterator->Next(); std::string path = file->GetSource().path; + std::string output_path = path; + bool is_resource = path.find("res/") == 0; + if (is_resource) { + auto it = options.shortened_path_map.find(path); + if (it != options.shortened_path_map.end()) { + output_path = it->second; + } + } + // Skip resources that are not referenced if requested. - if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) { + if (is_resource && referenced_resources.find(output_path) == referenced_resources.end()) { if (context->IsVerbose()) { context->GetDiagnostics()->Note(DiagMessage() << "Removing resource '" << path << "' from APK."); @@ -283,7 +292,8 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table return false; } } else { - if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) { + if (!io::CopyFileToArchivePreserveCompression( + context, file, output_path, writer)) { return false; } } diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 328b0beda372..2e6af18c1948 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -41,6 +41,7 @@ #include "optimize/MultiApkGenerator.h" #include "optimize/ResourceDeduper.h" #include "optimize/ResourceFilter.h" +#include "optimize/ResourcePathShortener.h" #include "optimize/VersionCollapser.h" #include "split/TableSplitter.h" #include "util/Files.h" @@ -52,6 +53,7 @@ using ::android::ConfigDescription; using ::android::ResTable_config; using ::android::StringPiece; using ::android::base::ReadFileToString; +using ::android::base::WriteStringToFile; using ::android::base::StringAppendF; using ::android::base::StringPrintf; @@ -143,6 +145,21 @@ class Optimizer { return 1; } + if (options_.shorten_resource_paths) { + ResourcePathShortener shortener(options_.table_flattener_options.shortened_path_map); + if (!shortener.Consume(context_, apk->GetResourceTable())) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed shortening resource paths"); + return 1; + } + if (options_.shortened_paths_map_path + && !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map, + options_.shortened_paths_map_path.value())) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to write shortened resource paths to file"); + return 1; + } + } + // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or // equal to the minSdk. options_.split_constraints = @@ -264,6 +281,15 @@ class Optimizer { ArchiveEntry::kAlign, writer); } + bool WriteShortenedPathsMap(const std::map<std::string, std::string> &path_map, + const std::string &file_path) { + std::stringstream ss; + for (auto it = path_map.cbegin(); it != path_map.cend(); ++it) { + ss << it->first << " -> " << it->second << "\n"; + } + return WriteStringToFile(ss.str(), file_path); + } + OptimizeOptions options_; OptimizeContext* context_; }; diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index d07305bc3e04..7f4a3ed85364 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -55,6 +55,12 @@ struct OptimizeOptions { // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts // are kept and will be written as output. std::unordered_set<std::string> kept_artifacts; + + // Whether or not to shorten resource paths in the APK. + bool shorten_resource_paths; + + // Path to the output map of original resource paths to shortened paths. + Maybe<std::string> shortened_paths_map_path; }; class OptimizeCommand : public Command { @@ -101,6 +107,12 @@ class OptimizeCommand : public Command { AddOptionalSwitch("--enable-resource-obfuscation", "Enables obfuscation of key string pool to single value", &options_.table_flattener_options.collapse_key_stringpool); + AddOptionalSwitch("--enable-resource-path-shortening", + "Enables shortening of the path of the resources inside the APK.", + &options_.shorten_resource_paths); + AddOptionalFlag("--resource-path-shortening-map", + "Path to output the map of old resource paths to shortened paths.", + &options_.shortened_paths_map_path); AddOptionalSwitch("-v", "Enables verbose logging", &verbose_); } @@ -109,6 +121,9 @@ class OptimizeCommand : public Command { private: OptimizeOptions options_; + bool WriteObfuscatedPathsMap(const std::map<std::string, std::string> &path_map, + const std::string &file_path); + Maybe<std::string> config_path_; Maybe<std::string> whitelist_path_; Maybe<std::string> resources_config_path_; @@ -122,4 +137,4 @@ class OptimizeCommand : public Command { }// namespace aapt -#endif //AAPT2_OPTIMIZE_H
\ No newline at end of file +#endif //AAPT2_OPTIMIZE_H diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 635cb21f514c..71330e3fb74f 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -46,6 +46,9 @@ struct TableFlattenerOptions { // When true, sort the entries in the values string pool by priority and configuration. bool sort_stringpool_entries = true; + + // Map from original resource paths to shortened resource paths. + std::map<std::string, std::string> shortened_path_map; }; class TableFlattener : public IResourceTableConsumer { diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/ResourcePathShortener.cpp new file mode 100644 index 000000000000..c5df3dd00db9 --- /dev/null +++ b/tools/aapt2/optimize/ResourcePathShortener.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "optimize/ResourcePathShortener.h" + +#include <math.h> +#include <unordered_set> + +#include "androidfw/StringPiece.h" + +#include "ResourceTable.h" +#include "ValueVisitor.h" + + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + +namespace aapt { + +ResourcePathShortener::ResourcePathShortener( + std::map<std::string, std::string>& path_map_out) + : path_map_(path_map_out) { +} + +std::string ShortenFileName(const android::StringPiece& file_path, int output_length) { + std::size_t hash_num = std::hash<android::StringPiece>{}(file_path); + std::string result = ""; + // Convert to (modified) base64 so that it is a proper file path. + for (int i = 0; i < output_length; i++) { + uint8_t sextet = hash_num & 0x3f; + hash_num >>= 6; + result += base64_chars[sextet]; + } + return result; +} + + +// Calculate the optimal hash length such that an average of 10% of resources +// collide in their shortened path. +// Reference: http://matt.might.net/articles/counting-hash-collisions/ +int OptimalShortenedLength(int num_resources) { + int num_chars = 2; + double N = 64*64; // hash space when hash is 2 chars long + double max_collisions = num_resources * 0.1; + while (num_resources - N + N * pow((N - 1) / N, num_resources) > max_collisions) { + N *= 64; + num_chars++; + } + return num_chars; +} + +std::string GetShortenedPath(const android::StringPiece& shortened_filename, + const android::StringPiece& extension, int collision_count) { + std::string shortened_path = "res/" + shortened_filename.to_string(); + if (collision_count > 0) { + shortened_path += std::to_string(collision_count); + } + shortened_path += extension; + return shortened_path; +} + +bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) { + // used to detect collisions + std::unordered_set<std::string> shortened_paths; + std::unordered_set<FileReference*> file_refs; + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); + if (file_ref) { + file_refs.insert(file_ref); + } + } + } + } + } + int num_chars = OptimalShortenedLength(file_refs.size()); + for (auto& file_ref : file_refs) { + android::StringPiece res_subdir, actual_filename, extension; + util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension); + + std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars); + int collision_count = 0; + std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); + while (shortened_paths.find(shortened_path) != shortened_paths.end()) { + collision_count++; + shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); + } + shortened_paths.insert(shortened_path); + path_map_.insert({*file_ref->path, shortened_path}); + file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext()); + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/optimize/ResourcePathShortener.h b/tools/aapt2/optimize/ResourcePathShortener.h new file mode 100644 index 000000000000..f1074ef083bd --- /dev/null +++ b/tools/aapt2/optimize/ResourcePathShortener.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H +#define AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H + +#include <map> + +#include "android-base/macros.h" + +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +class ResourceTable; + +// Maps resources in the apk to shortened paths. +class ResourcePathShortener : public IResourceTableConsumer { + public: + explicit ResourcePathShortener(std::map<std::string, std::string>& path_map_out); + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ResourcePathShortener); + std::map<std::string, std::string>& path_map_; +}; + +} // namespace aapt + +#endif // AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/ResourcePathShortener_test.cpp new file mode 100644 index 000000000000..88cadc76c336 --- /dev/null +++ b/tools/aapt2/optimize/ResourcePathShortener_test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "optimize/ResourcePathShortener.h" + +#include "ResourceTable.h" +#include "test/Test.h" + +using ::aapt::test::GetValue; +using ::testing::Not; +using ::testing::NotNull; +using ::testing::Eq; + +namespace aapt { + +TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml") + .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml") + .AddString("android:string/string", "res/should/still/be/the/same.png") + .Build(); + + std::map<std::string, std::string> path_map; + ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get())); + + // Expect that the path map is populated + ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end()))); + ASSERT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end()))); + + // The file paths were changed + EXPECT_THAT(path_map.at("res/drawables/xmlfile.xml"), Not(Eq("res/drawables/xmlfile.xml"))); + EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml"))); + + // Different file paths should remain different + EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], + Not(Eq(path_map["res/drawables/xmlfile2.xml"]))); + + FileReference* ref = + GetValue<FileReference>(table.get(), "android:drawable/xmlfile"); + ASSERT_THAT(ref, NotNull()); + // The map correctly points to the new location of the file + EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path)); + + // Strings should not be affected, only file paths + EXPECT_THAT( + *GetValue<String>(table.get(), "android:string/string")->value, + Eq("res/should/still/be/the/same.png")); + EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end())); +} + +} // namespace aapt diff --git a/tools/signedconfig/prod_public.pem b/tools/signedconfig/prod_public.pem new file mode 100644 index 000000000000..8c10215eb083 --- /dev/null +++ b/tools/signedconfig/prod_public.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc ++tzC8MvnrzVxodvTpVY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g== +-----END PUBLIC KEY----- + diff --git a/tools/signedconfig/verify_b64.sh b/tools/signedconfig/verify_b64.sh index 8e1f58ce7b45..a4ac6a816d14 100755 --- a/tools/signedconfig/verify_b64.sh +++ b/tools/signedconfig/verify_b64.sh @@ -7,4 +7,30 @@ # The arg values can be taken from the debug log for SignedConfigService when verbose logging is # enabled. -openssl dgst -sha256 -verify $(dirname $0)/debug_public.pem -signature <(echo $2 | base64 -d) <(echo $1 | base64 -d) +function verify() { + D=${1} + S=${2} + K=${3} + echo Trying ${K} + openssl dgst -sha256 -verify $(dirname $0)/${K} -signature <(echo ${S} | base64 -d) <(echo ${D} | base64 -d) +} + + +PROD_KEY_NAME=prod_public.pem +DEBUG_KEY_NAME=debug_public.pem +SIGNATURE="$2" +DATA="$1" + +echo DATA: ${DATA} +echo SIGNATURE: ${SIGNATURE} + +if verify "${DATA}" "${SIGNATURE}" "${PROD_KEY_NAME}"; then + echo Verified with ${PROD_KEY_NAME} + exit 0 +fi + +if verify "${DATA}" "${SIGNATURE}" "${DEBUG_KEY_NAME}"; then + echo Verified with ${DEBUG_KEY_NAME} + exit 0 +fi +exit 1 diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 26a6c08bee29..1fa1fd521a8e 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -35,6 +35,7 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; import libcore.util.HexEncoding; @@ -434,6 +435,8 @@ public class WifiAwareManager { null, // peerMac (not used in this method) pmk, passphrase, + 0, // no port info for deprecated IB APIs + -1, // no transport info for deprecated IB APIs Process.myUid()); } @@ -473,6 +476,8 @@ public class WifiAwareManager { peer, pmk, passphrase, + 0, // no port info for OOB APIs + -1, // no transport protocol info for OOB APIs Process.myUid()); } @@ -824,6 +829,8 @@ public class WifiAwareManager { private PeerHandle mPeerHandle; private String mPskPassphrase; private byte[] mPmk; + private int mPort = 0; // invalid value + private int mTransportProtocol = -1; // invalid value /** * Configure the {@link PublishDiscoverySession} or {@link SubscribeDiscoverySession} @@ -902,6 +909,55 @@ public class WifiAwareManager { } /** + * Configure the port number which will be used to create a connection over this link. This + * configuration should only be done on the server device, e.g. the device creating the + * {@link java.net.ServerSocket}. + * <p>Notes: + * <ul> + * <li>The server device must be the Publisher device! + * <li>The port information can only be specified on secure links, specified using + * {@link #setPskPassphrase(String)}. + * </ul> + * + * @param port A positive integer indicating the port to be used for communication. + * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder + * methods. + */ + public @NonNull NetworkSpecifierBuilder setPort(int port) { + if (port <= 0 || port > 65535) { + throw new IllegalArgumentException("The port must be a positive value (0, 65535]"); + } + mPort = port; + return this; + } + + /** + * Configure the transport protocol which will be used to create a connection over this + * link. This configuration should only be done on the server device, e.g. the device + * creating the {@link java.net.ServerSocket} for TCP. + * <p>Notes: + * <ul> + * <li>The server device must be the Publisher device! + * <li>The transport protocol information can only be specified on secure links, + * specified using {@link #setPskPassphrase(String)}. + * </ul> + * The transport protocol number is assigned by the Internet Assigned Numbers Authority + * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml. + * + * @param transportProtocol The transport protocol to be used for communication. + * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder + * methods. + */ + public @NonNull NetworkSpecifierBuilder setTransportProtocol(int transportProtocol) { + if (transportProtocol < 0 || transportProtocol > 255) { + throw new IllegalArgumentException( + "The transport protocol must be in range [0, 255]"); + } + mTransportProtocol = transportProtocol; + return this; + } + + /** * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} * for a WiFi Aware connection (link) to the specified peer. The * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to @@ -929,6 +985,18 @@ public class WifiAwareManager { ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER; + if (mPort != 0 || mTransportProtocol != -1) { + if (role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) { + throw new IllegalStateException( + "Port and transport protocol information can only " + + "be specified on the Publisher device (which is the server"); + } + if (TextUtils.isEmpty(mPskPassphrase) && mPmk == null) { + throw new IllegalStateException("Port and transport protocol information can " + + "only be specified on a secure link"); + } + } + if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && mPeerHandle == null) { throw new IllegalStateException("Null peerHandle!?"); } @@ -936,7 +1004,7 @@ public class WifiAwareManager { return new WifiAwareNetworkSpecifier( WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role, mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId, - null, mPmk, mPskPassphrase, Process.myUid()); + null, mPmk, mPskPassphrase, mPort, mTransportProtocol, Process.myUid()); } } } diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java index 0f29e081e2a0..b258906df522 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java @@ -38,17 +38,30 @@ import java.util.Objects; * android.net.NetworkCapabilities)} callback. * <p> * The Wi-Fi Aware-specific network information include the peer's scoped link-local IPv6 address - * for the Wi-Fi Aware link. The scoped link-local IPv6 can then be used to create a + * for the Wi-Fi Aware link, as well as (optionally) the port and transport protocol specified by + * the peer. + * The scoped link-local IPv6, port, and transport protocol can then be used to create a * {@link java.net.Socket} connection to the peer. + * <p> + * Note: these are the peer's IPv6 and port information - not the local device's! */ public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable { private Inet6Address mIpv6Addr; + private int mPort = 0; // a value of 0 is considered invalid + private int mTransportProtocol = -1; // a value of -1 is considered invalid /** @hide */ public WifiAwareNetworkInfo(Inet6Address ipv6Addr) { mIpv6Addr = ipv6Addr; } + /** @hide */ + public WifiAwareNetworkInfo(Inet6Address ipv6Addr, int port, int transportProtocol) { + mIpv6Addr = ipv6Addr; + mPort = port; + mTransportProtocol = transportProtocol; + } + /** * Get the scoped link-local IPv6 address of the Wi-Fi Aware peer (not of the local device!). * @@ -59,6 +72,34 @@ public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable { return mIpv6Addr; } + /** + * Get the port number to be used to create a network connection to the Wi-Fi Aware peer. + * The port information is provided by the app running on the peer which requested the + * connection, using the {@link WifiAwareManager.NetworkSpecifierBuilder#setPort(int)}. + * + * @return A port number on the peer. A value of 0 indicates that no port was specified by the + * peer. + */ + public int getPort() { + return mPort; + } + + /** + * Get the transport protocol to be used to communicate over a network connection to the Wi-Fi + * Aware peer. The transport protocol is provided by the app running on the peer which requested + * the connection, using the + * {@link WifiAwareManager.NetworkSpecifierBuilder#setTransportProtocol(int)}. + * <p> + * The transport protocol number is assigned by the Internet Assigned Numbers Authority + * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml. + * + * @return A transport protocol id. A value of -1 indicates that no transport protocol was + * specified by the peer. + */ + public int getTransportProtocol() { + return mTransportProtocol; + } + // parcelable methods @Override @@ -71,6 +112,8 @@ public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable { dest.writeByteArray(mIpv6Addr.getAddress()); NetworkInterface ni = mIpv6Addr.getScopedInterface(); dest.writeString(ni == null ? null : ni.getName()); + dest.writeInt(mPort); + dest.writeInt(mTransportProtocol); } public static final Creator<WifiAwareNetworkInfo> CREATOR = @@ -94,8 +137,10 @@ public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable { e.printStackTrace(); return null; } + int port = in.readInt(); + int transportProtocol = in.readInt(); - return new WifiAwareNetworkInfo(ipv6Addr); + return new WifiAwareNetworkInfo(ipv6Addr, port, transportProtocol); } @Override @@ -109,7 +154,9 @@ public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable { @Override public String toString() { - return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).toString(); + return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).append( + ", port=").append(mPort).append(", transportProtocol=").append( + mTransportProtocol).toString(); } /** @hide */ @@ -124,12 +171,13 @@ public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable { } WifiAwareNetworkInfo lhs = (WifiAwareNetworkInfo) obj; - return Objects.equals(mIpv6Addr, lhs.mIpv6Addr); + return Objects.equals(mIpv6Addr, lhs.mIpv6Addr) && mPort == lhs.mPort + && mTransportProtocol == lhs.mTransportProtocol; } /** @hide */ @Override public int hashCode() { - return Objects.hash(mIpv6Addr); + return Objects.hash(mIpv6Addr, mPort, mTransportProtocol); } } diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java index 6e37fcf4d338..a93a6d58a3ec 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java @@ -19,7 +19,6 @@ package android.net.wifi.aware; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; import java.util.Arrays; import java.util.Objects; @@ -117,6 +116,32 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements public final String passphrase; /** + * The port information to be used for this link. This information will be communicated to the + * peer as part of the layer 2 link setup. + * + * Information only allowed on secure links since a single layer-2 link is set up for all + * requestors. Therefore if multiple apps on a single device request links to the same peer + * device they all get the same link. However, the link is only set up on the first request - + * hence only the first can transmit the port information. But we don't want to expose that + * information to other apps. Limiting to secure links would (usually) imply single app usage. + * + * @hide + */ + public final int port; + + /** + * The transport protocol information to be used for this link. This information will be + * communicated to the peer as part of the layer 2 link setup. + * + * Information only allowed on secure links since a single layer-2 link is set up for all + * requestors. Therefore if multiple apps on a single device request links to the same peer + * device they all get the same link. However, the link is only set up on the first request - + * hence only the first can transmit the port information. But we don't want to expose that + * information to other apps. Limiting to secure links would (usually) imply single app usage. + */ + public final int transportProtocol; + + /** * The UID of the process initializing this network specifier. Validated by receiver using * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the * offered network. @@ -127,7 +152,8 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements /** @hide */ public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId, - byte[] peerMac, byte[] pmk, String passphrase, int requestorUid) { + byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol, + int requestorUid) { this.type = type; this.role = role; this.clientId = clientId; @@ -136,6 +162,8 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements this.peerMac = peerMac; this.pmk = pmk; this.passphrase = passphrase; + this.port = port; + this.transportProtocol = transportProtocol; this.requestorUid = requestorUid; } @@ -152,6 +180,8 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements in.createByteArray(), // peerMac in.createByteArray(), // pmk in.readString(), // passphrase + in.readInt(), // port + in.readInt(), // transportProtocol in.readInt()); // requestorUid } @@ -186,6 +216,8 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements dest.writeByteArray(peerMac); dest.writeByteArray(pmk); dest.writeString(passphrase); + dest.writeInt(port); + dest.writeInt(transportProtocol); dest.writeInt(requestorUid); } @@ -202,19 +234,8 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements /** @hide */ @Override public int hashCode() { - int result = 17; - - result = 31 * result + type; - result = 31 * result + role; - result = 31 * result + clientId; - result = 31 * result + sessionId; - result = 31 * result + peerId; - result = 31 * result + Arrays.hashCode(peerMac); - result = 31 * result + Arrays.hashCode(pmk); - result = 31 * result + Objects.hashCode(passphrase); - result = 31 * result + requestorUid; - - return result; + return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac), + Arrays.hashCode(pmk), passphrase, port, transportProtocol, requestorUid); } /** @hide */ @@ -238,6 +259,8 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements && Arrays.equals(peerMac, lhs.peerMac) && Arrays.equals(pmk, lhs.pmk) && Objects.equals(passphrase, lhs.passphrase) + && port == lhs.port + && transportProtocol == lhs.transportProtocol && requestorUid == lhs.requestorUid; } @@ -256,7 +279,8 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements .append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>") // masking PII .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>") - .append(", requestorUid=").append(requestorUid) + .append(", port=").append(port).append(", transportProtocol=") + .append(transportProtocol).append(", requestorUid=").append(requestorUid) .append("]"); return sb.toString(); } diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java index 7689fc34ff5b..59a7290eb8a9 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java @@ -572,7 +572,7 @@ public final class Credential implements Parcelable { @Override public int hashCode() { - return Objects.hash(mCertType, mCertSha256Fingerprint); + return Objects.hash(mCertType, Arrays.hashCode(mCertSha256Fingerprint)); } @Override @@ -842,24 +842,50 @@ public final class Credential implements Parcelable { } /** - * CA (Certificate Authority) X509 certificate. + * CA (Certificate Authority) X509 certificates. */ - private X509Certificate mCaCertificate = null; + private X509Certificate[] mCaCertificates = null; + /** * Set the CA (Certification Authority) certificate associated with this credential. * * @param caCertificate The CA certificate to set to */ public void setCaCertificate(X509Certificate caCertificate) { - mCaCertificate = caCertificate; + mCaCertificates = null; + if (caCertificate != null) { + mCaCertificates = new X509Certificate[] {caCertificate}; + } } + + /** + * Set the CA (Certification Authority) certificates associated with this credential. + * + * @param caCertificates The list of CA certificates to set to + * @hide + */ + public void setCaCertificates(X509Certificate[] caCertificates) { + mCaCertificates = caCertificates; + } + /** * Get the CA (Certification Authority) certificate associated with this credential. * - * @return CA certificate associated with this credential + * @return CA certificate associated with this credential, {@code null} if certificate is not + * set or certificate is more than one. */ public X509Certificate getCaCertificate() { - return mCaCertificate; + return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0]; + } + + /** + * Get the CA (Certification Authority) certificates associated with this credential. + * + * @return The list of CA certificates associated with this credential + * @hide + */ + public X509Certificate[] getCaCertificates() { + return mCaCertificates; } /** @@ -933,7 +959,11 @@ public final class Credential implements Parcelable { mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain, source.mClientCertificateChain.length); } - mCaCertificate = source.mCaCertificate; + if (source.mCaCertificates != null) { + mCaCertificates = Arrays.copyOf(source.mCaCertificates, + source.mCaCertificates.length); + } + mClientPrivateKey = source.mClientPrivateKey; } } @@ -952,7 +982,7 @@ public final class Credential implements Parcelable { dest.writeParcelable(mUserCredential, flags); dest.writeParcelable(mCertCredential, flags); dest.writeParcelable(mSimCredential, flags); - ParcelUtil.writeCertificate(dest, mCaCertificate); + ParcelUtil.writeCertificates(dest, mCaCertificates); ParcelUtil.writeCertificates(dest, mClientCertificateChain); ParcelUtil.writePrivateKey(dest, mClientPrivateKey); } @@ -977,16 +1007,17 @@ public final class Credential implements Parcelable { : mCertCredential.equals(that.mCertCredential)) && (mSimCredential == null ? that.mSimCredential == null : mSimCredential.equals(that.mSimCredential)) - && isX509CertificateEquals(mCaCertificate, that.mCaCertificate) + && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates) && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain) && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey); } @Override public int hashCode() { - return Objects.hash(mRealm, mCreationTimeInMillis, mExpirationTimeInMillis, + return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm, mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential, - mCaCertificate, mClientCertificateChain, mClientPrivateKey); + mClientPrivateKey, Arrays.hashCode(mCaCertificates), + Arrays.hashCode(mClientCertificateChain)); } @Override @@ -1067,7 +1098,7 @@ public final class Credential implements Parcelable { credential.setUserCredential(in.readParcelable(null)); credential.setCertCredential(in.readParcelable(null)); credential.setSimCredential(in.readParcelable(null)); - credential.setCaCertificate(ParcelUtil.readCertificate(in)); + credential.setCaCertificates(ParcelUtil.readCertificates(in)); credential.setClientCertificateChain(ParcelUtil.readCertificates(in)); credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in)); return credential; @@ -1100,7 +1131,7 @@ public final class Credential implements Parcelable { // CA certificate is required for R1 Passpoint profile. // For R2, it is downloaded using cert URL provided in PPS MO after validation completes. - if (isR1 && mCaCertificate == null) { + if (isR1 && mCaCertificates == null) { Log.d(TAG, "Missing CA Certificate for user credential"); return false; } @@ -1131,7 +1162,7 @@ public final class Credential implements Parcelable { // Verify required key and certificates for certificate credential. // CA certificate is required for R1 Passpoint profile. // For R2, it is downloaded using cert URL provided in PPS MO after validation completes. - if (isR1 && mCaCertificate == null) { + if (isR1 && mCaCertificates == null) { Log.d(TAG, "Missing CA Certificate for certificate credential"); return false; } diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java index 4189e40718d8..c3b62854f12c 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java @@ -62,6 +62,7 @@ public class WifiAwareAgentNetworkSpecifierTest { WifiAwareAgentNetworkSpecifier.CREATOR.createFromParcel(parcelR); assertEquals(dut, rereadDut); + assertEquals(dut.hashCode(), rereadDut.hashCode()); // Ensure that individual network specifiers are satisfied by both the original & marshaled // |WifiAwareNetworkAgentSpecifier instances. @@ -181,6 +182,6 @@ public class WifiAwareAgentNetworkSpecifierTest { WifiAwareNetworkSpecifier getDummyNetworkSpecifier(int clientId) { return new WifiAwareNetworkSpecifier(WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB, WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, clientId, 0, 0, new byte[6], - null, null, 0); + null, null, 10, 5, 0); } } diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index ed38c7613a62..6da6d4adeb62 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -16,8 +16,11 @@ package android.net.wifi.aware; +import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB; + import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -913,9 +916,10 @@ public class WifiAwareManagerTest { final int clientId = 4565; final int sessionId = 123; final PeerHandle peerHandle = new PeerHandle(123412); - final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER; final byte[] pmk = PMK_VALID; final String passphrase = PASSPHRASE_VALID; + final int port = 5; + final int transportProtocol = 10; final ConfigRequest configRequest = new ConfigRequest.Builder().build(); final PublishConfig publishConfig = new PublishConfig.Builder().build(); @@ -959,56 +963,70 @@ public class WifiAwareManagerTest { .setPeerHandle(peerHandle).build(); // validate format - collector.checkThat("role", role, equalTo(ns.role)); + collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, + equalTo(ns.role)); collector.checkThat("client_id", clientId, equalTo(ns.clientId)); collector.checkThat("session_id", sessionId, equalTo(ns.sessionId)); collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId)); - collector.checkThat("role", role, equalTo(nsb.role)); + collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, + equalTo(nsb.role)); collector.checkThat("client_id", clientId, equalTo(nsb.clientId)); collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId)); collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId)); + collector.checkThat("port", 0, equalTo(nsb.port)); + collector.checkThat("transportProtocol", -1, equalTo(nsb.transportProtocol)); // (4) request an encrypted (PMK) network specifier from the session ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk( peerHandle, pmk); - nsb = - (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() - .setDiscoverySession( - publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build(); + nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle) + .setPmk(pmk).setPort(port).setTransportProtocol(transportProtocol).build(); // validate format - collector.checkThat("role", role, equalTo(ns.role)); + collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, + equalTo(ns.role)); collector.checkThat("client_id", clientId, equalTo(ns.clientId)); collector.checkThat("session_id", sessionId, equalTo(ns.sessionId)); collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId)); collector.checkThat("pmk", pmk , equalTo(ns.pmk)); - collector.checkThat("role", role, equalTo(nsb.role)); + collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, + equalTo(nsb.role)); collector.checkThat("client_id", clientId, equalTo(nsb.clientId)); collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId)); collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId)); collector.checkThat("pmk", pmk , equalTo(nsb.pmk)); + collector.checkThat("port", port, equalTo(nsb.port)); + collector.checkThat("transportProtocol", transportProtocol, equalTo(nsb.transportProtocol)); // (5) request an encrypted (Passphrase) network specifier from the session - ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase( - peerHandle, passphrase); + ns = + (WifiAwareNetworkSpecifier) publishSession.getValue() + .createNetworkSpecifierPassphrase( + peerHandle, passphrase); nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle) - .setPskPassphrase(passphrase).build(); + .setPskPassphrase(passphrase).setPort(port).setTransportProtocol(transportProtocol) + .build(); // validate format - collector.checkThat("role", role, equalTo(ns.role)); + collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, + equalTo(ns.role)); collector.checkThat("client_id", clientId, equalTo(ns.clientId)); collector.checkThat("session_id", sessionId, equalTo(ns.sessionId)); collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId)); collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase)); - collector.checkThat("role", role, equalTo(nsb.role)); + collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, + equalTo(nsb.role)); collector.checkThat("client_id", clientId, equalTo(nsb.clientId)); collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId)); collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId)); collector.checkThat("passphrase", passphrase, equalTo(nsb.passphrase)); + collector.checkThat("port", port, equalTo(nsb.port)); + collector.checkThat("transportProtocol", transportProtocol, equalTo(nsb.transportProtocol)); verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService, mockPublishSession, mockRttListener); @@ -1325,6 +1343,140 @@ public class WifiAwareManagerTest { executeNetworkSpecifierDirect(null, false, null, PASSPHRASE_VALID, false); } + /** + * Validate that get an exception when creating a network specifier with an invalid port number + * (<=0). + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierBuilderInvalidPortNumber() throws Exception { + final PeerHandle peerHandle = new PeerHandle(123412); + final byte[] pmk = PMK_VALID; + final int port = 0; + + DiscoverySession publishSession = executeSessionStartup(true); + + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession).setPeerHandle(peerHandle) + .setPmk(pmk).setPort(port).build(); + } + + /** + * Validate that get an exception when creating a network specifier with port information + * without also requesting a secure link. + */ + @Test(expected = IllegalStateException.class) + public void testNetworkSpecifierBuilderInvalidPortOnInsecure() throws Exception { + final PeerHandle peerHandle = new PeerHandle(123412); + final int port = 5; + + DiscoverySession publishSession = executeSessionStartup(true); + + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession).setPeerHandle(peerHandle) + .setPort(port).build(); + } + + /** + * Validate that get an exception when creating a network specifier with port information on + * a responder. + */ + @Test(expected = IllegalStateException.class) + public void testNetworkSpecifierBuilderInvalidPortOnResponder() throws Exception { + final PeerHandle peerHandle = new PeerHandle(123412); + final int port = 5; + + DiscoverySession subscribeSession = executeSessionStartup(false); + + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(subscribeSession).setPeerHandle(peerHandle) + .setPort(port).build(); + } + + /** + * Validate that get an exception when creating a network specifier with an invalid transport + * protocol number (not in [0, 255]). + */ + @Test + public void testNetworkSpecifierBuilderInvalidTransportProtocolNumber() throws Exception { + final PeerHandle peerHandle = new PeerHandle(123412); + final byte[] pmk = PMK_VALID; + final int tpNegative = -1; + final int tpTooLarge = 256; + final int tpSmallest = 0; + final int tpLargest = 255; + + DiscoverySession publishSession = executeSessionStartup(true); + + try { + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession).setPeerHandle(peerHandle) + .setPmk(pmk).setTransportProtocol(tpNegative).build(); + assertTrue("No exception on negative transport protocol!", false); + } catch (IllegalArgumentException e) { + // nop - exception is correct! + } + try { + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession).setPeerHandle(peerHandle) + .setPmk(pmk).setTransportProtocol(tpTooLarge).build(); + assertTrue("No exception on >255 transport protocol!", false); + } catch (IllegalArgumentException e) { + // nop - exception is correct! + } + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession).setPeerHandle(peerHandle) + .setPmk(pmk).setTransportProtocol(tpSmallest).build(); + nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession( + publishSession).setPeerHandle(peerHandle).setPmk( + pmk).setTransportProtocol(tpLargest).build(); + } + + /** + * Validate that get an exception when creating a network specifier with transport protocol + * information without also requesting a secure link. + */ + @Test(expected = IllegalStateException.class) + public void testNetworkSpecifierBuilderInvalidTransportProtocolOnInsecure() throws Exception { + final PeerHandle peerHandle = new PeerHandle(123412); + final int transportProtocol = 5; + + DiscoverySession publishSession = executeSessionStartup(true); + + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(publishSession).setPeerHandle(peerHandle) + .setTransportProtocol(transportProtocol).build(); + } + + /** + * Validate that get an exception when creating a network specifier with transport protocol + * information on a responder. + */ + @Test(expected = IllegalStateException.class) + public void testNetworkSpecifierBuilderInvalidTransportProtocolOnResponder() throws Exception { + final PeerHandle peerHandle = new PeerHandle(123412); + final int transportProtocol = 5; + + DiscoverySession subscribeSession = executeSessionStartup(false); + + WifiAwareNetworkSpecifier nsb = + (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder() + .setDiscoverySession(subscribeSession).setPeerHandle(peerHandle) + .setTransportProtocol(transportProtocol).build(); + } + + /* + * Utilities + */ + private void executeNetworkSpecifierDirect(byte[] someMac, boolean doPmk, byte[] pmk, String passphrase, boolean doInitiator) throws Exception { final int clientId = 134; @@ -1356,7 +1508,83 @@ public class WifiAwareManagerTest { } } - // WifiAwareNetworkInfo tests + private DiscoverySession executeSessionStartup(boolean isPublish) throws Exception { + final int clientId = 4565; + final int sessionId = 123; + final PeerHandle peerHandle = new PeerHandle(123412); + final int port = 5; + final ConfigRequest configRequest = new ConfigRequest.Builder().build(); + final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build(); + final PublishConfig publishConfig = new PublishConfig.Builder().build(); + + ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass( + WifiAwareSession.class); + ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor + .forClass(IWifiAwareEventCallback.class); + ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor + .forClass(IWifiAwareDiscoverySessionCallback.class); + ArgumentCaptor<PublishDiscoverySession> publishSession = ArgumentCaptor + .forClass(PublishDiscoverySession.class); + ArgumentCaptor<SubscribeDiscoverySession> subscribeSession = ArgumentCaptor + .forClass(SubscribeDiscoverySession.class); + + + InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService, + mockPublishSession, mockRttListener); + + // (1) connect successfully + mDut.attach(mMockLooperHandler, configRequest, mockCallback, null); + inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(), + eq(configRequest), eq(false)); + clientProxyCallback.getValue().onConnectSuccess(clientId); + mMockLooper.dispatchAll(); + inOrder.verify(mockCallback).onAttached(sessionCaptor.capture()); + WifiAwareSession session = sessionCaptor.getValue(); + + if (isPublish) { + // (2) publish successfully + session.publish(publishConfig, mockSessionCallback, mMockLooperHandler); + inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig), + sessionProxyCallback.capture()); + sessionProxyCallback.getValue().onSessionStarted(sessionId); + mMockLooper.dispatchAll(); + inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture()); + return publishSession.getValue(); + } else { + // (2) subscribe successfully + session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler); + inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig), + sessionProxyCallback.capture()); + sessionProxyCallback.getValue().onSessionStarted(sessionId); + mMockLooper.dispatchAll(); + inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture()); + return subscribeSession.getValue(); + } + } + + // WifiAwareNetworkSpecifier && WifiAwareNetworkInfo tests + + @Test + public void testWifiAwareNetworkSpecifierParcel() { + WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(NETWORK_SPECIFIER_TYPE_IB, + WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, 5, 568, 334, + HexEncoding.decode("000102030405".toCharArray(), false), + "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4, 10001); + + Parcel parcelW = Parcel.obtain(); + ns.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + WifiAwareNetworkSpecifier rereadNs = + WifiAwareNetworkSpecifier.CREATOR.createFromParcel(parcelR); + + assertEquals(ns, rereadNs); + assertEquals(ns.hashCode(), rereadNs.hashCode()); + } @Test public void testWifiAwareNetworkCapabilitiesParcel() throws UnknownHostException { @@ -1364,9 +1592,11 @@ public class WifiAwareManagerTest { "11:22:33:44:55:66").getLinkLocalIpv6FromEui48Mac(); // note: dummy scope = 5 final Inet6Address inet6Scoped = Inet6Address.getByAddress(null, inet6.getAddress(), 5); + final int port = 5; + final int transportProtocol = 6; assertEquals(inet6Scoped.toString(), "/fe80::1322:33ff:fe44:5566%5"); - WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped); + WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped, port, transportProtocol); Parcel parcelW = Parcel.obtain(); cap.writeToParcel(parcelW, 0); @@ -1380,6 +1610,7 @@ public class WifiAwareManagerTest { WifiAwareNetworkInfo.CREATOR.createFromParcel(parcelR); assertEquals(cap.getPeerIpv6Addr().toString(), "/fe80::1322:33ff:fe44:5566%5"); + assertEquals(cap, rereadCap); assertEquals(cap.hashCode(), rereadCap.hashCode()); } diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java index a9d4b8f008f6..1ecc3fecf7c3 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java @@ -16,6 +16,7 @@ package android.net.wifi.hotspot2.pps; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -44,17 +45,16 @@ public class CredentialTest { * @param userCred Instance of UserCredential * @param certCred Instance of CertificateCredential * @param simCred Instance of SimCredential - * @param caCert CA certificate * @param clientCertificateChain Chain of client certificates * @param clientPrivateKey Client private key + * @param caCerts CA certificates * @return {@link Credential} */ private static Credential createCredential(Credential.UserCredential userCred, - Credential.CertificateCredential certCred, - Credential.SimCredential simCred, - X509Certificate caCert, - X509Certificate[] clientCertificateChain, - PrivateKey clientPrivateKey) { + Credential.CertificateCredential certCred, + Credential.SimCredential simCred, + X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey, + X509Certificate... caCerts) { Credential cred = new Credential(); cred.setCreationTimeInMillis(123455L); cred.setExpirationTimeInMillis(2310093L); @@ -63,7 +63,11 @@ public class CredentialTest { cred.setUserCredential(userCred); cred.setCertCredential(certCred); cred.setSimCredential(simCred); - cred.setCaCertificate(caCert); + if (caCerts != null && caCerts.length == 1) { + cred.setCaCertificate(caCerts[0]); + } else { + cred.setCaCertificates(caCerts); + } cred.setClientCertificateChain(clientCertificateChain); cred.setClientPrivateKey(clientPrivateKey); return cred; @@ -80,8 +84,8 @@ public class CredentialTest { certCred.setCertType("x509v3"); certCred.setCertSha256Fingerprint( MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded())); - return createCredential(null, certCred, null, FakeKeys.CA_CERT0, - new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1); + return createCredential(null, certCred, null, new X509Certificate[] {FakeKeys.CLIENT_CERT}, + FakeKeys.RSA_KEY1, FakeKeys.CA_CERT0, FakeKeys.CA_CERT1); } /** @@ -93,7 +97,7 @@ public class CredentialTest { Credential.SimCredential simCred = new Credential.SimCredential(); simCred.setImsi("1234*"); simCred.setEapType(EAPConstants.EAP_SIM); - return createCredential(null, null, simCred, null, null, null); + return createCredential(null, null, simCred, null, null, (X509Certificate[]) null); } /** @@ -110,7 +114,7 @@ public class CredentialTest { userCred.setSoftTokenApp("TestApp"); userCred.setEapType(EAPConstants.EAP_TTLS); userCred.setNonEapInnerMethod("MS-CHAP"); - return createCredential(userCred, null, null, FakeKeys.CA_CERT0, null, null); + return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0); } private static void verifyParcel(Credential writeCred) { @@ -120,6 +124,7 @@ public class CredentialTest { parcel.setDataPosition(0); // Rewind data position back to the beginning for read. Credential readCred = Credential.CREATOR.createFromParcel(parcel); assertTrue(readCred.equals(writeCred)); + assertEquals(writeCred.hashCode(), readCred.hashCode()); } /** |