Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 83f3a3dc authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement Bluetooth device volume memory" into pi-dev

parents da7e2afc 5bf4d096
Loading
Loading
Loading
Loading
+14 −0
Original line number Original line Diff line number Diff line
@@ -432,6 +432,8 @@ public class A2dpService extends ProfileService {
            if (device == null) {
            if (device == null) {
                // Clear the active device
                // Clear the active device
                mActiveDevice = null;
                mActiveDevice = null;
                // This needs to happen before we inform the audio manager that the device
                // disconnected. Please see comment in broadcastActiveDevice() for why.
                broadcastActiveDevice(null);
                broadcastActiveDevice(null);
                if (previousActiveDevice != null) {
                if (previousActiveDevice != null) {
                    // Make sure the Audio Manager knows the previous Active device is disconnected
                    // Make sure the Audio Manager knows the previous Active device is disconnected
@@ -467,6 +469,8 @@ public class A2dpService extends ProfileService {


            boolean deviceChanged = !Objects.equals(device, mActiveDevice);
            boolean deviceChanged = !Objects.equals(device, mActiveDevice);
            mActiveDevice = device;
            mActiveDevice = device;
            // This needs to happen before we inform the audio manager that the device
            // disconnected. Please see comment in broadcastActiveDevice() for why.
            broadcastActiveDevice(mActiveDevice);
            broadcastActiveDevice(mActiveDevice);
            if (deviceChanged) {
            if (deviceChanged) {
                // Send an intent with the active device codec config
                // Send an intent with the active device codec config
@@ -786,6 +790,16 @@ public class A2dpService extends ProfileService {
            Log.d(TAG, "broadcastActiveDevice(" + device + ")");
            Log.d(TAG, "broadcastActiveDevice(" + device + ")");
        }
        }


        // Currently the audio service can only remember the volume for a single device. We send
        // active device changed intent after informing AVRCP that the device switched so it can
        // set the stream volume to the new device before A2DP informs the audio service that the
        // device has changed. This is to avoid the indeterminate volume state that exists when
        // in the middle of switching devices.
        if (AvrcpTargetService.get() != null) {
            AvrcpTargetService.get().volumeDeviceSwitched(
                    device != null ? device.getAddress() : "");
        }

        Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
        Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+27 −2
Original line number Original line Diff line number Diff line
@@ -55,6 +55,7 @@ public class AvrcpTargetService extends ProfileService {
    private AudioManager mAudioManager;
    private AudioManager mAudioManager;
    private AvrcpBroadcastReceiver mReceiver;
    private AvrcpBroadcastReceiver mReceiver;
    private AvrcpNativeInterface mNativeInterface;
    private AvrcpNativeInterface mNativeInterface;
    private AvrcpVolumeManager mVolumeManager;


    // Only used to see if the metadata has changed from its previous value
    // Only used to see if the metadata has changed from its previous value
    private MediaData mCurrentData;
    private MediaData mCurrentData;
@@ -157,6 +158,8 @@ public class AvrcpTargetService extends ProfileService {
        mNativeInterface = AvrcpNativeInterface.getInterface();
        mNativeInterface = AvrcpNativeInterface.getInterface();
        mNativeInterface.init(AvrcpTargetService.this);
        mNativeInterface.init(AvrcpTargetService.this);


        mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);

        // Only allow the service to be used once it is initialized
        // Only allow the service to be used once it is initialized
        sInstance = this;
        sInstance = this;


@@ -186,12 +189,25 @@ public class AvrcpTargetService extends ProfileService {


    void deviceConnected(String bdaddr, boolean absoluteVolume) {
    void deviceConnected(String bdaddr, boolean absoluteVolume) {
        Log.i(TAG, "deviceConnected: bdaddr=" + bdaddr + " absoluteVolume=" + absoluteVolume);
        Log.i(TAG, "deviceConnected: bdaddr=" + bdaddr + " absoluteVolume=" + absoluteVolume);
        mAudioManager.avrcpSupportsAbsoluteVolume(bdaddr, absoluteVolume);
        mVolumeManager.deviceConnected(bdaddr, absoluteVolume);
        MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP);
        MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP);
    }
    }


    void deviceDisconnected(String bdaddr) {
    void deviceDisconnected(String bdaddr) {
        // Do nothing
        Log.i(TAG, "deviceDisconnected: bdaddr=" + bdaddr);
        mVolumeManager.deviceDisconnected(bdaddr);
    }

    /**
     * Signal to the service that the current audio out device has changed. The current volume
     * for the old device is saved and the new device has its volume restored. If there is no
     * saved volume use the current system volume.
     */
    public void volumeDeviceSwitched(String bdaddr) {
        if (DEBUG) {
            Log.d(TAG, "volumeDeviceSwitched: bdaddr=" + bdaddr);
        }
        mVolumeManager.volumeDeviceSwitched(bdaddr);
    }
    }


    // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
    // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
@@ -291,11 +307,20 @@ public class AvrcpTargetService extends ProfileService {
     * Dump debugging information to the string builder
     * Dump debugging information to the string builder
     */
     */
    public void dump(StringBuilder sb) {
    public void dump(StringBuilder sb) {
        sb.append("\nProfile: AvrcpTargetService:\n");
        if (sInstance == null) {
            sb.append("AvrcpTargetService not running");
            return;
        }

        if (mMediaPlayerList != null) {
        if (mMediaPlayerList != null) {
            mMediaPlayerList.dump(sb);
            mMediaPlayerList.dump(sb);
        } else {
        } else {
            sb.append("\nMedia Player List is empty\n");
            sb.append("\nMedia Player List is empty\n");
        }
        }

        mVolumeManager.dump(sb);
        sb.append("\n");
    }
    }


    private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub
    private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub
+191 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2018 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.bluetooth.avrcp;

import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

class AvrcpVolumeManager {
    public static final String TAG = "NewAvrcpVolumeManager";
    public static final boolean DEBUG = true;

    // All volumes are stored at system volume values, not AVRCP values
    public static final String VOLUME_MAP = "bluetooth_volume_map";
    public static final String VOLUME_BLACKLIST = "absolute_volume_blacklist";
    public static final int AVRCP_MAX_VOL = 127;
    public static int sDeviceMaxVolume = 0;
    public static final int STREAM_MUSIC = AudioManager.STREAM_MUSIC;

    Context mContext;
    AudioManager mAudioManager;
    AvrcpNativeInterface mNativeInterface;

    HashMap<String, Boolean> mDeviceMap = new HashMap<String, Boolean>();
    HashMap<String, Integer> mVolumeMap = new HashMap<String, Integer>();
    String mCurrentDeviceAddr = "";
    boolean mAbsoluteVolumeSupported = false;

    int avrcpToSystemVolume(int avrcpVolume) {
        return (int) Math.floor((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL);
    }

    int systemToAvrcpVolume(int deviceVolume) {
        int avrcpVolume = (int) Math.floor((double) deviceVolume
                * AVRCP_MAX_VOL / sDeviceMaxVolume);
        if (avrcpVolume > 127) avrcpVolume = 127;
        return avrcpVolume;
    }

    SharedPreferences getVolumeMap() {
        return mContext.getSharedPreferences(VOLUME_MAP, Context.MODE_PRIVATE);
    }

    AvrcpVolumeManager(Context context, AudioManager audioManager,
            AvrcpNativeInterface nativeInterface) {
        mContext = context;
        mAudioManager = audioManager;
        mNativeInterface = nativeInterface;
        sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);

        // Load the volume map into a hash map since shared preferences are slow
        Map<String, ?> allKeys = getVolumeMap().getAll();
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof Integer) {
                mVolumeMap.put(key, (Integer) value);
            }
        }
    }

    int getVolume(String bdaddr, int defaultValue) {
        if (!mVolumeMap.containsKey(bdaddr)) {
            Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + bdaddr);
            return defaultValue;
        }

        return mVolumeMap.get(bdaddr);
    }

    void storeVolume() {
        SharedPreferences.Editor pref = getVolumeMap().edit();
        int storeVolume =  mAudioManager.getStreamVolume(STREAM_MUSIC);
        Log.i(TAG, "storeVolume: Storing stream volume level for device " + mCurrentDeviceAddr
                + " : " + storeVolume);
        mVolumeMap.put(mCurrentDeviceAddr, storeVolume);
        pref.putInt(mCurrentDeviceAddr, storeVolume);
        pref.apply();
    }

    void deviceConnected(String bdaddr, boolean absoluteVolume) {
        if (DEBUG) {
            Log.d(TAG, "deviceConnected: bdaddr=" + bdaddr + " absoluteVolume=" + absoluteVolume);
        }

        mDeviceMap.put(bdaddr.toUpperCase(), absoluteVolume);

        // AVRCP features lookup has completed after the device became active. Switch to the new
        // device now.
        if (bdaddr == mCurrentDeviceAddr) {
            switchVolumeDevice(bdaddr);
        }
    }

    void volumeDeviceSwitched(String bdaddr) {
        if (DEBUG) {
            Log.d(TAG, "activeDeviceChanged: mCurrentDeviceAddr=" + mCurrentDeviceAddr
                    + " bdaddr=" + bdaddr);
        }

        if (bdaddr == null || bdaddr.equals(mCurrentDeviceAddr)) {
            return;
        }

        // Store the previous volume if a device was active.
        if (!mCurrentDeviceAddr.isEmpty()) {
            storeVolume();
        }

        // Set the current volume device to the new device.
        mCurrentDeviceAddr = bdaddr;

        // No new active device.
        if (bdaddr.isEmpty()) {
            return;
        }

        // A2DP can sometimes connect and set a device to active before AVRCP has determined if the
        // device supports absolute volume. Defer switching the device until AVRCP returns the
        // info.
        if (!mDeviceMap.containsKey(bdaddr)) {
            Log.w(TAG, "Device isn't connected: " + bdaddr);
            return;
        }

        switchVolumeDevice(bdaddr);
    }

    void switchVolumeDevice(String bdaddr) {
        // Inform the audio manager that the device has changed
        mAudioManager.avrcpSupportsAbsoluteVolume(bdaddr, mDeviceMap.get(bdaddr));

        // Get the current system volume and try to get the preference volume
        int currVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
        int savedVolume = getVolume(bdaddr, currVolume);

        // If the preference volume isn't equal to the current stream volume then that means
        // we had a stored preference.
        if (DEBUG) {
            Log.d(TAG, "activeDeviceChanged: currVolume=" + currVolume
                    + " savedVolume=" + savedVolume);
        }
        if (savedVolume != currVolume) {
            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, savedVolume,
                    AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
        }

        // If absolute volume for the device is supported, set the volume for the device
        if (mDeviceMap.get(bdaddr)) {
            int avrcpVolume = systemToAvrcpVolume(savedVolume);
            Log.e(TAG, "activeDeviceChanged: Updating device volume: avrcpVolume=" + avrcpVolume);
            mNativeInterface.sendVolumeChanged(avrcpVolume);
        }
    }

    void deviceDisconnected(String bdaddr) {
        Log.e(TAG, "deviceDisconnected: bdaddr=" + bdaddr);
        mDeviceMap.remove(bdaddr);
    }

    public void dump(StringBuilder sb) {
        sb.append("Bluetooth Device Volume Map:\n");
        Map<String, ?> allKeys = getVolumeMap().getAll();
        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof Integer) {
                sb.append("    " + key + " - " + (Integer) value + "\n");
                mVolumeMap.put(key, (Integer) value);
            }
        }
    }
}