Loading packages/SystemUI/res/drawable/hearing.xml 0 → 100644 +24 −0 Original line number Diff line number Diff line <!-- 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="48dp" android:height="48dp" android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/> </vector> packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java +89 −27 Original line number Diff line number Diff line Loading @@ -16,20 +16,34 @@ package com.android.systemui.volume; import static android.app.PendingIntent.FLAG_IMMUTABLE; import android.annotation.StringRes; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.CountDownTimer; import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; /** * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}: Loading @@ -53,34 +67,58 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; * communication between the native audio framework that implements the dose computation and the * audio service. */ public abstract class CsdWarningDialog extends SystemUIDialog public class CsdWarningDialog extends SystemUIDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { private static final String TAG = Util.logTag(CsdWarningDialog.class); private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds // time after which action is taken when the user hasn't ack'd or dismissed the dialog private static final int NO_ACTION_TIMEOUT_MS = 5000; public static final int NO_ACTION_TIMEOUT_MS = 5000; private final Context mContext; private final AudioManager mAudioManager; private final @AudioManager.CsdWarning int mCsdWarning; private final Object mTimerLock = new Object(); /** * Timer to keep track of how long the user has before an action (here volume reduction) is * taken on their behalf. */ @GuardedBy("mTimerLock") private final CountDownTimer mNoUserActionTimer; private Runnable mNoUserActionRunnable; private Runnable mCancelScheduledNoUserActionRunnable = null; private final DelayableExecutor mDelayableExecutor; private NotificationManager mNotificationManager; private Runnable mOnCleanup; private long mShowTime; public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context, AudioManager audioManager) { /** * To inject dependencies and allow for easier testing */ @AssistedFactory public interface Factory { /** * Create a dialog object */ CsdWarningDialog create(int csdWarning, Runnable onCleanup); } @AssistedInject public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context, AudioManager audioManager, NotificationManager notificationManager, @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) { super(context); mCsdWarning = csdWarning; mContext = context; mAudioManager = audioManager; mNotificationManager = notificationManager; mOnCleanup = onCleanup; mDelayableExecutor = delayableExecutor; getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); setShowForAllUsers(true); setMessage(mContext.getString(getStringForWarning(csdWarning))); Loading @@ -95,25 +133,24 @@ public abstract class CsdWarningDialog extends SystemUIDialog Context.RECEIVER_EXPORTED_UNAUDITED); if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) { @Override public void onTick(long millisUntilFinished) { } @Override public void onFinish() { mNoUserActionRunnable = () -> { if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { // unlike on the 5x dose repeat, level is only reduced to RS1 // when the warning is not acknowledged quick enough // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning // is not acknowledged quickly enough mAudioManager.lowerVolumeToRs1(); } sendNotification(); } }; } else { mNoUserActionTimer = null; mNoUserActionRunnable = null; } } protected abstract void cleanUp(); private void cleanUp() { if (mOnCleanup != null) { mOnCleanup.run(); } } // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new Loading Loading @@ -153,12 +190,9 @@ public abstract class CsdWarningDialog extends SystemUIDialog super.onStart(); mShowTime = System.currentTimeMillis(); synchronized (mTimerLock) { if (mNoUserActionTimer != null) { new Thread(() -> { synchronized (mTimerLock) { mNoUserActionTimer.start(); } }).start(); if (mNoUserActionRunnable != null) { mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed( mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS); } } } Loading @@ -166,8 +200,8 @@ public abstract class CsdWarningDialog extends SystemUIDialog @Override protected void onStop() { synchronized (mTimerLock) { if (mNoUserActionTimer != null) { mNoUserActionTimer.cancel(); if (mCancelScheduledNoUserActionRunnable != null) { mCancelScheduledNoUserActionRunnable.run(); } } } Loading Loading @@ -212,4 +246,32 @@ public abstract class CsdWarningDialog extends SystemUIDialog Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception()); return com.android.internal.R.string.csd_dose_reached_warning; } /** * In case user did not respond to the dialog, they still need to know volume was lowered. */ private void sendNotification() { Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, FLAG_IMMUTABLE); String text = mContext.getString(R.string.csd_system_lowered_text); String title = mContext.getString(R.string.csd_lowered_title); Notification.Builder builder = new Notification.Builder(mContext, NotificationChannels.ALERTS) .setSmallIcon(R.drawable.hearing) .setContentTitle(title) .setContentText(text) .setContentIntent(pendingIntent) .setStyle(new Notification.BigTextStyle().bigText(text)) .setVisibility(Notification.VISIBILITY_PUBLIC) .setLocalOnly(true) .setAutoCancel(true) .setCategory(Notification.CATEGORY_SYSTEM); mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO, builder.build()); } } packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +12 −9 Original line number Diff line number Diff line Loading @@ -263,6 +263,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final ConfigurationController mConfigurationController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final VolumePanelFactory mVolumePanelFactory; private final CsdWarningDialog.Factory mCsdWarningDialogFactory; private final ActivityStarter mActivityStarter; private boolean mShowing; Loading Loading @@ -311,6 +312,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, Executor executor, CsdWarningDialog.Factory csdWarningDialogFactory, DumpManager dumpManager) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); Loading @@ -322,6 +324,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mConfigurationController = configurationController; mMediaOutputDialogFactory = mediaOutputDialogFactory; mVolumePanelFactory = volumePanelFactory; mCsdWarningDialogFactory = csdWarningDialogFactory; mActivityStarter = activityStarter; mShowActiveStreamOnly = showActiveStreamOnly(); mHasSeenODICaptionsTooltip = Loading Loading @@ -2084,21 +2087,21 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, rescheduleTimeoutH(); } private void showCsdWarningH(int csdWarning, int durationMs) { @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) { synchronized (mSafetyWarningLock) { if (mCsdDialog != null) { return; } mCsdDialog = new CsdWarningDialog(csdWarning, mContext, mController.getAudioManager()) { @Override protected void cleanUp() { final Runnable cleanUp = () -> { synchronized (mSafetyWarningLock) { mCsdDialog = null; } recheckH(null); } }; mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp); mCsdDialog.show(); } recheckH(null); Loading packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +5 −2 Original line number Diff line number Diff line Loading @@ -30,17 +30,18 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.volume.CsdWarningDialog; 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 java.util.concurrent.Executor; import dagger.Binds; import dagger.Module; import dagger.Provides; import java.util.concurrent.Executor; /** Dagger Module for code in the volume package. */ @Module Loading @@ -63,6 +64,7 @@ public interface VolumeModule { InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, @Main Executor executor, CsdWarningDialog.Factory csdFactory, DumpManager dumpManager) { VolumeDialogImpl impl = new VolumeDialogImpl( context, Loading @@ -76,6 +78,7 @@ public interface VolumeModule { interactionJankMonitor, deviceConfigProxy, executor, csdFactory, dumpManager); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); Loading packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java 0 → 100644 +91 −0 Original line number Diff line number Diff line /* * 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.systemui.volume; import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X; import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.NotificationManager; import android.media.AudioManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class CsdWarningDialogTest extends SysuiTestCase { private NotificationManager mNotificationManager; private AudioManager mAudioManager; @Before public void setup() { mNotificationManager = mock(NotificationManager.class); getContext().addMockSystemService(NotificationManager.class, mNotificationManager); mAudioManager = mock(AudioManager.class); getContext().addMockSystemService(AudioManager.class, mAudioManager); } @Test public void create1XCsdDialogAndWait_sendsNotification() { FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); // instantiate directly instead of via factory; we don't want executor to be @Background CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext, mAudioManager, mNotificationManager, executor, null); dialog.show(); executor.advanceClockToLast(); executor.runAllReady(); dialog.dismiss(); verify(mNotificationManager).notify( eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); } @Test public void create5XCsdDiSalogAndWait_willNotSendNotification() { FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext, mAudioManager, mNotificationManager, executor, null); dialog.show(); executor.advanceClockToLast(); executor.runAllReady(); dialog.dismiss(); verify(mNotificationManager, never()).notify( eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); } } Loading
packages/SystemUI/res/drawable/hearing.xml 0 → 100644 +24 −0 Original line number Diff line number Diff line <!-- 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="48dp" android:height="48dp" android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/> </vector>
packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java +89 −27 Original line number Diff line number Diff line Loading @@ -16,20 +16,34 @@ package com.android.systemui.volume; import static android.app.PendingIntent.FLAG_IMMUTABLE; import android.annotation.StringRes; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.CountDownTimer; import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; /** * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}: Loading @@ -53,34 +67,58 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; * communication between the native audio framework that implements the dose computation and the * audio service. */ public abstract class CsdWarningDialog extends SystemUIDialog public class CsdWarningDialog extends SystemUIDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { private static final String TAG = Util.logTag(CsdWarningDialog.class); private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds // time after which action is taken when the user hasn't ack'd or dismissed the dialog private static final int NO_ACTION_TIMEOUT_MS = 5000; public static final int NO_ACTION_TIMEOUT_MS = 5000; private final Context mContext; private final AudioManager mAudioManager; private final @AudioManager.CsdWarning int mCsdWarning; private final Object mTimerLock = new Object(); /** * Timer to keep track of how long the user has before an action (here volume reduction) is * taken on their behalf. */ @GuardedBy("mTimerLock") private final CountDownTimer mNoUserActionTimer; private Runnable mNoUserActionRunnable; private Runnable mCancelScheduledNoUserActionRunnable = null; private final DelayableExecutor mDelayableExecutor; private NotificationManager mNotificationManager; private Runnable mOnCleanup; private long mShowTime; public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context, AudioManager audioManager) { /** * To inject dependencies and allow for easier testing */ @AssistedFactory public interface Factory { /** * Create a dialog object */ CsdWarningDialog create(int csdWarning, Runnable onCleanup); } @AssistedInject public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context, AudioManager audioManager, NotificationManager notificationManager, @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) { super(context); mCsdWarning = csdWarning; mContext = context; mAudioManager = audioManager; mNotificationManager = notificationManager; mOnCleanup = onCleanup; mDelayableExecutor = delayableExecutor; getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); setShowForAllUsers(true); setMessage(mContext.getString(getStringForWarning(csdWarning))); Loading @@ -95,25 +133,24 @@ public abstract class CsdWarningDialog extends SystemUIDialog Context.RECEIVER_EXPORTED_UNAUDITED); if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) { @Override public void onTick(long millisUntilFinished) { } @Override public void onFinish() { mNoUserActionRunnable = () -> { if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { // unlike on the 5x dose repeat, level is only reduced to RS1 // when the warning is not acknowledged quick enough // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning // is not acknowledged quickly enough mAudioManager.lowerVolumeToRs1(); } sendNotification(); } }; } else { mNoUserActionTimer = null; mNoUserActionRunnable = null; } } protected abstract void cleanUp(); private void cleanUp() { if (mOnCleanup != null) { mOnCleanup.run(); } } // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new Loading Loading @@ -153,12 +190,9 @@ public abstract class CsdWarningDialog extends SystemUIDialog super.onStart(); mShowTime = System.currentTimeMillis(); synchronized (mTimerLock) { if (mNoUserActionTimer != null) { new Thread(() -> { synchronized (mTimerLock) { mNoUserActionTimer.start(); } }).start(); if (mNoUserActionRunnable != null) { mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed( mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS); } } } Loading @@ -166,8 +200,8 @@ public abstract class CsdWarningDialog extends SystemUIDialog @Override protected void onStop() { synchronized (mTimerLock) { if (mNoUserActionTimer != null) { mNoUserActionTimer.cancel(); if (mCancelScheduledNoUserActionRunnable != null) { mCancelScheduledNoUserActionRunnable.run(); } } } Loading Loading @@ -212,4 +246,32 @@ public abstract class CsdWarningDialog extends SystemUIDialog Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception()); return com.android.internal.R.string.csd_dose_reached_warning; } /** * In case user did not respond to the dialog, they still need to know volume was lowered. */ private void sendNotification() { Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, FLAG_IMMUTABLE); String text = mContext.getString(R.string.csd_system_lowered_text); String title = mContext.getString(R.string.csd_lowered_title); Notification.Builder builder = new Notification.Builder(mContext, NotificationChannels.ALERTS) .setSmallIcon(R.drawable.hearing) .setContentTitle(title) .setContentText(text) .setContentIntent(pendingIntent) .setStyle(new Notification.BigTextStyle().bigText(text)) .setVisibility(Notification.VISIBILITY_PUBLIC) .setLocalOnly(true) .setAutoCancel(true) .setCategory(Notification.CATEGORY_SYSTEM); mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO, builder.build()); } }
packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +12 −9 Original line number Diff line number Diff line Loading @@ -263,6 +263,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final ConfigurationController mConfigurationController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final VolumePanelFactory mVolumePanelFactory; private final CsdWarningDialog.Factory mCsdWarningDialogFactory; private final ActivityStarter mActivityStarter; private boolean mShowing; Loading Loading @@ -311,6 +312,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, Executor executor, CsdWarningDialog.Factory csdWarningDialogFactory, DumpManager dumpManager) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); Loading @@ -322,6 +324,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mConfigurationController = configurationController; mMediaOutputDialogFactory = mediaOutputDialogFactory; mVolumePanelFactory = volumePanelFactory; mCsdWarningDialogFactory = csdWarningDialogFactory; mActivityStarter = activityStarter; mShowActiveStreamOnly = showActiveStreamOnly(); mHasSeenODICaptionsTooltip = Loading Loading @@ -2084,21 +2087,21 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, rescheduleTimeoutH(); } private void showCsdWarningH(int csdWarning, int durationMs) { @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) { synchronized (mSafetyWarningLock) { if (mCsdDialog != null) { return; } mCsdDialog = new CsdWarningDialog(csdWarning, mContext, mController.getAudioManager()) { @Override protected void cleanUp() { final Runnable cleanUp = () -> { synchronized (mSafetyWarningLock) { mCsdDialog = null; } recheckH(null); } }; mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp); mCsdDialog.show(); } recheckH(null); Loading
packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +5 −2 Original line number Diff line number Diff line Loading @@ -30,17 +30,18 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.volume.CsdWarningDialog; 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 java.util.concurrent.Executor; import dagger.Binds; import dagger.Module; import dagger.Provides; import java.util.concurrent.Executor; /** Dagger Module for code in the volume package. */ @Module Loading @@ -63,6 +64,7 @@ public interface VolumeModule { InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, @Main Executor executor, CsdWarningDialog.Factory csdFactory, DumpManager dumpManager) { VolumeDialogImpl impl = new VolumeDialogImpl( context, Loading @@ -76,6 +78,7 @@ public interface VolumeModule { interactionJankMonitor, deviceConfigProxy, executor, csdFactory, dumpManager); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); Loading
packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java 0 → 100644 +91 −0 Original line number Diff line number Diff line /* * 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.systemui.volume; import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X; import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.NotificationManager; import android.media.AudioManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class CsdWarningDialogTest extends SysuiTestCase { private NotificationManager mNotificationManager; private AudioManager mAudioManager; @Before public void setup() { mNotificationManager = mock(NotificationManager.class); getContext().addMockSystemService(NotificationManager.class, mNotificationManager); mAudioManager = mock(AudioManager.class); getContext().addMockSystemService(AudioManager.class, mAudioManager); } @Test public void create1XCsdDialogAndWait_sendsNotification() { FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); // instantiate directly instead of via factory; we don't want executor to be @Background CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext, mAudioManager, mNotificationManager, executor, null); dialog.show(); executor.advanceClockToLast(); executor.runAllReady(); dialog.dismiss(); verify(mNotificationManager).notify( eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); } @Test public void create5XCsdDiSalogAndWait_willNotSendNotification() { FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext, mAudioManager, mNotificationManager, executor, null); dialog.show(); executor.advanceClockToLast(); executor.runAllReady(); dialog.dismiss(); verify(mNotificationManager, never()).notify( eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); } }