diff options
106 files changed, 5110 insertions, 263 deletions
diff --git a/Android.bp b/Android.bp index 72e82eb8429b..a99cef879384 100644 --- a/Android.bp +++ b/Android.bp @@ -341,6 +341,8 @@ java_defaults { "modules-utils-preconditions", "modules-utils-os", "framework-permission-aidl-java", + "spatializer-aidl-java", + "audiopolicy-types-aidl-java", ], } diff --git a/core/api/current.txt b/core/api/current.txt index 784450ef1850..8f08658a76dc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -2012,6 +2012,9 @@ package android { public static final class R.id { ctor public R.id(); field public static final int accessibilityActionContextClick = 16908348; // 0x102003c + field public static final int accessibilityActionDragCancel; + field public static final int accessibilityActionDragDrop; + field public static final int accessibilityActionDragStart; field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045 field public static final int accessibilityActionImeEnter = 16908372; // 0x1020054 field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042 @@ -50655,6 +50658,9 @@ package android.view.accessibility { method public void setPackageName(CharSequence); method public void writeToParcel(android.os.Parcel, int); field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4 + field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200 + field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100 + field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80 field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10 field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20 field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8 @@ -50953,6 +50959,9 @@ package android.view.accessibility { field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_COPY; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_CUT; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS; + field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_CANCEL; + field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_DROP; + field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_START; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP; diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index cca179974100..139bff4b0118 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -49,6 +49,11 @@ public final class InputWindowHandle { */ @Nullable private IBinder windowToken; + /** + * Used to cache IWindow from the windowToken so we don't need to convert every time getWindow + * is called. + */ + private IWindow window; // The window name. public String name; @@ -151,7 +156,7 @@ public final class InputWindowHandle { .append(", visible=").append(visible) .append(", scaleFactor=").append(scaleFactor) .append(", transform=").append(transform) - .append(", windowToken=").append(getWindow()) + .append(", windowToken=").append(windowToken) .toString(); } @@ -186,9 +191,14 @@ public final class InputWindowHandle { public void setWindowToken(IWindow iwindow) { windowToken = iwindow.asBinder(); + window = iwindow; } public IWindow getWindow() { - return IWindow.Stub.asInterface(windowToken); + if (window != null) { + return window; + } + window = IWindow.Stub.asInterface(windowToken); + return window; } } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index f6d6fde6435f..52d3612b6f77 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -613,6 +613,36 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000040; /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * A drag has started while accessibility is enabled. This is either via an + * AccessibilityAction, or via touch events. This is sent from the source that initiated the + * drag. + * + * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START + */ + public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 0x00000080; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * A drag in with accessibility enabled has ended. This means the content has been + * successfully dropped. This is sent from the target that accepted the dragged content. + * + * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_DROP + */ + public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 0x00000100; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * A drag in with accessibility enabled has ended. This means the content has been + * unsuccessfully dropped, the user has canceled the action via an AccessibilityAction, or + * no drop has been detected within a timeout and the drag was automatically cancelled. This is + * sent from the source that initiated the drag. + * + * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_CANCEL + */ + public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 0x0000200; + + /** * Change type for {@link #TYPE_WINDOWS_CHANGED} event: * The window was added. */ diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 085eb81182f1..587a27074544 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ClipData; import android.graphics.Rect; import android.graphics.Region; import android.os.Build; @@ -4353,6 +4354,14 @@ public class AccessibilityNodeInfo implements Parcelable { case R.id.accessibilityActionImeEnter: return "ACTION_IME_ENTER"; default: + // TODO(197520937): Use finalized constants in switch + if (action == R.id.accessibilityActionDragStart) { + return "ACTION_DRAG"; + } else if (action == R.id.accessibilityActionDragCancel) { + return "ACTION_CANCEL_DRAG"; + } else if (action == R.id.accessibilityActionDragDrop) { + return "ACTION_DROP"; + } return "ACTION_UNKNOWN"; } } @@ -4995,6 +5004,46 @@ public class AccessibilityNodeInfo implements Parcelable { @NonNull public static final AccessibilityAction ACTION_IME_ENTER = new AccessibilityAction(R.id.accessibilityActionImeEnter); + /** + * Action to start a drag. + * <p> + * This action initiates a drag & drop within the system. The source's dragged content is + * prepared before the drag begins. In View, this action should prepare the arguments to + * {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} and then + * call {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)}. The + * equivalent should be performed for other UI toolkits. + * </p> + * + * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED + */ + @NonNull public static final AccessibilityAction ACTION_DRAG_START = + new AccessibilityAction(R.id.accessibilityActionDragStart); + + /** + * Action to trigger a drop of the content being dragged. + * <p> + * This action is added to potential drop targets if the source started a drag with + * {@link #ACTION_DRAG_START}. In View, these targets are Views that accepted + * {@link android.view.DragEvent#ACTION_DRAG_STARTED} and have an + * {@link View.OnDragListener}. + * </p> + * + * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED + */ + @NonNull public static final AccessibilityAction ACTION_DRAG_DROP = + new AccessibilityAction(R.id.accessibilityActionDragDrop); + + /** + * Action to cancel a drag. + * <p> + * This action is added to the source that started a drag with {@link #ACTION_DRAG_START}. + * </p> + * + * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_CANCELLED + */ + @NonNull public static final AccessibilityAction ACTION_DRAG_CANCEL = + new AccessibilityAction(R.id.accessibilityActionDragCancel); + private final int mActionId; private final CharSequence mLabel; diff --git a/core/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java index bca647a911d6..f5c4b1f5dbe9 100644 --- a/core/java/com/android/ims/internal/uce/common/CapInfo.java +++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java @@ -20,6 +20,10 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.os.Bundle; + +import java.util.Map; +import java.util.HashMap; /** Class for capability discovery information. * @hide */ @@ -88,6 +92,95 @@ public class CapInfo implements Parcelable { /** Time used to compute when to query again. */ private long mCapTimestamp = 0; + private Map<String, String> mCapInfoMap = new HashMap<String, String>(); + + /** IM session feature tag key. */ + public static final String INSTANT_MSG = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.im\""; + /** File transfer feature tag key. */ + public static final String FILE_TRANSFER = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.ft\""; + /** File transfer Thumbnail feature tag key. */ + public static final String FILE_TRANSFER_THUMBNAIL = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftthumb\""; + /** File transfer Store and forward feature tag key. */ + public static final String FILE_TRANSFER_SNF = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftstandfw\""; + /** File transfer HTTP feature tag key. */ + public static final String FILE_TRANSFER_HTTP = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\""; + /** Image sharing feature tag key. */ + public static final String IMAGE_SHARE = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.gsma-is\""; + /** Video sharing during a CS call feature tag key-- IR-74. */ + public static final String VIDEO_SHARE_DURING_CS = "+g.3gpp.cs-voice"; + /** Video sharing outside of voice call feature tag key-- IR-84. */ + public static final String VIDEO_SHARE = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.gsma-vs\""; + /** Social presence feature tag key. */ + public static final String SOCIAL_PRESENCE = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.sp\""; + /** Presence discovery feature tag key. */ + public static final String CAPDISC_VIA_PRESENCE = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.dp\""; + /** IP voice call feature tag key (IR-92/IR-58). */ + public static final String IP_VOICE = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\""; + /** IP video call feature tag key (IR-92/IR-58). */ + public static final String IP_VIDEO = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\";video"; + /** IP Geo location Pull using File Transfer feature tag key. */ + public static final String GEOPULL_FT = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geopullft\""; + /** IP Geo location Pull feature tag key. */ + public static final String GEOPULL = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geopull\""; + /** IP Geo location Push feature tag key. */ + public static final String GEOPUSH = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geopush\""; + /** Standalone messaging feature tag key. */ + public static final String STANDALONE_MSG = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg;" + + "urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.largemsg\""; + /** Full Store and Forward Group Chat information feature tag key. */ + public static final String FULL_SNF_GROUPCHAT = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fullsfgroupchat\""; + /** RCS IP Voice call feature tag key. */ + public static final String RCS_IP_VOICE_CALL = + "+g.gsma.rcs.ipcall"; + /** RCS IP Video call feature tag key. */ + public static final String RCS_IP_VIDEO_CALL = + "+g.gsma.rcs.ipvideocall"; + /** RCS IP Video only call feature tag key. */ + public static final String RCS_IP_VIDEO_ONLY_CALL = + "+g.gsma.rcs.ipvideoonlycall"; + /** IP Geo location Push using SMS feature tag key. */ + public static final String GEOSMS = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gppapplication.ims.iari.rcs.geosms\""; + /** RCS call composer feature tag key. */ + public static final String CALLCOMPOSER = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.gsma.callcomposer\""; + /** RCS post-call feature tag key. */ + public static final String POSTCALL = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.gsma.callunanswered\""; + /** Shared map feature tag key. */ + public static final String SHAREDMAP = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.gsma.sharedmap\""; + /** Shared Sketch feature tag key. */ + public static final String SHAREDSKETCH = + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.gsma.sharedsketch\""; + /** Chatbot communication feature tag key. */ + public static final String CHATBOT = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gppapplication.ims.iari.rcs.chatbot\""; + /** Chatbot role feature tag key. */ + public static final String CHATBOTROLE = "+g.gsma.rcs.isbot"; + /** Standalone Chatbot communication feature tag key. */ + public static final String STANDALONE_CHATBOT = + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot.sa\""; + /** MMtel based call composer feature tag key. */ + public static final String MMTEL_CALLCOMPOSER = "+g.gsma.callcomposer"; + + /** * Constructor for the CapInfo class. @@ -99,6 +192,7 @@ public class CapInfo implements Parcelable { /** * Checks whether IM is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isImSupported() { @@ -107,6 +201,7 @@ public class CapInfo implements Parcelable { /** * Sets IM as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setImSupported(boolean imSupported) { @@ -115,6 +210,7 @@ public class CapInfo implements Parcelable { /** * Checks whether FT Thumbnail is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isFtThumbSupported() { @@ -123,16 +219,16 @@ public class CapInfo implements Parcelable { /** * Sets FT thumbnail as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setFtThumbSupported(boolean ftThumbSupported) { this.mFtThumbSupported = ftThumbSupported; } - - /** * Checks whether FT Store and Forward is supported + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isFtSnFSupported() { @@ -141,6 +237,7 @@ public class CapInfo implements Parcelable { /** * Sets FT Store and Forward as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setFtSnFSupported(boolean ftSnFSupported) { @@ -149,6 +246,7 @@ public class CapInfo implements Parcelable { /** * Checks whether File transfer HTTP is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isFtHttpSupported() { @@ -157,6 +255,7 @@ public class CapInfo implements Parcelable { /** * Sets File transfer HTTP as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setFtHttpSupported(boolean ftHttpSupported) { @@ -165,6 +264,7 @@ public class CapInfo implements Parcelable { /** * Checks whether FT is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isFtSupported() { @@ -173,6 +273,7 @@ public class CapInfo implements Parcelable { /** * Sets FT as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setFtSupported(boolean ftSupported) { @@ -181,6 +282,7 @@ public class CapInfo implements Parcelable { /** * Checks whether IS is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isIsSupported() { @@ -189,6 +291,7 @@ public class CapInfo implements Parcelable { /** * Sets IS as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setIsSupported(boolean isSupported) { @@ -197,6 +300,7 @@ public class CapInfo implements Parcelable { /** * Checks whether video sharing is supported during a CS call. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isVsDuringCSSupported() { @@ -204,8 +308,9 @@ public class CapInfo implements Parcelable { } /** - * Sets video sharing as supported or not supported during a CS - * call. + * Sets video sharing as supported or not supported during a CS + * call. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setVsDuringCSSupported(boolean vsDuringCSSupported) { @@ -213,8 +318,9 @@ public class CapInfo implements Parcelable { } /** - * Checks whether video sharing outside a voice call is - * supported. + * Checks whether video sharing outside a voice call is + * supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isVsSupported() { @@ -223,6 +329,7 @@ public class CapInfo implements Parcelable { /** * Sets video sharing as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setVsSupported(boolean vsSupported) { @@ -231,6 +338,7 @@ public class CapInfo implements Parcelable { /** * Checks whether social presence is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isSpSupported() { @@ -239,6 +347,7 @@ public class CapInfo implements Parcelable { /** * Sets social presence as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setSpSupported(boolean spSupported) { @@ -248,6 +357,7 @@ public class CapInfo implements Parcelable { /** * Checks whether capability discovery via presence is * supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isCdViaPresenceSupported() { @@ -257,6 +367,7 @@ public class CapInfo implements Parcelable { /** * Sets capability discovery via presence as supported or not * supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setCdViaPresenceSupported(boolean cdViaPresenceSupported) { @@ -265,6 +376,7 @@ public class CapInfo implements Parcelable { /** * Checks whether IP voice call is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isIpVoiceSupported() { @@ -273,6 +385,7 @@ public class CapInfo implements Parcelable { /** * Sets IP voice call as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setIpVoiceSupported(boolean ipVoiceSupported) { @@ -281,6 +394,7 @@ public class CapInfo implements Parcelable { /** * Checks whether IP video call is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isIpVideoSupported() { @@ -289,6 +403,7 @@ public class CapInfo implements Parcelable { /** * Sets IP video call as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setIpVideoSupported(boolean ipVideoSupported) { @@ -298,6 +413,7 @@ public class CapInfo implements Parcelable { /** * Checks whether Geo location Pull using File Transfer is * supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isGeoPullFtSupported() { @@ -307,6 +423,7 @@ public class CapInfo implements Parcelable { /** * Sets Geo location Pull using File Transfer as supported or * not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setGeoPullFtSupported(boolean geoPullFtSupported) { @@ -315,6 +432,7 @@ public class CapInfo implements Parcelable { /** * Checks whether Geo Pull is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isGeoPullSupported() { @@ -323,6 +441,7 @@ public class CapInfo implements Parcelable { /** * Sets Geo Pull as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setGeoPullSupported(boolean geoPullSupported) { @@ -331,6 +450,7 @@ public class CapInfo implements Parcelable { /** * Checks whether Geo Push is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isGeoPushSupported() { @@ -339,6 +459,7 @@ public class CapInfo implements Parcelable { /** * Sets Geo Push as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setGeoPushSupported(boolean geoPushSupported) { @@ -347,6 +468,7 @@ public class CapInfo implements Parcelable { /** * Checks whether short messaging is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isSmSupported() { @@ -355,6 +477,7 @@ public class CapInfo implements Parcelable { /** * Sets short messaging as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setSmSupported(boolean smSupported) { @@ -363,22 +486,32 @@ public class CapInfo implements Parcelable { /** * Checks whether store/forward and group chat are supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isFullSnFGroupChatSupported() { return mFullSnFGroupChatSupported; } + /** + * @deprecated Use {@link #isCapabilitySupported(String)} instead. + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isRcsIpVoiceCallSupported() { return mRcsIpVoiceCallSupported; } + /** + * @deprecated Use {@link #isCapabilitySupported(String)} instead. + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isRcsIpVideoCallSupported() { return mRcsIpVideoCallSupported; } + /** + * @deprecated Use {@link #isCapabilitySupported(String)} instead. + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isRcsIpVideoOnlyCallSupported() { return mRcsIpVideoOnlyCallSupported; @@ -386,20 +519,32 @@ public class CapInfo implements Parcelable { /** * Sets store/forward and group chat supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setFullSnFGroupChatSupported(boolean fullSnFGroupChatSupported) { this.mFullSnFGroupChatSupported = fullSnFGroupChatSupported; } + /** + * @deprecated Use {@link #addCapability(String, String)} instead. + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setRcsIpVoiceCallSupported(boolean rcsIpVoiceCallSupported) { this.mRcsIpVoiceCallSupported = rcsIpVoiceCallSupported; } + + /** + * @deprecated Use {@link #addCapability(String, String)} instead. + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setRcsIpVideoCallSupported(boolean rcsIpVideoCallSupported) { this.mRcsIpVideoCallSupported = rcsIpVideoCallSupported; } + + /** + * @deprecated Use {@link #addCapability(String, String)} instead. + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setRcsIpVideoOnlyCallSupported(boolean rcsIpVideoOnlyCallSupported) { this.mRcsIpVideoOnlyCallSupported = rcsIpVideoOnlyCallSupported; @@ -407,6 +552,7 @@ public class CapInfo implements Parcelable { /** * Checks whether Geo Push via SMS is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isGeoSmsSupported() { return mGeoSmsSupported; @@ -414,6 +560,7 @@ public class CapInfo implements Parcelable { /** * Sets Geolocation Push via SMS as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setGeoSmsSupported(boolean geoSmsSupported) { this.mGeoSmsSupported = geoSmsSupported; @@ -421,6 +568,7 @@ public class CapInfo implements Parcelable { /** * Checks whether RCS call composer is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isCallComposerSupported() { return mCallComposerSupported; @@ -428,6 +576,7 @@ public class CapInfo implements Parcelable { /** * Sets call composer as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setCallComposerSupported(boolean callComposerSupported) { this.mCallComposerSupported = callComposerSupported; @@ -435,6 +584,7 @@ public class CapInfo implements Parcelable { /** * Checks whether post call is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isPostCallSupported(){ return mPostCallSupported; @@ -442,13 +592,15 @@ public class CapInfo implements Parcelable { /** * Sets post call as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ - public void setPostCallSupported(boolean postCallSupported) { - this.mPostCallSupported = postCallSupported; - } + public void setPostCallSupported(boolean postCallSupported) { + this.mPostCallSupported = postCallSupported; + } /** * Checks whether shared map is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isSharedMapSupported() { return mSharedMapSupported; @@ -456,6 +608,7 @@ public class CapInfo implements Parcelable { /** * Sets shared map as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setSharedMapSupported(boolean sharedMapSupported) { this.mSharedMapSupported = sharedMapSupported; @@ -463,6 +616,7 @@ public class CapInfo implements Parcelable { /** * Checks whether shared sketch is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isSharedSketchSupported() { return mSharedSketchSupported; @@ -470,6 +624,7 @@ public class CapInfo implements Parcelable { /** * Sets shared sketch as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setSharedSketchSupported(boolean sharedSketchSupported) { this.mSharedSketchSupported = sharedSketchSupported; @@ -477,6 +632,7 @@ public class CapInfo implements Parcelable { /** * Checks whether chatbot communication is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isChatbotSupported() { return mChatbotSupported; @@ -484,6 +640,7 @@ public class CapInfo implements Parcelable { /** * Sets chatbot communication as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setChatbotSupported(boolean chatbotSupported) { this.mChatbotSupported = chatbotSupported; @@ -491,6 +648,7 @@ public class CapInfo implements Parcelable { /** * Checks whether chatbot role is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isChatbotRoleSupported() { return mChatbotRoleSupported; @@ -498,6 +656,7 @@ public class CapInfo implements Parcelable { /** * Sets chatbot role as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setChatbotRoleSupported(boolean chatbotRoleSupported) { this.mChatbotRoleSupported = chatbotRoleSupported; @@ -505,6 +664,7 @@ public class CapInfo implements Parcelable { /** * Checks whether standalone chatbot communication is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isSmChatbotSupported() { return mSmChatbotSupported; @@ -512,6 +672,7 @@ public class CapInfo implements Parcelable { /** * Sets standalone chatbot communication as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setSmChatbotSupported(boolean smChatbotSupported) { this.mSmChatbotSupported = smChatbotSupported; @@ -519,6 +680,7 @@ public class CapInfo implements Parcelable { /** * Checks whether Mmtel based call composer is supported. + * @deprecated Use {@link #isCapabilitySupported(String)} instead. */ public boolean isMmtelCallComposerSupported() { return mMmtelCallComposerSupported; @@ -526,6 +688,7 @@ public class CapInfo implements Parcelable { /** * Sets Mmtel based call composer as supported or not supported. + * @deprecated Use {@link #addCapability(String, String)} instead. */ public void setMmtelCallComposerSupported(boolean mmtelCallComposerSupported) { this.mMmtelCallComposerSupported = mmtelCallComposerSupported; @@ -555,6 +718,84 @@ public class CapInfo implements Parcelable { this.mCapTimestamp = capTimestamp; } + /** + * Adds the feature tag string with supported versions to + * the mCapInfoMap. + * Map<String featureType, String versions> + * Versions format: + * "+g.gsma.rcs.botversion=\"#=1" -> Version 1 supported + * "+g.gsma.rcs.botversion=\"#=1,#=2\"" -> Versions 1 and 2 are supported + * + * Example #1: Add standard feature tag with one version support + * addCapability(CapInfo.STANDALONE_CHATBOT, "+g.gsma.rcs.botversion=\"#=1"); + * The above example indicates standalone chatbot feature tag is supported + * in version 1. + * + * Example #2: Add standard feature tag with multiple version support + * addCapability(CapInfo.CHATBOT, "+g.gsma.rcs.botversion=\"#=1,#=2\""); + * The above example indicates session based chatbot feature tag is supported + * in versions 1 and 2. + * + * Example #3: Add standard feature tag with no version support + * addCapability(CapInfo.INSTANT_MSG, ""); + * The above example indicates im feature tag does not have version support. + * + * Example #4: Add custom/extension feature tag with no version support + * addCapability("+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.custom_im\"", + * ""); + * Call setNewFeatureTag(int presenceServiceHdl, String featureTag, + * in PresServiceInfo serviceInfo, int userData) API + * in IPresenceService.aidl before calling addCapability() API + */ + public void addCapability(String featureTagName, String versions) { + this.mCapInfoMap.put(featureTagName, versions); + } + + /** + * Returns String of versions of the feature tag passed. + * Returns "" if versioning support is not present for the feature tag passed. + * Returns null if feature tag is not present. + * + * Example # 1: + * getCapabilityVersions(CapInfo.STANDALONE_CHATBOT); + * The above returns String in this format "+g.gsma.rcs.botversion=\"#=1,#=2\"", + * indicating more than one versions are supported for standalone chatbot feature tag + * + * Example # 2: + * getCapabilityVersions(CapInfo.INSTANT_MSG); + * The above returns empty String in this format "", + * indicating versions support is not present for im feature tag + * + * Example #3: + * getCapabilityVersions( + * "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.custom_im\"); + * The above returns String "", + * indicating version supported is not present for the custom feature tag passed. + */ + public String getCapabilityVersions(String featureTagName) { + return mCapInfoMap.get(featureTagName); + } + + /** Removes the entry of the feature tag passed, from the Map. */ + public void removeCapability(String featureTagName) { + this.mCapInfoMap.remove(featureTagName); + } + + /** Sets Map of feature tag string and string of supported versions. */ + public void setCapInfoMap(Map<String, String> capInfoMap) { + this.mCapInfoMap = capInfoMap; + } + + /** Gets Map of feature tag string and string of supported versions. */ + public Map<String, String> getCapInfoMap() { + return mCapInfoMap; + } + + /** Checks whether the featureTag is supported or not. */ + public boolean isCapabilitySupported(String featureTag) { + return mCapInfoMap.containsKey(featureTag); + } + public int describeContents() { // TODO Auto-generated method stub return 0; @@ -594,6 +835,12 @@ public class CapInfo implements Parcelable { dest.writeInt(mRcsIpVideoOnlyCallSupported ? 1 : 0); dest.writeStringArray(mExts); dest.writeLong(mCapTimestamp); + + Bundle capInfoBundle = new Bundle(); + for (Map.Entry<String, String> entry : mCapInfoMap.entrySet()) { + capInfoBundle.putString(entry.getKey(), entry.getValue()); + } + dest.writeBundle(capInfoBundle); } public static final Parcelable.Creator<CapInfo> CREATOR = new Parcelable.Creator<CapInfo>() { @@ -646,5 +893,10 @@ public class CapInfo implements Parcelable { mExts = source.createStringArray(); mCapTimestamp = source.readLong(); + + Bundle capInfoBundle = source.readBundle(); + for (String key: capInfoBundle.keySet()) { + mCapInfoMap.put(key, capInfoBundle.getString(key)); + } } } diff --git a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java index acea0f03742f..32420816f5ab 100644 --- a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java +++ b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java @@ -30,6 +30,7 @@ public class OptionsSipResponse implements Parcelable { private int mSipResponseCode = 0; private int mRetryAfter = 0; private String mReasonPhrase = ""; + private String mReasonHeader = ""; /** * Gets the Options command ID. @@ -117,6 +118,22 @@ public class OptionsSipResponse implements Parcelable { } /** + * Gets the reason header associated with the SIP response code. + * @hide + */ + public String getReasonHeader() { + return mReasonHeader; + } + + /** + * Sets the SIP response code reason phrase. + * @hide + */ + public void setReasonHeader(String reasonHeader) { + this.mReasonHeader = reasonHeader; + } + + /** * Constructor for the OptionsSipResponse class. * @hide */ @@ -138,6 +155,7 @@ public class OptionsSipResponse implements Parcelable { dest.writeString(mReasonPhrase); dest.writeParcelable(mCmdId, flags); dest.writeInt(mRetryAfter); + dest.writeString(mReasonHeader); } /** @hide */ @@ -164,5 +182,6 @@ public class OptionsSipResponse implements Parcelable { mReasonPhrase = source.readString(); mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader()); mRetryAfter = source.readInt(); + mReasonHeader = source.readString(); } } diff --git a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java index 9549152ce175..5e394efed294 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java +++ b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java @@ -29,6 +29,7 @@ public class PresSipResponse implements Parcelable { private int mSipResponseCode = 0; private int mRetryAfter = 0; private String mReasonPhrase = ""; + private String mReasonHeader = ""; /** * Gets the Presence command ID. @@ -123,6 +124,23 @@ public class PresSipResponse implements Parcelable { } /** + * Gets the reason header associated with the SIP response + * code. + * @hide + */ + public String getReasonHeader() { + return mReasonHeader; + } + + /** + * Sets the SIP response code reason header. + * @hide + */ + public void setReasonHeader(String reasonHeader) { + this.mReasonHeader = reasonHeader; + } + + /** * Constructor for the PresSipResponse class. * @hide */ @@ -141,6 +159,7 @@ public class PresSipResponse implements Parcelable { dest.writeString(mReasonPhrase); dest.writeParcelable(mCmdId, flags); dest.writeInt(mRetryAfter); + dest.writeString(mReasonHeader); } /** @hide */ @@ -168,5 +187,6 @@ public class PresSipResponse implements Parcelable { mReasonPhrase = source.readString(); mCmdId = source.readParcelable(PresCmdId.class.getClassLoader()); mRetryAfter = source.readInt(); + mReasonHeader = source.readString(); } }
\ No newline at end of file diff --git a/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java index 34a7b1e3de65..ce3d568f2103 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java +++ b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java @@ -27,6 +27,7 @@ public class PresTupleInfo implements Parcelable { private String mFeatureTag = ""; private String mContactUri = ""; private String mTimestamp = ""; + private String mVersion = ""; /** @@ -80,6 +81,22 @@ public class PresTupleInfo implements Parcelable { } /** + * Gets the version. + * @hide + */ + public String getVersion() { + return mVersion; + } + + /** + * Sets the version. + * @hide + */ + public void setVersion(String version) { + this.mVersion = version; + } + + /** * Constructor for the PresTupleInfo class. * @hide */ @@ -96,6 +113,7 @@ public class PresTupleInfo implements Parcelable { dest.writeString(mFeatureTag); dest.writeString(mContactUri); dest.writeString(mTimestamp); + dest.writeString(mVersion); } /** @hide */ @@ -121,5 +139,6 @@ public class PresTupleInfo implements Parcelable { mFeatureTag = source.readString(); mContactUri = source.readString(); mTimestamp = source.readString(); + mVersion = source.readString(); } }
\ No newline at end of file diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 5f4c0c12dfa1..4f27d218f05c 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -243,6 +243,8 @@ cc_library_shared { shared_libs: [ "audioclient-types-aidl-cpp", "audioflinger-aidl-cpp", + "audiopolicy-types-aidl-cpp", + "spatializer-aidl-cpp", "av-types-aidl-cpp", "android.hardware.camera.device@3.2", "libandroidicu", diff --git a/core/jni/android_media_AudioDeviceAttributes.cpp b/core/jni/android_media_AudioDeviceAttributes.cpp index 2a16dce99125..6879a6008f5a 100644 --- a/core/jni/android_media_AudioDeviceAttributes.cpp +++ b/core/jni/android_media_AudioDeviceAttributes.cpp @@ -24,6 +24,11 @@ using namespace android; static jclass gAudioDeviceAttributesClass; static jmethodID gAudioDeviceAttributesCstor; +static struct { + jfieldID mAddress; + jfieldID mNativeType; + // other fields unused by JNI +} gAudioDeviceAttributesFields; namespace android { @@ -33,12 +38,25 @@ jint createAudioDeviceAttributesFromNative(JNIEnv *env, jobject *jAudioDeviceAtt jint jNativeType = (jint)devTypeAddr->mType; ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->getAddress())); - *jAudioDeviceAttributes = env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor, - jNativeType, jAddress.get()); + *jAudioDeviceAttributes = + env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor, + jNativeType, jAddress.get()); return jStatus; } +jint createAudioDeviceTypeAddrFromJava(JNIEnv *env, AudioDeviceTypeAddr *devTypeAddr, + const jobject jAudioDeviceAttributes) { + devTypeAddr->mType = (audio_devices_t)env->GetIntField(jAudioDeviceAttributes, + gAudioDeviceAttributesFields.mNativeType); + + jstring jAddress = (jstring)env->GetObjectField(jAudioDeviceAttributes, + gAudioDeviceAttributesFields.mAddress); + devTypeAddr->setAddress(ScopedUtfChars(env, jAddress).c_str()); + + return AUDIO_JAVA_SUCCESS; +} + } // namespace android int register_android_media_AudioDeviceAttributes(JNIEnv *env) { @@ -48,5 +66,10 @@ int register_android_media_AudioDeviceAttributes(JNIEnv *env) { gAudioDeviceAttributesCstor = GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>", "(ILjava/lang/String;)V"); + gAudioDeviceAttributesFields.mNativeType = + GetFieldIDOrDie(env, gAudioDeviceAttributesClass, "mNativeType", "I"); + gAudioDeviceAttributesFields.mAddress = + GetFieldIDOrDie(env, gAudioDeviceAttributesClass, "mAddress", "Ljava/lang/String;"); + return 0; } diff --git a/core/jni/android_media_AudioDeviceAttributes.h b/core/jni/android_media_AudioDeviceAttributes.h index b49d9ba515b8..4a1f40d9bb7c 100644 --- a/core/jni/android_media_AudioDeviceAttributes.h +++ b/core/jni/android_media_AudioDeviceAttributes.h @@ -28,6 +28,9 @@ namespace android { extern jint createAudioDeviceAttributesFromNative(JNIEnv *env, jobject *jAudioDeviceAttributes, const AudioDeviceTypeAddr *devTypeAddr); + +extern jint createAudioDeviceTypeAddrFromJava(JNIEnv *env, AudioDeviceTypeAddr *devTypeAddr, + const jobject jAudioDeviceAttributes); } // namespace android #endif
\ No newline at end of file diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 47db3547f931..509b7ad74a29 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -27,6 +27,8 @@ #include "core_jni_helpers.h" #include <android/media/AudioVibratorInfo.h> +#include <android/media/INativeSpatializerCallback.h> +#include <android/media/ISpatializer.h> #include <audiomanager/AudioManager.h> #include <media/AudioPolicy.h> #include <media/AudioSystem.h> @@ -2023,6 +2025,18 @@ android_media_AudioSystem_registerRoutingCallback(JNIEnv *env, jobject thiz) AudioSystem::setRoutingCallback(android_media_AudioSystem_routing_callback); } +void javaAudioFormatToNativeAudioConfig(JNIEnv *env, audio_config_t *nConfig, + const jobject jFormat, bool isInput) { + *nConfig = AUDIO_CONFIG_INITIALIZER; + nConfig->format = audioFormatToNative(env->GetIntField(jFormat, gAudioFormatFields.mEncoding)); + nConfig->sample_rate = env->GetIntField(jFormat, gAudioFormatFields.mSampleRate); + jint jChannelMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelMask); + if (isInput) { + nConfig->channel_mask = inChannelMaskToNative(jChannelMask); + } else { + nConfig->channel_mask = outChannelMaskToNative(jChannelMask); + } +} static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, @@ -2043,13 +2057,7 @@ static jint convertAudioMixToNative(JNIEnv *env, nAudioMix->mCbFlags = env->GetIntField(jAudioMix, gAudioMixFields.mCallbackFlags); jobject jFormat = env->GetObjectField(jAudioMix, gAudioMixFields.mFormat); - nAudioMix->mFormat = AUDIO_CONFIG_INITIALIZER; - nAudioMix->mFormat.sample_rate = env->GetIntField(jFormat, - gAudioFormatFields.mSampleRate); - nAudioMix->mFormat.channel_mask = outChannelMaskToNative(env->GetIntField(jFormat, - gAudioFormatFields.mChannelMask)); - nAudioMix->mFormat.format = audioFormatToNative(env->GetIntField(jFormat, - gAudioFormatFields.mEncoding)); + javaAudioFormatToNativeAudioConfig(env, &nAudioMix->mFormat, jFormat, false /*isInput*/); env->DeleteLocalRef(jFormat); jobject jRule = env->GetObjectField(jAudioMix, gAudioMixFields.mRule); @@ -2712,6 +2720,58 @@ static jint android_media_AudioSystem_setVibratorInfos(JNIEnv *env, jobject thiz return (jint)check_AudioSystem_Command(AudioSystem::setVibratorInfos(vibratorInfos)); } +static jobject android_media_AudioSystem_getSpatializer(JNIEnv *env, jobject thiz, + jobject jISpatializerCallback) { + sp<media::INativeSpatializerCallback> nISpatializerCallback + = interface_cast<media::INativeSpatializerCallback>( + ibinderForJavaObject(env, jISpatializerCallback)); + sp<media::ISpatializer> nSpatializer; + status_t status = AudioSystem::getSpatializer(nISpatializerCallback, + &nSpatializer); + if (status != NO_ERROR) { + return nullptr; + } + return javaObjectForIBinder(env, IInterface::asBinder(nSpatializer)); +} + +static jboolean android_media_AudioSystem_canBeSpatialized(JNIEnv *env, jobject thiz, + jobject jaa, jobject jFormat, + jobjectArray jDeviceArray) { + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return false; + } + + AudioDeviceTypeAddrVector nDevices; + + const size_t numDevices = env->GetArrayLength(jDeviceArray); + for (size_t i = 0; i < numDevices; ++i) { + AudioDeviceTypeAddr device; + jobject jDevice = env->GetObjectArrayElement(jDeviceArray, i); + if (jDevice == nullptr) { + return false; + } + jStatus = createAudioDeviceTypeAddrFromJava(env, &device, jDevice); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return false; + } + nDevices.push_back(device); + } + + audio_config_t nConfig; + javaAudioFormatToNativeAudioConfig(env, &nConfig, jFormat, false /*isInput*/); + + bool canBeSpatialized; + status_t status = + AudioSystem::canBeSpatialized(paa.get(), &nConfig, nDevices, &canBeSpatialized); + if (status != NO_ERROR) { + ALOGW("%s native returned error %d", __func__, status); + return false; + } + return canBeSpatialized; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = @@ -2848,7 +2908,15 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_removeUserIdDeviceAffinities}, {"setCurrentImeUid", "(I)I", (void *)android_media_AudioSystem_setCurrentImeUid}, {"setVibratorInfos", "(Ljava/util/List;)I", - (void *)android_media_AudioSystem_setVibratorInfos}}; + (void *)android_media_AudioSystem_setVibratorInfos}, + {"nativeGetSpatializer", + "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;", + (void *)android_media_AudioSystem_getSpatializer}, + {"canBeSpatialized", + "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;" + "[Landroid/media/AudioDeviceAttributes;)Z", + (void *)android_media_AudioSystem_canBeSpatialized}}; + static const JNINativeMethod gEventHandlerMethods[] = { {"native_setup", diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index c4838b83347c..84f82fd5d99d 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -254,6 +254,15 @@ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER}. --> <item type="id" name="accessibilityActionImeEnter" /> + <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START}. --> + <item type="id" name="accessibilityActionDragStart" /> + + <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_DROP}. --> + <item type="id" name="accessibilityActionDragDrop" /> + + <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_CANCEL}. --> + <item type="id" name="accessibilityActionDragCancel" /> + <!-- View tag for remote views to store the index of the next child when adding nested remote views dynamically. --> <item type="id" name="remote_views_next_child" /> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index eb4919b61dc2..b57055ca4338 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3224,6 +3224,9 @@ </staging-public-group> <staging-public-group type="id" first-id="0x01fe0000"> + <public name="accessibilityActionDragStart" /> + <public name="accessibilityActionDragDrop" /> + <public name="accessibilityActionDragCancel" /> </staging-public-group> <staging-public-group type="style" first-id="0x01fd0000"> diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index f3cfcf18dec1..67358c4f3255 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -579,7 +579,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { // // Note: mNamespace == KeyProperties.NAMESPACE_APPLICATION implies that the target domain // is Domain.APP and Domain.SELINUX is the target domain otherwise. - if (alias != descriptor.alias + if (!alias.equals(descriptor.alias) || descriptor.domain != targetDomain || (descriptor.domain == Domain.SELINUX && descriptor.nspace != targetNamespace)) { throw new KeyStoreException("Can only replace keys with same alias: " + alias diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index c32dddf6dca0..3b9bcd36bea8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -93,6 +93,7 @@ public final class SplitLayout { private final Rect mDividerBounds = new Rect(); private final Rect mBounds1 = new Rect(); private final Rect mBounds2 = new Rect(); + private final Rect mTmpBounds = new Rect(); private final SplitLayoutHandler mSplitLayoutHandler; private final SplitWindowManager mSplitWindowManager; private final DisplayImeController mDisplayImeController; @@ -188,9 +189,10 @@ public final class SplitLayout { final int rotation = configuration.windowConfiguration.getRotation(); final Rect rootBounds = configuration.windowConfiguration.getBounds(); if (rotation != mRotation || !mRootBounds.equals(rootBounds)) { + mTmpBounds.set(mRootBounds); mRootBounds.set(rootBounds); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); - resetDividerPosition(); + initDividerPosition(mTmpBounds); affectsLayout = true; } @@ -202,6 +204,19 @@ public final class SplitLayout { return affectsLayout; } + private void initDividerPosition(Rect oldBounds) { + final float snapRatio = (float) mDividePosition + / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height()); + // Estimate position by previous ratio. + final float length = + (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height()); + final int estimatePosition = (int) (length * snapRatio); + // Init divider position by estimated position using current bounds snap algorithm. + mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget( + estimatePosition).position; + updateBounds(mDividePosition); + } + /** Updates recording bounds of divider window and both of the splits. */ private void updateBounds(int position) { mDividerBounds.set(mRootBounds); diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 69d1889d5762..5d9f2909903b 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.bluetooth.BluetoothCodecConfig; @@ -2000,6 +2001,46 @@ public class AudioSystem */ public static native int setVibratorInfos(@NonNull List<Vibrator> vibrators); + /** + * @hide + * If a spatializer effect is present on the platform, this will return an + * ISpatializer interface to control this feature. + * If no spatializer is present, a null interface is returned. + * The INativeSpatializerCallback passed must not be null. + * Only one ISpatializer interface can exist at a given time. The native audio policy + * service will reject the request if an interface was already acquired and previous owner + * did not die or call ISpatializer.release(). + * @param callback the callback to receive state updates if the ISpatializer + * interface is acquired. + * @return the ISpatializer interface made available to control the + * platform spatializer + */ + @Nullable + public static ISpatializer getSpatializer(INativeSpatializerCallback callback) { + return ISpatializer.Stub.asInterface(nativeGetSpatializer(callback)); + } + private static native IBinder nativeGetSpatializer(INativeSpatializerCallback callback); + + /** + * @hide + * Queries if some kind of spatialization will be performed if the audio playback context + * described by the provided arguments is present. + * The context is made of: + * - The audio attributes describing the playback use case. + * - The audio configuration describing the audio format, channels, sampling rate ... + * - The devices describing the sink audio device selected for playback. + * All arguments are optional and only the specified arguments are used to match against + * supported criteria. For instance, supplying no argument will tell if spatialization is + * supported or not in general. + * @param attributes audio attributes describing the playback use case + * @param format audio configuration describing the audio format, channels, sampling rate... + * @param devices the sink audio device selected for playback + * @return true if spatialization is enabled for this context, false otherwise. + */ + public static native boolean canBeSpatialized(AudioAttributes attributes, + AudioFormat format, + AudioDeviceAttributes[] devices); + // Items shared with audio service /** diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java index 86ed50bacb63..72ee00f03774 100644 --- a/media/java/android/media/PlayerBase.java +++ b/media/java/android/media/PlayerBase.java @@ -102,6 +102,13 @@ public abstract class PlayerBase { mState = AudioPlaybackConfiguration.PLAYER_STATE_IDLE; }; + /** @hide */ + public int getPlayerIId() { + synchronized (mLock) { + return mPlayerIId; + } + } + /** * Call from derived class when instantiation / initialization is successful */ diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml index f21e51cf847a..46414217d76e 100644 --- a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml +++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml @@ -22,6 +22,8 @@ android:layout_height="match_parent" android:gravity="start|center_vertical" android:orientation="horizontal" + android:paddingStart="?android:attr/listPreferredItemPaddingEnd" + android:paddingLeft="?android:attr/listPreferredItemPaddingEnd" android:paddingTop="16dp" android:paddingBottom="16dp"> <View diff --git a/packages/SystemUI/res/drawable/ic_arrow_forward.xml b/packages/SystemUI/res/drawable/ic_arrow_forward.xml new file mode 100644 index 000000000000..438e4c70dc71 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_arrow_forward.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:autoMirrored="true" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="@android:color/black" + android:pathData="M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml new file mode 100644 index 000000000000..2c34060ccd61 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10C20,8.9 19.1,8 18,8zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2H9V6zM18,20H6V10h12V20zM12,17c1.1,0 2,-0.9 2,-2c0,-1.1 -0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2C10,16.1 10.9,17 12,17z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_settings_24dp.xml b/packages/SystemUI/res/drawable/ic_settings_24dp.xml new file mode 100644 index 000000000000..ac4c43bd35b9 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_settings_24dp.xml @@ -0,0 +1,29 @@ +<!-- + Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml new file mode 100644 index 000000000000..f38a36804bc8 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="#FF000000" + android:pathData="M2,22l16,0l0,-2l-11,0l13,-13l0,1l2,0l0,-6z" + android:strokeAlpha="0.3" + android:fillAlpha="0.3"/> + <path + android:fillColor="#FF000000" + android:pathData="M20,10h2v8h-2z"/> + <path + android:fillColor="#FF000000" + android:pathData="M20,20h2v2h-2z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml new file mode 100644 index 000000000000..3ceb0f6ac06a --- /dev/null +++ b/packages/SystemUI/res/drawable/internet_dialog_background.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + <shape android:shape="rectangle"> + <corners android:radius="8dp" /> + <solid android:color="?android:attr/colorBackground" /> + </shape> +</inset> diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml new file mode 100644 index 000000000000..50267fda0b25 --- /dev/null +++ b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <stroke + android:color="?androidprv:attr/colorAccentPrimaryVariant" + android:width="1dp"/> + <corners android:radius="20dp"/> + <padding + android:left="8dp" + android:right="8dp" + android:top="4dp" + android:bottom="4dp" /> + <solid android:color="@android:color/transparent" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml new file mode 100644 index 000000000000..14672ef3dcfe --- /dev/null +++ b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + <shape android:shape="rectangle"> + <corners + android:topLeftRadius="@dimen/internet_dialog_corner_radius" + android:topRightRadius="@dimen/internet_dialog_corner_radius" + android:bottomLeftRadius="@dimen/internet_dialog_corner_radius" + android:bottomRightRadius="@dimen/internet_dialog_corner_radius"/> + <solid android:color="?android:attr/colorBackground" /> + </shape> +</inset> diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml new file mode 100644 index 000000000000..088e82bb4260 --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid android:color="@color/settingslib_state_off_color"/> + <corners android:radius="@dimen/settingslib_switch_bar_radius"/> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml new file mode 100644 index 000000000000..250188b892f4 --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid android:color="@color/settingslib_state_on_color"/> + <corners android:radius="@dimen/settingslib_switch_bar_radius"/> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml new file mode 100644 index 000000000000..b41762f7908e --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:top="@dimen/settingslib_switch_thumb_margin" + android:left="@dimen/settingslib_switch_thumb_margin" + android:right="@dimen/settingslib_switch_thumb_margin" + android:bottom="@dimen/settingslib_switch_thumb_margin"> + <shape android:shape="oval"> + <size + android:height="@dimen/settingslib_switch_thumb_size" + android:width="@dimen/settingslib_switch_thumb_size"/> + <solid + android:color="@color/settingslib_thumb_off_color" + android:alpha="?android:attr/disabledAlpha"/> + </shape> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_off.xml b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml new file mode 100644 index 000000000000..8b69ad1b2493 --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:top="@dimen/settingslib_switch_thumb_margin" + android:left="@dimen/settingslib_switch_thumb_margin" + android:right="@dimen/settingslib_switch_thumb_margin" + android:bottom="@dimen/settingslib_switch_thumb_margin"> + <shape android:shape="oval"> + <size + android:height="@dimen/settingslib_switch_thumb_size" + android:width="@dimen/settingslib_switch_thumb_size"/> + <solid android:color="@color/settingslib_thumb_off_color"/> + </shape> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml new file mode 100644 index 000000000000..0f27fc2f4ad8 --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:top="@dimen/settingslib_switch_thumb_margin" + android:left="@dimen/settingslib_switch_thumb_margin" + android:right="@dimen/settingslib_switch_thumb_margin" + android:bottom="@dimen/settingslib_switch_thumb_margin"> + <shape android:shape="oval"> + <size + android:height="@dimen/settingslib_switch_thumb_size" + android:width="@dimen/settingslib_switch_thumb_size"/> + <solid android:color="@color/settingslib_state_on_color"/> + </shape> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml new file mode 100644 index 000000000000..06bb779b91ef --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/settingslib_thumb_on" android:state_checked="true"/> + <item android:drawable="@drawable/settingslib_thumb_off" android:state_checked="false"/> + <item android:drawable="@drawable/settingslib_thumb_disabled" android:state_enabled="false"/> +</selector> diff --git a/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml new file mode 100644 index 000000000000..15dfcb70e25e --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" + android:width="@dimen/settingslib_switch_track_width" + android:height="@dimen/settingslib_switch_track_height"> + <solid + android:color="@color/settingslib_track_off_color" + android:alpha="?android:attr/disabledAlpha"/> + <corners android:radius="@dimen/settingslib_switch_track_radius"/> +</shape> diff --git a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml new file mode 100644 index 000000000000..4d79a6e20776 --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" + android:width="@dimen/settingslib_switch_track_width" + android:height="@dimen/settingslib_switch_track_height"> + <solid android:color="@color/settingslib_track_off_color"/> + <corners android:radius="@dimen/settingslib_switch_track_radius"/> +</shape> diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml new file mode 100644 index 000000000000..c12d012a0508 --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" + android:width="@dimen/settingslib_switch_track_width" + android:height="@dimen/settingslib_switch_track_height"> + <solid android:color="@color/settingslib_track_on_color"/> + <corners android:radius="@dimen/settingslib_switch_track_radius"/> +</shape> diff --git a/packages/SystemUI/res/drawable/settingslib_track_selector.xml b/packages/SystemUI/res/drawable/settingslib_track_selector.xml new file mode 100644 index 000000000000..a38c3b4241a3 --- /dev/null +++ b/packages/SystemUI/res/drawable/settingslib_track_selector.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/settingslib_track_on_background" android:state_checked="true"/> + <item android:drawable="@drawable/settingslib_track_off_background" android:state_checked="false"/> + <item android:drawable="@drawable/settingslib_track_disabled_background" android:state_enabled="false"/> +</selector> diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml new file mode 100644 index 000000000000..fddff0b1eb0f --- /dev/null +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -0,0 +1,351 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/internet_connectivity_dialog" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/internet_dialog_rounded_top_corner_background" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + style="@style/Widget.SliceView.Panel" + android:gravity="center_vertical|center_horizontal" + android:layout_marginTop="20dp" + android:layout_height="64dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/internet_dialog_title" + android:gravity="center_vertical|center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorPrimary" + android:fontFamily="google-sans" + android:textSize="24sp"/> + + <TextView + android:id="@+id/internet_dialog_subtitle" + android:gravity="center_vertical|center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:fontFamily="google-sans" + android:textSize="14sp"/> + </LinearLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="?android:attr/listDivider"/> + + <LinearLayout + android:id="@+id/internet_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/mobile_network_layout" + android:layout_width="match_parent" + android:layout_height="88dp" + android:clickable="true" + android:focusable="true" + android:background="?android:attr/selectableItemBackground" + android:gravity="center_vertical|center_horizontal" + android:orientation="horizontal" + android:layout_marginRight="@dimen/settingslib_switchbar_margin" + android:layout_marginLeft="@dimen/settingslib_switchbar_margin" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:paddingStart="@dimen/settingslib_switchbar_padding_left" + android:paddingEnd="@dimen/settingslib_switchbar_padding_right"> + + <FrameLayout + android:layout_width="36dp" + android:layout_height="36dp" + android:clickable="false" + android:layout_gravity="center_vertical|start"> + <ImageView + android:id="@+id/signal_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + </FrameLayout> + + <LinearLayout + android:layout_weight="1" + android:id="@+id/mobile_network_list" + android:orientation="vertical" + android:clickable="false" + android:layout_marginLeft="3dp" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="start|center_vertical"> + <TextView + android:id="@+id/mobile_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/textColorPrimary" + android:textSize="16sp" + android:fontFamily="google-sans"/> + <TextView + android:id="@+id/mobile_summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/textColorTertiary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </LinearLayout> + + <FrameLayout + android:layout_width="48dp" + android:layout_height="48dp"> + <Switch + android:id="@+id/mobile_toggle" + android:layout_gravity="center" + android:layout_width="48dp" + android:layout_height="wrap_content" + android:track="@drawable/settingslib_track_selector" + android:thumb="@drawable/settingslib_thumb_selector" + android:theme="@style/MainSwitch.Settingslib"/> + </FrameLayout> + + </LinearLayout> + + <ProgressBar + android:id="@+id/wifi_searching_progress" + android:indeterminate="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="1dp" + android:maxHeight="1dp" + style="@*android:style/Widget.Material.ProgressBar.Horizontal"/> + + <LinearLayout + android:id="@+id/turn_on_wifi_layout" + android:layout_width="match_parent" + android:layout_height="48dp" + android:clickable="true" + android:focusable="true" + android:background="?android:attr/selectableItemBackground" + android:gravity="center" + android:orientation="horizontal" + android:layout_marginTop="8dp" + android:layout_marginRight="@dimen/settingslib_switchbar_margin" + android:layout_marginLeft="@dimen/settingslib_switchbar_margin" + android:paddingStart="@dimen/settingslib_switchbar_padding_left" + android:paddingEnd="@dimen/settingslib_switchbar_padding_right"> + + <FrameLayout + android:layout_weight="1" + android:orientation="vertical" + android:clickable="false" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + <TextView + android:text="@string/turn_on_wifi" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="start|center_vertical" + android:textColor="?android:attr/textColorPrimary" + android:textSize="16sp" + android:fontFamily="google-sans"/> + </FrameLayout> + + <FrameLayout + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginTop="10dp" + android:layout_marginBottom="10dp"> + <Switch + android:id="@+id/wifi_toggle" + android:layout_gravity="center" + android:layout_width="48dp" + android:layout_height="wrap_content" + android:track="@drawable/settingslib_track_selector" + android:thumb="@drawable/settingslib_thumb_selector" + android:theme="@style/MainSwitch.Settingslib"/> + </FrameLayout> + + </LinearLayout> + + <LinearLayout + android:id="@+id/wifi_connected_layout" + android:layout_width="match_parent" + android:layout_height="72dp" + android:layout_gravity="center_vertical|start" + android:clickable="true" + android:focusable="true" + android:visibility="gone" + android:background="?android:attr/selectableItemBackground" + android:orientation="horizontal" + android:layout_marginTop="8dp" + android:layout_marginRight="@dimen/settingslib_switchbar_margin" + android:layout_marginLeft="@dimen/settingslib_switchbar_margin" + android:paddingStart="@dimen/settingslib_switchbar_padding_left" + android:paddingEnd="@dimen/settingslib_switchbar_padding_right"> + + <FrameLayout + android:layout_width="24dp" + android:layout_height="24dp" + android:clickable="false" + android:layout_gravity="center_vertical|start"> + <ImageView + android:id="@+id/wifi_connected_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + </FrameLayout> + + <LinearLayout + android:layout_weight="3" + android:id="@+id/wifi_connected_list" + android:orientation="vertical" + android:clickable="false" + android:layout_width="wrap_content" + android:layout_height="72dp" + android:gravity="start|center_vertical"> + <TextView + android:id="@+id/wifi_connected_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="16dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + <TextView + android:id="@+id/wifi_connected_summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="16dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/textColorTertiary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </LinearLayout> + + <FrameLayout + android:layout_width="24dp" + android:layout_height="match_parent" + android:layout_marginRight="5dp" + android:clickable="false" + android:gravity="center"> + <ImageView + android:id="@+id/wifi_settings_icon" + android:src="@drawable/ic_settings_24dp" + android:layout_width="24dp" + android:layout_gravity="center" + android:layout_height="wrap_content"/> + </FrameLayout> + + </LinearLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/wifi_list_layout" + android:scrollbars="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:overScrollMode="never" + android:nestedScrollingEnabled="false"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/see_all_layout" + android:layout_width="match_parent" + android:layout_height="64dp" + android:clickable="true" + android:focusable="true" + android:background="?android:attr/selectableItemBackground" + android:gravity="center_vertical|center_horizontal" + android:orientation="horizontal" + android:paddingStart="@dimen/settingslib_switchbar_padding_left" + android:paddingEnd="@dimen/settingslib_switchbar_padding_right"> + + <FrameLayout + android:layout_width="24dp" + android:layout_height="24dp" + android:clickable="false" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="16dp"> + <ImageView + android:id="@+id/arrow_forward" + android:src="@drawable/ic_arrow_forward" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + </FrameLayout> + + <FrameLayout + android:orientation="vertical" + android:clickable="false" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginLeft="16dp"> + <TextView + android:text="@string/see_all_networks" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="start|center_vertical" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </FrameLayout> + + </LinearLayout> + + <Space + android:id="@+id/space" + android:layout_width="match_parent" + android:layout_height="28dp" + android:visibility="gone"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="48dp" + android:layout_marginBottom="25dp" + android:gravity="end" + android:orientation="horizontal"> + <Button + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:id="@+id/done" + android:layout_width="60dp" + android:layout_height="30dp" + android:layout_marginRight="24dp" + android:layout_gravity="end" + android:background="@drawable/internet_dialog_footer_background" + android:textColor="?android:attr/textColorPrimary" + android:text="@string/inline_done_button" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml new file mode 100644 index 000000000000..cb51ab6a3361 --- /dev/null +++ b/packages/SystemUI/res/layout/internet_list_item.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/internet_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/wifi_list" + android:layout_width="match_parent" + android:layout_height="72dp" + android:layout_gravity="center_vertical|start" + android:clickable="true" + android:focusable="true" + android:background="?android:attr/selectableItemBackground" + android:orientation="horizontal" + android:paddingStart="@dimen/settingslib_switchbar_padding_left" + android:paddingEnd="@dimen/settingslib_switchbar_padding_right"> + + <FrameLayout + android:layout_width="24dp" + android:layout_height="24dp" + android:clickable="false" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="16dp"> + <ImageView + android:id="@+id/wifi_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + </FrameLayout> + + <LinearLayout + android:layout_weight="3" + android:id="@+id/wifi_network_layout" + android:orientation="vertical" + android:clickable="false" + android:layout_width="wrap_content" + android:layout_height="72dp"> + <TextView + android:id="@+id/wifi_title" + android:layout_weight="1" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_gravity="center_vertical" + android:gravity="start|center_vertical" + android:layout_marginLeft="16dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + <TextView + android:id="@+id/wifi_summary" + android:layout_weight="1" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_gravity="center_vertical" + android:gravity="start|center_vertical" + android:layout_marginLeft="16dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?android:attr/textColorSecondary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </LinearLayout> + + <FrameLayout + android:layout_width="24dp" + android:layout_height="match_parent" + android:layout_marginRight="@dimen/settingslib_switchbar_padding_right" + android:clickable="false" + android:gravity="center"> + <ImageView + android:id="@+id/wifi_locked_icon" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:layout_height="wrap_content"/> + </FrameLayout> + + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index f1288e31fcb8..d9a56707fb31 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -62,8 +62,7 @@ <com.android.systemui.statusbar.LightRevealScrim android:id="@+id/light_reveal_scrim" android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" /> + android:layout_height="match_parent" /> <include layout="@layout/status_bar_expanded" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 50710c406480..cddd27dd5bde 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -16,7 +16,7 @@ * limitations under the License. */ --> -<resources> +<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <drawable name="notification_number_text_color">#ffffffff</drawable> <drawable name="ticker_background_color">#ff1d1d1d</drawable> <drawable name="system_bar_background">@color/system_bar_background_opaque</drawable> @@ -284,4 +284,16 @@ <color name="wallet_card_border">#33FFFFFF</color> <color name="people_tile_background">@android:color/system_accent2_50</color> + + <!-- Internet Dialog --> + <!-- Material next state on color--> + <color name="settingslib_state_on_color">?androidprv:attr/colorAccentPrimary</color> + <!-- Material next state off color--> + <color name="settingslib_state_off_color">?androidprv:attr/colorAccentSecondary</color> + <!-- Material next track on color--> + <color name="settingslib_track_on_color">?androidprv:attr/colorAccentPrimaryVariant</color> + <!-- Material next track off color--> + <color name="settingslib_track_off_color">?androidprv:attr/colorAccentSecondaryVariant</color> + <color name="connected_network_primary_color">#ff000000</color> + <color name="connected_network_tertiary_color">#808080</color> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 689f8f7abf4f..b1be47229b43 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1600,4 +1600,35 @@ <dimen name="ongoing_call_chip_corner_radius">28dp</dimen> <dimen name="drag_and_drop_icon_size">70dp</dimen> + + <!-- Internet panel related dimensions --> + <dimen name="internet_dialog_list_margin">12dp</dimen> + <dimen name="internet_dialog_list_max_height">614dp</dimen> + + <!-- Signal icon in internet dialog --> + <dimen name="signal_strength_icon_size">24dp</dimen> + + <!-- Internet dialog related dimensions --> + <dimen name="internet_dialog_corner_radius">24dp</dimen> + + <!-- Size of internet dialog --> + <dimen name="settingslib_switchbar_margin">16dp</dimen> + <!-- Minimum width of switch --> + <dimen name="settingslib_min_switch_width">48dp</dimen> + <!-- Size of layout margin left --> + <dimen name="settingslib_switchbar_padding_left">20dp</dimen> + <!-- Size of layout margin right --> + <dimen name="settingslib_switchbar_padding_right">20dp</dimen> + <!-- Radius of switch bar --> + <dimen name="settingslib_switch_bar_radius">24dp</dimen> + <!-- Margin of switch thumb --> + <dimen name="settingslib_switch_thumb_margin">4dp</dimen> + <!-- Size of switch thumb --> + <dimen name="settingslib_switch_thumb_size">16dp</dimen> + <!-- Width of switch track --> + <dimen name="settingslib_switch_track_width">48dp</dimen> + <!-- Height of switch track --> + <dimen name="settingslib_switch_track_height">24dp</dimen> + <!-- Radius of switch track --> + <dimen name="settingslib_switch_track_radius">31dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a93eaab89924..1944a0fc6485 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2989,4 +2989,37 @@ <!-- Content description for a chip in the status bar showing that the user is currently on a phone call. [CHAR LIMIT=NONE] --> <string name="ongoing_phone_call_content_description">Ongoing phone call</string> + + <!-- Provider Model: Title of the airplane mode in the internet dialog. [CHAR LIMIT=50] --> + <string name="airplane_mode">Airplane mode</string> + <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] --> + <string name="mobile_data_settings_title">Mobile data</string> + <!-- Provider Model: Summary text separator for preferences including a short description + (eg. "Connected / 5G"). [CHAR LIMIT=50] --> + <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string> + <!-- Provider Model: + Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] --> + <string name="mobile_data_connection_active">Connected</string> + <!-- Provider Model: + Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] --> + <string name="mobile_data_off_summary">Internet won\u0027t auto\u2011connect</string> + <!-- Provider Model: + Summary indicating that a active SIM and no network available [CHAR LIMIT=50] --> + <string name="mobile_data_no_connection">No connection</string> + <!-- Provider Model: Summary indicating that no other networks available [CHAR LIMIT=50] --> + <string name="non_carrier_network_unavailable">No other networks available</string> + <!-- Provider Model: Summary indicating that no networks available [CHAR LIMIT=50] --> + <string name="all_network_unavailable">No networks available</string> + <!-- Provider Model: Panel title text for turning on the Wi-Fi networks. [CHAR LIMIT=40] --> + <string name="turn_on_wifi">Wi\u2011Fi</string> + <!-- Provider Model: Title for detail page of wifi network [CHAR LIMIT=30] --> + <string name="pref_title_network_details" msgid="7329759534269363308">"Network details"</string> + <!-- Provider Model: Panel subtitle for tapping a network to connect to internet. [CHAR LIMIT=60] --> + <string name="tap_a_network_to_connect">Tap a network to connect</string> + <!-- Provider Model: Wi-Fi settings. text displayed when Wi-Fi is on and network list is empty [CHAR LIMIT=50]--> + <string name="wifi_empty_list_wifi_on">Searching for networks\u2026</string> + <!-- Provider Model: Failure notification for connect --> + <string name="wifi_failed_connect_message">Failed to connect to network</string> + <!-- Provider Model: Title to see all the networks [CHAR LIMIT=50] --> + <string name="see_all_networks">See all</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 51eabf60385e..ca6fd4adbc08 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -903,4 +903,43 @@ <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen. --> <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item> </style> + + <style name="Animation.InternetDialog" parent="@android:style/Animation.InputMethod"> + </style> + + <style name="Widget.SliceView.Panel"> + <item name="titleSize">16sp</item> + <item name="rowStyle">@style/SliceRow</item> + <item name="android:background">?android:attr/colorBackgroundFloating</item> + </style> + + <style name="SliceRow"> + <!-- 2dp start padding for the start icon --> + <item name="titleItemStartPadding">2dp</item> + <item name="titleItemEndPadding">0dp</item> + + <!-- Padding between content and the start icon is 14dp --> + <item name="contentStartPadding">14dp</item> + <!-- Padding between content and end items is 16dp --> + <item name="contentEndPadding">16dp</item> + + <!-- Both side margins of end item are 16dp --> + <item name="endItemStartPadding">16dp</item> + <item name="endItemEndPadding">16dp</item> + + <!-- Both side margins of bottom divider are 12dp --> + <item name="bottomDividerStartPadding">12dp</item> + <item name="bottomDividerEndPadding">12dp</item> + + <item name="actionDividerHeight">32dp</item> + </style> + + <style name="Theme.SystemUI.Dialog.Internet"> + <item name="android:windowBackground">@drawable/internet_dialog_background</item> + </style> + + <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault"> + <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item> + </style> + </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index edb05691b530..dd3bb8990599 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -27,6 +27,7 @@ import android.widget.FrameLayout; import android.widget.ImageView; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; @@ -79,7 +80,11 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIcon.setImageDrawable(drawable); } - void setCenterLocation(@NonNull PointF center, int radius) { + /** + * Set the location of the lock icon. + */ + @VisibleForTesting + public void setCenterLocation(@NonNull PointF center, int radius) { mLockIconCenter = center; mRadius = radius; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index aa8cbd710e90..47f0714af164 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -68,7 +68,8 @@ import javax.inject.Inject; /** * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen. * - * This view will only be shown if the user has UDFPS or FaceAuth enrolled + * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock + * icon will show a set distance from the bottom of the device. */ @StatusBarComponent.StatusBarScope public class LockIconViewController extends ViewController<LockIconView> implements Dumpable { @@ -172,15 +173,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override protected void onInit() { + mAuthController.addCallback(mAuthControllerCallback); + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mView.setAccessibilityDelegate(mAccessibilityDelegate); } @Override protected void onViewAttached() { - // we check this here instead of onInit since the FingerprintManager + FaceManager may not - // have started up yet onInit - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; - updateConfiguration(); updateKeyguardShowing(); mUserUnlockedWithBiometric = false; @@ -584,4 +584,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme public void setAlpha(float alpha) { mView.setAlpha(alpha); } + + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + updateConfiguration(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index e996e71b6715..7adf8b591e12 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -66,6 +66,7 @@ import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.qs.ReduceBrightColorsController; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shared.plugins.PluginManager; @@ -358,6 +359,7 @@ public class Dependency { @Inject Lazy<UiEventLogger> mUiEventLogger; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; + @Inject Lazy<InternetDialogFactory> mInternetDialogFactory; @Inject public Dependency() { @@ -568,6 +570,7 @@ public class Dependency { mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); mProviders.put(EdgeBackGestureHandler.Factory.class, mEdgeBackGestureHandlerFactoryLazy::get); + mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index b126cddf8eec..3555e8d8e193 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -452,6 +452,12 @@ public class SwipeHelper implements Gefingerpoken { private boolean mCancelled; @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mCallback.onBeginDrag(animView); + } + + @Override public void onAnimationCancel(Animator animation) { mCancelled = true; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 0ce1846e7745..ab5f2b8289be 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -786,7 +786,11 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, .build(sensorIds, credentialAllowed, mFpProps, mFaceProps); } - interface Callback { + /** + * AuthController callback used to receive signal for when biometric authenticators are + * registered. + */ + public interface Callback { /** * Called when authenticators are registered. If authenticators are already * registered before this call, this callback will never be triggered. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index b5378cd22904..37de5997fc4f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -706,15 +706,21 @@ public class UdfpsController implements DozeReceiver { return mCoreLayoutParams; } + private void onOrientationChanged() { // When the configuration changes it's almost always necessary to destroy and re-create // the overlay's window to pass it the new LayoutParams. // Hiding the overlay will destroy its window. It's safe to hide the overlay regardless // of whether it is already hidden. + final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); hideUdfpsOverlay(); + // If the overlay needs to be shown, this will re-create and show the overlay with the // updated LayoutParams. Otherwise, the overlay will remain hidden. updateOverlay(); + if (wasShowingAltAuth) { + mKeyguardViewManager.showGenericBouncer(true); + } } private void showUdfpsOverlay(@NonNull ServerRequest request) { @@ -820,10 +826,14 @@ public class UdfpsController implements DozeReceiver { Log.v(TAG, "hideUdfpsOverlay | removing window"); // Reset the controller back to its starting state. onFingerUp(); + boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); mWindowManager.removeView(mView); mView.setOnTouchListener(null); mView.setOnHoverListener(null); mView.setAnimationViewController(null); + if (wasShowingAltAuth) { + mKeyguardViewManager.resetAlternateAuth(true); + } mAccessibilityManager.removeTouchExplorationStateChangeListener( mTouchExplorationStateChangeListener); mView = null; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index b81b54720fbd..1549a396a859 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -102,6 +102,12 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } @Override + public void onInit() { + super.onInit(); + mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); + } + + @Override protected void onViewAttached() { super.onViewAttached(); final float dozeAmount = mStatusBarStateController.getDozeAmount(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index 9cb9a362d460..0923caaf0d76 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl; import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; +import com.android.systemui.volume.dagger.VolumeModule; import javax.inject.Named; @@ -82,7 +83,8 @@ import dagger.Provides; @Module(includes = { MediaModule.class, PowerModule.class, - QSModule.class + QSModule.class, + VolumeModule.class }) public abstract class SystemUIDefaultModule { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 102fc40ef1ad..55aad5d5d145 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -75,7 +75,6 @@ import com.android.systemui.util.sensors.SensorModule; import com.android.systemui.util.settings.SettingsUtilModule; import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClockImpl; -import com.android.systemui.volume.dagger.VolumeModule; import com.android.systemui.wallet.dagger.WalletModule; import com.android.systemui.wmshell.BubblesManager; import com.android.wm.shell.bubbles.Bubbles; @@ -112,7 +111,6 @@ import dagger.Provides; TunerModule.class, UserModule.class, UtilModule.class, - VolumeModule.class, WalletModule.class }, subcomponents = { diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index 89786ee880ad..a617850ef0ae 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -139,7 +139,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener + " with ducking", e); } player.start(); - if (DEBUG) { Log.d(mTag, "player.start"); } + if (DEBUG) { Log.d(mTag, "player.start piid:" + player.getPlayerIId()); } } catch (Exception e) { if (player != null) { player.release(); @@ -155,7 +155,13 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener mPlayer = player; } if (mp != null) { - if (DEBUG) { Log.d(mTag, "mPlayer.release"); } + if (DEBUG) { + Log.d(mTag, "mPlayer.pause+release piid:" + player.getPlayerIId()); + } + mp.pause(); + try { + Thread.sleep(100); + } catch (InterruptedException ie) { } mp.release(); } this.notify(); @@ -244,6 +250,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener try { mp.stop(); } catch (Exception e) { } + if (DEBUG) { + Log.i(mTag, "About to release MediaPlayer piid:" + + mp.getPlayerIId() + " due to notif cancelled"); + } mp.release(); synchronized(mQueueAudioFocusLock) { if (mAudioManagerWithAudioFocus != null) { @@ -284,7 +294,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener public void onCompletion(MediaPlayer mp) { synchronized(mQueueAudioFocusLock) { if (mAudioManagerWithAudioFocus != null) { - if (DEBUG) Log.d(mTag, "onCompletion() abandonning AudioFocus"); + if (DEBUG) Log.d(mTag, "onCompletion() abandoning AudioFocus"); mAudioManagerWithAudioFocus.abandonAudioFocus(null); mAudioManagerWithAudioFocus = null; } else { @@ -310,6 +320,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } } if (mp != null) { + if (DEBUG) { + Log.i("NotificationPlayer", "About to release MediaPlayer piid:" + + mp.getPlayerIId() + " due to onCompletion"); + } mp.release(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 41a3fb0211a7..65e93c6f31a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -28,6 +28,7 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.Html; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; import android.widget.Switch; @@ -51,6 +52,7 @@ import com.android.systemui.qs.AlphaControlledSignalTileView; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; @@ -75,6 +77,8 @@ public class InternetTile extends QSTileImpl<SignalState> { private int mLastTileState = -1; protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback(); + private final InternetDialogFactory mInternetDialogFactory; + final Handler mHandler; @Inject public InternetTile( @@ -86,10 +90,13 @@ public class InternetTile extends QSTileImpl<SignalState> { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - NetworkController networkController + NetworkController networkController, + InternetDialogFactory internetDialogFactory ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); + mInternetDialogFactory = internetDialogFactory; + mHandler = mainHandler; mController = networkController; mDataController = mController.getMobileDataController(); mController.observe(getLifecycle(), mSignalCallback); @@ -114,7 +121,13 @@ public class InternetTile extends QSTileImpl<SignalState> { @Override protected void handleClick(@Nullable View view) { - mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0); + if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0); + } else { + mHandler.post(() -> { + mInternetDialogFactory.create(true); + }); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java new file mode 100644 index 000000000000..9ab6d47d6af1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.qs.tiles.dialog; + +import static com.android.wifitrackerlib.WifiEntry.SECURITY_NONE; +import static com.android.wifitrackerlib.WifiEntry.SECURITY_OWE; + +import android.annotation.ColorInt; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.text.Html; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settingslib.Utils; +import com.android.systemui.R; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +/** + * Adapter for showing Wi-Fi networks. + */ +public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> { + + private static final String TAG = "InternetAdapter"; + private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG"; + private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; + private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final InternetDialogController mInternetDialogController; + + protected View mHolderView; + protected Context mContext; + + public InternetAdapter(InternetDialogController controller) { + mInternetDialogController = controller; + } + + @Override + public InternetViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, + int viewType) { + mContext = viewGroup.getContext(); + mHolderView = LayoutInflater.from(mContext).inflate(R.layout.internet_list_item, + viewGroup, false); + return new InternetViewHolder(mHolderView, mInternetDialogController); + } + + @Override + public void onBindViewHolder(@NonNull InternetViewHolder viewHolder, int position) { + List<WifiEntry> wifiList = getWifiEntryList(); + if (wifiList != null && wifiList.size() != 0) { + int count = getItemCount(); + if (wifiList.size() > count) { + wifiList = getWifiEntryList().subList(0, count - 1); + } + + if (position < wifiList.size()) { + viewHolder.onBind(wifiList.get(position)); + } + } else if (DEBUG) { + Log.d(TAG, "onBindViewHolder, Wi-Fi entry list = null"); + } + } + + private List<WifiEntry> getWifiEntryList() { + if (mInternetDialogController.getWifiEntryList() == null) { + return null; + } + + return mInternetDialogController.getWifiEntryList().stream() + .filter(wifiAp -> wifiAp.getConnectedState() + != WifiEntry.CONNECTED_STATE_CONNECTED) + .limit(getItemCount()) + .collect(Collectors.toList()); + } + + /** + * The total number of networks (mobile network and entries of Wi-Fi) should be four in + * {@link InternetDialog}. + * + * Airplane mode is ON (mobile network is gone): + * Return four Wi-Fi's entries if no connected Wi-Fi. + * Return three Wi-Fi's entries if one connected Wi-Fi. + * Airplane mode is OFF (mobile network is visible): + * Return three Wi-Fi's entries if no connected Wi-Fi. + * Return two Wi-Fi's entries if one connected Wi-Fi. + * + * @return The total number of networks. + */ + @Override + public int getItemCount() { + boolean hasConnectedWifi = mInternetDialogController.getConnectedWifiEntry() != null; + if (mInternetDialogController.isAirplaneModeEnabled()) { + return hasConnectedWifi ? 3 : 4; + } else { + return hasConnectedWifi ? 2 : 3; + } + } + + /** + * ViewHolder for binding Wi-Fi view. + */ + static class InternetViewHolder extends RecyclerView.ViewHolder { + + final LinearLayout mContainerLayout; + final LinearLayout mWifiListLayout; + final LinearLayout mWifiNetworkLayout; + final ImageView mWifiIcon; + final TextView mWifiTitleText; + final TextView mWifiSummaryText; + final ImageView mWifiLockedIcon; + final Context mContext; + final InternetDialogController mInternetDialogController; + + InternetViewHolder(View view, InternetDialogController internetDialogController) { + super(view); + mContext = view.getContext(); + mInternetDialogController = internetDialogController; + mContainerLayout = view.requireViewById(R.id.internet_container); + mWifiListLayout = view.requireViewById(R.id.wifi_list); + mWifiNetworkLayout = view.requireViewById(R.id.wifi_network_layout); + mWifiIcon = view.requireViewById(R.id.wifi_icon); + mWifiTitleText = view.requireViewById(R.id.wifi_title); + mWifiSummaryText = view.requireViewById(R.id.wifi_summary); + mWifiLockedIcon = view.requireViewById(R.id.wifi_locked_icon); + } + + void onBind(WifiEntry wifiEntry) { + int security = wifiEntry.getSecurity(); + try { + mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry)); + if (isOpenNetwork(security)) { + mWifiLockedIcon.setVisibility(View.GONE); + } else { + mWifiLockedIcon.setVisibility(View.VISIBLE); + mWifiLockedIcon.setImageDrawable( + mContext.getDrawable(R.drawable.ic_friction_lock_closed)); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + + setWifiNetworkLayout(wifiEntry.getTitle(), + Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY)); + + mWifiListLayout.setOnClickListener(v -> { + if (!isOpenNetwork(security)) { + // Popup Wi-Fi password dialog condition: + // 1. The access point is a non-open network. + // 2. The Wi-Fi connection is not connected with this access point. + final Intent intent = new Intent(ACTION_WIFI_DIALOG); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey()); + intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false); + mContext.startActivity(intent); + } + mInternetDialogController.connect(wifiEntry); + }); + } + + /** Return true if this is an open network AccessPoint. */ + boolean isOpenNetwork(int security) { + return security == SECURITY_NONE + || security == SECURITY_OWE; + } + + void setWifiNetworkLayout(CharSequence title, CharSequence summary) { + mWifiNetworkLayout.setVisibility(View.VISIBLE); + mWifiTitleText.setText(title); + if (TextUtils.isEmpty(summary)) { + mWifiTitleText.setGravity(Gravity.CENTER); + mWifiSummaryText.setVisibility(View.GONE); + return; + } else { + mWifiTitleText.setGravity(Gravity.BOTTOM); + mWifiSummaryText.setGravity(Gravity.TOP); + mWifiSummaryText.setVisibility(View.VISIBLE); + } + mWifiSummaryText.setText(summary); + } + + Drawable getWifiDrawable(WifiEntry wifiEntry) throws Throwable { + Drawable drawable = mContext.getDrawable( + com.android.internal.R.drawable.ic_wifi_signal_0); + + AtomicReference<Drawable> shared = new AtomicReference<>(); + final @ColorInt int tint = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.colorControlNormal); + Drawable signalDrawable = mContext.getDrawable( + Utils.getWifiIconResource(wifiEntry.getLevel())); + signalDrawable.setTint(tint); + shared.set(signalDrawable); + drawable = shared.get(); + return drawable; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java new file mode 100644 index 000000000000..64809f3c9475 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.qs.tiles.dialog; + +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; + +import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; +import android.text.Html; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.Space; +import android.widget.Switch; +import android.widget.TextView; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.Utils; +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.List; + +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks. + */ +@SysUISingleton +public class InternetDialog extends SystemUIDialog implements + InternetDialogController.InternetDialogCallback, Window.Callback { + private static final String TAG = "InternetDialog"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private final Handler mHandler; + private final LinearLayoutManager mLayoutManager; + private final Runnable mHideProgressBarRunnable = () -> { + setProgressBarVisible(false); + }; + + @VisibleForTesting + protected InternetAdapter mAdapter; + @VisibleForTesting + protected WifiManager mWifiManager; + @VisibleForTesting + protected View mDialogView; + + private InternetDialogFactory mInternetDialogFactory; + private SubscriptionManager mSubscriptionManager; + private TelephonyManager mTelephonyManager; + private AlertDialog mAlertDialog; + private UiEventLogger mUiEventLogger; + private Context mContext; + private InternetDialogController mInternetDialogController; + private TextView mInternetDialogTitle; + private TextView mInternetDialogSubTitle; + private ProgressBar mProgressBar; + private LinearLayout mInternetListLayout; + private LinearLayout mConnectedWifListLayout; + private LinearLayout mConnectedWifList; + private LinearLayout mMobileNetworkLayout; + private LinearLayout mMobileNetworkList; + private LinearLayout mTurnWifiOnLayout; + private LinearLayout mSeeAllLayout; + private Space mSpace; + private RecyclerView mWifiRecyclerView; + private ImageView mConnectedWifiIcon; + private ImageView mWifiSettingsIcon; + private TextView mConnectedWifiTitleText; + private TextView mConnectedWifiSummaryText; + private ImageView mSignalIcon; + private TextView mMobileTitleText; + private TextView mMobileSummaryText; + private Switch mMobileDataToggle; + private Switch mWiFiToggle; + private Button mDoneButton; + private Drawable mBackgroundOn; + private WifiEntry mConnectedWifiEntry; + private int mListMaxHeight; + private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private boolean mIsProgressBarVisible; + + private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> { + // Set max height for list + if (mInternetListLayout.getHeight() > mListMaxHeight) { + ViewGroup.LayoutParams params = mInternetListLayout.getLayoutParams(); + params.height = mListMaxHeight; + mInternetListLayout.setLayoutParams(params); + } + }; + + public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, + InternetDialogController internetDialogController, + boolean aboveStatusBar, UiEventLogger uiEventLogger, @Main Handler handler) { + super(context, R.style.Theme_SystemUI_Dialog_Internet); + if (DEBUG) { + Log.d(TAG, "Init InternetDialog"); + } + mContext = context; + mHandler = handler; + mInternetDialogFactory = internetDialogFactory; + mInternetDialogController = internetDialogController; + mSubscriptionManager = mInternetDialogController.getSubscriptionManager(); + mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId(); + mTelephonyManager = mInternetDialogController.getTelephonyManager(); + mWifiManager = mInternetDialogController.getWifiManager(); + + mLayoutManager = new LinearLayoutManager(mContext) { + @Override + public boolean canScrollVertically() { + return false; + } + }; + mListMaxHeight = context.getResources().getDimensionPixelSize( + R.dimen.internet_dialog_list_max_height); + mUiEventLogger = uiEventLogger; + mAdapter = new InternetAdapter(mInternetDialogController); + if (!aboveStatusBar) { + getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + } + show(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (DEBUG) { + Log.d(TAG, "onCreate"); + } + mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW); + mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, + null); + final Window window = getWindow(); + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.gravity = Gravity.BOTTOM; + lp.setFitInsetsTypes(statusBars() | navigationBars()); + lp.setFitInsetsSides(WindowInsets.Side.all()); + lp.setFitInsetsIgnoringVisibility(true); + window.setAttributes(lp); + window.setContentView(mDialogView); + window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + window.setWindowAnimations(R.style.Animation_InternetDialog); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + + mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title); + mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); + mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress); + mInternetListLayout = mDialogView.requireViewById(R.id.internet_list); + mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout); + mMobileNetworkList = mDialogView.requireViewById(R.id.mobile_network_list); + mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout); + mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout); + mConnectedWifList = mDialogView.requireViewById(R.id.wifi_connected_list); + mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon); + mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title); + mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary); + mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon); + mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout); + mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout); + mSpace = mDialogView.requireViewById(R.id.space); + mDoneButton = mDialogView.requireViewById(R.id.done); + mSignalIcon = mDialogView.requireViewById(R.id.signal_icon); + mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title); + mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary); + mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); + mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); + mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); + mInternetListLayout.getViewTreeObserver().addOnGlobalLayoutListener( + mInternetListLayoutListener); + mInternetDialogTitle.setText(getDialogTitleText()); + mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); + + setOnClickListener(); + mTurnWifiOnLayout.setBackground(null); + mWifiRecyclerView.setLayoutManager(mLayoutManager); + mWifiRecyclerView.setAdapter(mAdapter); + } + + @Override + public void onStart() { + super.onStart(); + if (DEBUG) { + Log.d(TAG, "onStart"); + } + mInternetDialogController.onStart(this); + } + + @Override + public void onStop() { + super.onStop(); + if (DEBUG) { + Log.d(TAG, "onStop"); + } + mHandler.removeCallbacks(mHideProgressBarRunnable); + mMobileNetworkLayout.setOnClickListener(null); + mMobileDataToggle.setOnCheckedChangeListener(null); + mConnectedWifListLayout.setOnClickListener(null); + mSeeAllLayout.setOnClickListener(null); + mWiFiToggle.setOnCheckedChangeListener(null); + mDoneButton.setOnClickListener(null); + mInternetDialogController.onStop(); + mInternetDialogFactory.destroyDialog(); + } + + @Override + public void dismissDialog() { + if (DEBUG) { + Log.d(TAG, "dismissDialog"); + } + mInternetDialogFactory.destroyDialog(); + dismiss(); + } + + void updateDialog() { + if (DEBUG) { + Log.d(TAG, "updateDialog"); + } + if (mInternetDialogController.isAirplaneModeEnabled()) { + mInternetDialogSubTitle.setVisibility(View.GONE); + } else { + mInternetDialogSubTitle.setText(getSubtitleText()); + } + showProgressBar(); + setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()); + setConnectedWifiLayout(); + boolean isWifiEnabled = mWifiManager.isWifiEnabled(); + mWiFiToggle.setChecked(isWifiEnabled); + int visible = isWifiEnabled ? View.VISIBLE : View.GONE; + mWifiRecyclerView.setVisibility(visible); + mAdapter.notifyDataSetChanged(); + mSeeAllLayout.setVisibility(visible); + mSpace.setVisibility(isWifiEnabled ? View.GONE : View.VISIBLE); + } + + private void setOnClickListener() { + mMobileNetworkLayout.setOnClickListener(v -> { + if (mInternetDialogController.isMobileDataEnabled()) { + if (!mInternetDialogController.activeNetworkIsCellular()) { + mInternetDialogController.connectCarrierNetwork(); + } + } + }); + mMobileDataToggle.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + if (!isChecked && shouldShowMobileDialog()) { + showTurnOffMobileDialog(); + } else if (!shouldShowMobileDialog()) { + mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId, + isChecked, false); + } + }); + mConnectedWifListLayout.setOnClickListener(v -> { + // TODO(b/191475923): Need to launch the detailed page of Wi-Fi entry. + }); + mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); + mWiFiToggle.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + buttonView.setChecked(isChecked); + mWifiManager.setWifiEnabled(isChecked); + mSpace.setVisibility(isChecked ? View.GONE : View.VISIBLE); + }); + mDoneButton.setOnClickListener(v -> dismiss()); + } + + private void setMobileDataLayout(boolean isCellularNetwork) { + if (mInternetDialogController.isAirplaneModeEnabled() + || !mInternetDialogController.hasCarrier()) { + mMobileNetworkLayout.setVisibility(View.GONE); + } else { + mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled()); + mMobileNetworkLayout.setVisibility(View.VISIBLE); + mMobileTitleText.setText(getMobileNetworkTitle()); + mMobileSummaryText.setText( + Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY)); + mSignalIcon.setImageDrawable(getSignalStrengthDrawable()); + int titleColor = isCellularNetwork ? mContext.getColor( + R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorPrimary); + int summaryColor = isCellularNetwork ? mContext.getColor( + R.color.connected_network_tertiary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorTertiary); + mMobileTitleText.setTextColor(titleColor); + mMobileSummaryText.setTextColor(summaryColor); + mMobileNetworkLayout.setBackground(isCellularNetwork ? mBackgroundOn : null); + } + } + + private void setConnectedWifiLayout() { + if (!mWifiManager.isWifiEnabled() + || mInternetDialogController.getConnectedWifiEntry() == null) { + mConnectedWifListLayout.setBackground(null); + mConnectedWifListLayout.setVisibility(View.GONE); + return; + } + mConnectedWifListLayout.setVisibility(View.VISIBLE); + mConnectedWifiTitleText.setText(getConnectedWifiTitle()); + mConnectedWifiSummaryText.setText(getConnectedWifiSummary()); + mConnectedWifiIcon.setImageDrawable(getConnectedWifiDrawable()); + mConnectedWifiTitleText.setTextColor( + mContext.getColor(R.color.connected_network_primary_color)); + mConnectedWifiSummaryText.setTextColor( + mContext.getColor(R.color.connected_network_tertiary_color)); + mWifiSettingsIcon.setColorFilter( + mContext.getColor(R.color.connected_network_primary_color)); + mConnectedWifListLayout.setBackground(mBackgroundOn); + } + + void onClickSeeMoreButton() { + mInternetDialogController.launchNetworkSetting(); + } + + CharSequence getDialogTitleText() { + return mInternetDialogController.getDialogTitleText(); + } + + CharSequence getSubtitleText() { + return mInternetDialogController.getSubtitleText(mIsProgressBarVisible); + } + + private Drawable getConnectedWifiDrawable() { + try { + return mInternetDialogController.getWifiConnectedDrawable(mConnectedWifiEntry); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + private Drawable getSignalStrengthDrawable() { + return mInternetDialogController.getSignalStrengthDrawable(); + } + + CharSequence getMobileNetworkTitle() { + return mInternetDialogController.getMobileNetworkTitle(); + } + + String getMobileNetworkSummary() { + return mInternetDialogController.getMobileNetworkSummary(); + } + + String getConnectedWifiTitle() { + return mInternetDialogController.getConnectedWifiTitle(); + } + + String getConnectedWifiSummary() { + return mInternetDialogController.getConnectedWifiSummary(); + } + + private void showProgressBar() { + if (mWifiManager == null || !mWifiManager.isWifiEnabled()) { + setProgressBarVisible(false); + return; + } + setProgressBarVisible(true); + List<ScanResult> wifiScanResults = mWifiManager.getScanResults(); + if (wifiScanResults != null && wifiScanResults.size() > 0) { + mContext.getMainThreadHandler().postDelayed(mHideProgressBarRunnable, + 2000 /* delay millis */); + } + } + + private void setProgressBarVisible(boolean visible) { + if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null + && mAdapter.mHolderView.isAttachedToWindow()) { + mIsProgressBarVisible = true; + } + mIsProgressBarVisible = visible; + mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.INVISIBLE); + mInternetDialogSubTitle.setText(getSubtitleText()); + } + + private boolean shouldShowMobileDialog() { + boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, + false); + if (mInternetDialogController.isMobileDataEnabled() && !flag) { + return true; + } + return false; + } + + private void showTurnOffMobileDialog() { + CharSequence carrierName = + mSubscriptionManager.getDefaultDataSubscriptionInfo().getCarrierName(); + boolean isInService = mInternetDialogController.isVoiceStateInService(); + if (TextUtils.isEmpty(carrierName) || !isInService) { + carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); + } + mAlertDialog = new Builder(mContext) + .setTitle(R.string.mobile_data_disable_title) + .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName)) + .setNegativeButton(android.R.string.cancel, (d, w) -> { + mMobileDataToggle.setChecked(true); + }) + .setPositiveButton( + com.android.internal.R.string.alert_windows_notification_turn_off_action, + (d, w) -> { + mInternetDialogController.setMobileDataEnabled(mContext, + mDefaultDataSubId, false, false); + mMobileDataToggle.setChecked(false); + Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true); + }) + .create(); + mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true)); + mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mAlertDialog, true); + SystemUIDialog.registerDismissListener(mAlertDialog); + SystemUIDialog.setWindowOnTop(mAlertDialog); + mAlertDialog.show(); + } + + @Override + public void onRefreshCarrierInfo() { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onSimStateChanged() { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onSubscriptionsChanged(int defaultDataSubId) { + mDefaultDataSubId = defaultDataSubId; + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mHandler.post(() -> updateDialog()); + } + + @Override + public void onServiceStateChanged(ServiceState serviceState) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onDataConnectionStateChanged(int state, int networkType) { + mAdapter.notifyDataSetChanged(); + mHandler.post(() -> updateDialog()); + } + + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onAccessPointsChanged(List<WifiEntry> wifiEntryList, WifiEntry connectedEntry) { + mConnectedWifiEntry = connectedEntry; + mAdapter.notifyDataSetChanged(); + mHandler.post(() -> updateDialog()); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (mAlertDialog != null && !mAlertDialog.isShowing()) { + if (!hasFocus && isShowing()) { + dismiss(); + } + } + } + + @Override + public void onWifiStateReceived(Context context, Intent intent) { + if (intent == null) { + return; + } + + String action = intent.getAction(); + if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + mInternetDialogController.scanWifiAccessPoints(); + showProgressBar(); + return; + } + + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mHandler.post(() -> updateDialog()); + } + } + + public enum InternetDialogEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The Internet dialog became visible on the screen.") + INTERNET_DIALOG_SHOW(843); + + private final int mId; + + InternetDialogEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java new file mode 100644 index 000000000000..24c2fb1eb3f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -0,0 +1,851 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.qs.tiles.dialog; + +import static com.android.settingslib.mobile.MobileMappings.getIconKey; +import static com.android.settingslib.mobile.MobileMappings.mapIconSets; + +import android.annotation.ColorInt; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.provider.Settings; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.widget.Toast; + +import com.android.internal.logging.UiEventLogger; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.DeviceInfoUtils; +import com.android.settingslib.Utils; +import com.android.settingslib.graph.SignalDrawable; +import com.android.settingslib.mobile.MobileMappings; +import com.android.settingslib.net.SignalStrengthUtil; +import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkController.AccessPointController; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.wifitrackerlib.MergedCarrierEntry; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +public class InternetDialogController implements WifiEntry.DisconnectCallback, + NetworkController.AccessPointController.AccessPointCallback { + + private static final String TAG = "InternetDialogController"; + private static final String ACTION_NETWORK_PROVIDER_SETTINGS = + "android.settings.NETWORK_PROVIDER_SETTINGS"; + private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; + public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); + public static final int NO_CELL_DATA_TYPE_ICON = 0; + private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off; + private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT = + R.string.tap_a_network_to_connect; + private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS = + R.string.wifi_empty_list_wifi_on; + private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE = + R.string.non_carrier_network_unavailable; + private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE = + R.string.all_network_unavailable; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private WifiManager mWifiManager; + private Context mContext; + private ActivityStarter mActivityStarter; + private SubscriptionManager mSubscriptionManager; + private TelephonyManager mTelephonyManager; + private ConnectivityManager mConnectivityManager; + private TelephonyDisplayInfo mTelephonyDisplayInfo = + new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE); + private Handler mHandler; + private MobileMappings.Config mConfig = null; + private Executor mExecutor; + private AccessPointController mAccessPointController; + private IntentFilter mWifiStateFilter; + private InternetDialogCallback mCallback; + private List<WifiEntry> mWifiEntry; + private WifiEntry mConnectedEntry; + private UiEventLogger mUiEventLogger; + private BroadcastDispatcher mBroadcastDispatcher; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private GlobalSettings mGlobalSettings; + private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + @VisibleForTesting + protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener; + @VisibleForTesting + protected InternetTelephonyCallback mInternetTelephonyCallback; + + private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onRefreshCarrierInfo() { + mCallback.onRefreshCarrierInfo(); + } + + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + mCallback.onSimStateChanged(); + } + }; + + protected List<SubscriptionInfo> getSubscriptionInfo() { + return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false); + } + + @Inject + public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger, + ActivityStarter starter, AccessPointController accessPointController, + SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, + @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager, + @Main Handler handler, @Main Executor mainExecutor, + BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor, + GlobalSettings globalSettings) { + if (DEBUG) { + Log.d(TAG, "Init InternetDialogController"); + } + mHandler = handler; + mExecutor = mainExecutor; + mContext = context; + mGlobalSettings = globalSettings; + mWifiManager = wifiManager; + mTelephonyManager = telephonyManager; + mConnectivityManager = connectivityManager; + mSubscriptionManager = subscriptionManager; + mBroadcastDispatcher = broadcastDispatcher; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mWifiStateFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mWifiStateFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mUiEventLogger = uiEventLogger; + mActivityStarter = starter; + mAccessPointController = accessPointController; + mConfig = MobileMappings.Config.readConfig(mContext); + } + + void onStart(@NonNull InternetDialogCallback callback) { + if (DEBUG) { + Log.d(TAG, "onStart"); + } + mCallback = callback; + mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); + mAccessPointController.addAccessPointCallback(this); + mBroadcastDispatcher.registerReceiver(mWifiStateReceiver, mWifiStateFilter, mExecutor); + // Listen the subscription changes + mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener(); + mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, + mOnSubscriptionsChangedListener); + mDefaultDataSubId = getDefaultDataSubscriptionId(); + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mInternetTelephonyCallback = new InternetTelephonyCallback(); + mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback); + // Listen the connectivity changes + mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(), new DataConnectivityListener(), mHandler); + scanWifiAccessPoints(); + } + + void onStop() { + if (DEBUG) { + Log.d(TAG, "onStop"); + } + mBroadcastDispatcher.unregisterReceiver(mWifiStateReceiver); + mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback); + mSubscriptionManager.removeOnSubscriptionsChangedListener( + mOnSubscriptionsChangedListener); + mAccessPointController.removeAccessPointCallback(this); + mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback); + } + + @VisibleForTesting + boolean isAirplaneModeEnabled() { + return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + + @VisibleForTesting + protected int getDefaultDataSubscriptionId() { + return mSubscriptionManager.getDefaultDataSubscriptionId(); + } + + @VisibleForTesting + protected Intent getSettingsIntent() { + return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + + CharSequence getDialogTitleText() { + if (isAirplaneModeEnabled()) { + return mContext.getText(R.string.airplane_mode); + } + return mContext.getText(R.string.quick_settings_internet_label); + } + + CharSequence getSubtitleText(boolean isProgressBarVisible) { + if (isAirplaneModeEnabled()) { + return null; + } + + if (!mWifiManager.isWifiEnabled()) { + // When the airplane mode is off and Wi-Fi is disabled. + // Sub-Title: Wi-Fi is off + if (DEBUG) { + Log.d(TAG, "Airplane mode off + Wi-Fi off."); + } + return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF); + } + + if (isProgressBarVisible) { + // When the Wi-Fi scan result callback is received + // Sub-Title: Searching for networks... + return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS); + } + + final List<ScanResult> wifiList = mWifiManager.getScanResults(); + if (wifiList != null && wifiList.size() != 0) { + return mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT); + } + + // Sub-Title: + // show non_carrier_network_unavailable + // - while Wi-Fi on + no Wi-Fi item + // - while Wi-Fi on + no Wi-Fi item + mobile data off + // show all_network_unavailable: + // - while Wi-Fi on + no Wi-Fi item + no carrier item + // - while Wi-Fi on + no Wi-Fi item + service is out of service + // - while Wi-Fi on + no Wi-Fi item + mobile data on + no carrier data. + if (DEBUG) { + Log.d(TAG, "No Wi-Fi item."); + } + if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) { + if (DEBUG) { + Log.d(TAG, "No carrier or service is out of service."); + } + return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE); + } + + if (!isMobileDataEnabled()) { + if (DEBUG) { + Log.d(TAG, "Mobile data off"); + } + return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE); + } + + if (!activeNetworkIsCellular()) { + if (DEBUG) { + Log.d(TAG, "No carrier data."); + } + return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE); + } + + return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE); + } + + Drawable getWifiConnectedDrawable(WifiEntry wifiEntry) throws Throwable { + final @ColorInt int tint; + tint = Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.colorAccentPrimaryVariant); + final Drawable drawable = mContext.getDrawable( + com.android.settingslib.Utils.getWifiIconResource(wifiEntry.getLevel())); + drawable.setTint(tint); + + return drawable; + } + + Drawable getSignalStrengthDrawable() { + Drawable drawable = mContext.getDrawable( + R.drawable.ic_signal_strength_zero_bar_no_internet); + try { + if (mTelephonyManager == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null"); + } + return drawable; + } + + if (isDataStateInService() || isVoiceStateInService()) { + AtomicReference<Drawable> shared = new AtomicReference<>(); + shared.set(getSignalStrengthDrawableWithLevel()); + drawable = shared.get(); + } + + drawable.setTint( + Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal)); + if (activeNetworkIsCellular()) { + drawable.setTint(Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.colorAccentPrimaryVariant)); + } + + } catch (Throwable e) { + e.printStackTrace(); + } + return drawable; + } + + /** + * To get the signal bar icon with level. + * + * @return The Drawable which is a signal bar icon with level. + */ + Drawable getSignalStrengthDrawableWithLevel() { + final SignalStrength strength = mTelephonyManager.getSignalStrength(); + int level = (strength == null) ? 0 : strength.getLevel(); + int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) { + level += 1; + numLevels += 1; + } + return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON, false); + } + + Drawable getSignalStrengthIcon(Context context, int level, int numLevels, + int iconType, boolean cutOut) { + Log.d(TAG, "getSignalStrengthIcon"); + final SignalDrawable signalDrawable = new SignalDrawable(context); + signalDrawable.setLevel( + SignalDrawable.getState(level, numLevels, cutOut)); + + // Make the network type drawable + final Drawable networkDrawable = + iconType == NO_CELL_DATA_TYPE_ICON + ? EMPTY_DRAWABLE + : context.getResources().getDrawable(iconType, context.getTheme()); + + // Overlay the two drawables + final Drawable[] layers = {networkDrawable, signalDrawable}; + final int iconSize = + context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size); + + final LayerDrawable icons = new LayerDrawable(layers); + // Set the network type icon at the top left + icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT); + // Set the signal strength icon at the bottom right + icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT); + icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize); + icons.setTintList(Utils.getColorAttr(context, android.R.attr.colorControlNormal)); + return icons; + } + + private boolean shouldInflateSignalStrength(int subId) { + return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId); + } + + private CharSequence getUniqueSubscriptionDisplayName(int subscriptionId, Context context) { + final Map<Integer, CharSequence> displayNames = getUniqueSubscriptionDisplayNames(context); + return displayNames.getOrDefault(subscriptionId, ""); + } + + private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) { + class DisplayInfo { + public SubscriptionInfo subscriptionInfo; + public CharSequence originalName; + public CharSequence uniqueName; + } + + // Map of SubscriptionId to DisplayName + final Supplier<Stream<DisplayInfo>> originalInfos = + () -> getSubscriptionInfo() + .stream() + .filter(i -> { + // Filter out null values. + return (i != null && i.getDisplayName() != null); + }) + .map(i -> { + DisplayInfo info = new DisplayInfo(); + info.subscriptionInfo = i; + info.originalName = i.getDisplayName().toString().trim(); + return info; + }); + + // A Unique set of display names + Set<CharSequence> uniqueNames = new HashSet<>(); + // Return the set of duplicate names + final Set<CharSequence> duplicateOriginalNames = originalInfos.get() + .filter(info -> !uniqueNames.add(info.originalName)) + .map(info -> info.originalName) + .collect(Collectors.toSet()); + + // If a display name is duplicate, append the final 4 digits of the phone number. + // Creates a mapping of Subscription id to original display name + phone number display name + final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> { + if (duplicateOriginalNames.contains(info.originalName)) { + // This may return null, if the user cannot view the phone number itself. + final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context, + info.subscriptionInfo); + String lastFourDigits = ""; + if (phoneNumber != null) { + lastFourDigits = (phoneNumber.length() > 4) + ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber; + } + + if (TextUtils.isEmpty(lastFourDigits)) { + info.uniqueName = info.originalName; + } else { + info.uniqueName = info.originalName + " " + lastFourDigits; + } + + } else { + info.uniqueName = info.originalName; + } + return info; + }); + + // Check uniqueness a second time. + // We might not have had permission to view the phone numbers. + // There might also be multiple phone numbers whose last 4 digits the same. + uniqueNames.clear(); + final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get() + .filter(info -> !uniqueNames.add(info.uniqueName)) + .map(info -> info.uniqueName) + .collect(Collectors.toSet()); + + return uniqueInfos.get().map(info -> { + if (duplicatePhoneNames.contains(info.uniqueName)) { + info.uniqueName = info.originalName + " " + + info.subscriptionInfo.getSubscriptionId(); + } + return info; + }).collect(Collectors.toMap( + info -> info.subscriptionInfo.getSubscriptionId(), + info -> info.uniqueName)); + } + + CharSequence getMobileNetworkTitle() { + return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext); + } + + String getMobileNetworkSummary() { + String description = getNetworkTypeDescription(mContext, mConfig, + mTelephonyDisplayInfo, mDefaultDataSubId); + return getMobileSummary(mContext, mTelephonyManager, description); + } + + /** + * Get currently description of mobile network type. + */ + private String getNetworkTypeDescription(Context context, MobileMappings.Config config, + TelephonyDisplayInfo telephonyDisplayInfo, int subId) { + String iconKey = getIconKey(telephonyDisplayInfo); + + if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) { + if (DEBUG) { + Log.d(TAG, "The description of network type is empty."); + } + return ""; + } + + int resId = mapIconSets(config).get(iconKey).dataContentDescription; + return resId != 0 + ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : ""; + } + + private String getMobileSummary(Context context, TelephonyManager telephonyManager, + String networkTypeDescription) { + if (!isMobileDataEnabled()) { + return context.getString(R.string.mobile_data_off_summary); + } + if (!isDataStateInService()) { + return context.getString(R.string.mobile_data_no_connection); + } + String summary = networkTypeDescription; + if (activeNetworkIsCellular()) { + summary = context.getString(R.string.preference_summary_default_combination, + context.getString(R.string.mobile_data_connection_active), + networkTypeDescription); + } + return summary; + } + + String getConnectedWifiTitle() { + if (getConnectedWifiEntry() == null) { + if (DEBUG) { + Log.d(TAG, "connected entry is null"); + } + return ""; + } + return getConnectedWifiEntry().getTitle(); + } + + String getConnectedWifiSummary() { + if (getConnectedWifiEntry() == null) { + if (DEBUG) { + Log.d(TAG, "connected entry is null"); + } + return ""; + } + return getConnectedWifiEntry().getSummary(false); + } + + void launchNetworkSetting() { + mCallback.dismissDialog(); + mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0); + } + + void connectCarrierNetwork() { + final MergedCarrierEntry mergedCarrierEntry = + mAccessPointController.getMergedCarrierEntry(); + if (mergedCarrierEntry != null && mergedCarrierEntry.canConnect()) { + mergedCarrierEntry.connect(null /* ConnectCallback */); + } + } + + List<WifiEntry> getWifiEntryList() { + return mWifiEntry; + } + + WifiEntry getConnectedWifiEntry() { + return mConnectedEntry; + } + + WifiManager getWifiManager() { + return mWifiManager; + } + + TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + SubscriptionManager getSubscriptionManager() { + return mSubscriptionManager; + } + + /** + * @return whether there is the carrier item in the slice. + */ + boolean hasCarrier() { + if (mSubscriptionManager == null) { + if (DEBUG) { + Log.d(TAG, "SubscriptionManager is null, can not check carrier."); + } + return false; + } + + if (isAirplaneModeEnabled() || mTelephonyManager == null + || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) { + return false; + } + return true; + } + + /** + * Return {@code true} if mobile data is enabled + */ + boolean isMobileDataEnabled() { + if (mTelephonyManager == null || !mTelephonyManager.isDataEnabled()) { + return false; + } + return true; + } + + /** + * Set whether to enable data for {@code subId}, also whether to disable data for other + * subscription + */ + void setMobileDataEnabled(Context context, int subId, boolean enabled, + boolean disableOtherSubscriptions) { + if (mTelephonyManager == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null, can not set mobile data."); + } + return; + } + + if (mSubscriptionManager == null) { + if (DEBUG) { + Log.d(TAG, "SubscriptionManager is null, can not set mobile data."); + } + return; + } + + mTelephonyManager.setDataEnabled(enabled); + if (disableOtherSubscriptions) { + final List<SubscriptionInfo> subInfoList = + mSubscriptionManager.getActiveSubscriptionInfoList(); + if (subInfoList != null) { + for (SubscriptionInfo subInfo : subInfoList) { + // We never disable mobile data for opportunistic subscriptions. + if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) { + context.getSystemService(TelephonyManager.class).createForSubscriptionId( + subInfo.getSubscriptionId()).setDataEnabled(false); + } + } + } + } + } + + boolean isDataStateInService() { + if (mTelephonyManager == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null, can not detect mobile state."); + } + return false; + } + return mTelephonyManager.getDataState() == TelephonyManager.DATA_CONNECTED; + } + + boolean isVoiceStateInService() { + if (mTelephonyManager == null) { + if (DEBUG) { + Log.d(TAG, "TelephonyManager is null, can not detect voice state."); + } + return false; + } + + final ServiceState serviceState = mTelephonyManager.getServiceState(); + return serviceState != null + && serviceState.getState() == serviceState.STATE_IN_SERVICE; + } + + boolean activeNetworkIsCellular() { + if (mConnectivityManager == null) { + if (DEBUG) { + Log.d(TAG, "ConnectivityManager is null, can not check active network."); + } + return false; + } + + final Network activeNetwork = mConnectivityManager.getActiveNetwork(); + if (activeNetwork == null) { + return false; + } + final NetworkCapabilities networkCapabilities = + mConnectivityManager.getNetworkCapabilities(activeNetwork); + if (networkCapabilities == null) { + return false; + } + return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); + } + + boolean connect(WifiEntry ap) { + if (ap == null) { + if (DEBUG) { + Log.d(TAG, "No Wi-Fi ap to connect."); + } + return false; + } + + if (ap.getWifiConfiguration() != null) { + if (DEBUG) { + Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId); + } + } else { + if (DEBUG) { + Log.d(TAG, "connect to unsaved network " + ap.getTitle()); + } + } + ap.connect(new WifiEntryConnectCallback(mActivityStarter, mContext, ap)); + return false; + } + + static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback { + final ActivityStarter mActivityStarter; + final Context mContext; + final WifiEntry mWifiEntry; + + WifiEntryConnectCallback(ActivityStarter activityStarter, Context context, + WifiEntry connectWifiEntry) { + mActivityStarter = activityStarter; + mContext = context; + mWifiEntry = connectWifiEntry; + } + + @Override + public void onConnectResult(@ConnectStatus int status) { + if (DEBUG) { + Log.d(TAG, "onConnectResult " + status); + } + + if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) { + final Intent intent = new Intent("com.android.settings.WIFI_DIALOG") + .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mActivityStarter.startActivity(intent, true); + } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { + Toast.makeText(mContext, R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + } else { + if (DEBUG) { + Log.d(TAG, "connect failure reason=" + status); + } + } + } + } + + void scanWifiAccessPoints() { + mAccessPointController.scanForAccessPoints(); + } + + @Override + public void onAccessPointsChanged(List<WifiEntry> accessPoints) { + if (accessPoints == null) { + return; + } + + boolean hasConnectedWifi = false; + mWifiEntry = accessPoints; + for (WifiEntry wifiEntry : accessPoints) { + if (wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) { + mConnectedEntry = wifiEntry; + hasConnectedWifi = true; + break; + } + } + if (!hasConnectedWifi) { + mConnectedEntry = null; + } + + mCallback.onAccessPointsChanged(mWifiEntry, mConnectedEntry); + } + + @Override + public void onSettingsActivityTriggered(Intent settingsIntent) { + } + + @Override + public void onDisconnectResult(int status) { + } + + private class InternetTelephonyCallback extends TelephonyCallback implements + TelephonyCallback.DataConnectionStateListener, + TelephonyCallback.DisplayInfoListener, + TelephonyCallback.ServiceStateListener, + TelephonyCallback.SignalStrengthsListener { + + @Override + public void onServiceStateChanged(@NonNull ServiceState serviceState) { + mCallback.onServiceStateChanged(serviceState); + } + + @Override + public void onDataConnectionStateChanged(int state, int networkType) { + mCallback.onDataConnectionStateChanged(state, networkType); + } + + @Override + public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) { + mCallback.onSignalStrengthsChanged(signalStrength); + } + + @Override + public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) { + mTelephonyDisplayInfo = telephonyDisplayInfo; + mCallback.onDisplayInfoChanged(telephonyDisplayInfo); + } + } + + private class InternetOnSubscriptionChangedListener + extends SubscriptionManager.OnSubscriptionsChangedListener { + InternetOnSubscriptionChangedListener() { + super(); + } + + @Override + public void onSubscriptionsChanged() { + mDefaultDataSubId = getDefaultDataSubscriptionId(); + if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) { + mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback); + + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mTelephonyManager.registerTelephonyCallback(mHandler::post, + mInternetTelephonyCallback); + mCallback.onSubscriptionsChanged(mDefaultDataSubId); + } + } + } + + private class DataConnectivityListener extends ConnectivityManager.NetworkCallback { + @Override + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities) { + final Network activeNetwork = mConnectivityManager.getActiveNetwork(); + if (activeNetwork != null && activeNetwork.equals(network)) { + // update UI + mCallback.onCapabilitiesChanged(network, networkCapabilities); + } + } + } + + private final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mCallback.onWifiStateReceived(context, intent); + } + }; + + interface InternetDialogCallback { + + void onRefreshCarrierInfo(); + + void onSimStateChanged(); + + void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities); + + void onSubscriptionsChanged(int defaultDataSubId); + + void onServiceStateChanged(ServiceState serviceState); + + void onDataConnectionStateChanged(int state, int networkType); + + void onSignalStrengthsChanged(SignalStrength signalStrength); + + void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo); + + void dismissDialog(); + + void onAccessPointsChanged(List<WifiEntry> wifiEntryList, WifiEntry connectedEntry); + + void onWifiStateReceived(Context context, Intent intent); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt new file mode 100644 index 000000000000..d68ad4ba48e3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.qs.tiles.dialog + +import android.content.Context +import android.os.Handler +import android.util.Log +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import javax.inject.Inject + +private const val TAG = "InternetDialogFactory" +private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + +/** + * Factory to create [InternetDialog] objects. + */ +@SysUISingleton +class InternetDialogFactory @Inject constructor( + @Main private val handler: Handler, + private val internetDialogController: InternetDialogController, + private val context: Context, + private val uiEventLogger: UiEventLogger +) { + companion object { + var internetDialog: InternetDialog? = null + } + + /** Creates a [InternetDialog]. */ + fun create(aboveStatusBar: Boolean) { + if (internetDialog != null) { + if (DEBUG) { + Log.d(TAG, "InternetDialog is showing, do not create it twice.") + } + return + } else { + internetDialog = InternetDialog(context, this, internetDialogController, aboveStatusBar, + uiEventLogger, handler) + } + } + + fun destroyDialog() { + if (DEBUG) { + Log.d(TAG, "destroyDialog") + } + internetDialog = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java new file mode 100644 index 000000000000..6aaba997faad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java @@ -0,0 +1,14 @@ +package com.android.systemui.qs.tiles.dialog; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +public class InternetDialogUtil { + + public static boolean isProviderModelEnabled(Context context) { + if (context == null) { + return false; + } + return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 0eaef72ae29b..31d51f1d1a60 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -154,6 +154,7 @@ public class LongScreenshotActivity extends Activity { @Override public void onStart() { super.onStart(); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED); if (mPreview.getDrawable() != null) { // We already have an image, so no need to try to load again. @@ -245,6 +246,8 @@ public class LongScreenshotActivity extends Activity { } private void onCachedImageLoaded(ImageLoader.Result imageResult) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED); + BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap); mPreview.setImageDrawable(drawable); mPreview.setAlpha(1f); @@ -282,6 +285,8 @@ public class LongScreenshotActivity extends Activity { finish(); } if (isFinishing()) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED); + if (mScrollCaptureResponse != null) { mScrollCaptureResponse.close(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java index 2b7bcc04e43c..169b28c6b373 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java @@ -77,7 +77,13 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "The long screenshot capture failed") SCREENSHOT_LONG_SCREENSHOT_FAILURE(881), @UiEvent(doc = "The long screenshot capture completed successfully") - SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882); + SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882), + @UiEvent(doc = "Long screenshot editor activity started") + SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED(889), + @UiEvent(doc = "Long screenshot editor activity loaded a previously saved screenshot") + SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED(890), + @UiEvent(doc = "Long screenshot editor activity finished") + SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED(891); private final int mId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index a0ef1b674af3..e26fa045d297 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -28,8 +28,6 @@ import static com.android.systemui.statusbar.notification.collection.listbuilder import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING; import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING; -import static java.util.Objects.requireNonNull; - import android.annotation.MainThread; import android.annotation.Nullable; import android.util.ArrayMap; @@ -344,14 +342,8 @@ public class ShadeListBuilder implements Dumpable { mPipelineState.incrementTo(STATE_GROUP_STABILIZING); stabilizeGroupingNotifs(mNotifList); - // Step 5: Sort - // Assign each top-level entry a section, then sort the list by section and then within - // section by our list of custom comparators - dispatchOnBeforeSort(mReadOnlyNotifList); - mPipelineState.incrementTo(STATE_SORTING); - sortList(); - // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting + // Step 5: Filter out entries after pre-group filtering, grouping and promoting // Now filters can see grouping information to determine whether to filter or not. dispatchOnBeforeFinalizeFilter(mReadOnlyNotifList); mPipelineState.incrementTo(STATE_FINALIZE_FILTERING); @@ -359,6 +351,13 @@ public class ShadeListBuilder implements Dumpable { applyNewNotifList(); pruneIncompleteGroups(mNotifList); + // Step 6: Sort + // Assign each top-level entry a section, then sort the list by section and then within + // section by our list of custom comparators + dispatchOnBeforeSort(mReadOnlyNotifList); + mPipelineState.incrementTo(STATE_SORTING); + sortList(); + // Step 7: Lock in our group structure and log anything that's changed since the last run mPipelineState.incrementTo(STATE_FINALIZING); logChanges(); @@ -837,8 +836,8 @@ public class ShadeListBuilder implements Dumpable { private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> { int cmp = Integer.compare( - requireNonNull(o1.getSection()).getIndex(), - requireNonNull(o2.getSection()).getIndex()); + o1.getSectionIndex(), + o2.getSectionIndex()); if (cmp == 0) { for (int i = 0; i < mNotifComparators.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java index 798bfe7f39d0..027ac0f66b35 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java @@ -82,8 +82,8 @@ public class PipelineState { public static final int STATE_GROUPING = 4; public static final int STATE_TRANSFORMING = 5; public static final int STATE_GROUP_STABILIZING = 6; - public static final int STATE_SORTING = 7; - public static final int STATE_FINALIZE_FILTERING = 8; + public static final int STATE_FINALIZE_FILTERING = 7; + public static final int STATE_SORTING = 8; public static final int STATE_FINALIZING = 9; @IntDef(prefix = { "STATE_" }, value = { @@ -94,8 +94,8 @@ public class PipelineState { STATE_GROUPING, STATE_TRANSFORMING, STATE_GROUP_STABILIZING, - STATE_SORTING, STATE_FINALIZE_FILTERING, + STATE_SORTING, STATE_FINALIZING, }) @Retention(RetentionPolicy.SOURCE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index c1a63e969d8e..ef1d75e9fdaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -53,6 +53,9 @@ class ShadeViewManager constructor( notifList.firstOrNull()?.section?.headerController?.let { children.add(NodeSpecImpl(this, it)) } + notifList.firstOrNull()?.let { + children.add(buildNotifNode(it, this)) + } notifList.asSequence().zipWithNext().forEach { (prev, entry) -> // Insert new header if the section has changed between two entries entry.section.takeIf { it != prev.section }?.headerController?.let { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index dba3401cc28e..9c755e970a0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -85,23 +85,26 @@ public abstract class StackScrollerDecorView extends ExpandableView { } /** - * Set the content of this view to be visible in an animated way. - * - * @param contentVisible True if the content should be visible or false if it should be hidden. + * @param visible True if we should animate contents visible */ - public void setContentVisible(boolean contentVisible) { - setContentVisible(contentVisible, true /* animate */); + public void setContentVisible(boolean visible) { + setContentVisible(visible, true /* animate */, null /* runAfter */); } + /** - * Set the content of this view to be visible. - * @param contentVisible True if the content should be visible or false if it should be hidden. - * @param animate Should an animation be performed. + * @param visible True if the contents should be visible + * @param animate True if we should fade to new visibility + * @param runAfter Runnable to run after visibility updates */ - private void setContentVisible(boolean contentVisible, boolean animate) { - if (mContentVisible != contentVisible) { + public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) { + if (mContentVisible != visible) { mContentAnimating = animate; - mContentVisible = contentVisible; - setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable); + mContentVisible = visible; + Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> { + mContentVisibilityEndRunnable.run(); + runAfter.run(); + }; + setViewVisible(mContent, visible, animate, endRunnable); } if (!mContentAnimating) { @@ -113,6 +116,10 @@ public abstract class StackScrollerDecorView extends ExpandableView { return mContentVisible; } + public void setVisible(boolean nowVisible, boolean animate) { + setVisible(nowVisible, animate, null); + } + /** * Make this view visible. If {@code false} is passed, the view will fade out it's content * and set the view Visibility to GONE. If only the content should be changed @@ -121,7 +128,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { * @param nowVisible should the view be visible * @param animate should the change be animated. */ - public void setVisible(boolean nowVisible, boolean animate) { + public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) { if (mIsVisible != nowVisible) { mIsVisible = nowVisible; if (animate) { @@ -132,10 +139,10 @@ public abstract class StackScrollerDecorView extends ExpandableView { } else { setWillBeGone(true); } - setContentVisible(nowVisible, true /* animate */); + setContentVisible(nowVisible, true /* animate */, runAfter); } else { setVisibility(nowVisible ? VISIBLE : GONE); - setContentVisible(nowVisible, false /* animate */); + setContentVisible(nowVisible, false /* animate */, runAfter); setWillBeGone(false); notifyHeightChanged(false /* needsAnimation */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 23aefd9bfd8e..594afceab63a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -46,6 +46,7 @@ public class NotificationRoundnessManager { private Runnable mRoundingChangedCallback; private ExpandableNotificationRow mTrackedHeadsUp; private float mAppearFraction; + private boolean mIsDismissAllInProgress; private ExpandableView mSwipedView = null; private ExpandableView mViewBeforeSwipedView = null; @@ -162,6 +163,10 @@ public class NotificationRoundnessManager { } } + void setDismissAllInProgress(boolean isClearingAll) { + mIsDismissAllInProgress = isClearingAll; + } + private float getRoundness(ExpandableView view, boolean top) { if (view == null) { return 0f; @@ -171,6 +176,11 @@ public class NotificationRoundnessManager { || view == mViewAfterSwipedView) { return 1f; } + if (view instanceof ExpandableNotificationRow + && ((ExpandableNotificationRow) view).canViewBeDismissed() + && mIsDismissAllInProgress) { + return 1.0f; + } if ((view.isPinned() || (view.isHeadsUpAnimatingAway()) && !mExpanded)) { return 1.0f; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index d19eb124a3e8..7babcba293be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -139,6 +139,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean( "persist.debug.nssl.dismiss", false /* default */); + // Delay in milli-seconds before shade closes for clear all. + private final int DELAY_BEFORE_SHADE_CLOSE = 200; + private boolean mShadeNeedsToClose = false; + private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; @@ -253,7 +257,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mDismissAllInProgress; - private boolean mFadeNotificationsOnDismiss; private FooterDismissListener mFooterDismissListener; private boolean mFlingAfterUpEvent; @@ -1205,7 +1208,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void clampScrollPosition() { int scrollRange = getScrollRange(); - if (scrollRange < mOwnScrollY) { + if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) { boolean animateStackY = false; if (scrollRange < getScrollAmountToScrollBoundary() && mAnimateStackYForContentHeightChange) { @@ -1721,6 +1724,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { + if (child instanceof SectionHeaderView) { + ((StackScrollerDecorView) child).setContentVisible( + false /* visible */, true /* animate */, endRunnable); + return; + } mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration, true /* isDismissAll */); } @@ -4062,6 +4070,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable runAnimationFinishedRunnables(); clearTransient(); clearHeadsUpDisappearRunning(); + + if (mAmbientState.isDismissAllInProgress()) { + setDismissAllInProgress(false); + + if (mShadeNeedsToClose) { + mShadeNeedsToClose = false; + postDelayed( + () -> { + mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + }, + DELAY_BEFORE_SHADE_CLOSE /* delayMillis */); + } + } } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4430,6 +4451,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void setDismissAllInProgress(boolean dismissAllInProgress) { mDismissAllInProgress = dismissAllInProgress; mAmbientState.setDismissAllInProgress(dismissAllInProgress); + mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress); handleDismissAllClipping(); } @@ -4973,129 +4995,137 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mHeadsUpAppearanceController = headsUpAppearanceController; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - @VisibleForTesting - void clearNotifications(@SelectedRows int selection, boolean closeShade) { - // animate-swipe all dismissable notifications, then animate the shade closed - int numChildren = getChildCount(); + private boolean isVisible(View child) { + boolean hasClipBounds = child.getClipBounds(mTmpRect); + return child.getVisibility() == View.VISIBLE + && (!hasClipBounds || mTmpRect.height() > 0); + } - final ArrayList<View> viewsToHide = new ArrayList<>(numChildren); - final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren); - for (int i = 0; i < numChildren; i++) { - final View child = getChildAt(i); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; - boolean parentVisible = false; - boolean hasClipBounds = child.getClipBounds(mTmpRect); - if (includeChildInDismissAll(row, selection)) { - viewsToRemove.add(row); - if (child.getVisibility() == View.VISIBLE - && (!hasClipBounds || mTmpRect.height() > 0)) { - viewsToHide.add(child); - parentVisible = true; - } - } else if (child.getVisibility() == View.VISIBLE - && (!hasClipBounds || mTmpRect.height() > 0)) { - parentVisible = true; - } - List<ExpandableNotificationRow> children = row.getAttachedChildren(); - if (children != null) { - for (ExpandableNotificationRow childRow : children) { - if (includeChildInDismissAll(row, selection)) { - viewsToRemove.add(childRow); - if (parentVisible && row.areChildrenExpanded()) { - hasClipBounds = childRow.getClipBounds(mTmpRect); - if (childRow.getVisibility() == View.VISIBLE - && (!hasClipBounds || mTmpRect.height() > 0)) { - viewsToHide.add(childRow); - } - } - } - } - } + private boolean shouldHideParent(View view, @SelectedRows int selection) { + final boolean silentSectionWillBeGone = + !mController.hasNotifications(ROWS_GENTLE, false /* clearable */); + + // The only SectionHeaderView we have is the silent section header. + if (view instanceof SectionHeaderView && silentSectionWillBeGone) { + return true; + } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (isVisible(row) && includeChildInDismissAll(row, selection)) { + return true; } } + return false; + } - if (mDismissListener != null) { - mDismissListener.onDismiss(selection); - } + private boolean isChildrenVisible(ExpandableNotificationRow parent) { + List<ExpandableNotificationRow> children = parent.getAttachedChildren(); + return isVisible(parent) + && children != null + && parent.areChildrenExpanded(); + } + + // Similar to #getRowsToDismissInBackend, but filters for visible views. + private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) { + final int viewCount = getChildCount(); + final ArrayList<View> viewsToHide = new ArrayList<>(viewCount); + + for (int i = 0; i < viewCount; i++) { + final View view = getChildAt(i); - if (viewsToRemove.isEmpty()) { - if (closeShade && mShadeController != null) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + if (shouldHideParent(view, selection)) { + viewsToHide.add(view); } - return; - } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - performDismissAllAnimations( - viewsToHide, - closeShade, - () -> onDismissAllAnimationsEnd(viewsToRemove, selection)); + if (isChildrenVisible(parent)) { + for (ExpandableNotificationRow child : parent.getAttachedChildren()) { + if (isVisible(child) && includeChildInDismissAll(child, selection)) { + viewsToHide.add(child); + } + } + } + } + } + return viewsToHide; } - private boolean includeChildInDismissAll( - ExpandableNotificationRow row, + private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend( @SelectedRows int selection) { - return canChildBeDismissed(row) && matchesSelection(row, selection); + final int childCount = getChildCount(); + final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount); + + for (int i = 0; i < childCount; i++) { + final View view = getChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + continue; + } + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + if (includeChildInDismissAll(parent, selection)) { + viewsToRemove.add(parent); + } + List<ExpandableNotificationRow> children = parent.getAttachedChildren(); + if (isVisible(parent) && children != null) { + for (ExpandableNotificationRow child : children) { + if (includeChildInDismissAll(parent, selection)) { + viewsToRemove.add(child); + } + } + } + } + return viewsToRemove; } /** - * Given a list of rows, animates them away in a staggered fashion as if they were dismissed. - * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete - * handler. - * - * @param hideAnimatedList List of rows to animated away. Should only be views that are - * currently visible, or else the stagger will look funky. - * @param closeShade Whether to close the shade after the stagger animation completes. - * @param onAnimationComplete Called after the entire animation completes (including the shade - * closing if appropriate). The rows must be dismissed for real here. + * Collects a list of visible rows, and animates them away in a staggered fashion as if they + * were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd. */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void performDismissAllAnimations( - final ArrayList<View> hideAnimatedList, - final boolean closeShade, - final Runnable onAnimationComplete) { - - final Runnable onSlideAwayAnimationComplete = () -> { - if (closeShade) { - mShadeController.addPostCollapseAction(() -> { - setDismissAllInProgress(false); - onAnimationComplete.run(); - }); - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_NONE); - } else { - setDismissAllInProgress(false); - onAnimationComplete.run(); - } + @VisibleForTesting + void clearNotifications(@SelectedRows int selection, boolean closeShade) { + // Animate-swipe all dismissable notifications, then animate the shade closed + final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection); + final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend = + getRowsToDismissInBackend(selection); + if (mDismissListener != null) { + mDismissListener.onDismiss(selection); + } + final Runnable dismissInBackend = () -> { + onDismissAllAnimationsEnd(rowsToDismissInBackend, selection); }; - - if (hideAnimatedList.isEmpty()) { - onSlideAwayAnimationComplete.run(); + if (viewsToAnimateAway.isEmpty()) { + dismissInBackend.run(); return; } - - // let's disable our normal animations + // Disable normal animations setDismissAllInProgress(true); + mShadeNeedsToClose = closeShade; // Decrease the delay for every row we animate to give the sense of // accelerating the swipes - int rowDelayDecrement = 10; - int currentDelay = 140; - int totalDelay = 180; - int numItems = hideAnimatedList.size(); + final int rowDelayDecrement = 5; + int currentDelay = 60; + int totalDelay = 0; + final int numItems = viewsToAnimateAway.size(); for (int i = numItems - 1; i >= 0; i--) { - View view = hideAnimatedList.get(i); + View view = viewsToAnimateAway.get(i); Runnable endRunnable = null; if (i == 0) { - endRunnable = onSlideAwayAnimationComplete; + endRunnable = dismissInBackend; } dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE); - currentDelay = Math.max(50, currentDelay - rowDelayDecrement); + currentDelay = Math.max(30, currentDelay - rowDelayDecrement); totalDelay += currentDelay; } } + private boolean includeChildInDismissAll( + ExpandableNotificationRow row, + @SelectedRows int selection) { + return canChildBeDismissed(row) && matchesSelection(row, selection); + } + /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */ public void setManageButtonClickListener(@Nullable OnClickListener listener) { mManageButtonClickListener = listener; @@ -5114,6 +5144,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mFooterDismissListener.onDismiss(); } clearNotifications(ROWS_ALL, true /* closeShade */); + footerView.setSecondaryVisible(false /* visible */, true /* animate */); }); setFooterView(footerView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 04129b52b4ea..f85c749b1f19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1152,6 +1152,10 @@ public class NotificationStackScrollLayoutController { mZenModeController.areNotificationsHiddenInShade()); } + public boolean areNotificationsHiddenInShade() { + return mZenModeController.areNotificationsHiddenInShade(); + } + public boolean isShowingEmptyShadeView() { return mShowEmptyShadeView; } @@ -1200,6 +1204,10 @@ public class NotificationStackScrollLayoutController { * Return whether there are any clearable notifications */ public boolean hasActiveClearableNotifications(@SelectedRows int selection) { + return hasNotifications(selection, true /* clearable */); + } + + public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { if (mDynamicPrivacyController.isInLockedDownShade()) { return false; } @@ -1210,8 +1218,11 @@ public class NotificationStackScrollLayoutController { continue; } final ExpandableNotificationRow row = (ExpandableNotificationRow) child; - if (row.canViewBeDismissed() && - NotificationStackScrollLayout.matchesSelection(row, selection)) { + final boolean matchClearable = + isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed(); + final boolean inSection = + NotificationStackScrollLayout.matchesSelection(row, selection); + if (matchClearable && inSection) { return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 2c810c93b2ee..8be5de7ae56e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -366,6 +366,20 @@ public class StackScrollAlgorithm { return stackHeight / stackEndHeight; } + public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) { + for (int i = 0; i < algorithmState.visibleChildren.size(); i++) { + View child = algorithmState.visibleChildren.get(i); + if (!(child instanceof ExpandableNotificationRow)) { + continue; + } + final ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (!row.canViewBeDismissed()) { + return true; + } + } + return false; + } + // TODO(b/172289889) polish shade open from HUN /** * Populates the {@link ExpandableViewState} for a single child. @@ -430,7 +444,9 @@ public class StackScrollAlgorithm { + view.getIntrinsicHeight(); final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); ((FooterView.FooterViewState) viewState).hideContent = - isShelfShowing || noSpaceForFooter; + isShelfShowing || noSpaceForFooter + || (ambientState.isDismissAllInProgress() + && !hasOngoingNotifs(algorithmState)); } } else { if (view != ambientState.getTrackedHeadsUpRow()) { @@ -467,7 +483,6 @@ public class StackScrollAlgorithm { } } } - // Clip height of view right before shelf. viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 2702bf7d31da..e3a4bf0170fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -46,7 +46,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_WAKEUP = 500; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; - public static final int ANIMATION_DURATION_SWIPE = 260; + public static final int ANIMATION_DURATION_SWIPE = 200; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index a56b8acf0470..c639eecf037c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -204,13 +204,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ControlsListingController.ControlsListingCallback mListingCallback = new ControlsListingController.ControlsListingCallback() { public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) { - boolean available = !serviceInfos.isEmpty(); + post(() -> { + boolean available = !serviceInfos.isEmpty(); - if (available != mControlServicesAvailable) { - mControlServicesAvailable = available; - updateControlsVisibility(); - updateAffordanceColors(); - } + if (available != mControlServicesAvailable) { + mControlServicesAvailable = available; + updateControlsVisibility(); + updateAffordanceColors(); + } + }); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index ef3dcedf8315..3e604ec98e05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -205,6 +205,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mView.setTopClipping(notificationPanelTop - mView.getTop()); } + /** + * Updates the {@link KeyguardStatusBarView} state based on the provided values. + */ + public void updateViewState(float alpha, int visibility) { + mView.setAlpha(alpha); + mView.setVisibility(visibility); + } + /** */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("KeyguardStatusBarView:"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 07508a6c31a0..d559cd1604d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -652,7 +652,7 @@ public class NotificationPanelViewController extends PanelViewController { private KeyguardMediaController mKeyguardMediaController; - private boolean mStatusViewCentered = false; + private boolean mStatusViewCentered = true; private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() { @Override @@ -1082,7 +1082,7 @@ public class NotificationPanelViewController extends PanelViewController { constraintSet.applyTo(mNotificationContainerParent); mNotificationContainerParent.setSplitShadeEnabled(mShouldUseSplitNotificationShade); - updateKeyguardStatusViewAlignment(false /* animate */); + updateKeyguardStatusViewAlignment(/* animate= */false); mLockscreenSmartspaceController.onSplitShadeChanged(mShouldUseSplitNotificationShade); mKeyguardMediaController.refreshMediaPosition(); } @@ -1129,6 +1129,9 @@ public class NotificationPanelViewController extends PanelViewController { keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate( R.layout.keyguard_status_view, mNotificationContainerParent, false); mNotificationContainerParent.addView(keyguardStatusView, statusIndex); + // When it's reinflated, this is centered by default. If it shouldn't be, this will update + // below when resources are updated. + mStatusViewCentered = true; attachSplitShadeMediaPlayerContainer( keyguardStatusView.findViewById(R.id.status_view_media_container)); @@ -4494,8 +4497,9 @@ public class NotificationPanelViewController extends PanelViewController { } } } else { - mKeyguardStatusBar.setAlpha(1f); - mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); + mKeyguardStatusBarViewController.updateViewState( + /* alpha= */ 1f, + keyguardShowing ? View.VISIBLE : View.INVISIBLE); if (keyguardShowing && oldState != mBarState) { if (mQs != null) { mQs.hideImmediately(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index e3319e5ef200..59da27af9376 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -402,7 +402,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; scheduleUpdate(); - } else if ((oldState == ScrimState.AOD // leaving doze + } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED)) || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) { // Scheduling a frame isn't enough when: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index e33c9f84aa73..850b98691e1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -229,7 +229,8 @@ public enum ScrimState { ? mKeyguardFadingAwayDuration : StatusBar.FADE_KEYGUARD_DURATION; - mAnimateChange = !mLaunchingAffordanceWithPreview; + boolean fromAod = previousState == AOD || previousState == PULSING; + mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod; mFrontTint = Color.TRANSPARENT; mBehindTint = Color.BLACK; 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 0cfdf79b5497..cadd6648a90b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1028,7 +1028,7 @@ public class StatusBar extends SystemUI implements mNotificationShadeWindowViewController, mNotificationPanelViewController, mAmbientIndicationContainer); - mDozeParameters.addCallback(this::updateLightRevealScrimVisibility); + updateLightRevealScrimVisibility(); mConfigurationController.addCallback(mConfigurationListener); @@ -4174,12 +4174,6 @@ public class StatusBar extends SystemUI implements } mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha()); - if (mFeatureFlags.useNewLockscreenAnimations() - && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) { - mLightRevealScrim.setVisibility(View.VISIBLE); - } else { - mLightRevealScrim.setVisibility(View.GONE); - } } private final KeyguardUpdateMonitorCallback mUpdateCallback = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 119eff6f96a0..4316ccfbb620 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -308,8 +308,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Sets a new alt auth interceptor. */ public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) { - mAlternateAuthInterceptor = authInterceptor; - resetAlternateAuth(false); + if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) { + mAlternateAuthInterceptor = authInterceptor; + resetAlternateAuth(false); + } } private void registerListeners() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 8ebaf9188e91..832f317d5783 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -312,17 +312,11 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, private void onNotificationRemoved(String key, StatusBarNotification old, int reason) { if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); - if (old != null) { - if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() - && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { - if (mStatusBarStateController.getState() == StatusBarState.SHADE - && reason != NotificationListenerService.REASON_CLICK) { - mCommandQueue.animateCollapsePanels(); - } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED + if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() + && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded() + && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED && !isCollapsing()) { - mStatusBarStateController.setState(StatusBarState.KEYGUARD); - } - } + mStatusBarStateController.setState(StatusBarState.KEYGUARD); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java index ab58286859cc..5a3d72555d76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java @@ -40,6 +40,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.wifitrackerlib.MergedCarrierEntry; import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiPickerTracker; @@ -157,6 +158,15 @@ public class AccessPointControllerImpl } @Override + public MergedCarrierEntry getMergedCarrierEntry() { + if (mWifiPickerTracker == null) { + fireAcccessPointsCallback(Collections.emptyList()); + return null; + } + return mWifiPickerTracker.getMergedCarrierEntry(); + } + + @Override public int getIcon(WifiEntry ap) { int level = ap.getLevel(); return ICONS[Math.max(0, level)]; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index ef2ca985858d..6b71f46238e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -23,6 +23,7 @@ import android.telephony.SubscriptionInfo; import com.android.settingslib.net.DataUsageController; import com.android.systemui.demomode.DemoMode; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.wifitrackerlib.MergedCarrierEntry; import com.android.wifitrackerlib.WifiEntry; import java.util.List; @@ -223,6 +224,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D void addAccessPointCallback(AccessPointCallback callback); void removeAccessPointCallback(AccessPointCallback callback); void scanForAccessPoints(); + MergedCarrierEntry getMergedCarrierEntry(); int getIcon(WifiEntry ap); boolean connect(WifiEntry ap); boolean canConfigWifi(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 00ada4fbc23a..c24fb4160e82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -68,9 +68,12 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.qs.tiles.dialog.InternetDialogUtil; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.telephony.TelephonyListenerManager; @@ -189,6 +192,8 @@ public class NetworkControllerImpl extends BroadcastReceiver private boolean mUserSetup; private boolean mSimDetected; private boolean mForceCellularValidated; + private InternetDialogFactory mInternetDialogFactory; + private Handler mMainHandler; private ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @@ -218,7 +223,9 @@ public class NetworkControllerImpl extends BroadcastReceiver AccessPointControllerImpl accessPointController, DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + @Main Handler handler, + InternetDialogFactory internetDialogFactory) { this(context, connectivityManager, telephonyManager, telephonyListenerManager, @@ -238,6 +245,8 @@ public class NetworkControllerImpl extends BroadcastReceiver carrierConfigTracker, featureFlags); mReceiverHandler.post(mRegisterListeners); + mMainHandler = handler; + mInternetDialogFactory = internetDialogFactory; } @VisibleForTesting @@ -472,6 +481,9 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); + if (InternetDialogUtil.isProviderModelEnabled(mContext)) { + filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY); + } mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; @@ -780,6 +792,9 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = Config.readConfig(mContext); mReceiverHandler.post(this::handleConfigurationChanged); break; + case Settings.Panel.ACTION_INTERNET_CONNECTIVITY: + mMainHandler.post(() -> mInternetDialogFactory.create(true)); + break; default: int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 35251002fb7b..891244823271 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -70,6 +70,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerIm import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler; +import com.android.systemui.volume.dagger.VolumeModule; import javax.inject.Named; @@ -83,7 +84,8 @@ import dagger.Provides; */ @Module(includes = { PowerModule.class, - QSModule.class + QSModule.class, + VolumeModule.class, }, subcomponents = { }) diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index f2e031cfa1df..c083c14a3730 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; -import android.media.AudioManager; import android.media.VolumePolicy; import android.os.Bundle; import android.view.WindowManager.LayoutParams; @@ -84,7 +83,8 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna DemoModeController demoModeController, PluginDependencyProvider pluginDependencyProvider, ExtensionController extensionController, - TunerService tunerService) { + TunerService tunerService, + VolumeDialog volumeDialog) { mContext = context; mKeyguardViewMediator = keyguardViewMediator; mActivityStarter = activityStarter; @@ -94,7 +94,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna pluginDependencyProvider.allowPluginDependency(VolumeDialogController.class); extensionController.newExtension(VolumeDialog.class) .withPlugin(VolumeDialog.class) - .withDefault(this::createDefault) + .withDefault(() -> volumeDialog) .withCallback(dialog -> { if (mDialog != null) { mDialog.destroy(); @@ -108,14 +108,6 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna demoModeController.addCallback(this); } - protected VolumeDialog createDefault() { - VolumeDialogImpl impl = new VolumeDialogImpl(mContext); - impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); - impl.setAutomute(true); - impl.setSilentMode(false); - return impl; - } - @Override public void onTuningChanged(String key, String newValue) { boolean volumeDownToEnterSilent = mVolumePolicy.volumeDownToEnterSilent; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index dbf115b62a58..58f74a0d2a02 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -106,7 +106,6 @@ import androidx.annotation.Nullable; import com.android.internal.graphics.drawable.BackgroundBlurDrawable; import com.android.internal.view.RotationPolicy; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -235,6 +234,10 @@ public class VolumeDialogImpl implements VolumeDialog, private final Object mSafetyWarningLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); + private final ConfigurationController mConfigurationController; + private final MediaOutputDialogFactory mMediaOutputDialogFactory; + private final ActivityStarter mActivityStarter; + private boolean mShowing; private boolean mShowA11yStream; @@ -256,14 +259,24 @@ public class VolumeDialogImpl implements VolumeDialog, private Consumer<Boolean> mCrossWindowBlurEnabledListener; private BackgroundBlurDrawable mDialogRowsViewBackground; - public VolumeDialogImpl(Context context) { + public VolumeDialogImpl( + Context context, + VolumeDialogController volumeDialogController, + AccessibilityManagerWrapper accessibilityManagerWrapper, + DeviceProvisionedController deviceProvisionedController, + ConfigurationController configurationController, + MediaOutputDialogFactory mediaOutputDialogFactory, + ActivityStarter activityStarter) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); - mController = Dependency.get(VolumeDialogController.class); + mController = volumeDialogController; mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); - mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); - mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); + mAccessibilityMgr = accessibilityManagerWrapper; + mDeviceProvisionedController = deviceProvisionedController; + mConfigurationController = configurationController; + mMediaOutputDialogFactory = mediaOutputDialogFactory; + mActivityStarter = activityStarter; mShowActiveStreamOnly = showActiveStreamOnly(); mHasSeenODICaptionsTooltip = Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); @@ -306,14 +319,14 @@ public class VolumeDialogImpl implements VolumeDialog, mController.addCallback(mControllerCallbackH, mHandler); mController.getState(); - Dependency.get(ConfigurationController.class).addCallback(this); + mConfigurationController.addCallback(this); } @Override public void destroy() { mController.removeCallback(mControllerCallbackH); mHandler.removeCallbacksAndMessages(null); - Dependency.get(ConfigurationController.class).removeCallback(this); + mConfigurationController.removeCallback(this); } @Override @@ -1017,9 +1030,8 @@ public class VolumeDialogImpl implements VolumeDialog, Events.writeEvent(Events.EVENT_SETTINGS_CLICK); Intent intent = new Intent(Settings.Panel.ACTION_VOLUME); dismissH(DISMISS_REASON_SETTINGS_CLICKED); - Dependency.get(MediaOutputDialogFactory.class).dismiss(); - Dependency.get(ActivityStarter.class).startActivity(intent, - true /* dismissShade */); + mMediaOutputDialogFactory.dismiss(); + mActivityStarter.startActivity(intent, true /* dismissShade */); }); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index 1ef4c169b786..79aa643fc6c2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -16,11 +16,23 @@ package com.android.systemui.volume.dagger; +import android.content.Context; +import android.media.AudioManager; + +import com.android.systemui.media.dialog.MediaOutputDialogFactory; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.VolumeDialog; +import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; +import com.android.systemui.volume.VolumeDialogImpl; import dagger.Binds; import dagger.Module; +import dagger.Provides; /** Dagger Module for code in the volume package. */ @@ -29,4 +41,28 @@ public interface VolumeModule { /** */ @Binds VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent); + + /** */ + @Provides + static VolumeDialog provideVolumeDialog( + Context context, + VolumeDialogController volumeDialogController, + AccessibilityManagerWrapper accessibilityManagerWrapper, + DeviceProvisionedController deviceProvisionedController, + ConfigurationController configurationController, + MediaOutputDialogFactory mediaOutputDialogFactory, + ActivityStarter activityStarter) { + VolumeDialogImpl impl = new VolumeDialogImpl( + context, + volumeDialogController, + accessibilityManagerWrapper, + deviceProvisionedController, + configurationController, + mediaOutputDialogFactory, + activityStarter); + impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); + impl.setAutomute(true); + impl.setSilentMode(false); + return impl; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 59010803db61..ae009bcc7cd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -417,6 +417,21 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException { + // GIVEN overlay was showing and the udfps bouncer is showing + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, + IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true); + + // WHEN the overlay is hidden + mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); + mFgExecutor.runAllReady(); + + // THEN the udfps bouncer is reset + verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true)); + } + + @Test public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception { mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java new file mode 100644 index 000000000000..d279bbb02cee --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.keyguard; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.os.Vibrator; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.LockIconView; +import com.android.keyguard.LockIconViewController; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class LockIconViewControllerTest extends SysuiTestCase { + private @Mock LockIconView mLockIconView; + private @Mock Context mContext; + private @Mock Resources mResources; + private @Mock DisplayMetrics mDisplayMetrics; + private @Mock StatusBarStateController mStatusBarStateController; + private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private @Mock KeyguardViewController mKeyguardViewController; + private @Mock KeyguardStateController mKeyguardStateController; + private @Mock FalsingManager mFalsingManager; + private @Mock AuthController mAuthController; + private @Mock DumpManager mDumpManager; + private @Mock AccessibilityManager mAccessibilityManager; + private @Mock ConfigurationController mConfigurationController; + private @Mock DelayableExecutor mDelayableExecutor; + private @Mock Vibrator mVibrator; + private @Mock AuthRippleController mAuthRippleController; + + private LockIconViewController mLockIconViewController; + + // Capture listeners so that they can be used to send events + @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); + private View.OnAttachStateChangeListener mAttachListener; + + @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor; + private AuthController.Callback mAuthControllerCallback; + + @Captor private ArgumentCaptor<PointF> mPointCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mLockIconView.getResources()).thenReturn(mResources); + when(mLockIconView.getContext()).thenReturn(mContext); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + + mLockIconViewController = new LockIconViewController( + mLockIconView, + mStatusBarStateController, + mKeyguardUpdateMonitor, + mKeyguardViewController, + mKeyguardStateController, + mFalsingManager, + mAuthController, + mDumpManager, + mAccessibilityManager, + mConfigurationController, + mDelayableExecutor, + mVibrator, + mAuthRippleController + ); + } + + @Test + public void testUpdateFingerprintLocationOnInit() { + // GIVEN fp sensor location is available pre-init + final PointF udfpsLocation = new PointF(50, 75); + final int radius = 33; + final FingerprintSensorPropertiesInternal fpProps = + new FingerprintSensorPropertiesInternal( + /* sensorId */ 0, + /* strength */ 0, + /* max enrollments per user */ 5, + /* component info */ new ArrayList<>(), + /* sensorType */ 3, + /* resetLockoutRequiresHwToken */ false, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); + when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); + + // WHEN lock icon view controller is initialized and attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(null); + + // THEN lock icon view location is updated with the same coordinates as fpProps + verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); + assertEquals(udfpsLocation, mPointCaptor.getValue()); + } + + @Test + public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() { + // GIVEN fp sensor location is not available pre-init + when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); + when(mAuthController.getUdfpsProps()).thenReturn(null); + mLockIconViewController.init(); + + // GIVEN fp sensor location is available post-init + captureAuthControllerCallback(); + final PointF udfpsLocation = new PointF(50, 75); + final int radius = 33; + final FingerprintSensorPropertiesInternal fpProps = + new FingerprintSensorPropertiesInternal( + /* sensorId */ 0, + /* strength */ 0, + /* max enrollments per user */ 5, + /* component info */ new ArrayList<>(), + /* sensorType */ 3, + /* resetLockoutRequiresHwToken */ false, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); + when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); + + // WHEN all authenticators are registered + mAuthControllerCallback.onAllAuthenticatorsRegistered(); + + // THEN lock icon view location is updated with the same coordinates as fpProps + verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); + assertEquals(udfpsLocation, mPointCaptor.getValue()); + } + + private void captureAuthControllerCallback() { + verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); + mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); + } + + private void captureAttachListener() { + verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture()); + mAttachListener = mAttachCaptor.getValue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java new file mode 100644 index 000000000000..a8f6f5361d78 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java @@ -0,0 +1,102 @@ +package com.android.systemui.qs.tiles.dialog; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.wifitrackerlib.WifiEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class InternetAdapterTest extends SysuiTestCase { + + private static final String WIFI_TITLE = "Wi-Fi Title"; + private static final String WIFI_SUMMARY = "Wi-Fi Summary"; + private InternetDialogController mInternetDialogController = mock( + InternetDialogController.class); + private InternetAdapter mInternetAdapter; + private InternetAdapter.InternetViewHolder mViewHolder; + @Mock + private WifiEntry mWifiEntry = mock(WifiEntry.class); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mInternetAdapter = new InternetAdapter(mInternetDialogController); + mViewHolder = (InternetAdapter.InternetViewHolder) mInternetAdapter + .onCreateViewHolder(new LinearLayout(mContext), 0); + when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE); + when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY); + when(mInternetDialogController.getWifiEntryList()).thenReturn(Arrays.asList(mWifiEntry)); + } + + @Test + public void getItemCount_withApmOnWifiOnNoConnectedWifi_returnFour() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(4); + } + + @Test + public void getItemCount_withApmOnWifiOnHasConnectedWifi_returnThree() { + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mInternetDialogController.getConnectedWifiEntry()).thenReturn(mWifiEntry); + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(3); + } + + @Test + public void getItemCount_withApmOffWifiOnNoConnectedWifi_returnThree() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(3); + } + + @Test + public void getItemCount_withApmOffWifiOnHasConnectedWifi_returnTwo() { + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mInternetDialogController.getConnectedWifiEntry()).thenReturn(mWifiEntry); + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(2); + } + + @Test + public void onBindViewHolder_bindWithOpenWifiNetwork_verifyView() { + when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_NONE); + mInternetAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiLockedIcon.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_bindWithSecurityWifiNetwork_verifyView() { + when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_PSK); + mInternetAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiLockedIcon.getVisibility()).isEqualTo(View.VISIBLE); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java new file mode 100644 index 000000000000..83e4d00b3a8f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -0,0 +1,269 @@ +package com.android.systemui.qs.tiles.dialog; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.text.TextUtils; + +import com.android.internal.logging.UiEventLogger; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkController.AccessPointController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.time.FakeSystemClock; +import com.android.wifitrackerlib.WifiEntry; + +import androidx.annotation.Nullable; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class InternetDialogControllerTest extends SysuiTestCase { + + private static final int SUB_ID = 1; + private static final String CONNECTED_TITLE = "Connected Wi-Fi Title"; + private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary"; + + private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); + private MockInternetDialogController mInternetDialogController; + private InternetDialogController.InternetDialogCallback mCallback = + mock(InternetDialogController.InternetDialogCallback.class); + private ActivityStarter mStarter = mock(ActivityStarter.class); + private WifiManager mWifiManager = mock(WifiManager.class); + private ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class); + private TelephonyManager mTelephonyManager = mock(TelephonyManager.class); + private SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class); + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + @Mock + private Handler mHandler; + @Mock + private GlobalSettings mGlobalSettings; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private NetworkController.AccessPointController mAccessPointController; + @Mock + private WifiEntry mWifiEntryConnected = mock(WifiEntry.class); + @Mock + private WifiInfo mWifiInfo; + @Mock + private ServiceState mServiceState; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID); + when(mWifiManager.getConnectionInfo()).thenReturn(mWifiInfo); + mInternetDialogController = new MockInternetDialogController(mContext, mUiEventLogger, + mStarter, mAccessPointController, mSubscriptionManager, mTelephonyManager, + mWifiManager, mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher, + mKeyguardUpdateMonitor, mGlobalSettings); + mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, + mInternetDialogController.mOnSubscriptionsChangedListener); + mInternetDialogController.onStart(mCallback); + } + + @Test + public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() { + mInternetDialogController.setAirplaneModeEnabled(true); + + assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(), + getResourcesString("airplane_mode"))); + } + + @Test + public void getDialogTitleText_withAirplaneModeOff_returnInternet() { + mInternetDialogController.setAirplaneModeEnabled(false); + + assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(), + getResourcesString("quick_settings_internet_label"))); + } + + @Test + public void getSubtitleText_withAirplaneModeOn_returnNull() { + mInternetDialogController.setAirplaneModeEnabled(true); + + assertThat(mInternetDialogController.getSubtitleText(false)).isNull(); + } + + @Test + public void getSubtitleText_withWifiOff_returnWifiIsOff() { + mInternetDialogController.setAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(false); + + assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false), + getResourcesString("wifi_is_off"))); + } + + @Test + public void getSubtitleText_withWifiOn_returnSearchWifi() { + mInternetDialogController.setAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(true); + + assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(true), + getResourcesString("wifi_empty_list_wifi_on"))); + } + + @Test + public void getSubtitleText_withWifiEntry_returnTapToConnect() { + mInternetDialogController.setAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(true); + List<ScanResult> wifiScanResults = mock(ArrayList.class); + doReturn(1).when(wifiScanResults).size(); + when(mWifiManager.getScanResults()).thenReturn(wifiScanResults); + + assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false), + getResourcesString("tap_a_network_to_connect"))); + } + + @Test + public void getSubtitleText_withNoService_returnNoNetworksAvailable() { + mInternetDialogController.setAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(true); + List<ScanResult> wifiScanResults = new ArrayList<>(); + doReturn(wifiScanResults).when(mWifiManager).getScanResults(); + when(mWifiManager.getScanResults()).thenReturn(wifiScanResults); + when(mSubscriptionManager.getActiveSubscriptionIdList()) + .thenReturn(new int[] {SUB_ID}); + + doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState(); + doReturn(mServiceState).when(mTelephonyManager).getServiceState(); + doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState(); + + assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false), + getResourcesString("all_network_unavailable"))); + } + + @Test + public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() { + mInternetDialogController.setAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(true); + List<ScanResult> wifiScanResults = new ArrayList<>(); + doReturn(wifiScanResults).when(mWifiManager).getScanResults(); + when(mWifiManager.getScanResults()).thenReturn(wifiScanResults); + when(mSubscriptionManager.getActiveSubscriptionIdList()) + .thenReturn(new int[] {SUB_ID}); + + doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState(); + doReturn(mServiceState).when(mTelephonyManager).getServiceState(); + + when(mTelephonyManager.isDataEnabled()).thenReturn(false); + + assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false), + getResourcesString("non_carrier_network_unavailable"))); + } + + @Test + public void getConnectedWifiTitle_withNoConnectedEntry_returnNull() { + mInternetDialogController.setConnectedWifiEntry(null); + + assertTrue(TextUtils.equals(mInternetDialogController.getConnectedWifiTitle(), + "")); + } + + @Test + public void getConnectedWifiTitle_withConnectedEntry_returnTitle() { + mInternetDialogController.setConnectedWifiEntry(mWifiEntryConnected); + when(mWifiEntryConnected.getTitle()).thenReturn(CONNECTED_TITLE); + + assertTrue(TextUtils.equals(mInternetDialogController.getConnectedWifiTitle(), + CONNECTED_TITLE)); + } + + @Test + public void getConnectedWifiSummary_withNoConnectedEntry_returnNull() { + mInternetDialogController.setConnectedWifiEntry(null); + + assertTrue(TextUtils.equals(mInternetDialogController.getConnectedWifiSummary(), + "")); + } + + @Test + public void getConnectedWifiSummary_withConnectedEntry_returnSummary() { + mInternetDialogController.setConnectedWifiEntry(mWifiEntryConnected); + when(mWifiEntryConnected.getSummary(false)).thenReturn(CONNECTED_SUMMARY); + + assertTrue(TextUtils.equals(mInternetDialogController.getConnectedWifiSummary(), + CONNECTED_SUMMARY)); + } + + private String getResourcesString(String name) { + return mContext.getResources().getString(getResourcesId(name)); + } + + private int getResourcesId(String name) { + return mContext.getResources().getIdentifier(name, "string", + mContext.getPackageName()); + } + + private class MockInternetDialogController extends InternetDialogController { + + private WifiEntry mConnectedEntry; + private GlobalSettings mGlobalSettings; + private boolean mIsAirplaneModeOn; + + MockInternetDialogController(Context context, UiEventLogger uiEventLogger, + ActivityStarter starter, AccessPointController accessPointController, + SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, + @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager, + @Main Handler handler, @Main Executor mainExecutor, + BroadcastDispatcher broadcastDispatcher, + KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings) { + super(context, uiEventLogger, starter, accessPointController, subscriptionManager, + telephonyManager, wifiManager, connectivityManager, handler, mainExecutor, + broadcastDispatcher, keyguardUpdateMonitor, globalSettings); + mGlobalSettings = globalSettings; + } + + @Override + boolean isAirplaneModeEnabled() { + return mIsAirplaneModeOn; + } + + public void setAirplaneModeEnabled(boolean enabled) { + mIsAirplaneModeOn = enabled; + } + + @Override + WifiEntry getConnectedWifiEntry() { + return mConnectedEntry; + } + + public void setConnectedWifiEntry(WifiEntry connectedEntry) { + mConnectedEntry = connectedEntry; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java new file mode 100644 index 000000000000..5052affee9de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -0,0 +1,245 @@ +package com.android.systemui.qs.tiles.dialog; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.telephony.TelephonyManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.wifitrackerlib.WifiEntry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class InternetDialogTest extends SysuiTestCase { + + private static final int SUB_ID = 1; + private static final String MOBILE_NETWORK_TITLE = "Mobile Title"; + private static final String MOBILE_NETWORK_SUMMARY = "Mobile Summary"; + private static final String WIFI_TITLE = "Connected Wi-Fi Title"; + private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary"; + + private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); + + private InternetDialogFactory mInternetDialogFactory = mock(InternetDialogFactory.class); + private InternetAdapter mInternetAdapter = mock(InternetAdapter.class); + private InternetDialogController mInternetDialogController = mock( + InternetDialogController.class); + private InternetDialogController.InternetDialogCallback mCallback = + mock(InternetDialogController.InternetDialogCallback.class); + private MockInternetDialog mInternetDialog; + private WifiReceiver mWifiReceiver = null; + private WifiManager mMockWifiManager = mock(WifiManager.class); + private TelephonyManager mTelephonyManager = mock(TelephonyManager.class); + @Mock + private WifiEntry mWifiEntry = mock(WifiEntry.class); + @Mock + private WifiInfo mWifiInfo; + @Mock + private Handler mHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mInternetDialog = new MockInternetDialog(mContext, mInternetDialogFactory, + mInternetDialogController, true, mUiEventLogger, mHandler); + doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID); + when(mMockWifiManager.isWifiEnabled()).thenReturn(true); + when(mMockWifiManager.getConnectionInfo()).thenReturn(mWifiInfo); + mInternetDialog.setMobileNetworkTitle(MOBILE_NETWORK_TITLE); + mInternetDialog.setMobileNetworkSummary(MOBILE_NETWORK_SUMMARY); + mInternetDialog.setConnectedWifiTitle(WIFI_TITLE); + mInternetDialog.setConnectedWifiSummary(WIFI_SUMMARY); + mWifiReceiver = new WifiReceiver(); + IntentFilter mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mContext.registerReceiver(mWifiReceiver, mIntentFilter); + when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE); + when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY); + when(mInternetDialogController.getWifiEntryList()).thenReturn(Arrays.asList(mWifiEntry)); + } + + @After + public void tearDown() { + mInternetDialog.dismissDialog(); + } + + @Test + public void updateDialog_withApmOn_internetDialogSubTitleGone() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + mInternetDialog.updateDialog(); + final TextView view = mInternetDialog.mDialogView.requireViewById( + R.id.internet_dialog_subtitle); + + assertThat(view.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_withApmOff_internetDialogSubTitleVisible() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); + mInternetDialog.updateDialog(); + final TextView view = mInternetDialog.mDialogView.requireViewById( + R.id.internet_dialog_subtitle); + + assertThat(view.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void updateDialog_withApmOn_mobileDataLayoutGone() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + mInternetDialog.updateDialog(); + final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById( + R.id.mobile_network_layout); + + assertThat(linearLayout.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_withWifiOnAndHasConnectedWifi_connectedWifiLayoutVisible() { + doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); + when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE); + when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY); + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mInternetDialogController.getConnectedWifiEntry()).thenReturn(mWifiEntry); + mInternetDialog.updateDialog(); + final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById( + R.id.wifi_connected_layout); + + assertThat(linearLayout.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void updateDialog_withWifiOnAndNoConnectedWifi_connectedWifiLayoutGone() { + doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); + mInternetDialog.updateDialog(); + final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById( + R.id.wifi_connected_layout); + + assertThat(linearLayout.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_withWifiOff_WifiRecycleViewGone() { + when(mMockWifiManager.isWifiEnabled()).thenReturn(false); + mInternetDialog.updateDialog(); + final RecyclerView view = mInternetDialog.mDialogView.requireViewById( + R.id.wifi_list_layout); + + assertThat(view.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onClickSeeMoreButton_clickSeeMore_verifyLaunchNetworkSetting() { + final LinearLayout seeAllLayout = mInternetDialog.mDialogView.requireViewById( + R.id.see_all_layout); + seeAllLayout.performClick(); + + verify(mInternetDialogController).launchNetworkSetting(); + } + + private class MockInternetDialog extends InternetDialog { + + private String mMobileNetworkTitle; + private String mMobileNetworkSummary; + private String mConnectedWifiTitle; + private String mConnectedWifiSummary; + + MockInternetDialog(Context context, InternetDialogFactory internetDialogFactory, + InternetDialogController internetDialogController, + boolean aboveStatusBar, UiEventLogger uiEventLogger, @Main Handler handler) { + super(context, internetDialogFactory, internetDialogController, aboveStatusBar, + uiEventLogger, handler); + mAdapter = mInternetAdapter; + mWifiManager = mMockWifiManager; + } + + @Override + String getMobileNetworkTitle() { + return mMobileNetworkTitle; + } + + @Override + String getMobileNetworkSummary() { + return mMobileNetworkSummary; + } + + void setMobileNetworkTitle(String title) { + mMobileNetworkTitle = title; + } + + void setMobileNetworkSummary(String summary) { + mMobileNetworkSummary = summary; + } + + @Override + String getConnectedWifiTitle() { + return mConnectedWifiTitle; + } + + @Override + String getConnectedWifiSummary() { + return mConnectedWifiSummary; + } + + void setConnectedWifiTitle(String title) { + mConnectedWifiTitle = title; + } + + void setConnectedWifiSummary(String summary) { + mConnectedWifiSummary = summary; + } + + @Override + public void onWifiStateReceived(Context context, Intent intent) { + setMobileNetworkTitle(MOBILE_NETWORK_TITLE); + setMobileNetworkSummary(MOBILE_NETWORK_SUMMARY); + } + } + + private class WifiReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + return; + } + + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mInternetDialog.updateDialog(); + } + } + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 2ce22a6f71c9..3378003b0d44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -802,13 +802,13 @@ public class ShadeListBuilderTest extends SysuiTestCase { .onBeforeTransformGroups(anyList()); inOrder.verify(promoter, atLeastOnce()) .shouldPromoteToTopLevel(any(NotificationEntry.class)); + inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList()); + inOrder.verify(preRenderFilter, atLeastOnce()) + .shouldFilterOut(any(NotificationEntry.class), anyLong()); inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList()); inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class)); inOrder.verify(comparator, atLeastOnce()) .compare(any(ListEntry.class), any(ListEntry.class)); - inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList()); - inOrder.verify(preRenderFilter, atLeastOnce()) - .shouldFilterOut(any(NotificationEntry.class), anyLong()); inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList()); inOrder.verify(mOnRenderListListener).onRenderList(anyList()); } @@ -1045,12 +1045,32 @@ public class ShadeListBuilderTest extends SysuiTestCase { // because group changes aren't allowed by the stability manager verifyBuiltList( notif(0), + notif(2), group( summary(3), child(4), child(5) - ), - notif(2) + ) + ); + } + + @Test + public void testBrokenGroupNotificationOrdering() { + // GIVEN two group children with different sections & without a summary yet + + addGroupChild(0, PACKAGE_2, GROUP_1); + addNotif(1, PACKAGE_1); + addGroupChild(2, PACKAGE_2, GROUP_1); + addGroupChild(3, PACKAGE_2, GROUP_1); + + dispatchBuild(); + + // THEN all notifications are not grouped and posted in order by index + verifyBuiltList( + notif(0), + notif(1), + notif(2), + notif(3) ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 85e17ba441fa..d92816e190f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; +import android.view.View; import androidx.test.filters.SmallTest; @@ -162,4 +163,18 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(0); } + + @Test + public void updateViewState_alphaAndVisibilityGiven_viewUpdated() { + // Verify the initial values so we know the method triggers changes. + assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f); + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE); + + float newAlpha = 0.5f; + int newVisibility = View.INVISIBLE; + mController.updateViewState(newAlpha, newVisibility); + + assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(newAlpha); + assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(newVisibility); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 195390347d1c..69e9d939ac33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -20,6 +20,8 @@ import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE; import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -741,9 +743,9 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test - public void transitionToUnlockedFromAod() { - // Simulate unlock with fingerprint - mScrimController.transitionTo(ScrimState.AOD); + public void transitionToUnlockedFromOff() { + // Simulate unlock with fingerprint without AOD + mScrimController.transitionTo(ScrimState.OFF); mScrimController.setPanelExpansion(0f); finishAnimationsImmediately(); mScrimController.transitionTo(ScrimState.UNLOCKED); @@ -769,6 +771,28 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void transitionToUnlockedFromAod() { + // Simulate unlock with fingerprint + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.setPanelExpansion(0f); + finishAnimationsImmediately(); + mScrimController.transitionTo(ScrimState.UNLOCKED); + + finishAnimationsImmediately(); + + // All scrims should be transparent at the end of fade transition. + assertScrimAlpha(Map.of( + mScrimInFront, TRANSPARENT, + mScrimBehind, TRANSPARENT)); + + // Make sure at the very end of the animation, we're reset to transparent + assertScrimTinted(Map.of( + mScrimInFront, false, + mScrimBehind, true + )); + } + + @Test public void scrimBlanksBeforeLeavingAod() { // Simulate unlock with fingerprint mScrimController.transitionTo(ScrimState.AOD); @@ -1082,6 +1106,26 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void testDoesntAnimate_whenUnlocking() { + // LightRevealScrim will animate the transition, we should only hide the keyguard scrims. + ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + ScrimState.UNLOCKED.prepare(ScrimState.PULSING); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse(); + + ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + ScrimState.UNLOCKED.prepare(ScrimState.AOD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse(); + + // LightRevealScrim doesn't animate when AOD is disabled. We need to use the legacy anim. + ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + ScrimState.UNLOCKED.prepare(ScrimState.OFF); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + } + + @Test public void testScrimsVisible_whenShadeVisible_clippingQs() { mScrimController.setClipsQsScrim(true); mScrimController.transitionTo(ScrimState.UNLOCKED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index dd4830e893af..949345662e79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -41,9 +41,13 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.dialog.MediaOutputDialogFactory; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import org.junit.Before; import org.junit.Test; @@ -68,23 +72,34 @@ public class VolumeDialogImplTest extends SysuiTestCase { View mDrawerNormal; @Mock - VolumeDialogController mController; - + VolumeDialogController mVolumeDialogController; @Mock KeyguardManager mKeyguard; - @Mock AccessibilityManagerWrapper mAccessibilityMgr; + @Mock + DeviceProvisionedController mDeviceProvisionedController; + @Mock + ConfigurationController mConfigurationController; + @Mock + MediaOutputDialogFactory mMediaOutputDialogFactory; + @Mock + ActivityStarter mActivityStarter; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); - mController = mDependency.injectMockDependency(VolumeDialogController.class); - mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); getContext().addMockSystemService(KeyguardManager.class, mKeyguard); - mDialog = new VolumeDialogImpl(getContext()); + mDialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mActivityStarter); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -170,7 +185,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { Mockito.reset(mAccessibilityMgr); ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture = ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class); - verify(mController).addCallback(controllerCallbackCapture.capture(), any()); + verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any()); VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue(); callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI); verify(mAccessibilityMgr).getRecommendedTimeoutMillis( @@ -201,13 +216,13 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.onStateChangedH(initialSilentState); // expected: shouldn't call vibrate yet - verify(mController, never()).vibrate(any()); + verify(mVolumeDialogController, never()).vibrate(any()); // changed ringer to vibrate mDialog.onStateChangedH(vibrateState); // expected: vibrate device - verify(mController).vibrate(any()); + verify(mVolumeDialogController).vibrate(any()); } @Test @@ -225,7 +240,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.onStateChangedH(vibrateState); // shouldn't call vibrate - verify(mController, never()).vibrate(any()); + verify(mVolumeDialogController, never()).vibrate(any()); } @Test @@ -238,7 +253,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDrawerVibrate.performClick(); // Make sure we've actually changed the ringer mode. - verify(mController, times(1)).setRingerMode( + verify(mVolumeDialogController, times(1)).setRingerMode( AudioManager.RINGER_MODE_VIBRATE, false); } @@ -252,7 +267,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDrawerMute.performClick(); // Make sure we've actually changed the ringer mode. - verify(mController, times(1)).setRingerMode( + verify(mVolumeDialogController, times(1)).setRingerMode( AudioManager.RINGER_MODE_SILENT, false); } @@ -266,7 +281,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDrawerNormal.performClick(); // Make sure we've actually changed the ringer mode. - verify(mController, times(1)).setRingerMode( + verify(mVolumeDialogController, times(1)).setRingerMode( AudioManager.RINGER_MODE_NORMAL, false); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d3dc72e63126..73bcea6de115 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -190,7 +190,9 @@ public final class DisplayManagerService extends SystemService { private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top"; private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000; - private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f; + // This value needs to be in sync with the threshold + // in RefreshRateConfigs::getFrameRateDivider. + private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.0009f; private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1; private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2; @@ -826,7 +828,7 @@ public final class DisplayManagerService extends SystemService { // Override the refresh rate only if it is a divider of the current // refresh rate. This calculation needs to be in sync with the native code - // in RefreshRateConfigs::getRefreshRateDividerForUid + // in RefreshRateConfigs::getFrameRateDivider Display.Mode currentMode = info.getMode(); float numPeriods = currentMode.getRefreshRate() / frameRateHz; float numPeriodsRound = Math.round(numPeriods); diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index ce1592d87fcd..c715c39c7359 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -59,6 +59,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS; @@ -89,6 +91,7 @@ import android.util.ArrayMap; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -157,6 +160,9 @@ class ActivityMetricsLogger { private final ArrayList<TransitionInfo> mTransitionInfoList = new ArrayList<>(); /** Map : Last launched activity => {@link TransitionInfo} */ private final ArrayMap<ActivityRecord, TransitionInfo> mLastTransitionInfo = new ArrayMap<>(); + /** SparseArray : Package UID => {@link PackageCompatStateInfo} */ + private final SparseArray<PackageCompatStateInfo> mPackageUidToCompatStateInfo = + new SparseArray<>(0); private ArtManagerInternal mArtManagerInternal; private final StringBuilder mStringBuilder = new StringBuilder(); @@ -452,6 +458,15 @@ class ActivityMetricsLogger { } } + /** Information about the App Compat state logging associated with a package UID . */ + private static final class PackageCompatStateInfo { + /** All activities that have a visible state. */ + final ArrayList<ActivityRecord> mVisibleActivities = new ArrayList<>(); + /** The last logged state. */ + int mLastLoggedState = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + @Nullable ActivityRecord mLastLoggedActivity; + } + ActivityMetricsLogger(ActivityTaskSupervisor supervisor, Looper looper) { mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000; mSupervisor = supervisor; @@ -703,6 +718,7 @@ class ActivityMetricsLogger { // Always calculate the delay because the caller may need to know the individual drawn time. info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs); info.removePendingDrawActivity(r); + info.updatePendingDraw(false /* keepInitializing */); final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info); if (info.mLoggedTransitionStarting && info.allDrawn()) { done(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs); @@ -774,6 +790,21 @@ class ActivityMetricsLogger { /** Makes sure that the reference to the removed activity is cleared. */ void notifyActivityRemoved(@NonNull ActivityRecord r) { mLastTransitionInfo.remove(r); + + final int packageUid = r.info.applicationInfo.uid; + final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid); + if (compatStateInfo == null) { + return; + } + + compatStateInfo.mVisibleActivities.remove(r); + if (compatStateInfo.mLastLoggedActivity == r) { + compatStateInfo.mLastLoggedActivity = null; + } + if (compatStateInfo.mVisibleActivities.isEmpty()) { + // No need to keep the entry if there are no visible activities. + mPackageUidToCompatStateInfo.remove(packageUid); + } } /** @@ -1270,6 +1301,115 @@ class ActivityMetricsLogger { memoryStat.swapInBytes); } + /** + * Logs the current App Compat state of the given {@link ActivityRecord} with its package + * UID, if all of the following hold: + * <ul> + * <li>The current state is different than the last logged state for the package UID of the + * activity. + * <li>If the current state is NOT_VISIBLE, there is a previously logged state for the + * package UID and there are no other visible activities with the same package UID. + * <li>The last logged activity with the same package UID is either {@code activity} or the + * last logged state is NOT_VISIBLE or NOT_LETTERBOXED. + * </ul> + * + * <p>If the current state is NOT_VISIBLE and the previous state which was logged by {@code + * activity} wasn't, looks for the first visible activity with the same package UID that has + * a letterboxed state, or a non-letterboxed state if there isn't one, and logs that state. + * + * <p>This method assumes that the caller is wrapping the call with a synchronized block so + * that there won't be a race condition between two activities with the same package. + */ + void logAppCompatState(@NonNull ActivityRecord activity) { + final int packageUid = activity.info.applicationInfo.uid; + final int state = activity.getAppCompatState(); + + if (!mPackageUidToCompatStateInfo.contains(packageUid)) { + mPackageUidToCompatStateInfo.put(packageUid, new PackageCompatStateInfo()); + } + final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid); + final int lastLoggedState = compatStateInfo.mLastLoggedState; + final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity; + + final boolean isVisible = state != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities; + if (isVisible && !visibleActivities.contains(activity)) { + visibleActivities.add(activity); + } else if (!isVisible) { + visibleActivities.remove(activity); + if (visibleActivities.isEmpty()) { + // No need to keep the entry if there are no visible activities. + mPackageUidToCompatStateInfo.remove(packageUid); + } + } + + if (state == lastLoggedState) { + // We don’t want to log the same state twice or log DEFAULT_NOT_VISIBLE before any + // visible state was logged. + return; + } + + if (!isVisible && !visibleActivities.isEmpty()) { + // There is another visible activity for this package UID. + if (activity == lastLoggedActivity) { + // Make sure a new visible state is logged if needed. + findAppCompatStateToLog(compatStateInfo, packageUid); + } + return; + } + + if (activity != lastLoggedActivity + && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE + && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED) { + // Another visible activity for this package UID has logged a letterboxed state. + return; + } + + logAppCompatStateInternal(activity, state, packageUid, compatStateInfo); + } + + /** + * Looks for the first visible activity in {@code compatStateInfo} that has a letterboxed + * state, or a non-letterboxed state if there isn't one, and logs that state for the given + * {@code packageUid}. + */ + private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) { + final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities; + + ActivityRecord activityToLog = null; + int stateToLog = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + for (int i = 0; i < visibleActivities.size(); i++) { + ActivityRecord activity = visibleActivities.get(i); + int state = activity.getAppCompatState(); + if (state == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) { + // This shouldn't happen. + Slog.w(TAG, "Visible activity with NOT_VISIBLE App Compat state for package UID: " + + packageUid); + continue; + } + if (stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE || ( + stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED + && state != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED)) { + activityToLog = activity; + stateToLog = state; + } + } + if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) { + logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo); + } + } + + private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state, + int packageUid, PackageCompatStateInfo compatStateInfo) { + compatStateInfo.mLastLoggedState = state; + compatStateInfo.mLastLoggedActivity = activity; + FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state); + + if (DEBUG_METRICS) { + Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state)); + } + } + private ArtManagerInternal getArtManagerInternal() { if (mArtManagerInternal == null) { // Note that this may be null. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 856c1e02c99f..1218466854ce 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -127,6 +127,11 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.ActivityRecord.State.DESTROYED; @@ -676,6 +681,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mLastImeShown; /** + * When set to true, the IME insets will be frozen until the next app becomes IME input target. + * @see InsetsPolicy#adjustVisibilityForIme + */ + boolean mImeInsetsFrozenUntilStartInput; + + /** * A flag to determine if this AR is in the process of closing or entering PIP. This is needed * to help AR know that the app is in the process of closing but hasn't yet started closing on * the WM side. @@ -1460,6 +1471,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A associateStartingDataWithTask(); overrideConfigurationPropagation(mStartingWindow, task); } + mImeInsetsFrozenUntilStartInput = false; } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -3928,7 +3940,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (tStartingWindow != null && fromActivity.mStartingSurface != null) { // In this case, the starting icon has already been displayed, so start // letting windows get shown immediately without any more transitions. - getDisplayContent().mSkipAppTransitionAnimation = true; + if (fromActivity.mVisible) { + mDisplayContent.mSkipAppTransitionAnimation = true; + } ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving existing starting %s" + " from %s to %s", tStartingWindow, fromActivity, this); @@ -4683,6 +4697,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED); mTaskSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this); mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true; + logAppCompatState(); } @VisibleForTesting @@ -4938,6 +4953,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && imeInputTarget.getWindow().mActivityRecord == this && mDisplayContent.mInputMethodWindow != null && mDisplayContent.mInputMethodWindow.isVisible(); + mImeInsetsFrozenUntilStartInput = true; } final DisplayContent displayContent = getDisplayContent(); @@ -6069,6 +6085,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // closing activity having to wait until idle timeout to be stopped or destroyed if the // next activity won't report idle (e.g. repeated view animation). mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded(); + + // If the activity is visible, but no windows are eligible to start input, unfreeze + // to avoid permanently frozen IME insets. + if (mImeInsetsFrozenUntilStartInput && getWindow( + win -> WindowManager.LayoutParams.mayUseInputMethod(win.mAttrs.flags)) + == null) { + mImeInsetsFrozenUntilStartInput = false; + } } } @@ -7420,6 +7444,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } + + logAppCompatState(); } /** @@ -7429,18 +7455,55 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * LetterboxUiController#shouldShowLetterboxUi} for more context. */ boolean areBoundsLetterboxed() { + return getAppCompatState(/* ignoreVisibility= */ true) + != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; + } + + /** + * Logs the current App Compat state via {@link ActivityMetricsLogger#logAppCompatState}. + */ + private void logAppCompatState() { + mTaskSupervisor.getActivityMetricsLogger().logAppCompatState(this); + } + + /** + * Returns the current App Compat state of this activity. + * + * <p>The App Compat state indicates whether the activity is visible and letterboxed, and if so + * what is the reason for letterboxing. The state is used for logging the time spent in + * letterbox (sliced by the reason) vs non-letterbox per app. + */ + int getAppCompatState() { + return getAppCompatState(/* ignoreVisibility= */ false); + } + + /** + * Same as {@link #getAppCompatState()} except when {@code ignoreVisibility} the visibility + * of the activity is ignored. + * + * @param ignoreVisibility whether to ignore the visibility of the activity and not return + * NOT_VISIBLE if {@code mVisibleRequested} is false. + */ + private int getAppCompatState(boolean ignoreVisibility) { + if (!ignoreVisibility && !mVisibleRequested) { + return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; + } if (mInSizeCompatModeForBounds) { - return true; + return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; } // Letterbox for fixed orientation. This check returns true only when an activity is // letterboxed for fixed orientation. Aspect ratio restrictions are also applied if // present. But this doesn't return true when the activity is letterboxed only because // of aspect ratio restrictions. if (isLetterboxedForFixedOrientationAndAspectRatio()) { - return true; + return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; } // Letterbox for limited aspect ratio. - return mIsAspectRatioApplied; + if (mIsAspectRatioApplied) { + return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; + } + + return APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; } /** @@ -8053,6 +8116,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @Override + void onResize() { + // Reset freezing IME insets flag when the activity resized. + mImeInsetsFrozenUntilStartInput = false; + super.onResize(); + } + /** Returns true if the configuration is compatible with this activity. */ boolean isConfigurationCompatible(Configuration config) { final int orientation = getRequestedOrientation(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4aea942cce3b..74287c415f30 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4022,6 +4022,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void updateImeInputAndControlTarget(WindowState target) { if (mImeInputTarget != target) { ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target); + if (target != null && target.mActivityRecord != null) { + target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; + } setImeInputTarget(target); updateImeControlTarget(); } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 0a837849d5e7..869d351df7b3 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -213,7 +213,7 @@ class InsetsPolicy { InsetsState getInsetsForWindow(WindowState target) { final InsetsState originalState = mStateController.getInsetsForWindow(target); final InsetsState state = adjustVisibilityForTransientTypes(originalState); - return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state; + return adjustVisibilityForIme(target, state, state == originalState); } /** @@ -243,16 +243,37 @@ class InsetsPolicy { return state; } - // Navigation bar insets is always visible to IME. - private static InsetsState adjustVisibilityForIme(InsetsState originalState, + private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState, boolean copyState) { - final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR); - if (originalNavSource != null && !originalNavSource.isVisible()) { - final InsetsState state = copyState ? new InsetsState(originalState) : originalState; - final InsetsSource navSource = new InsetsSource(originalNavSource); - navSource.setVisible(true); - state.addSource(navSource); - return state; + if (w.mIsImWindow) { + // Navigation bar insets is always visible to IME. + final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR); + if (originalNavSource != null && !originalNavSource.isVisible()) { + final InsetsState state = copyState ? new InsetsState(originalState) + : originalState; + final InsetsSource navSource = new InsetsSource(originalNavSource); + navSource.setVisible(true); + state.addSource(navSource); + return state; + } + } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) { + // During switching tasks with gestural navigation, if the IME is attached to + // one app window on that time, even the next app window is behind the IME window, + // conceptually the window should not receive the IME insets if the next window is + // not eligible IME requester and ready to show IME on top of it. + final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp(); + final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME); + + if (shouldImeAttachedToApp && originalImeSource != null) { + final boolean imeVisibility = + w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME); + final InsetsState state = copyState ? new InsetsState(originalState) + : originalState; + final InsetsSource imeSource = new InsetsSource(originalImeSource); + imeSource.setVisible(imeVisibility); + state.addSource(imeSource); + return state; + } } return originalState; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 6e4792bdbd31..0b9d3dc9d043 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -324,6 +324,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment. if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { mAdjacentTaskFragment.mAdjacentTaskFragment = null; + mAdjacentTaskFragment.mDelayLastActivityRemoval = false; } mAdjacentTaskFragment = null; mDelayLastActivityRemoval = false; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 89a126b92bfa..0b918025bb50 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -476,6 +476,16 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { } @Test + public void testConsecutiveLaunch() { + mTrampolineActivity.setState(ActivityRecord.State.INITIALIZING, "test"); + onActivityLaunched(mTrampolineActivity); + mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent, + mTrampolineActivity /* caller */, mTrampolineActivity.getUid()); + notifyActivityLaunched(START_SUCCESS, mTopActivity); + transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); + } + + @Test public void testConsecutiveLaunchNewTask() { final IBinder launchCookie = mock(IBinder.class); final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 9ca09d20cd49..1b0aead8a70f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -40,6 +40,7 @@ import static android.os.Process.NOBODY_UID; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -2509,8 +2510,10 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTransferStartingWindow() { registerTestStartingWindowOrganizer(); - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); - final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true) + .setVisible(false).build(); activity1.addStartingWindow(mPackageName, android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, false, false); @@ -2519,6 +2522,7 @@ public class ActivityRecordTests extends WindowTestsBase { android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1, true, true, false, true, false, false); waitUntilHandlersIdle(); + assertFalse(mDisplayContent.mSkipAppTransitionAnimation); assertNoStartingWindow(activity1); assertHasStartingWindow(activity2); } @@ -2626,6 +2630,7 @@ public class ActivityRecordTests extends WindowTestsBase { false /* newTask */, false /* isTaskSwitch */, null /* options */, null /* sourceRecord */); + assertTrue(mDisplayContent.mSkipAppTransitionAnimation); assertNull(middle.mStartingWindow); assertHasStartingWindow(top); assertTrue(top.isVisible()); @@ -2907,6 +2912,73 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(activity.mDisplayContent.mClosingApps.contains(activity)); } + @Test + public void testImeInsetsFrozenFlag_resetWhenReparented() { + final ActivityRecord activity = createActivityWithTask(); + final WindowState app = createWindow(null, TYPE_APPLICATION, activity, "app"); + final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow"); + final Task newTask = new TaskBuilder(mSupervisor).build(); + makeWindowVisible(app, imeWindow); + mDisplayContent.mInputMethodWindow = imeWindow; + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + + // Simulate app is closing and expect the last IME is shown and IME insets is frozen. + app.mActivityRecord.commitVisibility(false, false); + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Expect IME insets frozen state will reset when the activity is reparent to the new task. + activity.setState(RESUMED, "test"); + activity.reparent(newTask, 0 /* top */, "test"); + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + } + + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testImeInsetsFrozenFlag_resetWhenResized() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + makeWindowVisibleAndDrawn(app, mImeWindow); + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + + // Simulate app is closing and expect the last IME is shown and IME insets is frozen. + app.mActivityRecord.commitVisibility(false, false); + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Expect IME insets frozen state will reset when the activity is reparent to the new task. + app.mActivityRecord.onResize(); + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + } + + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + makeWindowVisibleAndDrawn(app, mImeWindow); + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + + // Simulate app is closing and expect the last IME is shown and IME insets is frozen. + app.mActivityRecord.commitVisibility(false, false); + app.mActivityRecord.onWindowsGone(); + + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Expect IME insets frozen state will reset when the activity has no IME focusable window. + app.mActivityRecord.forAllWindowsUnchecked(w -> { + w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; + return true; + }, true); + + app.mActivityRecord.commitVisibility(true, false); + app.mActivityRecord.onWindowsVisible(); + + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + } + private void assertHasStartingWindow(ActivityRecord atoken) { assertNotNull(atoken.mStartingSurface); assertNotNull(atoken.mStartingData); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 3bebf6b39de1..6407c92ee2aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -40,6 +40,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityRecord.State.STOPPED; @@ -55,7 +60,9 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doCallRealMethod; @@ -101,10 +108,14 @@ public class SizeCompatTests extends WindowTestsBase { private Task mTask; private ActivityRecord mActivity; + private ActivityMetricsLogger mActivityMetricsLogger; private Properties mInitialConstrainDisplayApisFlags; @Before public void setUp() throws Exception { + mActivityMetricsLogger = mock(ActivityMetricsLogger.class); + clearInvocations(mActivityMetricsLogger); + doReturn(mActivityMetricsLogger).when(mAtm.mTaskSupervisor).getActivityMetricsLogger(); mInitialConstrainDisplayApisFlags = DeviceConfig.getProperties( NAMESPACE_CONSTRAIN_DISPLAY_APIS); DeviceConfig.setProperties( @@ -1939,13 +1950,19 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 2500); assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT); assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO); + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE); rotateDisplay(mActivity.mDisplayContent, ROTATION_0); // After returning to the original rotation, bounds are computed in @@ -1955,6 +1972,18 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO); + + // After setting the visibility of the activity to false, areBoundsLetterboxed() still + // returns true but the NOT_VISIBLE App Compat state is logged. + mActivity.setVisibility(false); + assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE); + mActivity.setVisibility(true); + assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO); } @Test @@ -1963,12 +1992,15 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION); } @Test @@ -1978,12 +2010,15 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); assertFalse(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED); rotateDisplay(mActivity.mDisplayContent, ROTATION_90); assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertTrue(mActivity.inSizeCompatMode()); assertTrue(mActivity.areBoundsLetterboxed()); + verifyLogAppCompatState(mActivity, + APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE); } /** @@ -2197,6 +2232,11 @@ public class SizeCompatTests extends WindowTestsBase { .isEqualTo(activity.getWindowConfiguration().getBounds()); } + private void verifyLogAppCompatState(ActivityRecord activity, int state) { + verify(mActivityMetricsLogger, atLeastOnce()).logAppCompatState( + argThat(r -> activity == r && r.getAppCompatState() == state)); + } + static Configuration rotateDisplay(DisplayContent display, int rotation) { final Configuration c = new Configuration(); display.getDisplayRotation().setRotation(rotation); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index a8e17534e6ed..8e7ba4bc3293 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -258,6 +258,7 @@ public class SystemServicesTestRule implements TestRule { final ActivityManagerInternal amInternal = mAmService.mInternal; spyOn(amInternal); doNothing().when(amInternal).trimApplications(); + doNothing().when(amInternal).scheduleAppGcs(); doNothing().when(amInternal).updateCpuStats(); doNothing().when(amInternal).updateOomAdj(); doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 17288c21adc8..b17ea5e48db5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -892,6 +892,40 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)); } + @Test + public void testAdjustImeInsetsVisibilityWhenSwitchingApps() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow"); + spyOn(imeWindow); + doReturn(true).when(imeWindow).isVisible(); + mDisplayContent.mInputMethodWindow = imeWindow; + + final InsetsStateController controller = mDisplayContent.getInsetsStateController(); + controller.getImeSourceProvider().setWindow(imeWindow, null, null); + + // Simulate app requests IME with updating all windows Insets State when IME is above app. + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + assertTrue(mDisplayContent.shouldImeAttachedToApp()); + controller.getImeSourceProvider().scheduleShowImePostLayout(app); + controller.getImeSourceProvider().getSource().setVisible(true); + controller.updateAboveInsetsState(imeWindow, false); + + // Expect all app windows behind IME can receive IME insets visible. + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); + assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible()); + + // Simulate app plays closing transition to app2. + app.mActivityRecord.commitVisibility(false, false); + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Verify the IME insets is visible on app, but not for app2 during app task switching. + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); + assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible()); + } + @UseTestDisplay(addWindows = { W_ACTIVITY }) @Test public void testUpdateImeControlTargetWhenLeavingMultiWindow() { |