diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 40f38ee66b1687674ce25113823e8893b1e94906..fe3e09f032b19f603b3b7d4688fa3a53d5408646 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5460,6 +5460,13 @@ public final class Settings { */ public static final String LONG_SCREEN_APPS = "long_screen_apps"; + /** + * Whether or not volume button music controls should be enabled to seek media tracks + * 0 = 0ff, 1 = on + * @hide + */ + public static final String VOLBTN_MUSIC_CONTROLS = "volbtn_music_controls"; + /** * IMPORTANT: If you add a new public settings you also have to add it to * PUBLIC_SETTINGS below. If the new setting is hidden you have to add diff --git a/core/java/com/android/internal/util/libremobileos/VolumeKeyHandler.java b/core/java/com/android/internal/util/libremobileos/VolumeKeyHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..c82175047870b1f6e5e4760baf1857f3e3c06ad4 --- /dev/null +++ b/core/java/com/android/internal/util/libremobileos/VolumeKeyHandler.java @@ -0,0 +1,171 @@ +/** + * Copyright (C) 2017 The LineageOS 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.util.libremobileos; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.media.AudioManager; +import android.media.session.MediaSessionLegacyHelper; +import android.os.Handler; +import android.os.Message; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Slog; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +public final class VolumeKeyHandler { + private final String TAG = "VolumeKeyHandler"; + private final boolean DEBUG = false; + + private static final int MSG_DISPATCH_VOLKEY_WITH_WAKELOCK = 1; + + private final Context mContext; + private ButtonHandler mHandler; + + private boolean mIsLongPress = false; + + private boolean mVolBtnMusicControls = false; + + private class ButtonHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DISPATCH_VOLKEY_WITH_WAKELOCK: + KeyEvent ev = (KeyEvent) msg.obj; + mIsLongPress = true; + if (DEBUG) { + Slog.d(TAG, "Dispatching key to audio service"); + } + dispatchMediaKeyToAudioService(ev); + dispatchMediaKeyToAudioService(KeyEvent.changeAction(ev, KeyEvent.ACTION_UP)); + break; + } + } + } + + public VolumeKeyHandler(Context context) { + mContext = context; + mHandler = new ButtonHandler(); + + SettingsObserver observer = new SettingsObserver(new Handler()); + observer.observe(); + } + + public boolean handleVolumeKey(KeyEvent event, boolean isInteractive) { + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final int keyCode = event.getKeyCode(); + + if (isInteractive) { + // nothing to do here for now + if (DEBUG) { + Slog.d(TAG, "Skipping because interactive"); + } + return false; + } + + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_UP: + if (!mVolBtnMusicControls) { + return false; + } + + if (down) { + mIsLongPress = false; + // queue skip event + int newKeyCode = (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + ? KeyEvent.KEYCODE_MEDIA_PREVIOUS + : KeyEvent.KEYCODE_MEDIA_NEXT); + + KeyEvent newEvent = new KeyEvent(event.getDownTime(), event.getEventTime(), + event.getAction(), newKeyCode, 0); + if (DEBUG) { + Slog.d(TAG, "Queueing media " + + (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? "previous" : "next") + + " event " + newEvent); + } + Message msg = mHandler.obtainMessage(MSG_DISPATCH_VOLKEY_WITH_WAKELOCK, + newEvent); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, ViewConfiguration.getLongPressTimeout()); + } else { + // cancel skip event + mHandler.removeMessages(MSG_DISPATCH_VOLKEY_WITH_WAKELOCK); + + if (mIsLongPress) { + // if key was long pressed, media next/prev action has been performed, + // so don't change volume + break; + } + // sendVolumeKeyEvent will only change the volume on ACTION_DOWN, + // so fake the ACTION_DOWN event. + KeyEvent newEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(newEvent, + AudioManager.USE_DEFAULT_STREAM_TYPE, true); + } + break; + default: + // key unhandled + return false; + } + return true; + } + + void dispatchMediaKeyToAudioService(KeyEvent ev) { + if (DEBUG) { + Slog.d(TAG, "Dispatching KeyEvent " + ev + " to audio service"); + } + MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(ev, true); + } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.VOLBTN_MUSIC_CONTROLS), + false, this, UserHandle.USER_ALL); + + update(); + } + + @Override + public void onChange(boolean selfChange) { + update(); + } + + private void update() { + ContentResolver resolver = mContext.getContentResolver(); + Resources res = mContext.getResources(); + + mVolBtnMusicControls = Settings.System.getIntForUser( + resolver, Settings.System.VOLBTN_MUSIC_CONTROLS, 1, + UserHandle.USER_CURRENT) == 1; + + if (DEBUG) { + Slog.d(TAG, "music controls enabled = " + mVolBtnMusicControls); + } + } + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 86fb4c4b7a0de4d5eca2c30a8031a721ad38dd8a..a61884639a0097b9735fce6892b3a82390d21fb4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -226,6 +226,8 @@ import com.android.server.wm.WindowManagerService; import dalvik.system.PathClassLoader; +import com.android.internal.util.libremobileos.VolumeKeyHandler; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; @@ -610,6 +612,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { private final List<DeviceKeyHandler> mDeviceKeyHandlers = new ArrayList<>(); + private VolumeKeyHandler mVolumeKeyHandler; + private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5; @@ -3676,6 +3680,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // {@link interceptKeyBeforeDispatching()}. result |= ACTION_PASS_TO_USER; } else if ((result & ACTION_PASS_TO_USER) == 0) { + if (mVolumeKeyHandler.handleVolumeKey(event, interactive)) { + break; + } + // If we aren't passing to the user and no one else // handled it send it to the session manager to // figure out. @@ -4842,6 +4850,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onBootCompleted(); } } + mVolumeKeyHandler = new VolumeKeyHandler(mContext); mSideFpsEventHandler.onFingerprintSensorReady(); startedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); finishedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);