diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 062b68e67dd54..731ea92072830 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -122,7 +122,8 @@ public final class DisplayManager { VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS, VIRTUAL_DISPLAY_FLAG_TRUSTED, VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, - VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED + VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED, + VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED }) @Retention(RetentionPolicy.SOURCE) public @interface VirtualDisplayFlag {} @@ -379,6 +380,15 @@ public final class DisplayManager { */ public static final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12; + /** + * Virtual display flags: Indicates that the display should not play sound effects or perform + * haptic feedback when the user touches the screen. + * + * @see #createVirtualDisplay + * @hide + */ + public static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13; + /** @hide */ @IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = { MATCH_CONTENT_FRAMERATE_UNKNOWN, diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index f8a848ed8c251..52d222b19b6ac 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -308,6 +308,15 @@ public final class Display { */ public static final int FLAG_ALWAYS_UNLOCKED = 1 << 9; + /** + * Flag: Indicates that the display should not play sound effects or perform haptic feedback + * when the user touches the screen. + * + * @hide + * @see #getFlags() + */ + public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10; + /** * Display flag: Indicates that the contents of the display should not be scaled * to fit the physical screen dimensions. Used for development only to emulate diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 9264d2ed42a3a..12ce8ee5e0ad0 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -865,6 +865,9 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) { result.append(", FLAG_ALWAYS_UNLOCKED"); } + if ((flags & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { + result.append(", FLAG_TOUCH_FEEDBACK_DISABLED"); + } return result.toString(); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 48c102bd48831..eabc13ad5ab0a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -8200,6 +8200,10 @@ public final class ViewRootImpl implements ViewParent, */ @Override public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) { + if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { + return; + } + checkThread(); try { @@ -8248,6 +8252,10 @@ public final class ViewRootImpl implements ViewParent, */ @Override public boolean performHapticFeedback(int effectId, boolean always) { + if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { + return false; + } + try { return mWindowSession.performHapticFeedback(effectId, always); } catch (RemoteException e) { diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index e958a965f01f4..1d0644897fc2d 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -35,16 +35,19 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.Instrumentation; import android.content.Context; +import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.platform.test.annotations.Presubmit; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -334,6 +337,55 @@ public class ViewRootImplTest { }, false /*shouldReceiveKey*/); } + @UiThreadTest + @Test + public void playSoundEffect_wrongEffectId_throwException() { + ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, + sContext.getDisplayNoVerify()); + View view = new View(sContext); + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( + TYPE_APPLICATION_OVERLAY); + layoutParams.token = new Binder(); + view.setLayoutParams(layoutParams); + viewRootImpl.setView(view, layoutParams, /* panelParentView= */ null); + + assertThrows(IllegalArgumentException.class, + () -> viewRootImpl.playSoundEffect(/* effectId= */ -1)); + } + + @UiThreadTest + @Test + public void playSoundEffect_wrongEffectId_touchFeedbackDisabled_doNothing() { + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.flags = Display.FLAG_TOUCH_FEEDBACK_DISABLED; + Display display = new Display(DisplayManagerGlobal.getInstance(), /* displayId= */ + 0, displayInfo, new DisplayAdjustments()); + ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display); + View view = new View(sContext); + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( + TYPE_APPLICATION_OVERLAY); + layoutParams.token = new Binder(); + view.setLayoutParams(layoutParams); + viewRootImpl.setView(view, layoutParams, /* panelParentView= */ null); + + viewRootImpl.playSoundEffect(/* effectId= */ -1); + } + + @UiThreadTest + @Test + public void performHapticFeedback_touchFeedbackDisabled_doNothing() { + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.flags = Display.FLAG_TOUCH_FEEDBACK_DISABLED; + Display display = new Display(DisplayManagerGlobal.getInstance(), /* displayId= */ + 0, displayInfo, new DisplayAdjustments()); + ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display); + + boolean result = viewRootImpl.performHapticFeedback( + HapticFeedbackConstants.CONTEXT_CLICK, true); + + assertThat(result).isFalse(); + } + class KeyView extends View { KeyView(Context context) { super(context); diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index edaa18a886373..dbbd354a2ff00 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -149,6 +149,13 @@ final class DisplayDeviceInfo { */ public static final int FLAG_ALWAYS_UNLOCKED = 1 << 15; + /** + * Flag: Indicates that the display should not play sound effects or perform haptic feedback + * when the user touches the screen. + * + * @hide + */ + public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 16; /** * Touch attachment: Display does not receive touch. diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index a640497d7e101..90b9967eef59c 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -381,6 +381,9 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_ALWAYS_UNLOCKED; } + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED; + } Rect maskingInsets = getMaskingInsets(deviceInfo); int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right; int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index dde8831090df6..fcb2eb45a6bd8 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -27,10 +27,12 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTAT import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED; import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP; +import static com.android.server.display.DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED; import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; import android.content.Context; @@ -457,6 +459,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { && (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) { mInfo.flags |= FLAG_ALWAYS_UNLOCKED; } + if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { + mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED; + } mInfo.type = Display.TYPE_VIRTUAL; mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?