Merge "Delete unused camera app helper"
diff --git a/core/api/current.txt b/core/api/current.txt
index 941897c..f786e36 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24664,11 +24664,15 @@
field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
- field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3; // 0x3
+ field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4; // 0x4
field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
- field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2; // 0x2
+ field public static final int SUBTEXT_DEVICE_LOW_POWER = 5; // 0x5
+ field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3; // 0x3
+ field public static final int SUBTEXT_ERROR_UNKNOWN = 1; // 0x1
field public static final int SUBTEXT_NONE = 0; // 0x0
- field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1; // 0x1
+ field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2; // 0x2
+ field public static final int SUBTEXT_TRACK_UNSUPPORTED = 7; // 0x7
+ field public static final int SUBTEXT_UNAUTHORIZED = 6; // 0x6
}
public static final class RouteListingPreference.Item.Builder {
@@ -32205,7 +32209,6 @@
method public void onEarlyReportFinished();
method public void onError(int);
method public void onFinished();
- method public void onFinished(@NonNull String);
method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float);
field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5
field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d6aa7fc..43d733c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1231,6 +1231,7 @@
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER";
field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
+ field public static final int EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS = 1; // 0x1
field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -6756,6 +6757,7 @@
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
+ field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5
field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
@@ -10073,6 +10075,10 @@
method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
}
+ public abstract static class BugreportManager.BugreportCallback {
+ method public void onFinished(@NonNull String);
+ }
+
public final class BugreportParams {
ctor public BugreportParams(int);
ctor public BugreportParams(int, int);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b494a20..3fe63d8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3877,13 +3877,24 @@
public static final int EXEMPT_FROM_APP_STANDBY = 0;
/**
+ * Prevent an app from dismissible notifications. Starting from Android U, notifications with
+ * the ongoing parameter can be dismissed by a user on an unlocked device. An app with
+ * this exemption can create non-dismissable notifications.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS = 1;
+
+ /**
* Exemptions to platform restrictions, given to an application through
* {@link #setApplicationExemptions(String, Set)}.
*
* @hide
*/
@IntDef(prefix = { "EXEMPT_FROM_"}, value = {
- EXEMPT_FROM_APP_STANDBY
+ EXEMPT_FROM_APP_STANDBY,
+ EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationExemptionConstants {}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 3a61ca1..1b5c196 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -304,4 +304,9 @@
* True if either the entire device or the user is organization managed.
*/
public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);
+
+ /**
+ * Returns whether the application exemptions feature flag is enabled.
+ */
+ public abstract boolean isApplicationExemptionsFlagEnabled();
}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 2d2b0fc..086b0e5 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -70,10 +70,7 @@
* An interface describing the callback for bugreport progress and status.
*
* <p>Callers will receive {@link #onProgress} calls as the bugreport progresses, followed by a
- * terminal call to either {@link #onFinished} or {@link #onError}. Note that
- * {@link #onFinished(String)} will only be invoked when calling {@code startBugreport} with the
- * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag set. Otherwise,
- * {@link #onFinished()} will be invoked.
+ * terminal call to either {@link #onFinished} or {@link #onError}.
*
* <p>If an issue is encountered while starting the bugreport asynchronously, callers will
* receive an {@link #onError} call without any {@link #onProgress} callbacks.
@@ -149,8 +146,7 @@
/** Called when taking bugreport finishes successfully.
*
* <p>This callback will be invoked if the
- * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. Otherwise, the
- * {@link #onFinished(String)} callback will be invoked.
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
*/
public void onFinished() {}
@@ -161,8 +157,10 @@
* {@link #onFinished()} callback will be invoked.
*
* @param bugreportFile the absolute path of the generated bugreport file.
+ * @hide
*/
+ @SystemApi
public void onFinished(@NonNull String bugreportFile) {}
/**
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
new file mode 100644
index 0000000..fe950c4
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 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.internal.expresslog;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/** CounterHistogram encapsulates StatsD write API calls */
+public final class Histogram {
+
+ private final long mMetricIdHash;
+ private final BinOptions mBinOptions;
+
+ public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
+ mMetricIdHash = Utils.hashString(metricId);
+ mBinOptions = binOptions;
+ }
+
+ /**
+ * Logs increment sample count for automatically calculated bin
+ *
+ * @hide
+ */
+ public void logSample(float sample) {
+ final int binIndex = mBinOptions.getBinForSample(sample);
+ FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
+ /*count*/ 1, binIndex);
+ }
+
+ /** Used by CounterHistogram to map data sample to corresponding bin */
+ public interface BinOptions {
+ /**
+ * Returns bins count to be used by counter histogram
+ *
+ * @return bins count used to initialize Options, including overflow & underflow bins
+ * @hide
+ */
+ int getBinsCount();
+
+ /**
+ * @return zero based index
+ * Calculates bin index for the input sample value
+ * index == 0 stands for underflow
+ * index == getBinsCount() - 1 stands for overflow
+ * @hide
+ */
+ int getBinForSample(float sample);
+ }
+
+ /** Used by CounterHistogram to map data sample to corresponding bin for on uniform bins */
+ public static final class UniformOptions implements BinOptions {
+
+ private final int mBinCount;
+ private final float mMinValue;
+ private final float mExclusiveMaxValue;
+ private final float mBinSize;
+
+ public UniformOptions(int binCount, float minValue, float exclusiveMaxValue) {
+ if (binCount < 1) {
+ throw new IllegalArgumentException("Bin count should be positive number");
+ }
+
+ if (exclusiveMaxValue <= minValue) {
+ throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
+ }
+
+ mMinValue = minValue;
+ mExclusiveMaxValue = exclusiveMaxValue;
+ mBinSize = (mExclusiveMaxValue - minValue) / binCount;
+
+ // Implicitly add 2 for the extra undeflow & overflow bins
+ mBinCount = binCount + 2;
+ }
+
+ @Override
+ public int getBinsCount() {
+ return mBinCount;
+ }
+
+ @Override
+ public int getBinForSample(float sample) {
+ if (sample < mMinValue) {
+ // goes to underflow
+ return 0;
+ } else if (sample >= mExclusiveMaxValue) {
+ // goes to overflow
+ return mBinCount - 1;
+ }
+ return (int) ((sample - mMinValue) / mBinSize + 1);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/expresslog/TEST_MAPPING b/core/java/com/android/internal/expresslog/TEST_MAPPING
new file mode 100644
index 0000000..c9b0cf8
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "ExpressLogTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/tests/expresslog/Android.bp b/core/tests/expresslog/Android.bp
new file mode 100644
index 0000000..cab49a7
--- /dev/null
+++ b/core/tests/expresslog/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "ExpressLogTests",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "modules-utils-build",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ platform_apis: true,
+ test_suites: [
+ "general-tests",
+ ],
+
+ certificate: "platform",
+}
diff --git a/core/tests/expresslog/AndroidManifest.xml b/core/tests/expresslog/AndroidManifest.xml
new file mode 100644
index 0000000..94a39e0
--- /dev/null
+++ b/core/tests/expresslog/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.internal.expresslog" >
+
+ <application >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.internal.expresslog"
+ android:label="Telemetry Express Logging Helper Tests" />
+
+</manifest>
diff --git a/core/tests/expresslog/OWNERS b/core/tests/expresslog/OWNERS
new file mode 100644
index 0000000..3dc958b
--- /dev/null
+++ b/core/tests/expresslog/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 719316
+# Stats/expresslog
+file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/tests/expresslog/TEST_MAPPING b/core/tests/expresslog/TEST_MAPPING
new file mode 100644
index 0000000..c9b0cf8
--- /dev/null
+++ b/core/tests/expresslog/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "ExpressLogTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
new file mode 100644
index 0000000..9fa6d06
--- /dev/null
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.internal.expresslog;
+
+import androidx.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UniformOptionsTest {
+ private static final String TAG = UniformOptionsTest.class.getSimpleName();
+
+ @Test
+ @SmallTest
+ public void testGetBinsCount() {
+ Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
+ assertEquals(3, options1.getBinsCount());
+
+ Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000);
+ assertEquals(12, options10.getBinsCount());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SmallTest
+ public void testConstructZeroBinsCount() {
+ new Histogram.UniformOptions(0, 100, 1000);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SmallTest
+ public void testConstructNegativeBinsCount() {
+ new Histogram.UniformOptions(-1, 100, 1000);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SmallTest
+ public void testConstructMaxValueLessThanMinValue() {
+ new Histogram.UniformOptions(10, 1000, 100);
+ }
+
+ @Test
+ @SmallTest
+ public void testBinIndexForRangeEqual1() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testBinIndexForRangeEqual2() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * 2));
+ assertEquals(i, options.getBinForSample(i * 2 - 1));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testBinIndexForRangeEqual5() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
+ assertEquals(4, options.getBinsCount());
+ for (int i = 0; i < 2; i++) {
+ for (int sample = 0; sample < 5; sample++) {
+ assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
+ }
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testBinIndexForRangeEqual10() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
+ assertEquals(0, options.getBinForSample(0));
+ assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
+ assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
+
+ final float binSize = (101 - 1) / 10f;
+ for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * binSize));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testBinIndexForRangeEqual90() {
+ final int binCount = 10;
+ final int minValue = 100;
+ final int maxValue = 100000;
+
+ Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue,
+ maxValue);
+
+ // logging underflow sample
+ assertEquals(0, options.getBinForSample(minValue - 1));
+
+ // logging overflow sample
+ assertEquals(binCount + 1, options.getBinForSample(maxValue));
+ assertEquals(binCount + 1, options.getBinForSample(maxValue + 1));
+
+ // logging min edge sample
+ assertEquals(1, options.getBinForSample(minValue));
+
+ // logging max edge sample
+ assertEquals(binCount, options.getBinForSample(maxValue - 1));
+
+ // logging single valid sample per bin
+ final int binSize = (maxValue - minValue) / binCount;
+
+ for (int i = 0; i < binCount; i++) {
+ assertEquals(i + 1, options.getBinForSample(minValue + binSize * i));
+ }
+ }
+}
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 77fa9dc..bf3612d 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -146,13 +146,16 @@
* @param register true for registering, false for unregistering
* @param device device for which volume is monitored
*/
+ @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED })
public void register(boolean register, @NonNull AudioDeviceAttributes device,
- @NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment) {
+ @NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment,
+ @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) {
try {
getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register,
this, mPackageName,
Objects.requireNonNull(device), Objects.requireNonNull(volumes),
- handlesVolumeAdjustment);
+ handlesVolumeAdjustment, behavior);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -234,6 +237,77 @@
@NonNull @CallbackExecutor Executor executor,
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
+ baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
+ handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ }
+
+ /**
+ * @hide
+ * Configures a device to use absolute volume model, and registers a listener for receiving
+ * volume updates to apply on that device.
+ *
+ * Should be used instead of {@link #setDeviceAbsoluteVolumeBehavior} when there is no reliable
+ * way to set the device's volume to a percentage.
+ *
+ * @param device the audio device set to absolute volume mode
+ * @param volume the type of volume this device responds to
+ * @param executor the Executor used for receiving volume updates through the listener
+ * @param vclistener the callback for volume updates
+ */
+ @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED })
+ public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ final ArrayList<VolumeInfo> volumes = new ArrayList<>(1);
+ volumes.add(volume);
+ setDeviceAbsoluteMultiVolumeAdjustOnlyBehavior(device, volumes, executor, vclistener,
+ handlesVolumeAdjustment);
+ }
+
+ /**
+ * @hide
+ * Configures a device to use absolute volume model applied to different volume types, and
+ * registers a listener for receiving volume updates to apply on that device.
+ *
+ * Should be used instead of {@link #setDeviceAbsoluteMultiVolumeBehavior} when there is
+ * no reliable way to set the device's volume to a percentage.
+ *
+ * @param device the audio device set to absolute multi-volume mode
+ * @param volumes the list of volumes the given device responds to
+ * @param executor the Executor used for receiving volume updates through the listener
+ * @param vclistener the callback for volume updates
+ */
+ @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED })
+ public void setDeviceAbsoluteMultiVolumeAdjustOnlyBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull List<VolumeInfo> volumes,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
+ handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+ }
+
+ /**
+ * Base method for configuring a device to use absolute volume behavior, or one of its variants.
+ * See {@link AudioManager#AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors.
+ *
+ * @param behavior the variant of absolute device volume behavior to adopt
+ */
+ @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED })
+ private void baseSetDeviceAbsoluteMultiVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull List<VolumeInfo> volumes,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment,
+ @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) {
Objects.requireNonNull(device);
Objects.requireNonNull(volumes);
Objects.requireNonNull(executor);
@@ -253,7 +327,8 @@
mDeviceVolumeListeners.removeIf(info -> info.mDevice.equalTypeAddress(device));
}
mDeviceVolumeListeners.add(listenerInfo);
- mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment);
+ mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment,
+ behavior);
}
}
@@ -375,6 +450,8 @@
return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE";
case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE";
+ case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
+ return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY";
default:
return "invalid volume behavior " + behavior;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 39d965f..7de3abc3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6231,6 +6231,15 @@
@SystemApi
public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
+ /**
+ * @hide
+ * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set
+ * the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have
+ * no effect, or an unreliable effect.
+ */
+ @SystemApi
+ public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5;
+
/** @hide */
@IntDef({
DEVICE_VOLUME_BEHAVIOR_VARIABLE,
@@ -6238,6 +6247,7 @@
DEVICE_VOLUME_BEHAVIOR_FIXED,
DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE,
+ DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceVolumeBehavior {}
@@ -6250,11 +6260,23 @@
DEVICE_VOLUME_BEHAVIOR_FIXED,
DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE,
+ DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceVolumeBehaviorState {}
/**
+ * Variants of absolute volume behavior that are set in {@link AudioDeviceVolumeManager}.
+ * @hide
+ */
+ @IntDef({
+ DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
+ DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AbsoluteDeviceVolumeBehavior {}
+
+ /**
* @hide
* Throws IAE on an invalid volume behavior value
* @param volumeBehavior behavior value to check
@@ -6266,6 +6288,7 @@
case DEVICE_VOLUME_BEHAVIOR_FIXED:
case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+ case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
return;
default:
throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
@@ -6305,6 +6328,16 @@
/**
* @hide
+ * Controls whether DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY may be returned by
+ * getDeviceVolumeBehavior. If this is disabled, DEVICE_VOLUME_BEHAVIOR_FULL is returned
+ * in its place.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long RETURN_DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 240663182L;
+
+ /**
+ * @hide
* Returns the volume device behavior for the given audio device
* @param device the audio device
* @return the volume behavior for the device
@@ -6322,7 +6355,12 @@
// communicate with service
final IAudioService service = getService();
try {
- return service.getDeviceVolumeBehavior(device);
+ int behavior = service.getDeviceVolumeBehavior(device);
+ if (!CompatChanges.isChangeEnabled(RETURN_DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY)
+ && behavior == DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
+ return AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL;
+ }
+ return behavior;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 3e0356f..1c517e7 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -629,12 +629,13 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
int[] getActiveAssistantServiceUids();
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register,
in IAudioDeviceVolumeDispatcher cb,
in String packageName,
in AudioDeviceAttributes device, in List<VolumeInfo> volumes,
- boolean handlesvolumeAdjustment);
+ boolean handlesvolumeAdjustment,
+ int volumeBehavior);
AudioHalVersionInfo getHalVersion();
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index 6caedda..8e5cd95 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -300,9 +300,13 @@
prefix = {"SUBTEXT_"},
value = {
SUBTEXT_NONE,
+ SUBTEXT_ERROR_UNKNOWN,
SUBTEXT_SUBSCRIPTION_REQUIRED,
SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
SUBTEXT_AD_ROUTING_DISALLOWED,
+ SUBTEXT_DEVICE_LOW_POWER,
+ SUBTEXT_UNAUTHORIZED,
+ SUBTEXT_TRACK_UNSUPPORTED,
SUBTEXT_CUSTOM
})
public @interface SubText {}
@@ -310,20 +314,40 @@
/** The corresponding route has no associated subtext. */
public static final int SUBTEXT_NONE = 0;
/**
+ * The corresponding route's subtext must indicate that it is not available because of an
+ * unknown error.
+ */
+ public static final int SUBTEXT_ERROR_UNKNOWN = 1;
+ /**
* The corresponding route's subtext must indicate that it requires a special subscription
* in order to be available for routing.
*/
- public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1;
+ public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2;
/**
* The corresponding route's subtext must indicate that downloaded content cannot be routed
* to it.
*/
- public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2;
+ public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3;
/**
* The corresponding route's subtext must indicate that it is not available because an ad is
* in progress.
*/
- public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3;
+ public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4;
+ /**
+ * The corresponding route's subtext must indicate that it is not available because the
+ * device is in low-power mode.
+ */
+ public static final int SUBTEXT_DEVICE_LOW_POWER = 5;
+ /**
+ * The corresponding route's subtext must indicate that it is not available because the user
+ * is not authorized to route to it.
+ */
+ public static final int SUBTEXT_UNAUTHORIZED = 6;
+ /**
+ * The corresponding route's subtext must indicate that it is not available because the
+ * device does not support the current media track.
+ */
+ public static final int SUBTEXT_TRACK_UNSUPPORTED = 7;
/**
* The corresponding route's subtext must be obtained from {@link
* #getCustomSubtextMessage()}.
@@ -415,10 +439,14 @@
* <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
* {@link #getCustomSubtextMessage()}.
*
- * @see #SUBTEXT_NONE
- * @see #SUBTEXT_SUBSCRIPTION_REQUIRED
- * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
- * @see #SUBTEXT_AD_ROUTING_DISALLOWED
+ * @see #SUBTEXT_NONE,
+ * @see #SUBTEXT_ERROR_UNKNOWN,
+ * @see #SUBTEXT_SUBSCRIPTION_REQUIRED,
+ * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
+ * @see #SUBTEXT_AD_ROUTING_DISALLOWED,
+ * @see #SUBTEXT_DEVICE_LOW_POWER,
+ * @see #SUBTEXT_UNAUTHORIZED ,
+ * @see #SUBTEXT_TRACK_UNSUPPORTED,
* @see #SUBTEXT_CUSTOM
*/
@SubText
diff --git a/packages/SettingsLib/res/drawable/ic_media_avr_device.xml b/packages/SettingsLib/res/drawable/ic_media_avr_device.xml
new file mode 100644
index 0000000..3de0d84
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_avr_device.xml
@@ -0,0 +1,40 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.918,7C18.963,7 19,7.037 19,7.082V14.918C19,14.963 18.963,15 18.918,15H3.082C3.037,15 3,14.963 3,14.918V7.082C3,7.037 3.037,7 3.082,7H18.918ZM18.918,5H3.082C1.932,5 1,5.932 1,7.082V14.918C1,16.068 1.932,17 3.082,17H18.918C20.068,17 21,16.068 21,14.918V7.082C21,5.932 20.068,5 18.918,5Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M18,15H17C16.448,15 16,15.448 16,16V17C16,17.552 16.448,18 17,18H18C18.552,18 19,17.552 19,17V16C19,15.448 18.552,15 18,15Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M5,15H4C3.448,15 3,15.448 3,16V17C3,17.552 3.448,18 4,18H5C5.552,18 6,17.552 6,17V16C6,15.448 5.552,15 5,15Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M15.5,12.5C16.328,12.5 17,11.828 17,11C17,10.172 16.328,9.5 15.5,9.5C14.672,9.5 14,10.172 14,11C14,11.828 14.672,12.5 15.5,12.5Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M15.5,10C16.052,10 16.5,10.448 16.5,11C16.5,11.552 16.052,12 15.5,12C14.948,12 14.5,11.552 14.5,11C14.5,10.448 14.948,10 15.5,10ZM15.5,9C14.397,9 13.5,9.897 13.5,11C13.5,12.103 14.397,13 15.5,13C16.603,13 17.5,12.103 17.5,11C17.5,9.897 16.603,9 15.5,9Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M5,9h7v4h-7z"
+ android:fillColor="#ffffff"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
new file mode 100644
index 0000000..c38dfe3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.settingslib.media;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
+
+import com.android.settingslib.R;
+
+/**
+ * ComplexMediaDevice extends MediaDevice to represents device with signals from a number of
+ * sources.
+ */
+public class ComplexMediaDevice extends MediaDevice {
+
+ private final String mSummary = "";
+
+ ComplexMediaDevice(Context context, MediaRouter2Manager routerManager,
+ MediaRoute2Info info, String packageName,
+ RouteListingPreference.Item item) {
+ super(context, routerManager, info, packageName, item);
+ }
+
+ // MediaRoute2Info.getName was made public on API 34, but exists since API 30.
+ @SuppressWarnings("NewApi")
+ @Override
+ public String getName() {
+ return mRouteInfo.getName().toString();
+ }
+
+ @Override
+ public String getSummary() {
+ return mSummary;
+ }
+
+ @Override
+ public Drawable getIcon() {
+ return mContext.getDrawable(R.drawable.ic_media_avr_device);
+ }
+
+ @Override
+ public Drawable getIconWithoutBackground() {
+ return mContext.getDrawable(R.drawable.ic_media_avr_device);
+ }
+
+ @Override
+ public String getId() {
+ return MediaDeviceUtils.getId(mRouteInfo);
+ }
+
+ public boolean isConnected() {
+ return true;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 77e514f..85d4fab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -22,6 +22,7 @@
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
@@ -562,6 +563,9 @@
route, mPackageName);
}
break;
+ case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
+ mediaDevice = new ComplexMediaDevice(mContext, mRouterManager, route,
+ mPackageName, mPreferenceItemMap.get(route.getId()));
default:
Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
break;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index d242198..38387f1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -31,6 +31,7 @@
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
+import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION_MANAGED;
import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED;
import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
@@ -257,6 +258,16 @@
}
/**
+ * Checks if device is the host for ongoing shared session, which allow user to adjust volume
+ *
+ * @return true if device is the host for ongoing shared session
+ */
+ public boolean isHostForOngoingSession() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && Api34Impl.isHostForOngoingSession(mItem);
+ }
+
+ /**
* Checks if device is suggested device from application
*
* @return true if device is suggested device
@@ -554,6 +565,13 @@
@RequiresApi(34)
private static class Api34Impl {
@DoNotInline
+ static boolean isHostForOngoingSession(RouteListingPreference.Item item) {
+ int flags = item != null ? item.getFlags() : 0;
+ return (flags & FLAG_ONGOING_SESSION) != 0
+ && (flags & FLAG_ONGOING_SESSION_MANAGED) != 0;
+ }
+
+ @DoNotInline
static boolean isSuggestedDevice(RouteListingPreference.Item item) {
return item != null && (item.getFlags() & FLAG_SUGGESTED) != 0;
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index b8d78fb..5aa7769 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -304,10 +304,16 @@
) {
val view =
openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground
- ?: throw IllegalStateException(
- "The animateFrom dialog was not animated using " +
- "DialogLaunchAnimator.showFrom(View|Dialog)"
- )
+ if (view == null) {
+ Log.w(
+ TAG,
+ "Showing dialog $dialog normally as the dialog it is shown from was not shown " +
+ "using DialogLaunchAnimator"
+ )
+ dialog.show()
+ return
+ }
+
showFromView(
dialog,
view,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6ff406c3..cea6a07 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -616,7 +616,11 @@
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
- // 2600 - keyboard shortcut
+ // 2600 - keyboard
// TODO(b/259352579): Tracking Bug
@JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
+
+ // TODO(b/259428678): Tracking Bug
+ @JvmField
+ val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator")
}
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 3b45615..ffcb8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -204,7 +204,7 @@
&& mController.isAdvancedLayoutSupported() && device.hasSubtext()) {
boolean isActiveWithOngoingSession =
(device.hasOngoingSession() && currentlyConnected);
- boolean isHost = mController.isVolumeControlEnabled(device)
+ boolean isHost = device.isHostForOngoingSession()
&& isActiveWithOngoingSession;
if (isHost) {
mCurrentActivePosition = position;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 206a620..039dafb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -800,6 +800,11 @@
}
@Override
+ public void onCarrierNetworkChange(boolean active) {
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+ }
+
+ @Override
@WorkerThread
public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
@Nullable WifiEntry connectedEntry, boolean hasMoreWifiEntries) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 534155c..f7e7366 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -206,6 +206,8 @@
protected boolean mHasEthernet = false;
@VisibleForTesting
protected ConnectedWifiInternetMonitor mConnectedWifiInternetMonitor;
+ @VisibleForTesting
+ protected boolean mCarrierNetworkChangeMode;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@@ -507,10 +509,13 @@
Drawable getSignalStrengthIcon(int subId, Context context, int level, int numLevels,
int iconType, boolean cutOut) {
boolean isForDds = subId == mDefaultDataSubId;
+ int levelDrawable =
+ mCarrierNetworkChangeMode ? SignalDrawable.getCarrierChangeState(numLevels)
+ : SignalDrawable.getState(level, numLevels, cutOut);
if (isForDds) {
- mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ mSignalDrawable.setLevel(levelDrawable);
} else {
- mSecondarySignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ mSecondarySignalDrawable.setLevel(levelDrawable);
}
// Make the network type drawable
@@ -672,10 +677,13 @@
}
int resId = Objects.requireNonNull(mapIconSets(config).get(iconKey)).dataContentDescription;
+ SignalIcon.MobileIconGroup iconGroup;
if (isCarrierNetworkActive()) {
- SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
- TelephonyIcons.CARRIER_MERGED_WIFI;
- resId = carrierMergedWifiIconGroup.dataContentDescription;
+ iconGroup = TelephonyIcons.CARRIER_MERGED_WIFI;
+ resId = iconGroup.dataContentDescription;
+ } else if (mCarrierNetworkChangeMode) {
+ iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
+ resId = iconGroup.dataContentDescription;
}
return resId != 0
@@ -1094,7 +1102,8 @@
TelephonyCallback.DisplayInfoListener,
TelephonyCallback.ServiceStateListener,
TelephonyCallback.SignalStrengthsListener,
- TelephonyCallback.UserMobileDataStateListener {
+ TelephonyCallback.UserMobileDataStateListener,
+ TelephonyCallback.CarrierNetworkListener{
private final int mSubId;
private InternetTelephonyCallback(int subId) {
@@ -1126,6 +1135,12 @@
public void onUserMobileDataStateChanged(boolean enabled) {
mCallback.onUserMobileDataStateChanged(enabled);
}
+
+ @Override
+ public void onCarrierNetworkChange(boolean active) {
+ mCarrierNetworkChangeMode = active;
+ mCallback.onCarrierNetworkChange(active);
+ }
}
private class InternetOnSubscriptionChangedListener
@@ -1295,6 +1310,8 @@
void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo);
+ void onCarrierNetworkChange(boolean active);
+
void dismissDialog();
void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 1e62fd23..316de59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -268,6 +268,12 @@
}
}
+ @Test
+ fun showFromDialogDoesNotCrashWhenShownFromRandomDialog() {
+ val dialog = createDialogAndShowFromDialog(animateFrom = TestDialog(context))
+ dialog.dismiss()
+ }
+
private fun createAndShowDialog(
animator: DialogLaunchAnimator = dialogLaunchAnimator,
): TestDialog {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt
new file mode 100644
index 0000000..1e4753e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.coroutines
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FlowTest : SysuiTestCase() {
+
+ @Test
+ fun collectLastValue() = runTest {
+ val flow = flowOf(0, 1, 2)
+ val lastValue by collectLastValue(flow)
+ assertThat(lastValue).isEqualTo(2)
+ }
+}
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 f779845..56e060d 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
@@ -440,6 +440,7 @@
when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
when(mMediaDevice1.hasSubtext()).thenReturn(true);
when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM);
when(mMediaDevice1.getSubtextString()).thenReturn(TEST_CUSTOM_SUBTEXT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 62404cb..84cc977 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -941,6 +941,26 @@
}
}
+ @Test
+ public void getMobileNetworkSummary_withCarrierNetworkChange() {
+ Resources res = mock(Resources.class);
+ doReturn("Carrier network changing").when(res).getString(anyInt());
+ when(SubscriptionManager.getResourcesForSubId(any(), eq(SUB_ID))).thenReturn(res);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap =
+ spyController.mSubIdTelephonyDisplayInfoMap;
+ TelephonyDisplayInfo info = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+
+ mSubIdTelephonyDisplayInfoMap.put(SUB_ID, info);
+ doReturn(true).when(spyController).isMobileDataEnabled();
+ doReturn(true).when(spyController).activeNetworkIsCellular();
+ spyController.mCarrierNetworkChangeMode = true;
+ String dds = spyController.getMobileNetworkSummary(SUB_ID);
+
+ assertThat(dds).contains(mContext.getString(R.string.carrier_network_change_mode));
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
index b7a8d2e..9b4f496 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -18,6 +18,8 @@
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
@@ -25,16 +27,35 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
-/** Collect [flow] in a new [Job] and return a getter for the last collected value. */
+/**
+ * Collect [flow] in a new [Job] and return a getter for the last collected value.
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val actual by collectLastValue(underTest.flow)
+ * assertThat(actual).isEqualTo(expected)
+ * }
+ * ```
+ */
fun <T> TestScope.collectLastValue(
flow: Flow<T>,
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
-): () -> T? {
+): FlowValue<T?> {
var lastValue: T? = null
backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
- return {
+ return FlowValueImpl {
runCurrent()
lastValue
}
}
+
+/** @see collectLastValue */
+interface FlowValue<T> : ReadOnlyProperty<Any?, T?> {
+ operator fun invoke(): T?
+}
+
+private class FlowValueImpl<T>(private val block: () -> T?) : FlowValue<T> {
+ override operator fun invoke(): T? = block()
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T? = invoke()
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c01424d..e55bddb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -692,6 +692,7 @@
// Devices where the framework sends a full scale audio signal, and controls the volume of
// the external audio system separately.
+ // For possible volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}.
Map<Integer, AbsoluteVolumeDeviceInfo> mAbsoluteVolumeDeviceInfoMap = new ArrayMap<>();
/**
@@ -702,13 +703,19 @@
private final List<VolumeInfo> mVolumeInfos;
private final IAudioDeviceVolumeDispatcher mCallback;
private final boolean mHandlesVolumeAdjustment;
+ private @AudioManager.AbsoluteDeviceVolumeBehavior int mDeviceVolumeBehavior;
- private AbsoluteVolumeDeviceInfo(AudioDeviceAttributes device, List<VolumeInfo> volumeInfos,
- IAudioDeviceVolumeDispatcher callback, boolean handlesVolumeAdjustment) {
+ private AbsoluteVolumeDeviceInfo(
+ AudioDeviceAttributes device,
+ List<VolumeInfo> volumeInfos,
+ IAudioDeviceVolumeDispatcher callback,
+ boolean handlesVolumeAdjustment,
+ @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) {
this.mDevice = device;
this.mVolumeInfos = volumeInfos;
this.mCallback = callback;
this.mHandlesVolumeAdjustment = handlesVolumeAdjustment;
+ this.mDeviceVolumeBehavior = behavior;
}
/**
@@ -7057,7 +7064,8 @@
public void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register,
IAudioDeviceVolumeDispatcher cb, String packageName,
AudioDeviceAttributes device, List<VolumeInfo> volumes,
- boolean handlesVolumeAdjustment) {
+ boolean handlesVolumeAdjustment,
+ @AudioManager.AbsoluteDeviceVolumeBehavior int deviceVolumeBehavior) {
// verify permissions
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED
@@ -7073,13 +7081,16 @@
int deviceOut = device.getInternalType();
if (register) {
AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo(
- device, volumes, cb, handlesVolumeAdjustment);
- boolean volumeBehaviorChanged =
- removeAudioSystemDeviceOutFromFullVolumeDevices(deviceOut)
- | removeAudioSystemDeviceOutFromFixedVolumeDevices(deviceOut)
- | (addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info) == null);
+ device, volumes, cb, handlesVolumeAdjustment, deviceVolumeBehavior);
+ AbsoluteVolumeDeviceInfo oldInfo = mAbsoluteVolumeDeviceInfoMap.get(deviceOut);
+ boolean volumeBehaviorChanged = (oldInfo == null)
+ || (oldInfo.mDeviceVolumeBehavior != deviceVolumeBehavior);
if (volumeBehaviorChanged) {
- dispatchDeviceVolumeBehavior(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ removeAudioSystemDeviceOutFromFullVolumeDevices(deviceOut);
+ removeAudioSystemDeviceOutFromFixedVolumeDevices(deviceOut);
+ addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info);
+
+ dispatchDeviceVolumeBehavior(device, deviceVolumeBehavior);
}
// Update stream volumes to the given device, if specified in a VolumeInfo.
// Mute state is not updated because it is stream-wide - the only way to mute a
@@ -7171,6 +7182,7 @@
!= null);
break;
case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+ case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
throw new IllegalArgumentException("Absolute volume unsupported for now");
}
@@ -7214,11 +7226,6 @@
// AudioDeviceInfo.convertDeviceTypeToInternalDevice()
final int audioSystemDeviceOut = device.getInternalType();
- int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
- if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
- return setDeviceVolumeBehavior;
- }
-
// setDeviceVolumeBehavior has not been explicitly called for the device type. Deduce the
// current volume behavior.
if (mFullVolumeDevices.contains(audioSystemDeviceOut)) {
@@ -7230,8 +7237,11 @@
if (mAbsVolumeMultiModeCaseDevices.contains(audioSystemDeviceOut)) {
return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
}
- if (isAbsoluteVolumeDevice(audioSystemDeviceOut)
- || isA2dpAbsoluteVolumeDevice(audioSystemDeviceOut)
+ if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) {
+ return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior;
+ }
+
+ if (isA2dpAbsoluteVolumeDevice(audioSystemDeviceOut)
|| AudioSystem.isLeAudioDeviceType(audioSystemDeviceOut)) {
return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
}
@@ -10680,6 +10690,13 @@
pw.println();
}
+ private Set<Integer> getAbsoluteVolumeDevicesWithBehavior(int behavior) {
+ return mAbsoluteVolumeDeviceInfoMap.entrySet().stream()
+ .filter(entry -> entry.getValue().mDeviceVolumeBehavior == behavior)
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toSet());
+ }
+
private String dumpDeviceTypes(@NonNull Set<Integer> deviceTypes) {
Iterator<Integer> it = deviceTypes.iterator();
if (!it.hasNext()) {
@@ -10728,14 +10745,20 @@
pw.print(" mNotifAliasRing="); pw.println(mNotifAliasRing);
pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
pw.print(" mFullVolumeDevices="); pw.println(dumpDeviceTypes(mFullVolumeDevices));
- pw.print(" mAbsoluteVolumeDevices.keySet()="); pw.println(dumpDeviceTypes(
- mAbsoluteVolumeDeviceInfoMap.keySet()));
+ pw.print(" absolute volume devices="); pw.println(dumpDeviceTypes(
+ getAbsoluteVolumeDevicesWithBehavior(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+ pw.print(" adjust-only absolute volume devices="); pw.println(dumpDeviceTypes(
+ getAbsoluteVolumeDevicesWithBehavior(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY)));
pw.print(" mExtVolumeController="); pw.println(mExtVolumeController);
pw.print(" mHdmiAudioSystemClient="); pw.println(mHdmiAudioSystemClient);
pw.print(" mHdmiPlaybackClient="); pw.println(mHdmiPlaybackClient);
pw.print(" mHdmiTvClient="); pw.println(mHdmiTvClient);
pw.print(" mHdmiSystemAudioSupported="); pw.println(mHdmiSystemAudioSupported);
- pw.print(" mHdmiCecVolumeControlEnabled="); pw.println(mHdmiCecVolumeControlEnabled);
+ synchronized (mHdmiClientLock) {
+ pw.print(" mHdmiCecVolumeControlEnabled="); pw.println(mHdmiCecVolumeControlEnabled);
+ }
pw.print(" mIsCallScreeningModeSupported="); pw.println(mIsCallScreeningModeSupported);
pw.print(" mic mute FromSwitch=" + mMicMuteFromSwitch
+ " FromRestrictions=" + mMicMuteFromRestrictions
@@ -12780,11 +12803,14 @@
}
/**
- * Returns whether the input device uses absolute volume behavior. This is distinct
- * from Bluetooth A2DP absolute volume behavior ({@link #isA2dpAbsoluteVolumeDevice}).
+ * Returns whether the input device uses absolute volume behavior, including its variants.
+ * For included volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}.
+ *
+ * This is distinct from Bluetooth A2DP absolute volume behavior
+ * ({@link #isA2dpAbsoluteVolumeDevice}).
*/
private boolean isAbsoluteVolumeDevice(int deviceType) {
- return mAbsoluteVolumeDeviceInfoMap.containsKey(deviceType);
+ return mAbsoluteVolumeDeviceInfoMap.containsKey(deviceType);
}
/**
@@ -12888,13 +12914,15 @@
return mFullVolumeDevices.remove(audioSystemDeviceOut);
}
- private AbsoluteVolumeDeviceInfo addAudioSystemDeviceOutToAbsVolumeDevices(
- int audioSystemDeviceOut, AbsoluteVolumeDeviceInfo info) {
+ private void addAudioSystemDeviceOutToAbsVolumeDevices(int audioSystemDeviceOut,
+ AbsoluteVolumeDeviceInfo info) {
if (DEBUG_VOL) {
Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut)
- + " from mAbsoluteVolumeDeviceInfoMap");
+ + " to mAbsoluteVolumeDeviceInfoMap with behavior "
+ + AudioDeviceVolumeManager.volumeBehaviorName(info.mDeviceVolumeBehavior)
+ );
}
- return mAbsoluteVolumeDeviceInfoMap.put(audioSystemDeviceOut, info);
+ mAbsoluteVolumeDeviceInfoMap.put(audioSystemDeviceOut, info);
}
private AbsoluteVolumeDeviceInfo removeAudioSystemDeviceOutFromAbsVolumeDevices(
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index cc0ecb84..0aa822b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -579,6 +579,8 @@
private static final boolean ONGOING_DISMISSAL = SystemProperties.getBoolean(
"persist.sysui.notification.ongoing_dismissal", true);
+ @VisibleForTesting
+ protected boolean mSystemExemptFromDismissal = false;
// used as a mutex for access to all active notifications & listeners
final Object mNotificationLock = new Object();
@@ -2595,6 +2597,8 @@
mAllowFgsDismissal = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, true);
+ mSystemExemptFromDismissal =
+ mDpm.isApplicationExemptionsFlagEnabled();
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_SYSTEMUI,
new HandlerExecutor(mHandler),
@@ -6801,12 +6805,20 @@
// partition.
boolean isSystemAppExempt = (ai.flags
& (ApplicationInfo.FLAG_UPDATED_SYSTEM_APP | ApplicationInfo.FLAG_SYSTEM)) > 0;
- return isSystemAppExempt || notification.isMediaNotification() || isEnterpriseExempted();
+ return isSystemAppExempt || notification.isMediaNotification() || isEnterpriseExempted(ai);
}
- // TODO: b/266237746 Enterprise app exemptions
- private boolean isEnterpriseExempted() {
- return false;
+ private boolean isEnterpriseExempted(ApplicationInfo ai) {
+ // Check if the app is an organization admin app
+ // TODO(b/234609037): Replace with new DPM APIs to check if organization admin
+ if (mDpm != null && (mDpm.isActiveProfileOwner(ai.uid)
+ || mDpm.isActiveDeviceOwner(ai.uid))) {
+ return true;
+ }
+ // Check if an app has been given system exemption
+ return mSystemExemptFromDismissal && mAppOps.checkOpNoThrow(
+ AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
+ ai.packageName) == AppOpsManager.MODE_ALLOWED;
}
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7797ce4..7d89c32 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -59,7 +59,6 @@
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static android.view.WindowManager.LayoutParams;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -4634,24 +4633,9 @@
*/
@VisibleForTesting
SurfaceControl computeImeParent() {
- if (mImeLayeringTarget != null) {
- // Ensure changing the IME parent when the layering target that may use IME has
- // became to the input target for preventing IME flickers.
- // Note that:
- // 1) For the imeLayeringTarget that may not use IME but requires IME on top
- // of it (e.g. an overlay window with NOT_FOCUSABLE|ALT_FOCUSABLE_IM flags), we allow
- // it to re-parent the IME on top the display to keep the legacy behavior.
- // 2) Even though the starting window won't use IME, the associated activity
- // behind the starting window may request the input. If so, then we should still hold
- // the IME parent change until the activity started the input.
- boolean imeLayeringTargetMayUseIme =
- LayoutParams.mayUseInputMethod(mImeLayeringTarget.mAttrs.flags)
- || mImeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING;
- if (imeLayeringTargetMayUseIme && (mImeInputTarget == null
- || mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord())) {
- // Do not change parent if the window hasn't requested IME.
- return null;
- }
+ if (!ImeTargetVisibilityPolicy.isReadyToComputeImeParent(mImeLayeringTarget,
+ mImeInputTarget)) {
+ return null;
}
// Attach it to app if the target is part of an app and such app is covering the entire
// screen. If it's not covering the entire screen the IME might extend beyond the apps
diff --git a/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java b/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
index 83d0f6c..49218ad 100644
--- a/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
+++ b/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
@@ -17,7 +17,10 @@
package com.android.server.wm;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+
import android.os.IBinder;
+import android.view.WindowManager;
/**
* A class for {@link com.android.server.inputmethod.InputMethodManagerService} to
@@ -42,4 +45,36 @@
* @return {@code true} if success, {@code false} otherwise.
*/
public abstract boolean updateImeParent(IBinder imeTarget, int displayId);
+
+ /**
+ * Called when {@link DisplayContent#computeImeParent()} to check if it's valid to keep
+ * computing the ime parent.
+ *
+ * @return {@code true} to keep computing the ime parent, {@code false} to defer this operation
+ */
+ public static boolean isReadyToComputeImeParent(WindowState imeLayeringTarget,
+ InputTarget imeInputTarget) {
+ if (imeLayeringTarget == null) {
+ return false;
+ }
+ // Ensure changing the IME parent when the layering target that may use IME has
+ // became to the input target for preventing IME flickers.
+ // Note that:
+ // 1) For the imeLayeringTarget that may not use IME but requires IME on top
+ // of it (e.g. an overlay window with NOT_FOCUSABLE|ALT_FOCUSABLE_IM flags), we allow
+ // it to re-parent the IME on top the display to keep the legacy behavior.
+ // 2) Even though the starting window won't use IME, the associated activity
+ // behind the starting window may request the input. If so, then we should still hold
+ // the IME parent change until the activity started the input.
+ boolean imeLayeringTargetMayUseIme =
+ WindowManager.LayoutParams.mayUseInputMethod(imeLayeringTarget.mAttrs.flags)
+ || imeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING;
+
+ // Do not change parent if the window hasn't requested IME.
+ var inputAndLayeringTargetsDisagree = (imeInputTarget == null
+ || imeLayeringTarget.mActivityRecord != imeInputTarget.getActivityRecord());
+ var inputTargetStale = imeLayeringTargetMayUseIme && inputAndLayeringTargetsDisagree;
+
+ return !inputTargetStale;
+ }
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 7066a33..5136670 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -20,6 +20,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
import android.annotation.IntDef;
@@ -42,10 +43,6 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM;
- @VisibleForTesting
- static final String DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS =
- "enable_compat_fake_focus";
-
/**
* Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
* set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -115,12 +112,6 @@
/** Letterboxed app window is aligned to the right side. */
static final int LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;
- @VisibleForTesting
- static final String PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN = "com.android.COMPAT_FAKE_FOCUS_OPT_IN";
- @VisibleForTesting
- static final String PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT =
- "com.android.COMPAT_FAKE_FOCUS_OPT_OUT";
-
final Context mContext;
// Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
@@ -317,6 +308,9 @@
mDeviceConfig.updateFlagActiveStatus(
/* isActive */ true,
/* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+ mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ mIsCompatFakeFocusEnabled,
+ /* key */ KEY_ENABLE_COMPAT_FAKE_FOCUS);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
@@ -1066,9 +1060,7 @@
/** Whether fake sending focus is enabled for unfocused apps in splitscreen */
boolean isCompatFakeFocusEnabled() {
- return mIsCompatFakeFocusEnabled
- && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, true);
+ return mIsCompatFakeFocusEnabled && mDeviceConfig.getFlag(KEY_ENABLE_COMPAT_FAKE_FOCUS);
}
/**
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index d004fa6..b364872 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -45,6 +45,9 @@
"allow_ignore_orientation_request";
private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+ static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus";
+ private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true;
+
@VisibleForTesting
static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
@@ -52,7 +55,9 @@
KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
- DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST
+ DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST,
+ KEY_ENABLE_COMPAT_FAKE_FOCUS,
+ DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS
);
// Whether camera compatibility treatment is enabled.
@@ -72,6 +77,11 @@
private boolean mIsAllowIgnoreOrientationRequest =
DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
+ // Whether sending compat fake focus for split screen resumed activities is enabled. This is
+ // needed because some game engines wait to get focus before drawing the content of the app
+ // which isn't guaranteed by default in multi-window modes.
+ private boolean mIsCompatFakeFocusAllowed = DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS;
+
// Set of active device configs that need to be updated in
// DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -117,6 +127,8 @@
return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
return mIsAllowIgnoreOrientationRequest;
+ case KEY_ENABLE_COMPAT_FAKE_FOCUS:
+ return mIsCompatFakeFocusAllowed;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
@@ -140,6 +152,10 @@
mIsAllowIgnoreOrientationRequest =
getDeviceConfig(key, defaultValue);
break;
+ case KEY_ENABLE_COMPAT_FAKE_FOCUS:
+ mIsCompatFakeFocusAllowed =
+ getDeviceConfig(key, defaultValue);
+ break;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9b26a54c..c1e7bba5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -30,6 +30,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
@@ -55,6 +56,7 @@
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -703,6 +705,9 @@
static {
APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
EXEMPT_FROM_APP_STANDBY, OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY);
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+ EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
+ OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS);
}
/**
@@ -750,6 +755,10 @@
private static final String HEADLESS_FLAG = "headless";
private static final boolean DEFAULT_HEADLESS_FLAG = true;
+ // TODO(b/266831522) remove the flag after rollout.
+ private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
+ private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
+
/**
* This feature flag is checked once after boot and this value us used until the next reboot to
* avoid needing to handle the flag changing on the fly.
@@ -14193,6 +14202,14 @@
public boolean isUserOrganizationManaged(@UserIdInt int userHandle) {
return getDeviceStateCache().isUserOrganizationManaged(userHandle);
}
+
+ @Override
+ public boolean isApplicationExemptionsFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ APPLICATION_EXEMPTIONS_FLAG,
+ DEFAULT_APPLICATION_EXEMPTIONS_FLAG);
+ }
}
private Intent createShowAdminSupportIntent(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index 38093de..7c736c7 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -40,13 +40,20 @@
import androidx.test.InstrumentationRegistry;
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import java.util.Collections;
import java.util.List;
public class AbsoluteVolumeBehaviorTest {
+ @Rule
+ public TestRule compatChangeRule = new CoreCompatChangeRule();
+
private static final String TAG = "AbsoluteVolumeBehaviorTest";
private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
@@ -92,7 +99,30 @@
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build());
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
- mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
+ mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ mTestLooper.dispatchAll();
+
+ assertThat(mAudioService.getDeviceVolumeBehavior(DEVICE_SPEAKER_OUT))
+ .isEqualTo(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ }
+
+ /**
+ * Tests that getDeviceVolumeBehavior returns the correct volume behavior, even if
+ * a different volume behavior was previously persisted via setDeviceVolumeBehavior
+ */
+ @Test
+ public void registerDispatcherAfterSetDeviceVolumeBehavior_setsVolumeBehaviorToAbsolute() {
+ List<VolumeInfo> volumes = Collections.singletonList(
+ new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build());
+
+ mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL, mPackageName);
+ mTestLooper.dispatchAll();
+
+ mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
+ mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
assertThat(mAudioService.getDeviceVolumeBehavior(DEVICE_SPEAKER_OUT))
@@ -109,7 +139,8 @@
.build());
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
- mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
+ mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
assertThat(mAudioService.getStreamVolume(AudioManager.STREAM_MUSIC))
@@ -124,11 +155,13 @@
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build());
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
- mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
+ mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(false,
- mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
+ mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
assertThat(mAudioService.getDeviceVolumeBehavior(DEVICE_SPEAKER_OUT))
@@ -147,7 +180,8 @@
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build());
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
- mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
+ mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT,
@@ -171,7 +205,8 @@
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
- Collections.singletonList(volumeInfo), true);
+ Collections.singletonList(volumeInfo), true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
// Set stream volume without FLAG_ABSOLUTE_VOLUME
@@ -194,7 +229,8 @@
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
- Collections.singletonList(volumeInfo), true);
+ Collections.singletonList(volumeInfo), true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
// Set stream volume with FLAG_ABSOLUTE_VOLUME
@@ -218,7 +254,8 @@
// Register dispatcher with handlesVolumeAdjustment = true
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
- Collections.singletonList(volumeInfo), true);
+ Collections.singletonList(volumeInfo), true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
// Adjust stream volume without FLAG_ABSOLUTE_VOLUME
@@ -247,7 +284,8 @@
// Register dispatcher with handlesVolumeAdjustment = false
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
- Collections.singletonList(volumeInfo), false);
+ Collections.singletonList(volumeInfo), false,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
// Adjust stream volume without FLAG_ABSOLUTE_VOLUME
@@ -274,7 +312,8 @@
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
- Collections.singletonList(volumeInfo), true);
+ Collections.singletonList(volumeInfo), true,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
mTestLooper.dispatchAll();
// Adjust stream volume with FLAG_ABSOLUTE_VOLUME set
@@ -289,4 +328,38 @@
verify(mMockDispatcher, never()).dispatchDeviceVolumeAdjusted(eq(DEVICE_SPEAKER_OUT), any(),
anyInt(), anyInt());
}
+
+ @Test
+ public void switchAbsoluteVolumeBehaviorToAdjustOnly_onlyDispatchesVolumeChangeForNewListener()
+ throws RemoteException {
+ VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMinVolumeIndex(0)
+ .setMaxVolumeIndex(250)
+ .setVolumeIndex(0)
+ .build();
+
+ mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
+ mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
+ Collections.singletonList(volumeInfo), false,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ mTestLooper.dispatchAll();
+
+ IAudioDeviceVolumeDispatcher.Stub mMockAdjustOnlyAbsoluteVolumeDispatcher =
+ mock(IAudioDeviceVolumeDispatcher.Stub.class);
+ mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
+ mMockAdjustOnlyAbsoluteVolumeDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
+ Collections.singletonList(volumeInfo), false,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+ mTestLooper.dispatchAll();
+
+ // Set stream volume without FLAG_ABSOLUTE_VOLUME
+ mAudioService.setStreamVolume(AudioManager.STREAM_MUSIC, 15, 0, mPackageName);
+ mTestLooper.dispatchAll();
+
+ // Volume change not dispatched for absolute volume listener
+ verify(mMockDispatcher, never()).dispatchDeviceVolumeChanged(eq(DEVICE_SPEAKER_OUT), any());
+ // Volume changed dispatched for adjust-only absolute volume listener
+ verify(mMockAdjustOnlyAbsoluteVolumeDispatcher).dispatchDeviceVolumeChanged(
+ DEVICE_SPEAKER_OUT, new VolumeInfo.Builder(volumeInfo).setVolumeIndex(150).build());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5c69c84..38c2f40 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -316,6 +316,8 @@
private Looper mMainLooper;
@Mock
private NotificationManager mMockNm;
+ @Mock
+ private DevicePolicyManagerInternal mDevicePolicyManager;
@Mock
IIntentSender pi1;
@@ -515,7 +517,7 @@
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
- mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
+ mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
@@ -10291,4 +10293,74 @@
mService.setOngoingDismissal(false);
}
+ @Test
+ public void fixOrganizationAdminNotification_withOnGoingFlag_shouldBeNonDismissible()
+ throws Exception {
+ when(mDevicePolicyManager.isActiveDeviceOwner(mUid)).thenReturn(true);
+ // Given: a notification has the flag FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ mService.setSystemExemptFromDismissal(false);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be set
+ assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixSystemExemptAppOpNotification_withFlag_shouldBeNonDismissible()
+ throws Exception {
+ when(mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
+ PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
+ // Given: a notification has the flag FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ mService.setSystemExemptFromDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be set
+ assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ mService.setSystemExemptFromDismissal(false);
+ }
+
+ @Test
+ public void fixSystemExemptAppOpNotification_withoutFlag_shouldBeNonDismissible()
+ throws Exception {
+ when(mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
+ PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
+ // Given: a notification has the flag FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ mService.setSystemExemptFromDismissal(false);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should not be set
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index b8c94da..1306d57 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -166,4 +166,8 @@
protected void setOngoingDismissal(boolean ongoingDismissal) {
ONGOING_DISMISSAL = ongoingDismissal;
}
+
+ protected void setSystemExemptFromDismissal(boolean isOn) {
+ mSystemExemptFromDismissal = isOn;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index 12b7c9d..e1fc0cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -18,7 +18,6 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.server.wm.LetterboxConfiguration.DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
@@ -26,8 +25,6 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -37,7 +34,6 @@
import android.content.Context;
import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
import androidx.test.filters.SmallTest;
@@ -233,34 +229,6 @@
LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
}
- @Test
- public void testIsCompatFakeFocusEnabledOnDevice() {
- boolean wasFakeFocusEnabled = DeviceConfig
- .getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, false);
-
- // Set runtime flag to true and build time flag to false
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
- mLetterboxConfiguration.setIsCompatFakeFocusEnabled(false);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
-
- // Set runtime flag to false and build time flag to true
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "false", false);
- mLetterboxConfiguration.setIsCompatFakeFocusEnabled(true);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
-
- // Set runtime flag to true so that both are enabled
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
- assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabled());
-
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, Boolean.toString(wasFakeFocusEnabled),
- false);
- }
-
private void assertForHorizontalMove(int from, int expected, int expectedTime,
boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) {
// We are in the current position