summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--core/api/current.txt9
-rw-r--r--core/java/android/view/InputWindowHandle.java14
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java30
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java49
-rw-r--r--core/java/com/android/ims/internal/uce/common/CapInfo.java270
-rw-r--r--core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java19
-rw-r--r--core/java/com/android/ims/internal/uce/presence/PresSipResponse.java20
-rw-r--r--core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java19
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/android_media_AudioDeviceAttributes.cpp27
-rw-r--r--core/jni/android_media_AudioDeviceAttributes.h3
-rw-r--r--core/jni/android_media_AudioSystem.cpp84
-rw-r--r--core/res/res/values/ids.xml9
-rw-r--r--core/res/res/values/public.xml3
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java17
-rw-r--r--media/java/android/media/AudioSystem.java41
-rw-r--r--media/java/android/media/PlayerBase.java7
-rw-r--r--packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml2
-rw-r--r--packages/SystemUI/res/drawable/ic_arrow_forward.xml26
-rw-r--r--packages/SystemUI/res/drawable/ic_friction_lock_closed.xml26
-rw-r--r--packages/SystemUI/res/drawable/ic_settings_24dp.xml29
-rw-r--r--packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml34
-rw-r--r--packages/SystemUI/res/drawable/internet_dialog_background.xml23
-rw-r--r--packages/SystemUI/res/drawable/internet_dialog_footer_background.xml30
-rw-r--r--packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml22
-rw-r--r--packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml33
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_off.xml31
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_on.xml31
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_selector.xml22
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_off_background.xml24
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_on_background.xml24
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_selector.xml22
-rw-r--r--packages/SystemUI/res/layout/internet_connectivity_dialog.xml351
-rw-r--r--packages/SystemUI/res/layout/internet_list_item.xml99
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml3
-rw-r--r--packages/SystemUI/res/values/colors.xml14
-rw-r--r--packages/SystemUI/res/values/dimens.xml31
-rw-r--r--packages/SystemUI/res/values/strings.xml33
-rw-r--r--packages/SystemUI/res/values/styles.xml39
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconView.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java228
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java552
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java851
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java219
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java189
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java269
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java245
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java41
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java140
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java78
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java3
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java41
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java76
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java34
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() {