summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ambientcontext/AmbientContextManager.java2
-rw-r--r--core/java/android/util/FeatureFlagUtils.java7
-rw-r--r--core/java/com/android/internal/widget/LocalImageResolver.java65
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java47
-rw-r--r--data/etc/com.android.systemui.xml1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java4
-rw-r--r--packages/SystemUI/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip.xml4
-rw-r--r--packages/SystemUI/res/layout/volume_panel_dialog.xml101
-rw-r--r--packages/SystemUI/res/layout/volume_panel_slice_slider_row.xml31
-rw-r--r--packages/SystemUI/res/values/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml5
-rw-r--r--packages/SystemUI/res/values/styles.xml24
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/ChooserSelector.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java190
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java299
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumePanelSlicesAdapter.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt179
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt125
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt227
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt68
-rw-r--r--services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java42
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java113
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java21
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java7
-rw-r--r--services/core/xsd/display-device-config/autobrightness.xsd33
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd71
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt15
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java64
-rw-r--r--services/tests/servicestests/src/com/android/server/ambientcontext/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java105
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java3
67 files changed, 2226 insertions, 691 deletions
diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java
index 7f913e798bc9..dd1dd0c75f76 100644
--- a/core/java/android/app/ambientcontext/AmbientContextManager.java
+++ b/core/java/android/app/ambientcontext/AmbientContextManager.java
@@ -153,7 +153,7 @@ public final class AmbientContextManager {
* eventTypes.add(AmbientContextEvent.EVENT_SNORE);
*
* // Create Consumer
- * Consumer<Integer> statusConsumer = response -> {
+ * Consumer<Integer> statusConsumer = status -> {
* int status = status.getStatusCode();
* if (status == AmbientContextManager.STATUS_SUCCESS) {
* // Show user it's enabled
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index e0e41d07d973..976a3e4d4cde 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -58,6 +58,12 @@ public class FeatureFlagUtils {
public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED =
"settings_app_locale_opt_in_enabled";
+ /**
+ * Launch the Volume panel in SystemUI.
+ * @hide
+ */
+ public static final String SETTINGS_VOLUME_PANEL_IN_SYSTEMUI =
+ "settings_volume_panel_in_systemui";
/** @hide */
public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
@@ -105,6 +111,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
DEFAULT_FLAGS.put("settings_search_always_expand", "true");
DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
+ DEFAULT_FLAGS.put(SETTINGS_VOLUME_PANEL_IN_SYSTEMUI, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b11ea2961c17..9ef7ce38fc09 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -19,6 +19,8 @@ package com.android.internal.widget;
import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.ImageDecoder;
@@ -109,13 +111,13 @@ public class LocalImageResolver {
}
break;
case Icon.TYPE_RESOURCE:
- if (!(TextUtils.isEmpty(icon.getResPackage())
- || context.getPackageName().equals(icon.getResPackage()))) {
- // We can't properly resolve icons from other packages here, so fall back.
+ Resources res = resolveResourcesForIcon(context, icon);
+ if (res == null) {
+ // We couldn't resolve resources properly, fall back to icon loading.
return icon.loadDrawable(context);
}
- Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
+ Drawable result = resolveImage(res, icon.getResId(), maxWidth, maxHeight);
if (result != null) {
return tintDrawable(icon, result);
}
@@ -159,6 +161,13 @@ public class LocalImageResolver {
}
@Nullable
+ private static Drawable resolveImage(Resources res, @DrawableRes int resId, int maxWidth,
+ int maxHeight) {
+ final ImageDecoder.Source source = ImageDecoder.createSource(res, resId);
+ return resolveImage(source, maxWidth, maxHeight);
+ }
+
+ @Nullable
private static Drawable resolveBitmapImage(Icon icon, Context context, int maxWidth,
int maxHeight) {
@@ -259,4 +268,52 @@ public class LocalImageResolver {
}
return icon.getUri();
}
+
+ /**
+ * Resolves the correct resources package for a given Icon - it may come from another
+ * package.
+ *
+ * @see Icon#loadDrawableInner(Context)
+ * @hide
+ *
+ * @return resources instance if the operation succeeded, null otherwise
+ */
+ @Nullable
+ @VisibleForTesting
+ public static Resources resolveResourcesForIcon(Context context, Icon icon) {
+ if (icon.getType() != Icon.TYPE_RESOURCE) {
+ return null;
+ }
+
+ // Icons cache resolved resources, use cache if available.
+ Resources res = icon.getResources();
+ if (res != null) {
+ return res;
+ }
+
+ String resPackage = icon.getResPackage();
+ // No package means we try to use current context.
+ if (TextUtils.isEmpty(resPackage) || context.getPackageName().equals(resPackage)) {
+ return context.getResources();
+ }
+
+ if ("android".equals(resPackage)) {
+ return Resources.getSystem();
+ }
+
+ final PackageManager pm = context.getPackageManager();
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(resPackage,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.GET_SHARED_LIBRARY_FILES);
+ if (ai != null) {
+ return pm.getResourcesForApplication(ai);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, String.format("Unable to resolve package %s for icon %s", resPackage, icon));
+ return null;
+ }
+
+ return null;
+ }
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 88206270ca7f..8152b79c8659 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1565,7 +1565,8 @@
<bool name="config_enableIdleScreenBrightnessMode">false</bool>
<!-- Array of desired screen brightness in nits corresponding to the lux values
- in the config_autoBrightnessLevels array. The display brightness is defined as the measured
+ in the config_autoBrightnessLevels array. As with config_screenBrightnessMinimumNits and
+ config_screenBrightnessMaximumNits, the display brightness is defined as the measured
brightness of an all-white image.
If this is defined then:
@@ -1586,7 +1587,7 @@
<array name="config_autoBrightnessDisplayValuesNitsIdle">
</array>
- <!-- Array of output values for button backlight corresponding to the lux values
+ <!-- Array of output values for button backlight corresponding to the luX values
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
The brightness values must be between 0 and 255 and be non-decreasing.
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index 0cee526651a6..271a20b71106 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -17,6 +17,8 @@
package com.android.internal.widget;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
@@ -279,4 +281,49 @@ public class LocalImageResolverTest {
// This drawable must not be loaded - if it was, the code ignored the package specification.
assertThat(d).isNull();
}
+
+ @Test
+ public void resolveResourcesForIcon_notAResourceIcon_returnsNull() {
+ Icon icon = Icon.createWithContentUri(Uri.parse("some_uri"));
+ assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isNull();
+ }
+
+ @Test
+ public void resolveResourcesForIcon_localPackageIcon_returnsPackageResources() {
+ Icon icon = Icon.createWithResource(mContext, R.drawable.test32x24);
+ assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon))
+ .isSameInstanceAs(mContext.getResources());
+ }
+
+ @Test
+ public void resolveResourcesForIcon_iconWithoutPackageSpecificed_returnsPackageResources() {
+ Icon icon = Icon.createWithResource("", R.drawable.test32x24);
+ assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon))
+ .isSameInstanceAs(mContext.getResources());
+ }
+
+ @Test
+ public void resolveResourcesForIcon_systemPackageSpecified_returnsSystemPackage() {
+ Icon icon = Icon.createWithResource("android", R.drawable.test32x24);
+ assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isSameInstanceAs(
+ Resources.getSystem());
+ }
+
+ @Test
+ public void resolveResourcesForIcon_differentPackageSpecified_returnsPackageResources() throws
+ PackageManager.NameNotFoundException {
+ String pkg = "com.android.settings";
+ Resources res = mContext.getPackageManager().getResourcesForApplication(pkg);
+ int resId = res.getIdentifier("ic_android", "drawable", pkg);
+ Icon icon = Icon.createWithResource(pkg, resId);
+
+ assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon).getDrawable(resId,
+ mContext.getTheme())).isNotNull();
+ }
+
+ @Test
+ public void resolveResourcesForIcon_invalidPackageSpecified_returnsNull() {
+ Icon icon = Icon.createWithResource("invalid.package", R.drawable.test32x24);
+ assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isNull();
+ }
}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index f030d80a3533..e0e13f59b706 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -81,5 +81,6 @@
<permission name="android.permission.READ_DEVICE_CONFIG" />
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
+ <permission name="android.permission.READ_SEARCH_INDEXABLES" />
</privapp-permissions>
</permissions>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 0d75bc451b72..f1465f421c5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -105,8 +105,8 @@ public class FullscreenTaskListener<T extends AutoCloseable>
state.mTaskInfo = taskInfo;
mTasks.put(taskInfo.taskId, state);
- updateRecentsForVisibleFullscreenTask(taskInfo);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ updateRecentsForVisibleFullscreenTask(taskInfo);
if (shouldShowWindowDecor(taskInfo) && mWindowDecorViewModelOptional.isPresent()) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
state.mWindowDecoration =
@@ -135,8 +135,8 @@ public class FullscreenTaskListener<T extends AutoCloseable>
mWindowDecorViewModelOptional.get().onTaskInfoChanged(
state.mTaskInfo, state.mWindowDecoration);
}
- updateRecentsForVisibleFullscreenTask(taskInfo);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ updateRecentsForVisibleFullscreenTask(taskInfo);
final Point positionInParent = state.mTaskInfo.positionInParent;
if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 78dea891bc12..abcd65b5071a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -85,7 +85,6 @@
<uses-permission android:name="android.permission.CONTROL_VPN" />
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
<uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
- <uses-permission android:name="android.permission.NETWORK_STACK"/>
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -152,6 +151,9 @@
<uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
+ <!-- For auto-grant the access to the Settings' slice preferences, e.g. volume slices. -->
+ <uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" />
+
<!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
@@ -956,5 +958,13 @@
</intent-filter>
</receiver>
+ <receiver android:name=".volume.VolumePanelDialogReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.settings.panel.action.VOLUME" />
+ <action android:name="com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG" />
+ <action android:name="com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG" />
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index d88680669fe0..ae8e38e2634b 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -16,7 +16,7 @@
<!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
is the root view of a window, we cannot change the root view's margins.) -->
<!-- Alphas start as 0 because the view will be animated in. -->
-<FrameLayout
+<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/media_ttt_sender_chip"
@@ -97,4 +97,4 @@
/>
</LinearLayout>
-</FrameLayout>
+</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
diff --git a/packages/SystemUI/res/layout/volume_panel_dialog.xml b/packages/SystemUI/res/layout/volume_panel_dialog.xml
new file mode 100644
index 000000000000..99a1b5cc19dd
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_panel_dialog.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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/volume_panel_dialog"
+ android:layout_width="@dimen/large_dialog_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Widget.SliceView.Panel"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_marginTop="@dimen/dialog_top_padding"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/volume_panel_dialog_title"
+ android:ellipsize="end"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sound_settings"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"/>
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/volume_panel_parent_layout"
+ android:scrollbars="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:minHeight="304dp"
+ android:layout_weight="1"
+ android:overScrollMode="never"/>
+
+ <LinearLayout
+ android:id="@+id/button_layout"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/dialog_button_vertical_padding"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:baselineAligned="false"
+ android:clickable="false"
+ android:focusable="false">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="start|center_vertical"
+ android:orientation="vertical">
+ <Button
+ android:id="@+id/settings_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/volume_panel_dialog_settings_button"
+ android:ellipsize="end"
+ android:maxLines="1"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:clickable="true"
+ android:focusable="true"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/dialog_button_horizontal_padding"
+ android:layout_gravity="end|center_vertical">
+ <Button
+ android:id="@+id/done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/inline_done_button"
+ style="@style/Widget.Dialog.Button"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:clickable="true"
+ android:focusable="true"/>
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/volume_panel_slice_slider_row.xml b/packages/SystemUI/res/layout/volume_panel_slice_slider_row.xml
new file mode 100644
index 000000000000..d1303ed88964
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_panel_slice_slider_row.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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/slice_slider_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <androidx.slice.widget.SliceView
+ android:id="@+id/slice_view"
+ style="@style/Widget.SliceView.Panel.Slider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/volume_panel_slice_vertical_padding"
+ android:paddingHorizontal="@dimen/volume_panel_slice_horizontal_padding"/>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c9776dd58788..98202371d98f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -479,6 +479,10 @@
<dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen>
+ <!-- Volume panel slices dimensions -->
+ <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
+ <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+
<!-- Size of each item in the ringer selector drawer. -->
<dimen name="volume_ringer_drawer_item_size">42dp</dimen>
<dimen name="volume_ringer_drawer_item_size_half">21dp</dimen>
@@ -1178,7 +1182,6 @@
<item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item>
<!-- Output switcher panel related dimensions -->
- <dimen name="media_output_dialog_list_margin">12dp</dimen>
<dimen name="media_output_dialog_list_max_height">355dp</dimen>
<dimen name="media_output_dialog_header_album_icon_size">72dp</dimen>
<dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e4fefc7c6705..2d0fa53b1f37 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1139,6 +1139,11 @@
<!-- Content description for accessibility: Hint if click will disable. [CHAR LIMIT=NONE] -->
<string name="volume_odi_captions_hint_disable">disable</string>
+ <!-- Sound and vibration settings dialog title. [CHAR LIMIT=30] -->
+ <string name="sound_settings">Sound &amp; vibration</string>
+ <!-- Label for button to go to sound settings screen [CHAR_LIMIT=30] -->
+ <string name="volume_panel_dialog_settings_button">Settings</string>
+
<!-- content description for audio output chooser [CHAR LIMIT=NONE]-->
<!-- Screen pinning dialog title. -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 112d903c609c..6b2ff376d2c6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -928,6 +928,10 @@
<item name="rowStyle">@style/SliceRow</item>
</style>
+ <style name="Widget.SliceView.Panel.Slider">
+ <item name="rowStyle">@style/SliceRow.Slider</item>
+ </style>
+
<style name="SliceRow">
<!-- 2dp start padding for the start icon -->
<item name="titleItemStartPadding">2dp</item>
@@ -949,6 +953,26 @@
<item name="actionDividerHeight">32dp</item>
</style>
+ <style name="SliceRow.Slider">
+ <!-- Padding between content and the start icon is 5dp -->
+ <item name="contentStartPadding">5dp</item>
+ <item name="contentEndPadding">0dp</item>
+
+ <!-- 0dp start padding for the end item -->
+ <item name="endItemStartPadding">0dp</item>
+ <!-- 8dp end padding for the end item -->
+ <item name="endItemEndPadding">8dp</item>
+
+ <item name="titleSize">20sp</item>
+ <!-- Align text with slider -->
+ <item name="titleStartPadding">11dp</item>
+ <item name="subContentStartPadding">11dp</item>
+
+ <!-- Padding for indeterminate progress bar -->
+ <item name="progressBarStartPadding">12dp</item>
+ <item name="progressBarEndPadding">16dp</item>
+ </style>
+
<style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index e1957c09fef5..93175e19a287 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -418,6 +418,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state);
getCurrentSecurityController().onResume(reason);
+ updateSideFpsVisibility();
}
mView.onResume(
mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
new file mode 100644
index 000000000000..109be40ce10f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -0,0 +1,67 @@
+package com.android.systemui
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FlagListenable
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class ChooserSelector @Inject constructor(
+ context: Context,
+ private val featureFlags: FeatureFlags,
+ @Application private val coroutineScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher
+) : CoreStartable(context) {
+
+ private val packageManager = context.packageManager
+ private val chooserComponent = ComponentName.unflattenFromString(
+ context.resources.getString(ChooserSelectorResourceHelper.CONFIG_CHOOSER_ACTIVITY))
+
+ override fun start() {
+ coroutineScope.launch {
+ val listener = FlagListenable.Listener { event ->
+ if (event.flagId == Flags.CHOOSER_UNBUNDLED.id) {
+ launch { updateUnbundledChooserEnabled() }
+ event.requestNoRestart()
+ }
+ }
+ featureFlags.addListener(Flags.CHOOSER_UNBUNDLED, listener)
+ updateUnbundledChooserEnabled()
+
+ awaitCancellationAndThen { featureFlags.removeListener(listener) }
+ }
+ }
+
+ private suspend fun updateUnbundledChooserEnabled() {
+ setUnbundledChooserEnabled(withContext(bgDispatcher) {
+ featureFlags.isEnabled(Flags.CHOOSER_UNBUNDLED)
+ })
+ }
+
+ private fun setUnbundledChooserEnabled(enabled: Boolean) {
+ val newState = if (enabled) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ } else {
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ }
+ packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+ }
+
+ suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { }
+ suspend inline fun awaitCancellationAndThen(block: () -> Unit): Nothing = try {
+ awaitCancellation()
+ } finally {
+ block()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java b/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
new file mode 100644
index 000000000000..7a2de7b6a78d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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;
+
+import androidx.annotation.StringRes;
+
+import com.android.internal.R;
+
+/** Helper class for referencing resources */
+class ChooserSelectorResourceHelper {
+
+ private ChooserSelectorResourceHelper() {
+ }
+
+ @StringRes
+ static final int CONFIG_CHOOSER_ACTIVITY = R.string.config_chooserActivity;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 8ba6f1c4a411..d60a22204b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -26,6 +26,7 @@ import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.screenshot.ActionProxyReceiver;
import com.android.systemui.screenshot.DeleteScreenshotReceiver;
import com.android.systemui.screenshot.SmartActionsReceiver;
+import com.android.systemui.volume.VolumePanelDialogReceiver;
import dagger.Binds;
import dagger.Module;
@@ -78,6 +79,15 @@ public abstract class DefaultBroadcastReceiverBinder {
*/
@Binds
@IntoMap
+ @ClassKey(VolumePanelDialogReceiver.class)
+ public abstract BroadcastReceiver bindVolumePanelDialogReceiver(
+ VolumePanelDialogReceiver broadcastReceiver);
+
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
@ClassKey(PeopleSpaceWidgetPinnedReceiver.class)
public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 6db3e82a77b0..8bb27a7bc217 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.dagger
import com.android.keyguard.KeyguardBiometricLockoutLogger
+import com.android.systemui.ChooserSelector
import com.android.systemui.CoreStartable
import com.android.systemui.LatencyTester
import com.android.systemui.ScreenDecorations
@@ -60,6 +61,12 @@ abstract class SystemUICoreStartableModule {
@ClassKey(AuthController::class)
abstract fun bindAuthController(service: AuthController): CoreStartable
+ /** Inject into ChooserCoreStartable. */
+ @Binds
+ @IntoMap
+ @ClassKey(ChooserSelector::class)
+ abstract fun bindChooserSelector(sysui: ChooserSelector): CoreStartable
+
/** Inject into ClipboardListener. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index ccbadddddbc0..14823030b72e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -253,6 +253,9 @@ public class Flags {
// 1400 - columbus, b/242800729
public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
+ // 1500 - chooser
+ public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 012d76651b23..b02393b4f73a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -488,8 +488,8 @@ public class MediaControlPanel {
TextView deviceName = mMediaViewHolder.getSeamlessText();
final MediaDeviceData device = data.getDevice();
- final boolean enabled;
- final boolean seamlessDisabled;
+ final boolean isTapEnabled;
+ final boolean useDisabledAlpha;
final int iconResource;
CharSequence deviceString;
if (showBroadcastButton) {
@@ -499,21 +499,25 @@ public class MediaControlPanel {
&& TextUtils.equals(device.getName(),
MediaDataUtils.getAppLabel(mContext, mPackageName, mContext.getString(
R.string.bt_le_audio_broadcast_dialog_unknown_name)));
- seamlessDisabled = !mIsCurrentBroadcastedApp;
+ useDisabledAlpha = !mIsCurrentBroadcastedApp;
// Always be enabled if the broadcast button is shown
- enabled = true;
+ isTapEnabled = true;
+
+ // Defaults for broadcasting state
deviceString = mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name);
iconResource = R.drawable.settings_input_antenna;
} else {
// Disable clicking on output switcher for invalid devices and resumption controls
- seamlessDisabled = (device != null && !device.getEnabled()) || data.getResumption();
- enabled = !seamlessDisabled;
+ useDisabledAlpha = (device != null && !device.getEnabled()) || data.getResumption();
+ isTapEnabled = !useDisabledAlpha;
+
+ // Defaults for non-broadcasting state
deviceString = mContext.getString(R.string.media_seamless_other_device);
iconResource = R.drawable.ic_media_home_devices;
}
- mMediaViewHolder.getSeamlessButton().setAlpha(seamlessDisabled ? DISABLED_ALPHA : 1.0f);
- seamlessView.setEnabled(enabled);
+ mMediaViewHolder.getSeamlessButton().setAlpha(useDisabledAlpha ? DISABLED_ALPHA : 1.0f);
+ seamlessView.setEnabled(isTapEnabled);
if (device != null) {
Drawable icon = device.getIcon();
@@ -524,7 +528,9 @@ public class MediaControlPanel {
} else {
iconView.setImageDrawable(icon);
}
- deviceString = device.getName();
+ if (device.getName() != null) {
+ deviceString = device.getName();
+ }
} else {
// Set to default icon
iconView.setImageResource(iconResource);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index c8826757355a..fb37446c1cec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -518,9 +518,20 @@ class MediaDataManager(
}
val actions = createActionsFromState(it.packageName,
mediaControllerFactory.create(it.token), UserHandle(it.userId))
- val data = it.copy(
- semanticActions = actions,
- isPlaying = isPlayingState(state.state))
+
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState,
+ // create actions from session info
+ // otherwise, no need to update semantic actions.
+ val data = if (actions != null) {
+ it.copy(
+ semanticActions = actions,
+ isPlaying = isPlayingState(state.state))
+ } else {
+ it.copy(
+ isPlaying = isPlayingState(state.state)
+ )
+ }
if (DEBUG) Log.d(TAG, "State updated outside of notification")
onMediaDataLoaded(key, key, data)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 83050503a18f..267c1f55a681 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -265,7 +265,6 @@ class MediaDeviceManager @Inject constructor(
updateCurrent()
}
-
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastStarted(), reason = $reason , broadcastId = $broadcastId")
@@ -279,8 +278,10 @@ class MediaDeviceManager @Inject constructor(
}
}
- override fun onBroadcastMetadataChanged(broadcastId: Int,
- metadata: BluetoothLeBroadcastMetadata) {
+ override fun onBroadcastMetadataChanged(
+ broadcastId: Int,
+ metadata: BluetoothLeBroadcastMetadata
+ ) {
if (DEBUG) {
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
"metadata = $metadata")
@@ -291,7 +292,6 @@ class MediaDeviceManager @Inject constructor(
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
if (DEBUG) {
Log.d(TAG, "onBroadcastStopped(), reason = $reason , broadcastId = $broadcastId")
-
}
updateCurrent()
}
@@ -344,7 +344,11 @@ class MediaDeviceManager @Inject constructor(
// If we have a controller but get a null route, then don't trust the device
val enabled = device != null && (controller == null || route != null)
- val name = route?.name?.toString() ?: device?.name
+ val name = if (controller == null || route != null) {
+ route?.name?.toString() ?: device?.name
+ } else {
+ null
+ }
current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
id = device?.id, showBroadcastButton = false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index e360d10d9362..ee5956105b7b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog;
+import android.annotation.DrawableRes;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -42,9 +43,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final String TAG = "MediaOutputAdapter";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private ViewGroup mConnectedItem;
- private boolean mIncludeDynamicGroup;
-
public MediaOutputAdapter(MediaOutputController controller) {
super(controller);
setHasStableIds(true);
@@ -102,141 +100,90 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
super.onBind(device, topMargin, bottomMargin, position);
boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
- final boolean currentlyConnected = !mIncludeDynamicGroup
- && isCurrentlyConnected(device);
+ final boolean currentlyConnected = isCurrentlyConnected(device);
boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
- if (currentlyConnected) {
- mConnectedItem = mContainerLayout;
- }
- mCheckBox.setVisibility(View.GONE);
- mStatusIcon.setVisibility(View.GONE);
- mEndTouchArea.setVisibility(View.GONE);
- mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- mContainerLayout.setOnClickListener(null);
- mContainerLayout.setContentDescription(null);
- mTitleText.setTextColor(mController.getColorItemContent());
- mSubTitleText.setTextColor(mController.getColorItemContent());
- mTwoLineTitleText.setTextColor(mController.getColorItemContent());
- mSeekBar.getProgressDrawable().setColorFilter(
- new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
- PorterDuff.Mode.SRC_IN));
if (mCurrentActivePosition == position) {
mCurrentActivePosition = -1;
}
- if (mController.isTransferring()) {
+ if (mController.isAnyDeviceTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING
&& !mController.hasAdjustVolumeUserRestriction()) {
setUpDeviceIcon(device);
- mProgressBar.getIndeterminateDrawable().setColorFilter(
- new PorterDuffColorFilter(
- mController.getColorItemContent(),
- PorterDuff.Mode.SRC_IN));
- setSingleLineLayout(getItemTitle(device), true /* bFocused */,
- false /* showSeekBar*/,
- true /* showProgressBar */, false /* showStatus */);
+ updateProgressBarColor();
+ setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/,
+ true /* showProgressBar */, false /* showCheckBox */,
+ false /* showEndTouchArea */);
} else {
setUpDeviceIcon(device);
- setSingleLineLayout(getItemTitle(device), false /* bFocused */);
+ setSingleLineLayout(getItemTitle(device));
}
} else {
// Set different layout for each device
if (device.isMutingExpectedDevice()
&& !mController.isCurrentConnectedDeviceRemote()) {
- mTitleIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.media_output_icon_volume));
- mTitleIcon.setColorFilter(mController.getColorItemContent());
- mTitleText.setTextColor(mController.getColorItemContent());
- setSingleLineLayout(getItemTitle(device), true /* bFocused */,
- false /* showSeekBar */,
- false /* showProgressBar */, false /* showStatus */);
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
initMutingExpectedDevice();
mCurrentActivePosition = position;
- mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
+ updateContainerClickListener(v -> onItemClick(v, device));
+ setSingleLineLayout(getItemTitle(device));
} else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
setUpDeviceIcon(device);
- mStatusIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.media_output_status_failed));
- mStatusIcon.setColorFilter(mController.getColorItemContent());
- setTwoLineLayout(device, false /* bFocused */,
- false /* showSeekBar */, false /* showProgressBar */,
- true /* showSubtitle */, true /* showStatus */);
+ updateConnectionFailedStatusIcon();
mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
- mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
+ updateContainerClickListener(v -> onItemClick(v, device));
+ setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
+ false /* showProgressBar */, true /* showSubtitle */,
+ true /* showStatus */);
} else if (device.getState() == MediaDeviceState.STATE_GROUPING) {
setUpDeviceIcon(device);
- mProgressBar.getIndeterminateDrawable().setColorFilter(
- new PorterDuffColorFilter(
- mController.getColorItemContent(),
- PorterDuff.Mode.SRC_IN));
- setSingleLineLayout(getItemTitle(device), true /* bFocused */,
- false /* showSeekBar*/,
- true /* showProgressBar */, false /* showStatus */);
+ updateProgressBarColor();
+ setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/,
+ true /* showProgressBar */, false /* showCheckBox */,
+ false /* showEndTouchArea */);
} else if (mController.getSelectedMediaDevice().size() > 1
&& isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
- mTitleText.setTextColor(mController.getColorItemContent());
- mTitleIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.media_output_icon_volume));
- mTitleIcon.setColorFilter(mController.getColorItemContent());
- setSingleLineLayout(getItemTitle(device), true /* bFocused */,
- true /* showSeekBar */,
- false /* showProgressBar */, false /* showStatus */);
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ updateGroupableCheckBox(true, isDeviceDeselectable, device);
+ updateEndClickArea(device, isDeviceDeselectable);
setUpContentDescriptionForView(mContainerLayout, false, device);
- mCheckBox.setOnCheckedChangeListener(null);
- mCheckBox.setVisibility(View.VISIBLE);
- mCheckBox.setChecked(true);
- mCheckBox.setOnCheckedChangeListener(isDeviceDeselectable
- ? (buttonView, isChecked) -> onGroupActionTriggered(false, device)
- : null);
- mCheckBox.setEnabled(isDeviceDeselectable);
- setCheckBoxColor(mCheckBox, mController.getColorItemContent());
+ setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+ false /* showProgressBar */, true /* showCheckBox */,
+ true /* showEndTouchArea */);
initSeekbar(device, isCurrentSeekbarInvisible);
- mEndTouchArea.setVisibility(View.VISIBLE);
- mEndTouchArea.setOnClickListener(null);
- mEndTouchArea.setOnClickListener(
- isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
- mEndTouchArea.setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- setUpContentDescriptionForView(mEndTouchArea, true, device);
} else if (!mController.hasAdjustVolumeUserRestriction()
&& currentlyConnected) {
if (isMutingExpectedDeviceExist
&& !mController.isCurrentConnectedDeviceRemote()) {
// mark as disconnected and set special click listener
setUpDeviceIcon(device);
- setSingleLineLayout(getItemTitle(device), false /* bFocused */);
- mContainerLayout.setOnClickListener(v -> cancelMuteAwaitConnection());
+ updateContainerClickListener(v -> cancelMuteAwaitConnection());
+ setSingleLineLayout(getItemTitle(device));
} else {
- mTitleIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.media_output_icon_volume));
- mTitleIcon.setColorFilter(mController.getColorItemContent());
- mTitleText.setTextColor(mController.getColorItemContent());
- setSingleLineLayout(getItemTitle(device), true /* bFocused */,
- true /* showSeekBar */,
- false /* showProgressBar */, false /* showStatus */);
- initSeekbar(device, isCurrentSeekbarInvisible);
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
setUpContentDescriptionForView(mContainerLayout, false, device);
mCurrentActivePosition = position;
+ setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+ false /* showProgressBar */, false /* showCheckBox */,
+ false /* showEndTouchArea */);
+ initSeekbar(device, isCurrentSeekbarInvisible);
}
} else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
setUpDeviceIcon(device);
- mCheckBox.setOnCheckedChangeListener(null);
- mCheckBox.setVisibility(View.VISIBLE);
- mCheckBox.setChecked(false);
- mCheckBox.setOnCheckedChangeListener(
- (buttonView, isChecked) -> onGroupActionTriggered(true, device));
- mEndTouchArea.setVisibility(View.VISIBLE);
- mContainerLayout.setOnClickListener(v -> onGroupActionTriggered(true, device));
- setCheckBoxColor(mCheckBox, mController.getColorItemContent());
- setSingleLineLayout(getItemTitle(device), false /* bFocused */,
- false /* showSeekBar */,
- false /* showProgressBar */, false /* showStatus */);
+ updateGroupableCheckBox(false, true, device);
+ updateContainerClickListener(v -> onGroupActionTriggered(true, device));
+ setSingleLineLayout(getItemTitle(device), false /* showSeekBar */,
+ false /* showProgressBar */, true /* showCheckBox */,
+ true /* showEndTouchArea */);
} else {
setUpDeviceIcon(device);
- setSingleLineLayout(getItemTitle(device), false /* bFocused */);
- mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
+ setSingleLineLayout(getItemTitle(device));
+ updateContainerClickListener(v -> onItemClick(v, device));
}
}
}
@@ -248,15 +195,56 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
ColorStateList(states, colors));
}
+ private void updateConnectionFailedStatusIcon() {
+ mStatusIcon.setImageDrawable(
+ mContext.getDrawable(R.drawable.media_output_status_failed));
+ mStatusIcon.setColorFilter(mController.getColorItemContent());
+ }
+
+ private void updateProgressBarColor() {
+ mProgressBar.getIndeterminateDrawable().setColorFilter(
+ new PorterDuffColorFilter(
+ mController.getColorItemContent(),
+ PorterDuff.Mode.SRC_IN));
+ }
+
+ public void updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable) {
+ mEndTouchArea.setOnClickListener(null);
+ mEndTouchArea.setOnClickListener(
+ isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
+ mEndTouchArea.setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ setUpContentDescriptionForView(mEndTouchArea, true, device);
+ }
+
+ private void updateGroupableCheckBox(boolean isSelected, boolean isGroupable,
+ MediaDevice device) {
+ mCheckBox.setOnCheckedChangeListener(null);
+ mCheckBox.setChecked(isSelected);
+ mCheckBox.setOnCheckedChangeListener(
+ isGroupable ? (buttonView, isChecked) -> onGroupActionTriggered(!isSelected,
+ device) : null);
+ mCheckBox.setEnabled(isGroupable);
+ setCheckBoxColor(mCheckBox, mController.getColorItemContent());
+ }
+
+ private void updateTitleIcon(@DrawableRes int id, int color) {
+ mTitleIcon.setImageDrawable(mContext.getDrawable(id));
+ mTitleIcon.setColorFilter(color);
+ }
+
+ private void updateContainerClickListener(View.OnClickListener listener) {
+ mContainerLayout.setOnClickListener(listener);
+ }
+
@Override
void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
mTitleText.setTextColor(mController.getColorItemContent());
mCheckBox.setVisibility(View.GONE);
- setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
- false /* bFocused */);
- final Drawable d = mContext.getDrawable(R.drawable.ic_add);
- mTitleIcon.setImageDrawable(d);
+ setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new));
+ final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
+ mTitleIcon.setImageDrawable(addDrawable);
mTitleIcon.setColorFilter(mController.getColorItemContent());
mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
}
@@ -273,7 +261,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
private void onItemClick(View view, MediaDevice device) {
- if (mController.isTransferring()) {
+ if (mController.isAnyDeviceTransferring()) {
return;
}
if (isCurrentlyConnected(device)) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 3b4ca48046eb..3f7b2261ea52 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -63,8 +63,6 @@ public abstract class MediaOutputBaseAdapter extends
protected final MediaOutputController mController;
- private int mMargin;
-
Context mContext;
View mHolderView;
boolean mIsDragging;
@@ -82,8 +80,6 @@ public abstract class MediaOutputBaseAdapter extends
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
mContext = viewGroup.getContext();
- mMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_list_margin);
mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
viewGroup, false);
@@ -168,16 +164,28 @@ public abstract class MediaOutputBaseAdapter extends
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
mDeviceId = device.getId();
+ mCheckBox.setVisibility(View.GONE);
+ mStatusIcon.setVisibility(View.GONE);
+ mEndTouchArea.setVisibility(View.GONE);
+ mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mContainerLayout.setOnClickListener(null);
+ mContainerLayout.setContentDescription(null);
+ mTitleText.setTextColor(mController.getColorItemContent());
+ mSubTitleText.setTextColor(mController.getColorItemContent());
+ mTwoLineTitleText.setTextColor(mController.getColorItemContent());
+ mSeekBar.getProgressDrawable().setColorFilter(
+ new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
+ PorterDuff.Mode.SRC_IN));
}
abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
- void setSingleLineLayout(CharSequence title, boolean bFocused) {
- setSingleLineLayout(title, bFocused, false, false, false);
+ void setSingleLineLayout(CharSequence title) {
+ setSingleLineLayout(title, false, false, false, false);
}
- void setSingleLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar,
- boolean showProgressBar, boolean showStatus) {
+ void setSingleLineLayout(CharSequence title, boolean showSeekBar,
+ boolean showProgressBar, boolean showCheckBox, boolean showEndTouchArea) {
mTwoLineLayout.setVisibility(View.GONE);
boolean isActive = showSeekBar || showProgressBar;
if (!mCornerAnimator.isRunning()) {
@@ -188,10 +196,6 @@ public abstract class MediaOutputBaseAdapter extends
.mutate() : mContext.getDrawable(
R.drawable.media_output_item_background)
.mutate();
- backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
- isActive ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
mItemLayout.setBackground(backgroundDrawable);
if (showSeekBar) {
final ClipDrawable clipDrawable =
@@ -201,27 +205,21 @@ public abstract class MediaOutputBaseAdapter extends
(GradientDrawable) clipDrawable.getDrawable();
progressDrawable.setCornerRadius(mController.getActiveRadius());
}
- } else {
- mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
- isActive ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
}
+ mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
+ isActive ? mController.getColorConnectedItemBackground()
+ : mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
if (!showSeekBar) {
mSeekBar.resetVolume();
}
- mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
mTitleText.setText(title);
mTitleText.setVisibility(View.VISIBLE);
- }
-
- void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
- boolean showProgressBar, boolean showSubtitle) {
- setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle,
- false);
+ mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
+ mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
}
void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -230,12 +228,6 @@ public abstract class MediaOutputBaseAdapter extends
showStatus);
}
- void setTwoLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar,
- boolean showProgressBar, boolean showSubtitle) {
- setTwoLineLayout(null, title, bFocused, showSeekBar, showProgressBar, showSubtitle,
- false);
- }
-
private void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
boolean showSeekBar, boolean showProgressBar, boolean showSubtitle,
boolean showStatus) {
@@ -254,20 +246,11 @@ public abstract class MediaOutputBaseAdapter extends
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
mTwoLineTitleText.setTranslationY(0);
- if (device == null) {
- mTwoLineTitleText.setText(title);
- } else {
- mTwoLineTitleText.setText(getItemTitle(device));
- }
-
- if (bFocused) {
- mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
- com.android.internal.R.string.config_headlineFontFamilyMedium),
- Typeface.NORMAL));
- } else {
- mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
- com.android.internal.R.string.config_headlineFontFamily), Typeface.NORMAL));
- }
+ mTwoLineTitleText.setText(device == null ? title : getItemTitle(device));
+ mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
+ bFocused ? com.android.internal.R.string.config_headlineFontFamilyMedium
+ : com.android.internal.R.string.config_headlineFontFamily),
+ Typeface.NORMAL));
}
void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
@@ -327,35 +310,6 @@ public abstract class MediaOutputBaseAdapter extends
mItemLayout.setBackground(backgroundDrawable);
}
- void initSessionSeekbar() {
- disableSeekBar();
- mSeekBar.setMax(mController.getSessionVolumeMax());
- mSeekBar.setMin(0);
- final int currentVolume = mController.getSessionVolume();
- if (mSeekBar.getProgress() != currentVolume) {
- mSeekBar.setProgress(currentVolume, true);
- }
- mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (!fromUser) {
- return;
- }
- mController.adjustSessionVolume(progress);
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- mIsDragging = true;
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- mIsDragging = false;
- }
- });
- }
-
private void animateCornerAndVolume(int fromProgress, int toProgress) {
final GradientDrawable layoutBackgroundDrawable =
(GradientDrawable) mItemLayout.getBackground();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index dad6544a87ba..8dd843a6f921 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -421,7 +421,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
device.getId());
boolean isSelectedDeviceInGroup = getSelectedMediaDevice().size() > 1
&& getSelectedMediaDevice().contains(device);
- return (!hasAdjustVolumeUserRestriction() && isConnected && !isTransferring())
+ return (!hasAdjustVolumeUserRestriction() && isConnected && !isAnyDeviceTransferring())
|| isSelectedDeviceInGroup;
}
@@ -726,7 +726,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
UserHandle.of(UserHandle.myUserId()));
}
- boolean isTransferring() {
+ boolean isAnyDeviceTransferring() {
synchronized (mMediaDevicesLock) {
for (MediaDevice device : mMediaDevices) {
if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index a153cb6c0d31..f93c671f6740 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -26,6 +26,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.media.taptotransfer.common.DEFAULT_TIMEOUT_MILLIS
+import com.android.systemui.plugins.FalsingManager
/**
* A class enumerating all the possible states of the media tap-to-transfer chip on the sender
@@ -106,12 +107,15 @@ enum class ChipStateSender(
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: FalsingManager,
): View.OnClickListener? {
if (undoCallback == null) {
return null
}
return View.OnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
uiEventLogger.logUndoClicked(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
)
@@ -141,12 +145,15 @@ enum class ChipStateSender(
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: FalsingManager,
): View.OnClickListener? {
if (undoCallback == null) {
return null
}
return View.OnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
uiEventLogger.logUndoClicked(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
)
@@ -212,7 +219,8 @@ enum class ChipStateSender(
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: FalsingManager,
): View.OnClickListener? = null
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 933548963390..5ad82fd9fd8f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -22,21 +22,25 @@ import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
import android.view.Gravity
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.ChipInfoCommon
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -58,7 +62,9 @@ class MediaTttChipControllerSender @Inject constructor(
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
- private val uiEventLogger: MediaTttSenderUiEventLogger
+ private val uiEventLogger: MediaTttSenderUiEventLogger,
+ private val falsingManager: FalsingManager,
+ private val falsingCollector: FalsingCollector,
) : MediaTttChipControllerCommon<ChipSenderInfo>(
context,
logger,
@@ -70,6 +76,9 @@ class MediaTttChipControllerSender @Inject constructor(
powerManager,
R.layout.media_ttt_chip,
) {
+
+ private lateinit var parent: MediaTttChipRootView
+
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
@@ -121,6 +130,15 @@ class MediaTttChipControllerSender @Inject constructor(
val chipState = newChipInfo.state
+ // Detect falsing touches on the chip.
+ parent = currentChipView as MediaTttChipRootView
+ parent.touchHandler = object : Gefingerpoken {
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ falsingCollector.onTouchEvent(ev)
+ return false
+ }
+ }
+
// App icon
val iconName = setIcon(currentChipView, newChipInfo.routeInfo.clientPackageName)
@@ -136,7 +154,11 @@ class MediaTttChipControllerSender @Inject constructor(
// Undo
val undoView = currentChipView.requireViewById<View>(R.id.undo)
val undoClickListener = chipState.undoClickListener(
- this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger
+ this,
+ newChipInfo.routeInfo,
+ newChipInfo.undoCallback,
+ uiEventLogger,
+ falsingManager,
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
new file mode 100644
index 000000000000..3373159fba4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.sender
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import com.android.systemui.Gefingerpoken
+
+/** A simple subclass that allows for observing touch events on chip. */
+class MediaTttChipRootView(
+ context: Context,
+ attrs: AttributeSet?
+) : FrameLayout(context, attrs) {
+
+ /** Assign this field to observe touch events. */
+ var touchHandler: Gefingerpoken? = null
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ touchHandler?.onTouchEvent(ev)
+ return super.dispatchTouchEvent(ev)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
index 3f931088ec83..5da480968b89 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -17,7 +17,14 @@ import java.util.Set;
import javax.inject.Inject;
-/** */
+/**
+ * Plays a animation to reveal newly added QS tiles.
+ *
+ * The aniumation is played when the user fully opens Quick Settings, and is only shown for
+ * <li> tiles added automatically (not through user customization)
+ * <li> tiles not have been revealed before (memoized via {@code QS_TILE_SPECS_REVEALED}
+ * preference)
+ */
public class QSTileRevealController {
private static final long QS_REVEAL_TILES_DELAY = 500L;
@@ -39,6 +46,7 @@ public class QSTileRevealController {
});
}
};
+
QSTileRevealController(Context context, QSPanelController qsPanelController,
PagedTileLayout pagedTileLayout, QSCustomizerController qsCustomizerController) {
mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index a918e5d9e106..309059fdb9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot
import android.graphics.Insets
+import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
@@ -61,8 +62,9 @@ class RequestProcessor @Inject constructor(
) {
val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+ Log.d(TAG, "findPrimaryContent: $info")
- result = if (policy.isManagedProfile(info.userId)) {
+ result = if (policy.isManagedProfile(info.user.identifier)) {
val image = capture.captureTask(info.taskId)
?: error("Task snapshot returned a null Bitmap!")
@@ -70,7 +72,7 @@ class RequestProcessor @Inject constructor(
ScreenshotRequest(
TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
HardwareBitmapBundler.hardwareBitmapToBundle(image),
- info.bounds, Insets.NONE, info.taskId, info.userId, info.component
+ info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
)
} else {
// Create a new request of the same type which includes the top component
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
index 3580010cc1e8..f73d2041af95 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
@@ -19,6 +19,7 @@ package com.android.systemui.screenshot
import android.annotation.UserIdInt
import android.content.ComponentName
import android.graphics.Rect
+import android.os.UserHandle
import android.view.Display
/**
@@ -42,7 +43,7 @@ interface ScreenshotPolicy {
data class DisplayContentInfo(
val component: ComponentName,
val bounds: Rect,
- @UserIdInt val userId: Int,
+ val user: UserHandle,
val taskId: Int,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
index ba809f676f1e..c2a50609b6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -29,9 +29,11 @@ import android.content.Intent
import android.graphics.Rect
import android.os.Process
import android.os.RemoteException
+import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import android.view.Display.DEFAULT_DISPLAY
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.infra.ServiceConnector
import com.android.systemui.SystemUIService
import com.android.systemui.dagger.SysUISingleton
@@ -45,21 +47,13 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@SysUISingleton
-internal class ScreenshotPolicyImpl @Inject constructor(
+internal open class ScreenshotPolicyImpl @Inject constructor(
context: Context,
private val userMgr: UserManager,
private val atmService: IActivityTaskManager,
@Background val bgDispatcher: CoroutineDispatcher,
) : ScreenshotPolicy {
- private val systemUiContent =
- DisplayContentInfo(
- ComponentName(context, SystemUIService::class.java),
- Rect(),
- ActivityTaskManager.INVALID_TASK_ID,
- Process.myUserHandle().identifier,
- )
-
private val proxyConnector: ServiceConnector<IScreenshotProxy> =
ServiceConnector.Impl(
context,
@@ -78,6 +72,9 @@ internal class ScreenshotPolicyImpl @Inject constructor(
}
private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
+ if (DEBUG) {
+ debugLogRootTaskInfo(info)
+ }
return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
info.isVisible &&
info.isRunning &&
@@ -99,58 +96,46 @@ internal class ScreenshotPolicyImpl @Inject constructor(
}
val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)
- if (DEBUG) {
- debugLogRootTaskInfos(taskInfoList)
- }
// If no visible task is located, then report SystemUI as the foreground content
val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
-
- val topActivity: ComponentName = target.topActivity ?: error("should not be null")
- val topChildTask = target.childTaskIds.size - 1
- val childTaskId = target.childTaskIds[topChildTask]
- val childTaskUserId = target.childTaskUserIds[topChildTask]
- val childTaskBounds = target.childTaskBounds[topChildTask]
-
- return DisplayContentInfo(topActivity, childTaskBounds, childTaskId, childTaskUserId)
+ return target.toDisplayContentInfo()
}
- private fun debugLogRootTaskInfos(taskInfoList: List<RootTaskInfo>) {
- for (info in taskInfoList) {
- Log.d(
- TAG,
- "[root task info] " +
- "taskId=${info.taskId} " +
- "parentTaskId=${info.parentTaskId} " +
- "position=${info.position} " +
- "positionInParent=${info.positionInParent} " +
- "isVisible=${info.isVisible()} " +
- "visible=${info.visible} " +
- "isFocused=${info.isFocused} " +
- "isSleeping=${info.isSleeping} " +
- "isRunning=${info.isRunning} " +
- "windowMode=${windowingModeToString(info.windowingMode)} " +
- "activityType=${activityTypeToString(info.activityType)} " +
- "topActivity=${info.topActivity} " +
- "topActivityInfo=${info.topActivityInfo} " +
- "numActivities=${info.numActivities} " +
- "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
- "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
- "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
- "childTaskNames=${Arrays.toString(info.childTaskNames)}"
- )
-
- for (j in 0 until info.childTaskIds.size) {
- Log.d(TAG, " *** [$j] ******")
- Log.d(TAG, " *** childTaskIds[$j]: ${info.childTaskIds[j]}")
- Log.d(TAG, " *** childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
- Log.d(TAG, " *** childTaskBounds[$j]: ${info.childTaskBounds[j]}")
- Log.d(TAG, " *** childTaskNames[$j]: ${info.childTaskNames[j]}")
- }
+ private fun debugLogRootTaskInfo(info: RootTaskInfo) {
+ Log.d(TAG, "RootTaskInfo={" +
+ "taskId=${info.taskId} " +
+ "parentTaskId=${info.parentTaskId} " +
+ "position=${info.position} " +
+ "positionInParent=${info.positionInParent} " +
+ "isVisible=${info.isVisible()} " +
+ "visible=${info.visible} " +
+ "isFocused=${info.isFocused} " +
+ "isSleeping=${info.isSleeping} " +
+ "isRunning=${info.isRunning} " +
+ "windowMode=${windowingModeToString(info.windowingMode)} " +
+ "activityType=${activityTypeToString(info.activityType)} " +
+ "topActivity=${info.topActivity} " +
+ "topActivityInfo=${info.topActivityInfo} " +
+ "numActivities=${info.numActivities} " +
+ "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
+ "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
+ "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
+ "childTaskNames=${Arrays.toString(info.childTaskNames)}" +
+ "}"
+ )
+
+ for (j in 0 until info.childTaskIds.size) {
+ Log.d(TAG, " *** [$j] ******")
+ Log.d(TAG, " *** childTaskIds[$j]: ${info.childTaskIds[j]}")
+ Log.d(TAG, " *** childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
+ Log.d(TAG, " *** childTaskBounds[$j]: ${info.childTaskBounds[j]}")
+ Log.d(TAG, " *** childTaskNames[$j]: ${info.childTaskNames[j]}")
}
}
- private suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
+ @VisibleForTesting
+ open suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
withContext(bgDispatcher) {
try {
atmService.getAllRootTaskInfosOnDisplay(displayId)
@@ -160,7 +145,8 @@ internal class ScreenshotPolicyImpl @Inject constructor(
}
}
- private suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
+ @VisibleForTesting
+ open suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
proxyConnector
.postForResult { it.isNotificationShadeExpanded }
.whenComplete { expanded, error ->
@@ -171,8 +157,30 @@ internal class ScreenshotPolicyImpl @Inject constructor(
}
}
- companion object {
- const val TAG: String = "ScreenshotPolicyImpl"
- const val DEBUG: Boolean = false
- }
+ @VisibleForTesting
+ internal val systemUiContent =
+ DisplayContentInfo(
+ ComponentName(context, SystemUIService::class.java),
+ Rect(),
+ Process.myUserHandle(),
+ ActivityTaskManager.INVALID_TASK_ID
+ )
+}
+
+private const val TAG: String = "ScreenshotPolicyImpl"
+private const val DEBUG: Boolean = false
+
+@VisibleForTesting
+internal fun RootTaskInfo.toDisplayContentInfo(): DisplayContentInfo {
+ val topActivity: ComponentName = topActivity ?: error("should not be null")
+ val topChildTask = childTaskIds.size - 1
+ val childTaskId = childTaskIds[topChildTask]
+ val childTaskUserId = childTaskUserIds[topChildTask]
+ val childTaskBounds = childTaskBounds[topChildTask]
+
+ return DisplayContentInfo(
+ topActivity,
+ childTaskBounds,
+ UserHandle.of(childTaskUserId),
+ childTaskId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index dbf4810b4fd7..126a986ee5f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -18,10 +18,8 @@ package com.android.systemui.statusbar.notification
import android.animation.ObjectAnimator
import android.util.FloatProperty
-import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -34,20 +32,17 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
-import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.min
@SysUISingleton
class NotificationWakeUpCoordinator @Inject constructor(
- dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener,
- Dumpable {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
"notificationVisibility") {
@@ -65,7 +60,6 @@ class NotificationWakeUpCoordinator @Inject constructor(
private var mLinearDozeAmount: Float = 0.0f
private var mDozeAmount: Float = 0.0f
- private var mDozeAmountSource: String = "init"
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
@@ -148,7 +142,6 @@ class NotificationWakeUpCoordinator @Inject constructor(
}
init {
- dumpManager.registerDumpable(this)
mHeadsUpManager.addListener(this)
statusBarStateController.addCallback(this)
addListener(object : WakeUpListener {
@@ -255,14 +248,13 @@ class NotificationWakeUpCoordinator @Inject constructor(
// Let's notify the scroller that an animation started
notifyAnimationStart(mLinearDozeAmount == 1.0f)
}
- setDozeAmount(linear, eased, source = "StatusBar")
+ setDozeAmount(linear, eased)
}
- fun setDozeAmount(linear: Float, eased: Float, source: String) {
+ fun setDozeAmount(linear: Float, eased: Float) {
val changed = linear != mLinearDozeAmount
mLinearDozeAmount = linear
mDozeAmount = eased
- mDozeAmountSource = source
mStackScrollerController.setDozeAmount(mDozeAmount)
updateHideAmount()
if (changed && linear == 0.0f) {
@@ -279,7 +271,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
// undefined state, so it's an indication that we should do state cleanup. We override
// the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
- setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
+ setDozeAmount(0f, 0f)
}
if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -319,11 +311,12 @@ class NotificationWakeUpCoordinator @Inject constructor(
*/
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
- if (statusBarStateController.state == StatusBarState.KEYGUARD) {
- setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
- } else {
- setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
+ var amount = 1.0f
+ if (statusBarStateController.state == StatusBarState.SHADE ||
+ statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
+ amount = 0.0f
}
+ setDozeAmount(amount, amount)
return true
}
return false
@@ -339,7 +332,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
*/
private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
- setDozeAmount(1f, 1f, source = "Override: animating screen off")
+ setDozeAmount(1f, 1f)
return true
}
@@ -433,24 +426,4 @@ class NotificationWakeUpCoordinator @Inject constructor(
*/
@JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
}
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("mLinearDozeAmount: $mLinearDozeAmount")
- pw.println("mDozeAmount: $mDozeAmount")
- pw.println("mDozeAmountSource: $mDozeAmountSource")
- pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
- pw.println("mNotificationsVisible: $mNotificationsVisible")
- pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
- pw.println("mVisibilityAmount: $mVisibilityAmount")
- pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount")
- pw.println("pulseExpanding: $pulseExpanding")
- pw.println("state: ${StatusBarState.toString(state)}")
- pw.println("fullyAwake: $fullyAwake")
- pw.println("wakingUp: $wakingUp")
- pw.println("willWakeUp: $willWakeUp")
- pw.println("collapsedEnoughToHide: $collapsedEnoughToHide")
- pw.println("pulsing: $pulsing")
- pw.println("notificationsFullyHidden: $notificationsFullyHidden")
- pw.println("canShowPulsingHuns: $canShowPulsingHuns")
- }
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c94a915b2747..e6a3e74c1caf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -48,7 +48,6 @@ import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -248,6 +247,7 @@ public class VolumeDialogImpl implements VolumeDialog,
private final ConfigurationController mConfigurationController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final VolumePanelFactory mVolumePanelFactory;
private final ActivityStarter mActivityStarter;
private boolean mShowing;
@@ -279,6 +279,7 @@ public class VolumeDialogImpl implements VolumeDialog,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
MediaOutputDialogFactory mediaOutputDialogFactory,
+ VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor) {
mContext =
@@ -290,6 +291,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mDeviceProvisionedController = deviceProvisionedController;
mConfigurationController = configurationController;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mVolumePanelFactory = volumePanelFactory;
mActivityStarter = activityStarter;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
@@ -1043,10 +1045,9 @@ public class VolumeDialogImpl implements VolumeDialog,
if (mSettingsIcon != null) {
mSettingsIcon.setOnClickListener(v -> {
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
- Intent intent = new Intent(Settings.Panel.ACTION_VOLUME);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
mMediaOutputDialogFactory.dismiss();
- mActivityStarter.startActivity(intent, true /* dismissShade */);
+ mVolumePanelFactory.create(true /* aboveStatusBar */, null);
});
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
new file mode 100644
index 000000000000..2c74fb911688
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 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.volume;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.provider.SettingsSlicesContract;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceMetadata;
+import androidx.slice.widget.EventInfo;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.MediaOutputConstants;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Visual presentation of the volume panel dialog.
+ */
+public class VolumePanelDialog extends SystemUIDialog implements LifecycleOwner {
+ private static final String TAG = "VolumePanelDialog";
+
+ private static final int DURATION_SLICE_BINDING_TIMEOUT_MS = 200;
+ private static final int DEFAULT_SLICE_SIZE = 4;
+
+ private RecyclerView mVolumePanelSlices;
+ private VolumePanelSlicesAdapter mVolumePanelSlicesAdapter;
+ private final LifecycleRegistry mLifecycleRegistry;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Map<Uri, LiveData<Slice>> mSliceLiveData = new LinkedHashMap<>();
+ private final HashSet<Uri> mLoadedSlices = new HashSet<>();
+ private boolean mSlicesReadyToLoad;
+ private LocalBluetoothProfileManager mProfileManager;
+
+ public VolumePanelDialog(Context context, boolean aboveStatusBar) {
+ super(context);
+ mLifecycleRegistry = new LifecycleRegistry(this);
+ if (!aboveStatusBar) {
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate");
+
+ View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.volume_panel_dialog,
+ null);
+ final Window window = getWindow();
+ window.setContentView(dialogView);
+
+ Button doneButton = dialogView.findViewById(R.id.done_button);
+ doneButton.setOnClickListener(v -> dismiss());
+ Button settingsButton = dialogView.findViewById(R.id.settings_button);
+ settingsButton.setOnClickListener(v -> {
+ getContext().startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK));
+ dismiss();
+ });
+
+ LocalBluetoothManager localBluetoothManager = LocalBluetoothManager.getInstance(
+ getContext(), null);
+ if (localBluetoothManager != null) {
+ mProfileManager = localBluetoothManager.getProfileManager();
+ }
+
+ mVolumePanelSlices = dialogView.findViewById(R.id.volume_panel_parent_layout);
+ mVolumePanelSlices.setLayoutManager(new LinearLayoutManager(getContext()));
+
+ loadAllSlices();
+
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+ }
+
+ private void loadAllSlices() {
+ mSliceLiveData.clear();
+ mLoadedSlices.clear();
+ final List<Uri> sliceUris = getSlices();
+
+ for (Uri uri : sliceUris) {
+ final LiveData<Slice> sliceLiveData = SliceLiveData.fromUri(getContext(), uri,
+ (int type, Throwable source) -> {
+ if (!removeSliceLiveData(uri)) {
+ mLoadedSlices.add(uri);
+ }
+ });
+
+ // Add slice first to make it in order. Will remove it later if there's an error.
+ mSliceLiveData.put(uri, sliceLiveData);
+
+ sliceLiveData.observe(this, slice -> {
+ if (mLoadedSlices.contains(uri)) {
+ return;
+ }
+ Log.d(TAG, "received slice: " + (slice == null ? null : slice.getUri()));
+ final SliceMetadata metadata = SliceMetadata.from(getContext(), slice);
+ if (slice == null || metadata.isErrorSlice()) {
+ if (!removeSliceLiveData(uri)) {
+ mLoadedSlices.add(uri);
+ }
+ } else if (metadata.getLoadingState() == SliceMetadata.LOADED_ALL) {
+ mLoadedSlices.add(uri);
+ } else {
+ mHandler.postDelayed(() -> {
+ mLoadedSlices.add(uri);
+ setupAdapterWhenReady();
+ }, DURATION_SLICE_BINDING_TIMEOUT_MS);
+ }
+
+ setupAdapterWhenReady();
+ });
+ }
+ }
+
+ private void setupAdapterWhenReady() {
+ if (mLoadedSlices.size() == mSliceLiveData.size() && !mSlicesReadyToLoad) {
+ mSlicesReadyToLoad = true;
+ mVolumePanelSlicesAdapter = new VolumePanelSlicesAdapter(this, mSliceLiveData);
+ mVolumePanelSlicesAdapter.setOnSliceActionListener((eventInfo, sliceItem) -> {
+ if (eventInfo.actionType == EventInfo.ACTION_TYPE_SLIDER) {
+ return;
+ }
+ this.dismiss();
+ });
+ if (mSliceLiveData.size() < DEFAULT_SLICE_SIZE) {
+ mVolumePanelSlices.setMinimumHeight(0);
+ }
+ mVolumePanelSlices.setAdapter(mVolumePanelSlicesAdapter);
+ }
+ }
+
+ private boolean removeSliceLiveData(Uri uri) {
+ boolean removed = false;
+ // Keeps observe media output slice
+ if (!uri.equals(MEDIA_OUTPUT_INDICATOR_SLICE_URI)) {
+ Log.d(TAG, "remove uri: " + uri);
+ removed = mSliceLiveData.remove(uri) != null;
+ if (mVolumePanelSlicesAdapter != null) {
+ mVolumePanelSlicesAdapter.updateDataSet(new ArrayList<>(mSliceLiveData.values()));
+ }
+ }
+ return removed;
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.d(TAG, "onStart");
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.d(TAG, "onStop");
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
+ }
+
+ private List<Uri> getSlices() {
+ final List<Uri> uris = new ArrayList<>();
+ uris.add(REMOTE_MEDIA_SLICE_URI);
+ uris.add(VOLUME_MEDIA_URI);
+ Uri controlUri = getExtraControlUri();
+ if (controlUri != null) {
+ Log.d(TAG, "add extra control slice");
+ uris.add(controlUri);
+ }
+ uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI);
+ uris.add(VOLUME_CALL_URI);
+ uris.add(VOLUME_RINGER_URI);
+ uris.add(VOLUME_ALARM_URI);
+ return uris;
+ }
+
+ private static final String SETTINGS_SLICE_AUTHORITY = "com.android.settings.slices";
+ private static final Uri REMOTE_MEDIA_SLICE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SETTINGS_SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath(MediaOutputConstants.KEY_REMOTE_MEDIA)
+ .build();
+ private static final Uri VOLUME_MEDIA_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SETTINGS_SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("media_volume")
+ .build();
+ private static final Uri MEDIA_OUTPUT_INDICATOR_SLICE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SETTINGS_SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_INTENT)
+ .appendPath("media_output_indicator")
+ .build();
+ private static final Uri VOLUME_CALL_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SETTINGS_SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("call_volume")
+ .build();
+ private static final Uri VOLUME_RINGER_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SETTINGS_SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("ring_volume")
+ .build();
+ private static final Uri VOLUME_ALARM_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SETTINGS_SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("alarm_volume")
+ .build();
+
+ private Uri getExtraControlUri() {
+ Uri controlUri = null;
+ final BluetoothDevice bluetoothDevice = findActiveDevice();
+ if (bluetoothDevice != null) {
+ // The control slice width = dialog width - horizontal padding of two sides
+ final int dialogWidth =
+ getWindow().getWindowManager().getCurrentWindowMetrics().getBounds().width();
+ final int controlSliceWidth = dialogWidth
+ - getContext().getResources().getDimensionPixelSize(
+ R.dimen.volume_panel_slice_horizontal_padding) * 2;
+ final String uri = BluetoothUtils.getControlUriMetaData(bluetoothDevice);
+ if (!TextUtils.isEmpty(uri)) {
+ try {
+ controlUri = Uri.parse(uri + controlSliceWidth);
+ } catch (NullPointerException exception) {
+ Log.d(TAG, "unable to parse extra control uri");
+ controlUri = null;
+ }
+ }
+ }
+ return controlUri;
+ }
+
+ private BluetoothDevice findActiveDevice() {
+ if (mProfileManager != null) {
+ final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+ if (a2dpProfile != null) {
+ return a2dpProfile.getActiveDevice();
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycleRegistry;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
new file mode 100644
index 000000000000..f11d5d18ac84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.volume
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.Log
+import javax.inject.Inject
+
+private const val TAG = "VolumePanelDialogReceiver"
+private const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
+private const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
+
+/**
+ * BroadcastReceiver for handling volume panel dialog intent
+ */
+class VolumePanelDialogReceiver @Inject constructor(
+ private val volumePanelFactory: VolumePanelFactory
+) : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.d(TAG, "onReceive intent" + intent.action)
+ if (TextUtils.equals(LAUNCH_ACTION, intent.action) ||
+ TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)) {
+ volumePanelFactory.create(true, null)
+ } else if (TextUtils.equals(DISMISS_ACTION, intent.action)) {
+ volumePanelFactory.dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
new file mode 100644
index 000000000000..c2fafbf9f55b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.volume
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+private const val TAG = "VolumePanelFactory"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Factory to create [VolumePanelDialog] objects. This is the dialog that allows the user to adjust
+ * multiple streams with sliders.
+ */
+@SysUISingleton
+class VolumePanelFactory @Inject constructor(
+ private val context: Context,
+ private val dialogLaunchAnimator: DialogLaunchAnimator
+) {
+ companion object {
+ var volumePanelDialog: VolumePanelDialog? = null
+ }
+
+ /** Creates a [VolumePanelDialog]. The dialog will be animated from [view] if it is not null. */
+ fun create(aboveStatusBar: Boolean, view: View? = null) {
+ if (volumePanelDialog?.isShowing == true) {
+ return
+ }
+
+ val dialog = VolumePanelDialog(context, aboveStatusBar)
+ volumePanelDialog = dialog
+
+ // Show the dialog.
+ if (view != null) {
+ dialogLaunchAnimator.showFromView(dialog, view, animateBackgroundBoundsChange = true)
+ } else {
+ dialog.show()
+ }
+ }
+
+ /** Dismiss [VolumePanelDialog] if exist. */
+ fun dismiss() {
+ if (DEBUG) {
+ Log.d(TAG, "dismiss dialog")
+ }
+ volumePanelDialog?.dismiss()
+ volumePanelDialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelSlicesAdapter.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelSlicesAdapter.java
new file mode 100644
index 000000000000..23714021a2cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelSlicesAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 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.volume;
+
+import static android.app.slice.Slice.HINT_ERROR;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+
+import android.content.Context;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.widget.SliceView;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RecyclerView adapter for Slices in Settings Panels.
+ */
+public class VolumePanelSlicesAdapter extends
+ RecyclerView.Adapter<VolumePanelSlicesAdapter.SliceRowViewHolder> {
+
+ private final List<LiveData<Slice>> mSliceLiveData;
+ private final LifecycleOwner mLifecycleOwner;
+ private SliceView.OnSliceActionListener mOnSliceActionListener;
+
+ public VolumePanelSlicesAdapter(LifecycleOwner lifecycleOwner,
+ Map<Uri, LiveData<Slice>> sliceLiveData) {
+ mLifecycleOwner = lifecycleOwner;
+ mSliceLiveData = new ArrayList<>(sliceLiveData.values());
+ }
+
+ @NonNull
+ @Override
+ public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
+ final Context context = viewGroup.getContext();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ View view = inflater.inflate(R.layout.volume_panel_slice_slider_row, viewGroup, false);
+ return new SliceRowViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) {
+ sliceRowViewHolder.onBind(mSliceLiveData.get(position), position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSliceLiveData.size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position;
+ }
+
+ void setOnSliceActionListener(SliceView.OnSliceActionListener listener) {
+ mOnSliceActionListener = listener;
+ }
+
+ void updateDataSet(ArrayList<LiveData<Slice>> list) {
+ mSliceLiveData.clear();
+ mSliceLiveData.addAll(list);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * ViewHolder for binding Slices to SliceViews.
+ */
+ public class SliceRowViewHolder extends RecyclerView.ViewHolder {
+
+ private final SliceView mSliceView;
+
+ public SliceRowViewHolder(View view) {
+ super(view);
+ mSliceView = view.findViewById(R.id.slice_view);
+ mSliceView.setMode(SliceView.MODE_LARGE);
+ mSliceView.setShowTitleItems(true);
+ mSliceView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mSliceView.setOnSliceActionListener(mOnSliceActionListener);
+ }
+
+ /**
+ * Called when the view is displayed.
+ */
+ public void onBind(LiveData<Slice> sliceLiveData, int position) {
+ sliceLiveData.observe(mLifecycleOwner, mSliceView);
+
+ // Do not show the divider above media devices switcher slice per request
+ final Slice slice = sliceLiveData.getValue();
+
+ // Hides slice which reports with error hint or not contain any slice sub-item.
+ if (slice == null || !isValidSlice(slice)) {
+ mSliceView.setVisibility(View.GONE);
+ } else {
+ mSliceView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private boolean isValidSlice(Slice slice) {
+ if (slice.getHints().contains(HINT_ERROR)) {
+ return false;
+ }
+ for (SliceItem item : slice.getItems()) {
+ if (item.getFormat().equals(FORMAT_SLICE)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
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 f3855bddfe48..c5792b923e48 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -30,6 +30,7 @@ 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 com.android.systemui.volume.VolumePanelFactory;
import dagger.Binds;
import dagger.Module;
@@ -52,6 +53,7 @@ public interface VolumeModule {
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
MediaOutputDialogFactory mediaOutputDialogFactory,
+ VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor) {
VolumeDialogImpl impl = new VolumeDialogImpl(
@@ -61,6 +63,7 @@ public interface VolumeModule {
deviceProvisionedController,
configurationController,
mediaOutputDialogFactory,
+ volumePanelFactory,
activityStarter,
interactionJankMonitor);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index aecec9d100cc..d68e8bd36c40 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -387,6 +387,33 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
}
@Test
+ public void onResume_sideFpsHintShouldBeShown_sideFpsHintShown() {
+ setupGetSecurityView();
+ setupConditionsToEnableSideFpsHint();
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onResume(0);
+
+ verify(mSidefpsController).show();
+ verify(mSidefpsController, never()).hide();
+ }
+
+ @Test
+ public void onResume_sideFpsHintShouldNotBeShown_sideFpsHintHidden() {
+ setupGetSecurityView();
+ setupConditionsToEnableSideFpsHint();
+ setSideFpsHintEnabledFromResources(false);
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onResume(0);
+
+ verify(mSidefpsController).hide();
+ verify(mSidefpsController, never()).show();
+ }
+
+ @Test
public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
// GIVEN the current security method is SimPin
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
new file mode 100644
index 000000000000..6b1ef389a98e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -0,0 +1,179 @@
+package com.android.systemui
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flag
+import com.android.systemui.flags.FlagListenable
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ChooserSelectorTest : SysuiTestCase() {
+
+ private val flagListener = kotlinArgumentCaptor<FlagListenable.Listener>()
+
+ private val testDispatcher = TestCoroutineDispatcher()
+ private val testScope = CoroutineScope(testDispatcher)
+
+ private lateinit var chooserSelector: ChooserSelector
+
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockPackageManager: PackageManager
+ @Mock private lateinit var mockResources: Resources
+ @Mock private lateinit var mockFeatureFlags: FeatureFlags
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(mockContext.packageManager).thenReturn(mockPackageManager)
+ `when`(mockContext.resources).thenReturn(mockResources)
+ `when`(mockResources.getString(anyInt())).thenReturn(
+ ComponentName("TestPackage", "TestClass").flattenToString())
+
+ chooserSelector = ChooserSelector(mockContext, mockFeatureFlags, testScope, testDispatcher)
+ }
+
+ @After
+ fun tearDown() {
+ testDispatcher.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun initialize_registersFlagListenerUntilScopeCancelled() {
+ // Arrange
+
+ // Act
+ chooserSelector.start()
+
+ // Assert
+ verify(mockFeatureFlags).addListener(
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ verify(mockFeatureFlags, never()).removeListener(any())
+
+ // Act
+ testScope.cancel()
+
+ // Assert
+ verify(mockFeatureFlags).removeListener(eq(flagListener.value))
+ }
+
+ @Test
+ fun initialize_enablesUnbundledChooser_whenFlagEnabled() {
+ // Arrange
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+
+ // Act
+ chooserSelector.start()
+
+ // Assert
+ verify(mockPackageManager).setComponentEnabledSetting(
+ eq(ComponentName("TestPackage", "TestClass")),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ anyInt())
+ }
+
+ @Test
+ fun initialize_disablesUnbundledChooser_whenFlagDisabled() {
+ // Arrange
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+
+ // Act
+ chooserSelector.start()
+
+ // Assert
+ verify(mockPackageManager).setComponentEnabledSetting(
+ eq(ComponentName("TestPackage", "TestClass")),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ anyInt())
+ }
+
+ @Test
+ fun enablesUnbundledChooser_whenFlagBecomesEnabled() {
+ // Arrange
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ chooserSelector.start()
+ verify(mockFeatureFlags).addListener(
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ verify(mockPackageManager, never()).setComponentEnabledSetting(
+ any(), eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED), anyInt())
+
+ // Act
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+
+ // Assert
+ verify(mockPackageManager).setComponentEnabledSetting(
+ eq(ComponentName("TestPackage", "TestClass")),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ anyInt())
+ }
+
+ @Test
+ fun disablesUnbundledChooser_whenFlagBecomesDisabled() {
+ // Arrange
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ chooserSelector.start()
+ verify(mockFeatureFlags).addListener(
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ verify(mockPackageManager, never()).setComponentEnabledSetting(
+ any(), eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED), anyInt())
+
+ // Act
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+
+ // Assert
+ verify(mockPackageManager).setComponentEnabledSetting(
+ eq(ComponentName("TestPackage", "TestClass")),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ anyInt())
+ }
+
+ @Test
+ fun doesNothing_whenAnotherFlagChanges() {
+ // Arrange
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ chooserSelector.start()
+ verify(mockFeatureFlags).addListener(
+ eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+ clearInvocations(mockPackageManager)
+
+ // Act
+ `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
+
+ // Assert
+ verifyZeroInteractions(mockPackageManager)
+ }
+
+ private class TestFlagEvent(override val flagId: Int) : FlagListenable.FlagEvent {
+ override fun requestNoRestart() {}
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index ff579a1cc4aa..318f2bc1c227 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -41,7 +41,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() {
* specified. If not, an exception is thrown.
*/
@Test
- fun throwsIfUnspecifiedFlagIsAccessed() {
+ fun accessingUnspecifiedFlags_throwsException() {
val flags: FeatureFlags = FakeFeatureFlags()
try {
assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
@@ -88,7 +88,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() {
}
@Test
- fun specifiedFlagsReturnCorrectValues() {
+ fun specifiedFlags_returnCorrectValues() {
val flags = FakeFeatureFlags()
flags.set(unreleasedFlag, false)
flags.set(releasedFlag, false)
@@ -114,4 +114,125 @@ class FakeFeatureFlagsTest : SysuiTestCase() {
assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue()
assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android")
}
+
+ @Test
+ fun listenerForBooleanFlag_calledOnlyWhenFlagChanged() {
+ val flags = FakeFeatureFlags()
+ val listener = VerifyingListener()
+ flags.addListener(unreleasedFlag, listener)
+
+ flags.set(unreleasedFlag, true)
+ flags.set(unreleasedFlag, true)
+ flags.set(unreleasedFlag, false)
+ flags.set(unreleasedFlag, false)
+
+ listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id)
+ }
+
+ @Test
+ fun listenerForStringFlag_calledOnlyWhenFlagChanged() {
+ val flags = FakeFeatureFlags()
+ val listener = VerifyingListener()
+ flags.addListener(stringFlag, listener)
+
+ flags.set(stringFlag, "Test")
+ flags.set(stringFlag, "Test")
+
+ listener.verifyInOrder(stringFlag.id)
+ }
+
+ @Test
+ fun listenerForBooleanFlag_notCalledAfterRemoved() {
+ val flags = FakeFeatureFlags()
+ val listener = VerifyingListener()
+ flags.addListener(unreleasedFlag, listener)
+ flags.set(unreleasedFlag, true)
+ flags.removeListener(listener)
+ flags.set(unreleasedFlag, false)
+
+ listener.verifyInOrder(unreleasedFlag.id)
+ }
+
+ @Test
+ fun listenerForStringFlag_notCalledAfterRemoved() {
+ val flags = FakeFeatureFlags()
+ val listener = VerifyingListener()
+
+ flags.addListener(stringFlag, listener)
+ flags.set(stringFlag, "Test")
+ flags.removeListener(listener)
+ flags.set(stringFlag, "Other")
+
+ listener.verifyInOrder(stringFlag.id)
+ }
+
+ @Test
+ fun listenerForMultipleFlags_calledWhenFlagsChange() {
+ val flags = FakeFeatureFlags()
+ val listener = VerifyingListener()
+ flags.addListener(unreleasedFlag, listener)
+ flags.addListener(releasedFlag, listener)
+
+ flags.set(releasedFlag, true)
+ flags.set(unreleasedFlag, true)
+
+ listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+ }
+
+ @Test
+ fun listenerForMultipleFlags_notCalledAfterRemoved() {
+ val flags = FakeFeatureFlags()
+ val listener = VerifyingListener()
+
+ flags.addListener(unreleasedFlag, listener)
+ flags.addListener(releasedFlag, listener)
+ flags.set(releasedFlag, true)
+ flags.set(unreleasedFlag, true)
+ flags.removeListener(listener)
+ flags.set(releasedFlag, false)
+ flags.set(unreleasedFlag, false)
+
+ listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+ }
+
+ @Test
+ fun multipleListenersForSingleFlag_allAreCalledWhenChanged() {
+ val flags = FakeFeatureFlags()
+ val listener1 = VerifyingListener()
+ val listener2 = VerifyingListener()
+ flags.addListener(releasedFlag, listener1)
+ flags.addListener(releasedFlag, listener2)
+
+ flags.set(releasedFlag, true)
+
+ listener1.verifyInOrder(releasedFlag.id)
+ listener2.verifyInOrder(releasedFlag.id)
+ }
+
+ @Test
+ fun multipleListenersForSingleFlag_removedListenerNotCalledAfterRemoval() {
+ val flags = FakeFeatureFlags()
+ val listener1 = VerifyingListener()
+ val listener2 = VerifyingListener()
+ flags.addListener(releasedFlag, listener1)
+ flags.addListener(releasedFlag, listener2)
+
+ flags.set(releasedFlag, true)
+ flags.removeListener(listener2)
+ flags.set(releasedFlag, false)
+
+ listener1.verifyInOrder(releasedFlag.id, releasedFlag.id)
+ listener2.verifyInOrder(releasedFlag.id)
+ }
+
+ class VerifyingListener : FlagListenable.Listener {
+ var flagEventIds = mutableListOf<Int>()
+ override fun onFlagChanged(event: FlagListenable.FlagEvent) {
+ flagEventIds.add(event.flagId)
+ }
+
+ fun verifyInOrder(vararg eventIds: Int) {
+ assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList())
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 178502269e73..bef46953395b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -1051,6 +1051,17 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ fun bindDeviceWithNullName() {
+ val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
+ player.attachPlayer(viewHolder)
+ val state = mediaData.copy(device = device.copy(name = null))
+ player.bindPlayer(state, PACKAGE)
+ assertThat(seamless.isEnabled()).isTrue()
+ assertThat(seamlessText.getText()).isEqualTo(fallbackString)
+ assertThat(seamless.contentDescription).isEqualTo(fallbackString)
+ }
+
+ @Test
fun bindDeviceResumptionPlayer() {
player.attachPlayer(viewHolder)
val state = mediaData.copy(resumption = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index d1ed8e983cdd..f9c7d2d5cb41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -31,7 +31,6 @@ import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
@@ -108,6 +107,7 @@ class MediaDataManagerTest : SysuiTestCase() {
private val clock = FakeSystemClock()
@Mock private lateinit var tunerService: TunerService
@Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
+ @Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -974,7 +974,6 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testPlaybackStateChange_keyExists_callsListener() {
// Notification has been added
addNotificationAndLoad()
- val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
// Callback gets an updated state
@@ -992,7 +991,6 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testPlaybackStateChange_keyDoesNotExist_doesNothing() {
val state = PlaybackState.Builder().build()
- val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
// No media added with this key
@@ -1013,7 +1011,6 @@ class MediaDataManagerTest : SysuiTestCase() {
// And then get a state update
val state = PlaybackState.Builder().build()
- val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
// Then no changes are made
@@ -1022,6 +1019,83 @@ class MediaDataManagerTest : SysuiTestCase() {
anyBoolean())
}
+ @Test
+ fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ val state = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+ .build()
+ whenever(controller.playbackState).thenReturn(state)
+
+ addNotificationAndLoad()
+ verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
+ callbackCaptor.value.invoke(KEY, state)
+
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
+ capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+ }
+
+ @Test
+ fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
+ val desc = MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ val state = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+ .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+ .build()
+
+ // Add resumption controls in order to have semantic actions.
+ // To make sure that they are not null after changing state.
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
+ )
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+
+ verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
+ callbackCaptor.value.invoke(PACKAGE_NAME, state)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(PACKAGE_NAME),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+ }
+
+ @Test
+ fun testPlaybackStateNull_Pause_keyExists_callsListener() {
+ whenever(controller.playbackState).thenReturn(null)
+ val state = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+ .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+ .build()
+
+ addNotificationAndLoad()
+ verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
+ callbackCaptor.value.invoke(KEY, state)
+
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
+ capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ assertThat(mediaDataCaptor.value.semanticActions).isNull()
+ }
+
/**
* Helper function to add a media notification and capture the resulting MediaData
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index ee104262dc29..121c8946d164 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -59,8 +59,8 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
private const val KEY = "TEST_KEY"
private const val KEY_OLD = "TEST_KEY_OLD"
@@ -402,9 +402,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
- // THEN the device is disabled
+ // THEN the device is disabled and name is set to null
val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
+ assertThat(data.name).isNull()
}
@Test
@@ -421,9 +422,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
deviceCallback.onSelectedDeviceStateChanged(device, 1)
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
- // THEN the device is disabled
+ // THEN the device is disabled and name is set to null
val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
+ assertThat(data.name).isNull()
}
@Test
@@ -440,9 +442,24 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
deviceCallback.onDeviceListUpdate(mutableListOf(device))
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
- // THEN the device is disabled
+ // THEN the device is disabled and name is set to null
val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
+ assertThat(data.name).isNull()
+ }
+
+ @Test
+ fun mr2ReturnsRouteWithNullName_useLocalDeviceName() {
+ // GIVEN that MR2Manager returns a routing session that does not have a name
+ whenever(route.name).thenReturn(null)
+ // WHEN a notification is added
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+ // THEN the device is enabled and uses the current connected device name
+ val data = captureDeviceData(KEY)
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ assertThat(data.enabled).isTrue()
}
@Test
@@ -647,12 +664,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
- override fun onBroadcastMetadataChanged(broadcastId: Int,
- metadata: BluetoothLeBroadcastMetadata) {}
+ override fun onBroadcastMetadataChanged(
+ broadcastId: Int,
+ metadata: BluetoothLeBroadcastMetadata
+ ) {}
}
bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback)
- return callback;
+ return callback
}
fun setupLeAudioConfiguration(isLeAudio: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 260bb8760f1c..22ecb4b93743 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -78,7 +78,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
- when(mMediaOutputController.isTransferring()).thenReturn(false);
+ when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
@@ -208,7 +208,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() {
- when(mMediaOutputController.isTransferring()).thenReturn(true);
+ when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -224,7 +224,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() {
- when(mMediaOutputController.isTransferring()).thenReturn(true);
+ when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice2.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 1061e3c6b0d5..fa47a746f8ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -35,7 +35,9 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -48,11 +50,12 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -78,6 +81,10 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var viewUtil: ViewUtil
@Mock
private lateinit var commandQueue: CommandQueue
+ @Mock
+ private lateinit var falsingManager: FalsingManager
+ @Mock
+ private lateinit var falsingCollector: FalsingCollector
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
private lateinit var fakeClock: FakeSystemClock
@@ -115,7 +122,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
accessibilityManager,
configurationController,
powerManager,
- senderUiEventLogger
+ senderUiEventLogger,
+ falsingManager,
+ falsingCollector
)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -421,6 +430,38 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
}
@Test
+ fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isFalse()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isTrue()
+ }
+
+ @Test
fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 48fbd354b98d..073c23cec569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -23,6 +23,7 @@ import android.graphics.Insets
import android.graphics.Rect
import android.hardware.HardwareBuffer
import android.os.Bundle
+import android.os.UserHandle
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
@@ -97,7 +98,7 @@ class RequestProcessorTest {
policy.setManagedProfile(USER_ID, false)
policy.setDisplayContentInfo(
policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
val processor = RequestProcessor(imageCapture, policy, flags, scope)
@@ -120,7 +121,7 @@ class RequestProcessorTest {
// Indicate that the primary content belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
val processor = RequestProcessor(imageCapture, policy, flags, scope)
@@ -160,7 +161,7 @@ class RequestProcessorTest {
policy.setManagedProfile(USER_ID, false)
policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
val processedRequest = processor.process(request)
@@ -183,7 +184,7 @@ class RequestProcessorTest {
// Indicate that the primary content belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
val processedRequest = processor.process(request)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
new file mode 100644
index 000000000000..17396b13036c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import android.app.IActivityTaskManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.Rect
+import android.os.UserHandle
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// The following values are chosen to be distinct from commonly seen real values
+private const val DISPLAY_ID = 100
+private const val PRIMARY_USER = 2000
+private const val MANAGED_PROFILE_USER = 3000
+
+@RunWith(AndroidTestingRunner::class)
+class ScreenshotPolicyImplTest : SysuiTestCase() {
+
+ @Test
+ fun testToDisplayContentInfo() {
+ assertThat(fullScreenWorkProfileTask.toDisplayContentInfo())
+ .isEqualTo(
+ DisplayContentInfo(
+ ComponentName(
+ "com.google.android.apps.nbu.files",
+ "com.google.android.apps.nbu.files.home.HomeActivity"
+ ),
+ Rect(0, 0, 1080, 2400),
+ UserHandle.of(MANAGED_PROFILE_USER),
+ 65))
+ }
+
+ @Test
+ fun findPrimaryContent_ignoresPipTask() = runBlocking {
+ val policy = fakeTasksPolicyImpl(
+ mContext,
+ shadeExpanded = false,
+ tasks = listOf(
+ pipTask,
+ fullScreenWorkProfileTask,
+ launcherTask,
+ emptyTask)
+ )
+
+ val info = policy.findPrimaryContent(DISPLAY_ID)
+ assertThat(info).isEqualTo(fullScreenWorkProfileTask.toDisplayContentInfo())
+ }
+
+ @Test
+ fun findPrimaryContent_shadeExpanded_ignoresTopTask() = runBlocking {
+ val policy = fakeTasksPolicyImpl(
+ mContext,
+ shadeExpanded = true,
+ tasks = listOf(
+ fullScreenWorkProfileTask,
+ launcherTask,
+ emptyTask)
+ )
+
+ val info = policy.findPrimaryContent(DISPLAY_ID)
+ assertThat(info).isEqualTo(policy.systemUiContent)
+ }
+
+ @Test
+ fun findPrimaryContent_emptyTaskList() = runBlocking {
+ val policy = fakeTasksPolicyImpl(
+ mContext,
+ shadeExpanded = false,
+ tasks = listOf()
+ )
+
+ val info = policy.findPrimaryContent(DISPLAY_ID)
+ assertThat(info).isEqualTo(policy.systemUiContent)
+ }
+
+ @Test
+ fun findPrimaryContent_workProfileNotOnTop() = runBlocking {
+ val policy = fakeTasksPolicyImpl(
+ mContext,
+ shadeExpanded = false,
+ tasks = listOf(
+ launcherTask,
+ fullScreenWorkProfileTask,
+ emptyTask)
+ )
+
+ val info = policy.findPrimaryContent(DISPLAY_ID)
+ assertThat(info).isEqualTo(launcherTask.toDisplayContentInfo())
+ }
+
+ private fun fakeTasksPolicyImpl(
+ context: Context,
+ shadeExpanded: Boolean,
+ tasks: List<RootTaskInfo>
+ ): ScreenshotPolicyImpl {
+ val userManager = mock<UserManager>()
+ val atmService = mock<IActivityTaskManager>()
+ val dispatcher = Dispatchers.Unconfined
+
+ return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher) {
+ override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER)
+ override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks
+ override suspend fun isNotificationShadeExpanded() = shadeExpanded
+ }
+ }
+
+ private val pipTask = RootTaskInfo().apply {
+ configuration.windowConfiguration.apply {
+ windowingMode = WINDOWING_MODE_PINNED
+ bounds = Rect(628, 1885, 1038, 2295)
+ activityType = ACTIVITY_TYPE_STANDARD
+ }
+ displayId = DISPLAY_ID
+ userId = PRIMARY_USER
+ taskId = 66
+ visible = true
+ isVisible = true
+ isRunning = true
+ numActivities = 1
+ topActivity = ComponentName(
+ "com.google.android.youtube",
+ "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
+ )
+ childTaskIds = intArrayOf(66)
+ childTaskNames = arrayOf("com.google.android.youtube/" +
+ "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity")
+ childTaskUserIds = intArrayOf(0)
+ childTaskBounds = arrayOf(Rect(628, 1885, 1038, 2295))
+ }
+
+ private val fullScreenWorkProfileTask = RootTaskInfo().apply {
+ configuration.windowConfiguration.apply {
+ windowingMode = WINDOWING_MODE_FULLSCREEN
+ bounds = Rect(0, 0, 1080, 2400)
+ activityType = ACTIVITY_TYPE_STANDARD
+ }
+ displayId = DISPLAY_ID
+ userId = MANAGED_PROFILE_USER
+ taskId = 65
+ visible = true
+ isVisible = true
+ isRunning = true
+ numActivities = 1
+ topActivity = ComponentName(
+ "com.google.android.apps.nbu.files",
+ "com.google.android.apps.nbu.files.home.HomeActivity"
+ )
+ childTaskIds = intArrayOf(65)
+ childTaskNames = arrayOf("com.google.android.apps.nbu.files/" +
+ "com.google.android.apps.nbu.files.home.HomeActivity")
+ childTaskUserIds = intArrayOf(MANAGED_PROFILE_USER)
+ childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
+ }
+
+ private val launcherTask = RootTaskInfo().apply {
+ configuration.windowConfiguration.apply {
+ windowingMode = WINDOWING_MODE_FULLSCREEN
+ bounds = Rect(0, 0, 1080, 2400)
+ activityType = ACTIVITY_TYPE_HOME
+ }
+ displayId = DISPLAY_ID
+ taskId = 1
+ userId = PRIMARY_USER
+ visible = true
+ isVisible = true
+ isRunning = true
+ numActivities = 1
+ topActivity = ComponentName(
+ "com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity",
+ )
+ childTaskIds = intArrayOf(1)
+ childTaskNames = arrayOf("com.google.android.apps.nexuslauncher/" +
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
+ childTaskUserIds = intArrayOf(0)
+ childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
+ }
+
+ private val emptyTask = RootTaskInfo().apply {
+ configuration.windowConfiguration.apply {
+ windowingMode = WINDOWING_MODE_FULLSCREEN
+ bounds = Rect(0, 0, 1080, 2400)
+ activityType = ACTIVITY_TYPE_UNDEFINED
+ }
+ displayId = DISPLAY_ID
+ taskId = 2
+ userId = PRIMARY_USER
+ visible = false
+ isVisible = false
+ isRunning = false
+ numActivities = 0
+ childTaskIds = intArrayOf(3, 4)
+ childTaskNames = arrayOf("", "")
+ childTaskUserIds = intArrayOf(0, 0)
+ childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400), Rect(0, 2400, 1080, 4800))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 98389c2c7a6f..e2ce939cb66c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -458,7 +458,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
- mDumpManager,
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
mInteractionJankMonitor),
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 312db2d7066a..2e74bf5474f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -85,6 +85,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Mock
MediaOutputDialogFactory mMediaOutputDialogFactory;
@Mock
+ VolumePanelFactory mVolumePanelFactory;
+ @Mock
ActivityStarter mActivityStarter;
@Mock
InteractionJankMonitor mInteractionJankMonitor;
@@ -102,6 +104,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mDeviceProvisionedController,
mConfigurationController,
mMediaOutputDialogFactory,
+ mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor);
mDialog.init(0, null);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index b53ad0a3726f..c56fdb17b5f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -16,14 +16,12 @@
package com.android.systemui.flags
-import android.util.SparseArray
-import android.util.SparseBooleanArray
-import androidx.core.util.containsKey
-
class FakeFeatureFlags : FeatureFlags {
- private val booleanFlags = SparseBooleanArray()
- private val stringFlags = SparseArray<String>()
+ private val booleanFlags = mutableMapOf<Int, Boolean>()
+ private val stringFlags = mutableMapOf<Int, String>()
private val knownFlagNames = mutableMapOf<Int, String>()
+ private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>()
+ private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
init {
Flags.getFlagFields().forEach { field ->
@@ -33,27 +31,52 @@ class FakeFeatureFlags : FeatureFlags {
}
fun set(flag: BooleanFlag, value: Boolean) {
- booleanFlags.put(flag.id, value)
+ if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+ notifyFlagChanged(flag)
+ }
}
fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
- booleanFlags.put(flag.id, value)
+ if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+ notifyFlagChanged(flag)
+ }
}
fun set(flag: ResourceBooleanFlag, value: Boolean) {
- booleanFlags.put(flag.id, value)
+ if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+ notifyFlagChanged(flag)
+ }
}
fun set(flag: SysPropBooleanFlag, value: Boolean) {
- booleanFlags.put(flag.id, value)
+ if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+ notifyFlagChanged(flag)
+ }
}
fun set(flag: StringFlag, value: String) {
- stringFlags.put(flag.id, value)
+ if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+ notifyFlagChanged(flag)
+ }
}
fun set(flag: ResourceStringFlag, value: String) {
- stringFlags.put(flag.id, value)
+ if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+ notifyFlagChanged(flag)
+ }
+ }
+
+ private fun notifyFlagChanged(flag: Flag<*>) {
+ flagListeners[flag.id]?.let { listeners ->
+ listeners.forEach { listener ->
+ listener.onFlagChanged(
+ object : FlagListenable.FlagEvent {
+ override val flagId = flag.id
+ override fun requestNoRestart() {}
+ }
+ )
+ }
+ }
}
override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id)
@@ -70,25 +93,30 @@ class FakeFeatureFlags : FeatureFlags {
override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
- override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {}
+ override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
+ flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener)
+ listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id)
+ }
- override fun removeListener(listener: FlagListenable.Listener) {}
+ override fun removeListener(listener: FlagListenable.Listener) {
+ listenerFlagIds.remove(listener)?.let {
+ flagIds -> flagIds.forEach {
+ id -> flagListeners[id]?.remove(listener)
+ }
+ }
+ }
private fun flagName(flagId: Int): String {
return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
}
private fun requireBooleanValue(flagId: Int): Boolean {
- if (!booleanFlags.containsKey(flagId)) {
- throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
- }
return booleanFlags[flagId]
+ ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
}
private fun requireStringValue(flagId: Int): String {
- if (!stringFlags.containsKey(flagId)) {
- throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
- }
return stringFlags[flagId]
+ ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
}
}
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
index e2b22dc1bd3d..ec6c2f00b3b1 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
@@ -21,12 +21,12 @@ import static java.lang.System.out;
import android.annotation.NonNull;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.AmbientContextManager;
import android.content.ComponentName;
import android.os.Binder;
import android.os.RemoteCallback;
import android.os.ShellCommand;
import android.service.ambientcontext.AmbientContextDetectionResult;
-import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
import java.io.PrintWriter;
@@ -51,13 +51,13 @@ final class AmbientContextShellCommand extends ShellCommand {
/** Callbacks for AmbientContextEventService results used internally for testing. */
static class TestableCallbackInternal {
private AmbientContextDetectionResult mLastResult;
- private AmbientContextDetectionServiceStatus mLastStatus;
+ private int mLastStatus;
public AmbientContextDetectionResult getLastResult() {
return mLastResult;
}
- public AmbientContextDetectionServiceStatus getLastStatus() {
+ public int getLastStatus() {
return mLastStatus;
}
@@ -80,13 +80,10 @@ final class AmbientContextShellCommand extends ShellCommand {
@NonNull
private RemoteCallback createRemoteStatusCallback() {
return new RemoteCallback(result -> {
- AmbientContextDetectionServiceStatus status =
- (AmbientContextDetectionServiceStatus) result.get(
- AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
+ int status = result.getInt(AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY);
final long token = Binder.clearCallingIdentity();
try {
mLastStatus = status;
- out.println("Status available: " + status);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -110,8 +107,6 @@ final class AmbientContextShellCommand extends ShellCommand {
return runStopDetection();
case "get-last-status-code":
return getLastStatusCode();
- case "get-last-package-name":
- return getLastPackageName();
case "query-service-status":
return runQueryServiceStatus();
case "get-bound-package":
@@ -126,7 +121,8 @@ final class AmbientContextShellCommand extends ShellCommand {
private int runStartDetection() {
final int userId = Integer.parseInt(getNextArgRequired());
final String packageName = getNextArgRequired();
- mService.startDetection(userId, REQUEST, packageName,
+ mService.startDetection(
+ userId, REQUEST, packageName,
sTestableCallbackInternal.createRemoteDetectionResultCallback(),
sTestableCallbackInternal.createRemoteStatusCallback());
return 0;
@@ -151,18 +147,9 @@ final class AmbientContextShellCommand extends ShellCommand {
}
private int getLastStatusCode() {
- AmbientContextDetectionServiceStatus lastResponse =
- sTestableCallbackInternal.getLastStatus();
- if (lastResponse == null) {
- return -1;
- }
- return lastResponse.getStatusCode();
- }
-
- private int getLastPackageName() {
- AmbientContextDetectionServiceStatus lastResponse =
- sTestableCallbackInternal.getLastStatus();
- out.println(lastResponse == null ? "" : lastResponse.getPackageName());
+ final PrintWriter resultPrinter = getOutPrintWriter();
+ int lastStatus = sTestableCallbackInternal.getLastStatus();
+ resultPrinter.println(lastStatus);
return 0;
}
@@ -174,22 +161,21 @@ final class AmbientContextShellCommand extends ShellCommand {
pw.println(" Print this help text.");
pw.println();
pw.println(" start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection.");
- pw.println(" stop-detection USER_ID: Stops AmbientContextEvent detection.");
+ pw.println(" stop-detection USER_ID PACKAGE_NAME: Stops AmbientContextEvent detection.");
pw.println(" get-last-status-code: Prints the latest request status code.");
- pw.println(" get-last-package-name: Prints the latest request package name.");
- pw.println(" query-event-status USER_ID PACKAGE_NAME: Prints the event status code.");
+ pw.println(" query-service-status USER_ID PACKAGE_NAME: Prints the service status code.");
pw.println(" get-bound-package USER_ID:"
+ " Print the bound package that implements the service.");
- pw.println(" set-temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" set-temporary-service USER_ID [PACKAGE_NAME] [COMPONENT_NAME DURATION]");
pw.println(" Temporarily (for DURATION ms) changes the service implementation.");
pw.println(" To reset, call with just the USER_ID argument.");
}
private int getBoundPackageName() {
- final PrintWriter out = getOutPrintWriter();
+ final PrintWriter resultPrinter = getOutPrintWriter();
final int userId = Integer.parseInt(getNextArgRequired());
final ComponentName componentName = mService.getComponentName(userId);
- out.println(componentName == null ? "" : componentName.getPackageName());
+ resultPrinter.println(componentName == null ? "" : componentName.getPackageName());
return 0;
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 25d0752844fd..c835d2fe1bbd 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -116,8 +116,10 @@ public abstract class BrightnessMappingStrategy {
luxLevels = getLuxLevels(resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevelsIdle));
} else {
- brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
- luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
+ brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
+ luxLevels = getLuxLevels(resources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevels));
}
// Display independent, mode independent values
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 3b627ef6a786..4f3fd6409cd8 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
import android.os.Environment;
@@ -150,22 +149,12 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </quirks>
*
* <autoBrightness>
- * <brighteningLightDebounceMillis>
+ * <brighteningLightDebounceMillis>
* 2000
- * </brighteningLightDebounceMillis>
+ * </brighteningLightDebounceMillis>
* <darkeningLightDebounceMillis>
* 1000
* </darkeningLightDebounceMillis>
- * <displayBrightnessMapping>
- * <displayBrightnessPoint>
- * <lux>50</lux>
- * <nits>45</nits>
- * </displayBrightnessPoint>
- * <displayBrightnessPoint>
- * <lux>80</lux>
- * <nits>75</nits>
- * </displayBrightnessPoint>
- * </displayBrightnessMapping>
* </autoBrightness>
*
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -279,39 +268,6 @@ public class DisplayDeviceConfig {
// for the corresponding values above
private float[] mBrightness;
-
- /**
- * Array of desired screen brightness in nits corresponding to the lux values
- * in the mBrightnessLevelsLux array. The display brightness is defined as the
- * measured brightness of an all-white image. The brightness values must be non-negative and
- * non-decreasing. This must be overridden in platform specific overlays
- */
- private float[] mBrightnessLevelsNits;
-
- /**
- * Array of light sensor lux values to define our levels for auto backlight
- * brightness support.
- * The N entries of this array define N + 1 control points as follows:
- * (1-based arrays)
- *
- * Point 1: (0, value[1]): lux <= 0
- * Point 2: (level[1], value[2]): 0 < lux <= level[1]
- * Point 3: (level[2], value[3]): level[2] < lux <= level[3]
- * ...
- * Point N+1: (level[N], value[N+1]): level[N] < lux
- *
- * The control points must be strictly increasing. Each control point
- * corresponds to an entry in the brightness backlight values arrays.
- * For example, if lux == level[1] (first element of the levels array)
- * then the brightness will be determined by value[2] (second element
- * of the brightness values array).
- *
- * Spline interpolation is used to determine the auto-brightness
- * backlight values for lux levels between these control points.
- *
- */
- private float[] mBrightnessLevelsLux;
-
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
@@ -705,20 +661,6 @@ public class DisplayDeviceConfig {
return mAutoBrightnessBrighteningLightDebounce;
}
- /**
- * @return Auto brightness brightening ambient lux levels
- */
- public float[] getAutoBrightnessBrighteningLevelsLux() {
- return mBrightnessLevelsLux;
- }
-
- /**
- * @return Auto brightness brightening nits levels
- */
- public float[] getAutoBrightnessBrighteningLevelsNits() {
- return mBrightnessLevelsNits;
- }
-
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -761,8 +703,6 @@ public class DisplayDeviceConfig {
+ mAutoBrightnessBrighteningLightDebounce
+ ", mAutoBrightnessDarkeningLightDebounce= "
+ mAutoBrightnessDarkeningLightDebounce
- + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
- + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ "}";
}
@@ -839,7 +779,6 @@ public class DisplayDeviceConfig {
loadBrightnessRampsFromConfigXml();
loadAmbientLightSensorFromConfigXml();
setProxSensorUnspecified();
- loadAutoBrightnessConfigsFromConfigXml();
mLoadedFrom = "<config.xml>";
}
@@ -1052,7 +991,6 @@ public class DisplayDeviceConfig {
private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
- loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
}
/**
@@ -1085,33 +1023,6 @@ public class DisplayDeviceConfig {
}
}
- /**
- * Loads the auto-brightness display brightness mappings. Internally, this takes care of
- * loading the value from the display config, and if not present, falls back to config.xml.
- */
- private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
- if (autoBrightnessConfig == null
- || autoBrightnessConfig.getDisplayBrightnessMapping() == null) {
- mBrightnessLevelsNits = getFloatArray(mContext.getResources()
- .obtainTypedArray(com.android.internal.R.array
- .config_autoBrightnessDisplayValuesNits));
- mBrightnessLevelsLux = getFloatArray(mContext.getResources()
- .obtainTypedArray(com.android.internal.R.array
- .config_autoBrightnessLevels));
- } else {
- final int size = autoBrightnessConfig.getDisplayBrightnessMapping()
- .getDisplayBrightnessPoint().size();
- mBrightnessLevelsNits = new float[size];
- mBrightnessLevelsLux = new float[size];
- for (int i = 0; i < size; i++) {
- mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
- .getDisplayBrightnessPoint().get(i).getNits().floatValue();
- mBrightnessLevelsLux[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
- .getDisplayBrightnessPoint().get(i).getLux().floatValue();
- }
- }
- }
-
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -1337,10 +1248,6 @@ public class DisplayDeviceConfig {
com.android.internal.R.string.config_displayLightSensorType);
}
- private void loadAutoBrightnessConfigsFromConfigXml() {
- loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
- }
-
private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
final SensorDetails sensorDetails = config.getLightSensor();
if (sensorDetails != null) {
@@ -1483,22 +1390,6 @@ public class DisplayDeviceConfig {
}
}
- /**
- * Extracts a float array from the specified {@link TypedArray}.
- *
- * @param array The array to convert.
- * @return the given array as a float array.
- */
- public static float[] getFloatArray(TypedArray array) {
- final int n = array.length();
- float[] vals = new float[n];
- for (int i = 0; i < n; i++) {
- vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
- }
- array.recycle();
- return vals;
- }
-
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a174c54eae98..6fcd285fde79 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1582,13 +1582,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (newParent != null && isState(RESUMED)) {
newParent.setResumedActivity(this, "onParentChanged");
- if (mStartingWindow != null && mStartingData != null
- && mStartingData.mAssociatedTask == null && newParent.isEmbedded()) {
- // The starting window should keep covering its task when the activity is
- // reparented to a task fragment that may not fill the task bounds.
- associateStartingDataWithTask();
- attachStartingSurfaceToAssociatedTask();
- }
mImeInsetsFrozenUntilStartInput = false;
}
@@ -2679,14 +2672,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ /** Called when the starting window is added to this activity. */
void attachStartingWindow(@NonNull WindowState startingWindow) {
startingWindow.mStartingData = mStartingData;
mStartingWindow = startingWindow;
+ // The snapshot type may have called associateStartingDataWithTask().
if (mStartingData != null && mStartingData.mAssociatedTask != null) {
attachStartingSurfaceToAssociatedTask();
}
}
+ /** Makes starting window always fill the associated task. */
private void attachStartingSurfaceToAssociatedTask() {
// Associate the configuration of starting window with the task.
overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
@@ -2694,6 +2690,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mStartingData.mAssociatedTask.mSurfaceControl);
}
+ /** Called when the starting window is not added yet but its data is known to fill the task. */
private void associateStartingDataWithTask() {
mStartingData.mAssociatedTask = task;
task.forAllActivities(r -> {
@@ -2703,6 +2700,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
});
}
+ /** Associates and attaches an added starting window to the current task. */
+ void associateStartingWindowWithTaskIfNeeded() {
+ if (mStartingWindow == null || mStartingData == null
+ || mStartingData.mAssociatedTask != null) {
+ return;
+ }
+ associateStartingDataWithTask();
+ attachStartingSurfaceToAssociatedTask();
+ }
+
void removeStartingWindow() {
boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0376974900e3..b64409c21280 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3176,8 +3176,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();
setRotationAnimation(null);
+ // Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
+ // -> this DisplayContent.
+ setRemoteInsetsController(null);
mWmService.mAnimator.removeDisplayLocked(mDisplayId);
mOverlayLayer.release();
+ mWindowingLayer.release();
mInputMonitor.onDisplayRemoved();
mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 18b0e3311a94..522a6c19c6be 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1433,6 +1433,13 @@ class Task extends TaskFragment {
final TaskFragment childTaskFrag = child.asTaskFragment();
if (childTaskFrag != null && childTaskFrag.asTask() == null) {
childTaskFrag.setMinDimensions(mMinWidth, mMinHeight);
+
+ // The starting window should keep covering its task when a pure TaskFragment is added
+ // because its bounds may not fill the task.
+ final ActivityRecord top = getTopMostActivity();
+ if (top != null) {
+ top.associateStartingWindowWithTaskIfNeeded();
+ }
}
}
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
new file mode 100644
index 000000000000..477625a36cbd
--- /dev/null
+++ b/services/core/xsd/display-device-config/autobrightness.xsd
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<xs:schema version="2.0"
+ elementFormDefault="qualified"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+</xs:schema> \ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 98f83d8c0d09..bea5e2c2de74 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,6 +23,7 @@
<xs:schema version="2.0"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:include schemaLocation="autobrightness.xsd" />
<xs:element name="displayConfiguration">
<xs:complexType>
<xs:sequence>
@@ -342,74 +343,4 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
-
- <xs:complexType name="autoBrightness">
- <xs:sequence>
- <!-- Sets the debounce for autoBrightness brightening in millis-->
- <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- <!-- Sets the debounce for autoBrightness darkening in millis-->
- <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- <!-- Sets the brightness mapping of the desired screen brightness in nits to the
- corresponding lux for the current display -->
- <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
-
- <!-- Represents the brightness mapping of the desired screen brightness in nits to the
- corresponding lux for the current display -->
- <xs:complexType name="displayBrightnessMapping">
- <xs:sequence>
- <!-- Sets the list of display brightness points, each representing the desired screen
- brightness in nits to the corresponding lux for the current display
-
- The N entries of this array define N + 1 control points as follows:
- (1-based arrays)
-
- Point 1: (0, nits[1]): currentLux <= 0
- Point 2: (lux[1], nits[2]): 0 < currentLux <= lux[1]
- Point 3: (lux[2], nits[3]): lux[2] < currentLux <= lux[3]
- ...
- Point N+1: (lux[N], nits[N+1]): lux[N] < currentLux
-
- The control points must be strictly increasing. Each control point
- corresponds to an entry in the brightness backlight values arrays.
- For example, if currentLux == lux[1] (first element of the levels array)
- then the brightness will be determined by nits[2] (second element
- of the brightness values array).
- -->
- <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
- minOccurs="1" maxOccurs="unbounded">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
-
- <!-- Represents a point in the display brightness mapping, representing the lux level from the
- light sensor to the desired screen brightness in nits at this level -->
- <xs:complexType name="displayBrightnessPoint">
- <xs:sequence>
- <!-- The lux level from the light sensor. This must be a non-negative integer -->
- <xs:element name="lux" type="xs:nonNegativeInteger"
- minOccurs="1" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
-
- <!-- Desired screen brightness in nits corresponding to the suggested lux values.
- The display brightness is defined as the measured brightness of an all-white image.
- This must be a non-negative integer -->
- <xs:element name="nits" type="xs:nonNegativeInteger"
- minOccurs="1" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e5d26177b725..e9a926946764 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -5,10 +5,8 @@ package com.android.server.display.config {
ctor public AutoBrightness();
method public final java.math.BigInteger getBrighteningLightDebounceMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
- method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
- method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
}
public class BrightnessThresholds {
@@ -45,19 +43,6 @@ package com.android.server.display.config {
method public java.util.List<com.android.server.display.config.Density> getDensity();
}
- public class DisplayBrightnessMapping {
- ctor public DisplayBrightnessMapping();
- method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
- }
-
- public class DisplayBrightnessPoint {
- ctor public DisplayBrightnessPoint();
- method public final java.math.BigInteger getLux();
- method public final java.math.BigInteger getNits();
- method public final void setLux(java.math.BigInteger);
- method public final void setNits(java.math.BigInteger);
- }
-
public class DisplayConfiguration {
ctor public DisplayConfiguration();
method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 220cd890e045..617321beadd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -149,12 +149,6 @@ public class LocalDisplayAdapterTest {
.thenReturn(mockArray);
when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
.thenReturn(mockArray);
- when(mMockedResources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
- .thenReturn(mockArray);
- when(mMockedResources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessLevels))
- .thenReturn(mockArray);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java
new file mode 100644
index 000000000000..6bb494d3a92a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ambientcontext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.content.Intent;
+import android.os.RemoteCallback;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link AmbientContextManagerService}.
+ * atest FrameworksServicesTests:AmbientContextManagerServiceTest
+ */
+public class AmbientContextManagerServiceTest {
+ public static final String SYSTEM_PACKAGE_NAME = "com.android.frameworks.servicestests";
+ private static final int USER_ID = UserHandle.USER_SYSTEM;
+
+ @SmallTest
+ @Test
+ public void testClientRequest() {
+ AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
+ .addEventType(AmbientContextEvent.EVENT_COUGH)
+ .build();
+ Intent intent = new Intent();
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ InstrumentationRegistry.getTargetContext(), 0,
+ intent, PendingIntent.FLAG_IMMUTABLE);
+ AmbientContextManagerService.ClientRequest clientRequest =
+ new AmbientContextManagerService.ClientRequest(USER_ID, request,
+ pendingIntent, new RemoteCallback(result -> {}));
+
+ assertThat(clientRequest.getRequest()).isEqualTo(request);
+ assertThat(clientRequest.getPackageName()).isEqualTo(SYSTEM_PACKAGE_NAME);
+ assertThat(clientRequest.hasUserId(USER_ID)).isTrue();
+ assertThat(clientRequest.hasUserId(-1)).isFalse();
+ assertThat(clientRequest.hasUserIdAndPackageName(USER_ID, SYSTEM_PACKAGE_NAME)).isTrue();
+ assertThat(clientRequest.hasUserIdAndPackageName(-1, SYSTEM_PACKAGE_NAME)).isFalse();
+ assertThat(clientRequest.hasUserIdAndPackageName(USER_ID, "random.package.name"))
+ .isFalse();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ambientcontext/OWNERS b/services/tests/servicestests/src/com/android/server/ambientcontext/OWNERS
new file mode 100644
index 000000000000..ddfb6e39fd3a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ambientcontext/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/ambientcontext/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 261b882319d8..03ea6137074d 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -19,19 +19,16 @@ package com.android.server.display;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,16 +52,22 @@ public final class DisplayDeviceConfigTest {
private Resources mResources;
@Before
- public void setUp() {
+ public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
mockDeviceConfigs();
+ try {
+ Path tempFile = Files.createTempFile("display_config", ".tmp");
+ Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+ mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+ } catch (IOException e) {
+ throw new IOException("Failed to setup the display device config.", e);
+ }
}
@Test
- public void testConfigValuesFromDisplayConfig() throws IOException {
- setupDisplayDeviceConfigFromDisplayConfigFile();
-
+ public void testConfigValues() {
assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
@@ -85,24 +88,10 @@ public final class DisplayDeviceConfigTest {
assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{50.0f, 80.0f}, 0.0f);
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{45.0f, 75.0f}, 0.0f);
- // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
- // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
- }
- @Test
- public void testConfigValuesFromDeviceConfig() {
- setupDisplayDeviceConfigFromDeviceConfigFile();
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 110.0f, 500.0f}, 0.0f);
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{2.0f, 200.0f, 600.0f}, 0.0f);
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-
+ // Also add test for the case where optional display configs are null
}
private String getContent() {
@@ -125,16 +114,6 @@ public final class DisplayDeviceConfigTest {
+ "<autoBrightness>\n"
+ "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+ "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
- + "<displayBrightnessMapping>\n"
- + "<displayBrightnessPoint>\n"
- + "<lux>50</lux>\n"
- + "<nits>45</nits>\n"
- + "</displayBrightnessPoint>\n"
- + "<displayBrightnessPoint>\n"
- + "<lux>80</lux>\n"
- + "<nits>75</nits>\n"
- + "</displayBrightnessPoint>\n"
- + "</displayBrightnessMapping>\n"
+ "</autoBrightness>\n"
+ "<highBrightnessMode enabled=\"true\">\n"
+ "<transitionPoint>0.62</transitionPoint>\n"
@@ -206,64 +185,4 @@ public final class DisplayDeviceConfigTest {
when(mResources.getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
}
-
- private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
- Path tempFile = Files.createTempFile("display_config", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
- mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
- mDisplayDeviceConfig.initFromFile(tempFile.toFile());
- }
-
- private void setupDisplayDeviceConfigFromDeviceConfigFile() {
- TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
- when(mResources.obtainTypedArray(
- com.android.internal.R.array.config_screenBrightnessNits))
- .thenReturn(screenBrightnessNits);
- TypedArray screenBrightnessBacklight = createFloatTypedArray(new
- float[]{0.0f, 120.0f, 255.0f});
- when(mResources.obtainTypedArray(
- com.android.internal.R.array.config_screenBrightnessBacklight))
- .thenReturn(screenBrightnessBacklight);
- when(mResources.getIntArray(com.android.internal.R.array
- .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
-
- when(mResources.getIntArray(com.android.internal.R.array
- .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
- when(mResources.getIntArray(com.android.internal.R.array
- .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
-
- TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
- float[]{2.0f, 200.0f, 600.0f});
- when(mResources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
- .thenReturn(screenBrightnessLevelNits);
- TypedArray screenBrightnessLevelLux = createFloatTypedArray(new
- float[]{0.0f, 110.0f, 500.0f});
- when(mResources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessLevels))
- .thenReturn(screenBrightnessLevelLux);
-
- mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
-
- }
-
- private TypedArray createFloatTypedArray(float[] vals) {
- TypedArray mockArray = mock(TypedArray.class);
- when(mockArray.length()).thenAnswer(invocation -> {
- return vals.length;
- });
- when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
- final float def = (float) invocation.getArguments()[1];
- if (vals == null) {
- return def;
- }
- int idx = (int) invocation.getArguments()[0];
- if (idx >= 0 && idx < vals.length) {
- return vals[idx];
- } else {
- return def;
- }
- });
- return mockArray;
- }
}
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 a8b864bab553..eb61a9ce57a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2871,6 +2871,7 @@ public class ActivityRecordTests extends WindowTestsBase {
mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
task.addChild(taskFragment1, POSITION_TOP);
+ assertEquals(task, activity1.mStartingData.mAssociatedTask);
final TaskFragment taskFragment2 = new TaskFragment(
mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2892,7 +2893,6 @@ public class ActivityRecordTests extends WindowTestsBase {
eq(task.mSurfaceControl));
assertEquals(activity1.mStartingData, startingWindow.mStartingData);
assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
- assertEquals(task, activity1.mStartingData.mAssociatedTask);
assertEquals(taskFragment1.getBounds(), activity1.getBounds());
// The activity was resized by task fragment, but starting window must still cover the task.
assertEquals(taskBounds, activity1.mStartingWindow.getBounds());
@@ -2900,7 +2900,6 @@ public class ActivityRecordTests extends WindowTestsBase {
// The starting window is only removed when all embedded activities are drawn.
final WindowState activityWindow = mock(WindowState.class);
activity1.onFirstWindowDrawn(activityWindow);
- assertNotNull(activity1.mStartingWindow);
activity2.onFirstWindowDrawn(activityWindow);
assertNull(activity1.mStartingWindow);
}