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

Commit c3a932d4 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Implementation for Bluetooth silence mode"

parents db4357e3 6b73802a
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -390,6 +390,33 @@ static jboolean disconnectA2dpNative(JNIEnv* env, jobject object,
  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

static jboolean setSilenceDeviceNative(JNIEnv* env, jobject object,
                                       jbyteArray address, jboolean silence) {
  ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sBluetoothA2dpInterface) {
    ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__);
    return JNI_FALSE;
  }

  jbyte* addr = env->GetByteArrayElements(address, nullptr);

  RawAddress bd_addr = RawAddress::kEmpty;
  if (addr) {
    bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
  }
  if (bd_addr == RawAddress::kEmpty) {
    return JNI_FALSE;
  }
  bt_status_t status =
      sBluetoothA2dpInterface->set_silence_device(bd_addr, silence);
  if (status != BT_STATUS_SUCCESS) {
    ALOGE("%s: Failed A2DP set_silence_device, status: %d", __func__, status);
  }
  env->ReleaseByteArrayElements(address, addr, 0);
  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
                                      jbyteArray address) {
  ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
@@ -405,6 +432,9 @@ static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
  if (addr) {
    bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
  }
  if (bd_addr == RawAddress::kEmpty) {
    return JNI_FALSE;
  }
  bt_status_t status = sBluetoothA2dpInterface->set_active_device(bd_addr);
  if (status != BT_STATUS_SUCCESS) {
    ALOGE("%s: Failed A2DP set_active_device, status: %d", __func__, status);
@@ -450,6 +480,7 @@ static JNINativeMethod sMethods[] = {
    {"cleanupNative", "()V", (void*)cleanupNative},
    {"connectA2dpNative", "([B)Z", (void*)connectA2dpNative},
    {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative},
    {"setSilenceDeviceNative", "([BZ)Z", (void*)setSilenceDeviceNative},
    {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative},
    {"setCodecConfigPreferenceNative",
     "([B[Landroid/bluetooth/BluetoothCodecConfig;)Z",
+11 −0
Original line number Diff line number Diff line
@@ -106,6 +106,16 @@ public class A2dpNativeInterface {
        return disconnectA2dpNative(getByteAddress(device));
    }

    /**
     * Sets a connected A2DP remote device to silence mode.
     *
     * @param device the remote device
     * @return true on success, otherwise false.
     */
    public boolean setSilenceDevice(BluetoothDevice device, boolean silence) {
        return setSilenceDeviceNative(getByteAddress(device), silence);
    }

    /**
     * Sets a connected A2DP remote device as active.
     *
@@ -199,6 +209,7 @@ public class A2dpNativeInterface {
    private native void cleanupNative();
    private native boolean connectA2dpNative(byte[] address);
    private native boolean disconnectA2dpNative(byte[] address);
    private native boolean setSilenceDeviceNative(byte[] address, boolean silence);
    private native boolean setActiveDeviceNative(byte[] address);
    private native boolean setCodecConfigPreferenceNative(byte[] address,
                BluetoothCodecConfig[] codecConfigArray);
+28 −0
Original line number Diff line number Diff line
@@ -466,6 +466,34 @@ public class A2dpService extends ProfileService {
        }
    }

    /**
     * Process a change in the silence mode for a {@link BluetoothDevice}.
     *
     * @param device the device to change silence mode
     * @param silence true to enable silence mode, false to disable.
     * @return true on success, false on error
     */
    @VisibleForTesting
    public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
        if (DBG) {
            Log.d(TAG, "setSilenceMode(" + device + "): " + silence);
        }
        if (silence && Objects.equals(mActiveDevice, device)) {
            if (mActiveDevice != null && AvrcpTargetService.get() != null) {
                AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
            }
            removeActiveDevice(true);
        } else if (!silence && mActiveDevice == null) {
            // Set the device as the active device if currently no active device.
            setActiveDevice(device);
        }
        if (!mA2dpNativeInterface.setSilenceDevice(device, silence)) {
            Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer");
            return false;
        }
        return true;
    }

    /**
     * Set the active device.
     *
+49 −1
Original line number Diff line number Diff line
@@ -189,6 +189,7 @@ public class AdapterService extends Service {
    private PhonePolicy mPhonePolicy;
    private ActiveDeviceManager mActiveDeviceManager;
    private DatabaseManager mDatabaseManager;
    private SilenceDeviceManager mSilenceDeviceManager;
    private AppOpsManager mAppOps;

    /**
@@ -423,6 +424,9 @@ public class AdapterService extends Service {
                MetadataDatabase.class, MetadataDatabase.DATABASE_NAME).build();
        mDatabaseManager.start(database);

        mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(),
                Looper.getMainLooper());
        mSilenceDeviceManager.start();

        setAdapterService(this);

@@ -704,6 +708,10 @@ public class AdapterService extends Service {
            mPhonePolicy.cleanup();
        }

        if (mSilenceDeviceManager != null) {
            mSilenceDeviceManager.cleanup();
        }

        if (mActiveDeviceManager != null) {
            mActiveDeviceManager.cleanup();
        }
@@ -1363,6 +1371,34 @@ public class AdapterService extends Service {
            return service.getPhonebookAccessPermission(device);
        }

        @Override
        public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
            if (!Utils.checkCaller()) {
                Log.w(TAG, "setSilenceMode() - Not allowed for non-active user");
                return false;
            }

            AdapterService service = getService();
            if (service == null) {
                return false;
            }
            return service.setSilenceMode(device, silence);
        }

        @Override
        public boolean getSilenceMode(BluetoothDevice device) {
            if (!Utils.checkCaller()) {
                Log.w(TAG, "getSilenceMode() - Not allowed for non-active user");
                return false;
            }

            AdapterService service = getService();
            if (service == null) {
                return false;
            }
            return service.getSilenceMode(device);
        }

        @Override
        public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
            if (!Utils.checkCaller()) {
@@ -2180,9 +2216,20 @@ public class AdapterService extends Service {
                : BluetoothDevice.ACCESS_REJECTED;
    }

    boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
    boolean setSilenceMode(BluetoothDevice device, boolean silence) {
        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                "Need BLUETOOTH PRIVILEGED permission");
        mSilenceDeviceManager.setSilenceMode(device, silence);
        return true;
    }

    boolean getSilenceMode(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                "Need BLUETOOTH PRIVILEGED permission");
        return mSilenceDeviceManager.getSilenceMode(device);
    }

    boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
        SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
                Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = pref.edit();
@@ -2672,6 +2719,7 @@ public class AdapterService extends Service {
        for (ProfileService profile : mRegisteredProfiles) {
            profile.dump(sb);
        }
        mSilenceDeviceManager.dump(fd, writer, args);

        writer.write(sb.toString());
        writer.flush();
+355 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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.btservice;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.util.Log;

import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.internal.annotations.VisibleForTesting;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The silence device manager controls silence mode for A2DP, HFP, and AVRCP.
 *
 * 1) If an active device (for A2DP or HFP) enters silence mode, the active device
 *    for that profile will be set to null.
 * 2) If a device exits silence mode while the A2DP or HFP active device is null,
 *    the device will be set as the active device for that profile.
 * 3) If a device is disconnected, it exits silence mode.
 * 4) If a device is set as the active device for A2DP or HFP, while silence mode
 *    is enabled, then the device will exit silence mode.
 * 5) If a device is in silence mode, AVRCP position change event and HFP AG indicators
 *    will be disabled.
 * 6) If a device is not connected with A2DP or HFP, it cannot enter silence mode.
 */
public class SilenceDeviceManager {
    private static final boolean DBG = true;
    private static final boolean VERBOSE = false;
    private static final String TAG = "SilenceDeviceManager";

    private final AdapterService mAdapterService;
    private final ServiceFactory mFactory;
    private Handler mHandler = null;
    private Looper mLooper = null;
    private A2dpService mA2dpService = null;
    private HeadsetService mHeadsetService = null;

    private final Map<BluetoothDevice, Boolean> mSilenceDevices = new HashMap<>();
    private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>();
    private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>();

    private static final int MSG_SILENCE_DEVICE_STATE_CHANGED = 1;
    private static final int MSG_A2DP_CONNECTION_STATE_CHANGED = 10;
    private static final int MSG_HFP_CONNECTION_STATE_CHANGED = 11;
    private static final int MSG_A2DP_ACTIVE_DEIVCE_CHANGED = 20;
    private static final int MSG_HFP_ACTIVE_DEVICE_CHANGED = 21;
    private static final int ENABLE_SILENCE = 0;
    private static final int DISABLE_SILENCE = 1;

    // Broadcast receiver for all changes
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action == null) {
                Log.e(TAG, "Received intent with null action");
                return;
            }
            switch (action) {
                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                    mHandler.obtainMessage(MSG_A2DP_CONNECTION_STATE_CHANGED,
                                           intent).sendToTarget();
                    break;
                case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
                    mHandler.obtainMessage(MSG_HFP_CONNECTION_STATE_CHANGED,
                                           intent).sendToTarget();
                    break;
                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
                    mHandler.obtainMessage(MSG_A2DP_ACTIVE_DEIVCE_CHANGED,
                                           intent).sendToTarget();
                    break;
                case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
                    mHandler.obtainMessage(MSG_HFP_ACTIVE_DEVICE_CHANGED,
                        intent).sendToTarget();
                    break;
                default:
                    Log.e(TAG, "Received unexpected intent, action=" + action);
                    break;
            }
        }
    };

    class SilenceDeviceManagerHandler extends Handler {
        SilenceDeviceManagerHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            if (VERBOSE) {
                Log.d(TAG, "handleMessage: " + msg.what);
            }
            switch (msg.what) {
                case MSG_SILENCE_DEVICE_STATE_CHANGED: {
                    BluetoothDevice device = (BluetoothDevice) msg.obj;
                    boolean state = (msg.arg1 == ENABLE_SILENCE);
                    handleSilenceDeviceStateChanged(device, state);
                }
                break;

                case MSG_A2DP_CONNECTION_STATE_CHANGED: {
                    Intent intent = (Intent) msg.obj;
                    BluetoothDevice device =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                    int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);

                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
                        // enter connected state
                        addConnectedDevice(device, BluetoothProfile.A2DP);
                        if (!mSilenceDevices.containsKey(device)) {
                            mSilenceDevices.put(device, false);
                        }
                    } else if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // exiting from connected state
                        removeConnectedDevice(device, BluetoothProfile.A2DP);
                        if (!isBluetoothAudioConnected(device)) {
                            handleSilenceDeviceStateChanged(device, false);
                            mSilenceDevices.remove(device);
                        }
                    }
                }
                break;

                case MSG_HFP_CONNECTION_STATE_CHANGED: {
                    Intent intent = (Intent) msg.obj;
                    BluetoothDevice device =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                    int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);

                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
                        // enter connected state
                        addConnectedDevice(device, BluetoothProfile.HEADSET);
                        if (!mSilenceDevices.containsKey(device)) {
                            mSilenceDevices.put(device, false);
                        }
                    } else if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // exiting from connected state
                        removeConnectedDevice(device, BluetoothProfile.HEADSET);
                        if (!isBluetoothAudioConnected(device)) {
                            handleSilenceDeviceStateChanged(device, false);
                            mSilenceDevices.remove(device);
                        }
                    }
                }
                break;

                case MSG_A2DP_ACTIVE_DEIVCE_CHANGED: {
                    Intent intent = (Intent) msg.obj;
                    BluetoothDevice a2dpActiveDevice =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    if (getSilenceMode(a2dpActiveDevice)) {
                        // Resume the device from silence mode.
                        setSilenceMode(a2dpActiveDevice, false);
                    }
                }
                break;

                case MSG_HFP_ACTIVE_DEVICE_CHANGED: {
                    Intent intent = (Intent) msg.obj;
                    BluetoothDevice hfpActiveDevice =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    if (getSilenceMode(hfpActiveDevice)) {
                        // Resume the device from silence mode.
                        setSilenceMode(hfpActiveDevice, false);
                    }
                }
                break;

                default: {
                    Log.e(TAG, "Unknown message: " + msg.what);
                }
                break;
            }
        }
    };

    SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper) {
        mAdapterService = service;
        mFactory = factory;
        mLooper = looper;
    }

    void start() {
        if (VERBOSE) {
            Log.v(TAG, "start()");
        }
        mHandler = new SilenceDeviceManagerHandler(mLooper);
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
        mAdapterService.registerReceiver(mReceiver, filter);
        mA2dpService = mFactory.getA2dpService();
        mHeadsetService = mFactory.getHeadsetService();
    }

    void cleanup() {
        if (VERBOSE) {
            Log.v(TAG, "cleanup()");
        }
        mSilenceDevices.clear();
        mA2dpService = null;
        mHeadsetService = null;
        mAdapterService.unregisterReceiver(mReceiver);
    }

    @VisibleForTesting
    boolean setSilenceMode(BluetoothDevice device, boolean silence) {
        if (mHandler == null) {
            Log.e(TAG, "setSilenceMode() mHandler is null!");
            return false;
        }
        Log.d(TAG, "setSilenceMode: " + device.getAddress() + ", " + silence);
        Message message = mHandler.obtainMessage(MSG_SILENCE_DEVICE_STATE_CHANGED,
                silence ? ENABLE_SILENCE : DISABLE_SILENCE, 0, device);
        mHandler.sendMessage(message);
        return true;
    }

    void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) {
        boolean oldState = getSilenceMode(device);
        if (oldState == state) {
            return;
        }
        if (!isBluetoothAudioConnected(device)) {
            if (oldState) {
                // Device is disconnected, resume all silenced profiles.
                state = false;
            } else {
                Log.d(TAG, "Deivce is not connected to any Bluetooth audio.");
                return;
            }
        }
        mSilenceDevices.replace(device, state);

        if (mA2dpService == null) {
            Log.d(TAG, "A2dpService is null!");
            return;
        }
        if (mHeadsetService == null) {
            Log.d(TAG, "HeadsetService is null!");
            return;
        }
        mA2dpService.setSilenceMode(device, state);
        mHeadsetService.setSilenceMode(device, state);
        Log.i(TAG, "Silence mode change " + device.getAddress() + ": " + oldState + " -> "
                + state);
        broadcastSilenceStateChange(device, state);
    }

    void broadcastSilenceStateChange(BluetoothDevice device, boolean state) {
        Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_SILENCE_ENABLED, state);
        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);

    }

    @VisibleForTesting
    boolean getSilenceMode(BluetoothDevice device) {
        boolean state = false;
        if (mSilenceDevices.containsKey(device)) {
            state = mSilenceDevices.get(device);
        }
        return state;
    }

    void addConnectedDevice(BluetoothDevice device, int profile) {
        if (VERBOSE) {
            Log.d(TAG, "addConnectedDevice: " + device.getAddress() + ", profile:" + profile);
        }
        switch (profile) {
            case BluetoothProfile.A2DP:
                if (!mA2dpConnectedDevices.contains(device)) {
                    mA2dpConnectedDevices.add(device);
                }
                break;
            case BluetoothProfile.HEADSET:
                if (!mHfpConnectedDevices.contains(device)) {
                    mHfpConnectedDevices.add(device);
                }
                break;
        }
    }

    void removeConnectedDevice(BluetoothDevice device, int profile) {
        if (VERBOSE) {
            Log.d(TAG, "removeConnectedDevice: " + device.getAddress() + ", profile:" + profile);
        }
        switch (profile) {
            case BluetoothProfile.A2DP:
                if (mA2dpConnectedDevices.contains(device)) {
                    mA2dpConnectedDevices.remove(device);
                }
                break;
            case BluetoothProfile.HEADSET:
                if (mHfpConnectedDevices.contains(device)) {
                    mHfpConnectedDevices.remove(device);
                }
                break;
        }
    }

    boolean isBluetoothAudioConnected(BluetoothDevice device) {
        return (mA2dpConnectedDevices.contains(device) || mHfpConnectedDevices.contains(device));
    }

    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
        writer.println("\nSilenceDeviceManager:");
        writer.println("  Address            | Is silenced?");
        for (BluetoothDevice device : mSilenceDevices.keySet()) {
            writer.println("  " + device.getAddress() + "  | " + getSilenceMode(device));
        }
    }

    @VisibleForTesting
    BroadcastReceiver getBroadcastReceiver() {
        return mReceiver;
    }
}
Loading