public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
/**
+ * What behavior should be invoked when the volume hush gesture is triggered
+ * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE.
+ *
+ * @hide
+ */
+ public static final String VOLUME_HUSH_GESTURE = "volume_hush_gesture";
+
+ /** @hide */ public static final int VOLUME_HUSH_OFF = 0;
+ /** @hide */ public static final int VOLUME_HUSH_VIBRATE = 1;
+ /** @hide */ public static final int VOLUME_HUSH_MUTE = 2;
+
+ private static final Validator VOLUME_HUSH_GESTURE_VALIDATOR =
+ NON_NEGATIVE_INTEGER_VALIDATOR;
+
+ /**
* The number of times (integer) the user has manually enabled battery saver.
* @hide
*/
SCREENSAVER_ACTIVATE_ON_SLEEP,
LOCKDOWN_IN_POWER_MENU,
SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
+ VOLUME_HUSH_GESTURE
};
/**
VALIDATORS.put(LOCKDOWN_IN_POWER_MENU, LOCKDOWN_IN_POWER_MENU_VALIDATOR);
VALIDATORS.put(SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
SHOW_FIRST_CRASH_DIALOG_DEV_OPTION_VALIDATOR);
+ VALIDATORS.put(VOLUME_HUSH_GESTURE, VOLUME_HUSH_GESTURE_VALIDATOR);
VALIDATORS.put(ENABLED_NOTIFICATION_LISTENERS,
ENABLED_NOTIFICATION_LISTENERS_VALIDATOR); //legacy restore setting
VALIDATORS.put(ENABLED_NOTIFICATION_ASSISTANT,
optional SettingProto backup_manager_constants = 193;
optional SettingProto backup_local_transport_parameters = 194;
optional SettingProto bluetooth_on_while_driving = 195 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingsProto volume_hush_gesture = 196 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Please insert fields in the same order as in
// frameworks/base/core/java/android/provider/Settings.java.
- // Next tag = 196
+ // Next tag = 197
}
message SystemSettingsProto {
is non-interactive. -->
<bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
+ <!-- Allow the gesture power + volume up to change the ringer mode while the device
+ is interactive. -->
+ <bool name="config_volumeHushGestureEnabled">true</bool>
+
<!-- Name of the component to handle network policy notifications. If present,
disables NetworkPolicyManagerService's presentation of data-usage notifications. -->
<string translatable="false" name="config_networkPolicyNotificationComponent"></string>
<!-- Notification action for editing a screenshot (drawing on it, cropping it, etc) -->
<string name="screenshot_edit">Edit</string>
+ <string name="volume_dialog_ringer_guidance_vibrate">Calls and notifications will vibrate</string>
+ <string name="volume_dialog_ringer_guidance_silent">Calls and notifications will be muted</string>
+
<!-- Title for the notification channel notifying user of settings system changes. [CHAR LIMIT=NONE] -->
<string name="notification_channel_system_changes">System changes</string>
<!-- Title for the notification channel notifying user of do not disturb system changes (i.e. Do Not Disturb has changed). [CHAR LIMIT=NONE] -->
<java-symbol type="string" name="volume_icon_description_media" />
<java-symbol type="string" name="volume_icon_description_notification" />
<java-symbol type="string" name="volume_icon_description_ringer" />
+ <java-symbol type="string" name="volume_dialog_ringer_guidance_vibrate" />
+ <java-symbol type="string" name="volume_dialog_ringer_guidance_silent" />
<java-symbol type="string" name="wait" />
<java-symbol type="string" name="webpage_unresponsive" />
<java-symbol type="string" name="whichApplication" />
<java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
+ <java-symbol type="bool" name="config_volumeHushGestureEnabled" />
<java-symbol type="drawable" name="platlogo_m" />
public abstract void setRingerModeInternal(int ringerMode, String caller);
+ public abstract void silenceRingerModeInternal(String caller);
+
public abstract void updateRingerModeAffectedStreamsInternal();
public abstract void setAccessibilityServiceUids(IntArray uids);
dumpSetting(s, p,
Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
SecureSettingsProto.BLUETOOTH_ON_WHILE_DRIVING);
+ dumpSetting(s, p,
+ Settings.Secure.VOLUME_HUSH_GESTURE,
+ SecureSettingsProto.VOLUME_HUSH_GESTURE);
// Please insert new settings using the same order as in Settings.Secure.
p.end(token);
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 161;
+ private static final int SETTINGS_VERSION = 162;
private final int mUserId;
currentVersion = 161;
}
+ if (currentVersion == 161) {
+ // Version 161: Add a gesture for silencing phones
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting currentSetting = secureSettings.getSettingLocked(
+ Secure.VOLUME_HUSH_GESTURE);
+ if (currentSetting.isNull()) {
+ secureSettings.insertSettingLocked(
+ Secure.VOLUME_HUSH_GESTURE,
+ Integer.toString(Secure.VOLUME_HUSH_VIBRATE),
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 162;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
<string name="volume_dialog_title">%s volume controls</string>
- <string name="volume_dialog_ringer_guidance_vibrate">Calls and notifications will vibrate</string>
- <string name="volume_dialog_ringer_guidance_silent">Calls and notifications will be muted</string>
<string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring</string>
<string name="output_title">Media output</string>
toastText = R.string.volume_dialog_ringer_guidance_ring;
break;
case RINGER_MODE_SILENT:
- toastText = R.string.volume_dialog_ringer_guidance_silent;
+ toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_silent;
break;
case RINGER_MODE_VIBRATE:
default:
- toastText = R.string.volume_dialog_ringer_guidance_vibrate;
+ toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate;
}
Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show();
import static android.media.AudioManager.STREAM_MUSIC;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
+import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
+import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import android.Manifest;
import android.annotation.NonNull;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.UserManagerInternal.UserRestrictionsListener;
+import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.System;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
+import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
// Is there a vibrator
private final boolean mHasVibrator;
+ // Used to play vibrations
+ private Vibrator mVibrator;
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
- Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- mHasVibrator = vibrator == null ? false : vibrator.hasVibrator();
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
// Initialize volume
int maxCallVolume = SystemProperties.getInt("ro.config.vc_call_vol_steps", -1);
setRingerMode(ringerMode, caller, false /*external*/);
}
+ public void silenceRingerModeInternal(String reason) {
+ VibrationEffect effect = null;
+ int ringerMode = AudioManager.RINGER_MODE_SILENT;
+ int toastText = 0;
+
+ int silenceRingerSetting = Settings.Secure.VOLUME_HUSH_OFF;
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled)) {
+ silenceRingerSetting = Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.VOLUME_HUSH_GESTURE, VOLUME_HUSH_OFF,
+ UserHandle.USER_CURRENT);
+ }
+
+ switch(silenceRingerSetting) {
+ case VOLUME_HUSH_MUTE:
+ effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ ringerMode = AudioManager.RINGER_MODE_SILENT;
+ toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_silent;
+ break;
+ case VOLUME_HUSH_VIBRATE:
+ effect = VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+ ringerMode = AudioManager.RINGER_MODE_VIBRATE;
+ toastText = com.android.internal.R.string.volume_dialog_ringer_guidance_vibrate;
+ break;
+ }
+ maybeVibrate(effect);
+ setRingerModeInternal(ringerMode, reason);
+ Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show();
+ }
+
+ private boolean maybeVibrate(VibrationEffect effect) {
+ if (!mHasVibrator) {
+ return false;
+ }
+ final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
+ if (hapticsDisabled) {
+ return false;
+ }
+
+ if (effect == null) {
+ return false;
+ }
+ mVibrator.vibrate(
+ Binder.getCallingUid(), mContext.getOpPackageName(), effect, VIBRATION_ATTRIBUTES);
+ return true;
+ }
+
private void setRingerMode(int ringerMode, String caller, boolean external) {
if (mUseFixedVolume || mIsSingleVolume) {
return;
}
@Override
+ public void silenceRingerModeInternal(String caller) {
+ AudioService.this.silenceRingerModeInternal(caller);
+ }
+
+ @Override
public void updateRingerModeAffectedStreamsInternal() {
synchronized (mSettingsLock) {
if (updateRingerAndZenModeAffectedStreams()) {
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
+import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.STATE_OFF;
import static android.view.WindowManager.DOCKED_LEFT;
import android.hardware.power.V1_0.PowerHint;
import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.AudioManagerInternal;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.session.MediaSessionLegacyHelper;
PowerManagerInternal mPowerManagerInternal;
IStatusBarService mStatusBarService;
StatusBarManagerInternal mStatusBarManagerInternal;
+ AudioManagerInternal mAudioManagerInternal;
boolean mPreloadedRecentApps;
final Object mServiceAquireLock = new Object();
Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
private boolean mScreenshotChordPowerKeyTriggered;
private long mScreenshotChordPowerKeyTime;
+ // Ringer toggle should reuse timing and triggering from screenshot power and a11y vol up
+ private int mRingerToggleChord = VOLUME_HUSH_OFF;
+
private static final long BUGREPORT_TV_GESTURE_TIMEOUT_MILLIS = 1000;
private boolean mBugreportTvKey1Pressed;
private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 27;
private static final int MSG_POWER_VERY_LONG_PRESS = 28;
private static final int MSG_NOTIFY_USER_ACTIVITY = 29;
+ private static final int MSG_RINGER_TOGGLE_CHORD = 30;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
android.Manifest.permission.USER_ACTIVITY);
+ case MSG_RINGER_TOGGLE_CHORD:
+ handleRingerChordGesture();
break;
}
}
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.SHOW_ROTATION_SUGGESTIONS), false, this,
UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_HUSH_GESTURE), false, this,
+ UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.POLICY_CONTROL), false, this,
UserHandle.USER_ALL);
@VisibleForTesting
SystemGesturesPointerEventListener mSystemGestures;
+ private void handleRingerChordGesture() {
+ if (mRingerToggleChord == VOLUME_HUSH_OFF) {
+ return;
+ }
+ getAudioManagerInternal();
+ mAudioManagerInternal.silenceRingerModeInternal("volume_hush");
+ }
+
IStatusBarService getStatusBarService() {
synchronized (mServiceAquireLock) {
if (mStatusBarService == null) {
}
}
+ AudioManagerInternal getAudioManagerInternal() {
+ synchronized (mServiceAquireLock) {
+ if (mAudioManagerInternal == null) {
+ mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+ }
+ return mAudioManagerInternal;
+ }
+ }
+
/*
* We always let the sensor be switched on by default except when
* the user has explicitly disabled sensor based rotation or when the
mScreenshotChordPowerKeyTriggered = true;
mScreenshotChordPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
+ interceptRingerToggleChord();
}
// Stop ringing or end call if configured to do so when power is pressed.
}
}
+ private void interceptRingerToggleChord() {
+ if (mRingerToggleChord != Settings.Secure.VOLUME_HUSH_OFF
+ && mScreenshotChordPowerKeyTriggered && mA11yShortcutChordVolumeUpKeyTriggered) {
+ final long now = SystemClock.uptimeMillis();
+ if (now <= mA11yShortcutChordVolumeUpKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
+ && now <= mScreenshotChordPowerKeyTime
+ + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
+ mA11yShortcutChordVolumeUpKeyConsumed = true;
+ cancelPendingPowerKeyAction();
+
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RINGER_TOGGLE_CHORD),
+ getRingerToggleChordDelay());
+ }
+ }
+ }
+
private long getAccessibilityShortcutTimeout() {
ViewConfiguration config = ViewConfiguration.get(mContext);
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
return ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout();
}
+ private long getRingerToggleChordDelay() {
+ // Always timeout like a tap
+ return ViewConfiguration.getTapTimeout();
+ }
+
private void cancelPendingScreenshotChordAction() {
mHandler.removeCallbacks(mScreenshotRunnable);
}
mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
}
+ private void cancelPendingRingerToggleChordAction() {
+ mHandler.removeMessages(MSG_RINGER_TOGGLE_CHORD);
+ }
+
private final Runnable mEndCallLongPress = new Runnable() {
@Override
public void run() {
mSystemNavigationKeysEnabled = Settings.Secure.getIntForUser(resolver,
Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
0, UserHandle.USER_CURRENT) == 1;
-
+ mRingerToggleChord = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.VOLUME_HUSH_GESTURE, VOLUME_HUSH_OFF,
+ UserHandle.USER_CURRENT);
+ if (!mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled)) {
+ mRingerToggleChord = Settings.Secure.VOLUME_HUSH_OFF;
+ }
// Configure rotation suggestions.
int showRotationSuggestions = Settings.Secure.getIntForUser(resolver,
Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
}
}
+ // If a ringer toggle chord could be on the way but we're not sure, then tell the dispatcher
+ // to wait a little while and try again later before dispatching.
+ if (mRingerToggleChord != VOLUME_HUSH_OFF && (flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ if (mA11yShortcutChordVolumeUpKeyTriggered && !mScreenshotChordPowerKeyTriggered) {
+ final long now = SystemClock.uptimeMillis();
+ final long timeoutTime = mA11yShortcutChordVolumeUpKeyTime
+ + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
+ if (now < timeoutTime) {
+ return timeoutTime - now;
+ }
+ }
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mA11yShortcutChordVolumeUpKeyConsumed) {
+ if (!down) {
+ mA11yShortcutChordVolumeUpKeyConsumed = false;
+ }
+ return -1;
+ }
+ }
+
// Cancel any pending meta actions if we see any other keys being pressed between the down
// of the meta key and its corresponding up.
if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
+ // Any activity on the vol down button stops the ringer toggle shortcut
+ cancelPendingRingerToggleChordAction();
+
if (interactive && !mScreenshotChordVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mScreenshotChordVolumeDownKeyTriggered = true;
mA11yShortcutChordVolumeUpKeyConsumed = false;
cancelPendingPowerKeyAction();
cancelPendingScreenshotChordAction();
+ cancelPendingRingerToggleChordAction();
+
interceptAccessibilityShortcutChord();
+ interceptRingerToggleChord();
}
} else {
mA11yShortcutChordVolumeUpKeyTriggered = false;
cancelPendingScreenshotChordAction();
cancelPendingAccessibilityShortcutAction();
+ cancelPendingRingerToggleChordAction();
}
}
if (down) {