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

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

Merge "Clean up A2DP State Machine"

parents 73a1b750 d560c888
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -393,8 +393,8 @@ static JNINativeMethod sMethods[] = {
};

int register_com_android_bluetooth_a2dp(JNIEnv* env) {
  return jniRegisterNativeMethods(env,
                                  "com/android/bluetooth/a2dp/A2dpStateMachine",
                                  sMethods, NELEM(sMethods));
  return jniRegisterNativeMethods(
      env, "com/android/bluetooth/a2dp/A2dpNativeInterface", sMethods,
      NELEM(sMethods));
}
}
+190 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 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.
 */

/*
 * Defines the native inteface that is used by state machine/service to
 * send or receive messages from the native stack. This file is registered
 * for the native methods in the corresponding JNI C++ file.
 */
package com.android.bluetooth.a2dp;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothDevice;
import android.support.annotation.VisibleForTesting;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.GuardedBy;

/**
 * A2DP Native Interface to/from JNI.
 */
public class A2dpNativeInterface {
    private static final String TAG = "A2dpNativeInterface";
    private static final boolean DBG = true;
    private BluetoothAdapter mAdapter;

    @GuardedBy("INSTANCE_LOCK")
    private static A2dpNativeInterface sInstance;
    private static final Object INSTANCE_LOCK = new Object();

    static {
        classInitNative();
    }

    @VisibleForTesting
    private A2dpNativeInterface() {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mAdapter == null) {
            Log.wtf(TAG, "No Bluetooth Adapter Available");
        }
    }

    /**
     * Get singleton instance.
     */
    public static A2dpNativeInterface getInstance() {
        synchronized (INSTANCE_LOCK) {
            if (sInstance == null) {
                sInstance = new A2dpNativeInterface();
            }
            return sInstance;
        }
    }

    /**
     * Initializes the native interface.
     *
     * @param codecConfigPriorities an array with the codec configuration
     * priorities to configure.
     */
    public void init(BluetoothCodecConfig[] codecConfigPriorities) {
        initNative(codecConfigPriorities);
    }

    /**
     * Cleanup the native interface.
     */
    public void cleanup() {
        cleanupNative();
    }

    /**
     * Initiates A2DP connection to a remote device.
     *
     * @param device the remote device
     * @return true on success, otherwise false.
     */
    public boolean connectA2dp(BluetoothDevice device) {
        return connectA2dpNative(getByteAddress(device));
    }

    /**
     * Disconnects A2DP from a remote device.
     *
     * @param device the remote device
     * @return true on success, otherwise false.
     */
    public boolean disconnectA2dp(BluetoothDevice device) {
        return disconnectA2dpNative(getByteAddress(device));
    }

    /**
     * Sets the codec configuration preferences.
     *
     * @param codecConfigArray an array with the codec configurations to
     * configure.
     * @return true on success, otherwise false.
     */
    public boolean setCodecConfigPreference(BluetoothCodecConfig[] codecConfigArray) {
        return setCodecConfigPreferenceNative(codecConfigArray);
    }

    private BluetoothDevice getDevice(byte[] address) {
        return mAdapter.getRemoteDevice(address);
    }

    private byte[] getByteAddress(BluetoothDevice device) {
        return Utils.getBytesFromAddress(device.getAddress());
    }

    private void sendMessageToService(A2dpStackEvent event) {
        A2dpService service = A2dpService.getA2dpService();
        if (service != null) {
            service.messageFromNative(event);
        } else {
            Log.w(TAG, "Event ignored, service not available: " + event);
        }
    }

    // Callbacks from the native stack back into the Java framework.
    // All callbacks are routed via the Service which will disambiguate which
    // state machine the message should be routed to.

    private void onConnectionStateChanged(int state, byte[] address) {
        A2dpStackEvent event =
                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
        event.valueInt = state;
        event.device = getDevice(address);

        if (DBG) {
            Log.d(TAG, "onConnectionStateChanged: " + event);
        }
        sendMessageToService(event);
    }

    private void onAudioStateChanged(int state, byte[] address) {
        A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
        event.valueInt = state;
        event.device = getDevice(address);

        if (DBG) {
            Log.d(TAG, "onAudioStateChanged: " + event);
        }
        sendMessageToService(event);
    }

    private void onCodecConfigChanged(BluetoothCodecConfig newCodecConfig,
            BluetoothCodecConfig[] codecsLocalCapabilities,
            BluetoothCodecConfig[] codecsSelectableCapabilities) {
        if (DBG) {
            Log.d(TAG, "onCodecConfigChanged: " + newCodecConfig);
        }
        // TODO: We need to use A2dpStackEvent instead of specialized service calls.
        A2dpService service = A2dpService.getA2dpService();
        if (service != null) {
            service.onCodecConfigChangedFromNative(newCodecConfig,
                                                   codecsLocalCapabilities,
                                                   codecsSelectableCapabilities);
        } else {
            Log.w(TAG, "onCodecConfigChanged ignored: service not available");
        }
    }

    // Native methods that call into the JNI interface
    private static native void classInitNative();

    private native void initNative(BluetoothCodecConfig[] codecConfigPriorities);

    private native void cleanupNative();

    private native boolean connectA2dpNative(byte[] address);

    private native boolean disconnectA2dpNative(byte[] address);

    private native boolean setCodecConfigPreferenceNative(BluetoothCodecConfig[] codecConfigArray);
}
+55 −3
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.HandlerThread;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.util.Log;
@@ -44,14 +45,20 @@ import java.util.Objects;
 * @hide
 */
public class A2dpService extends ProfileService {
    private static final boolean DBG = false;
    private static final boolean DBG = true;
    private static final String TAG = "A2dpService";

    private HandlerThread mStateMachinesThread = null;
    private A2dpStateMachine mStateMachine;
    private Avrcp mAvrcp;
    private A2dpNativeInterface mA2dpNativeInterface = null;

    private BroadcastReceiver mConnectionStateChangedReceiver = null;

    public A2dpService() {
        mA2dpNativeInterface = A2dpNativeInterface.getInstance();
    }

    private class CodecSupportReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -113,8 +120,16 @@ public class A2dpService extends ProfileService {

    @Override
    protected boolean start() {
        if (DBG) {
            Log.d(TAG, "start()");
        }

        mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
        mStateMachinesThread.start();

        mAvrcp = Avrcp.make(this);
        mStateMachine = A2dpStateMachine.make(this, this);
        mStateMachine = A2dpStateMachine.make(this, this, mA2dpNativeInterface,
                                              mStateMachinesThread.getLooper());
        setA2dpService(this);
        if (mConnectionStateChangedReceiver == null) {
            IntentFilter filter = new IntentFilter();
@@ -127,17 +142,29 @@ public class A2dpService extends ProfileService {

    @Override
    protected boolean stop() {
        if (DBG) {
            Log.d(TAG, "stop()");
        }

        if (mStateMachine != null) {
            mStateMachine.doQuit();
        }
        if (mAvrcp != null) {
            mAvrcp.doQuit();
        }
        if (mStateMachinesThread != null) {
            mStateMachinesThread.quit();
            mStateMachinesThread = null;
        }
        return true;
    }

    @Override
    protected boolean cleanup() {
        if (DBG) {
            Log.d(TAG, "cleanup()");
        }

        if (mConnectionStateChangedReceiver != null) {
            unregisterReceiver(mConnectionStateChangedReceiver);
            mConnectionStateChangedReceiver = null;
@@ -176,7 +203,7 @@ public class A2dpService extends ProfileService {
    private static synchronized void setA2dpService(A2dpService instance) {
        if (instance != null && instance.isAvailable()) {
            if (DBG) {
                Log.d(TAG, "setA2dpService(): set to: " + sA2dpService);
                Log.d(TAG, "setA2dpService(): set to: " + instance);
            }
            sA2dpService = instance;
        } else {
@@ -195,6 +222,10 @@ public class A2dpService extends ProfileService {
    }

    public boolean connect(BluetoothDevice device) {
        if (DBG) {
            Log.d(TAG, "connect(): " + device);
        }

        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");

        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
@@ -218,6 +249,10 @@ public class A2dpService extends ProfileService {
    }

    boolean disconnect(BluetoothDevice device) {
        if (DBG) {
            Log.d(TAG, "disconnect(): " + device);
        }

        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
        int connectionState = mStateMachine.getConnectionState(device);
        if (connectionState != BluetoothProfile.STATE_CONNECTED
@@ -362,6 +397,23 @@ public class A2dpService extends ProfileService {
                value);
    }

    // Handle messages from native (JNI) to Java
    void messageFromNative(A2dpStackEvent stackEvent) {
        if (DBG) {
            Log.d(TAG, "messageFromNative(): " + stackEvent);
        }
        mStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent);
    }

    // TODO: This method should go away and should be replaced with
    // the messageFromNative(A2dpStackEvent) mechanism
    void onCodecConfigChangedFromNative(BluetoothCodecConfig newCodecConfig,
                                        BluetoothCodecConfig[] codecsLocalCapabilities,
                                        BluetoothCodecConfig[] codecsSelectableCapabilities) {
        mStateMachine.onCodecConfigChanged(newCodecConfig, codecsLocalCapabilities,
                                           codecsSelectableCapabilities);
    }

    //Binder object: Must be static class or memory leak may occur
    private static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub
            implements IProfileServiceBinder {
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 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.a2dp;

import android.bluetooth.BluetoothDevice;

/**
 * Stack event sent via a callback from JNI to Java, or generated
 * internally by the A2DP State Machine.
 */
public class A2dpStackEvent {
    // Event types for STACK_EVENT message (coming from native)
    private static final int EVENT_TYPE_NONE = 0;
    public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
    public static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;

    public int type = EVENT_TYPE_NONE;
    public int valueInt = 0;
    public BluetoothDevice device = null;

    A2dpStackEvent(int type) {
        this.type = type;
    }

    @Override
    public String toString() {
        // event dump
        StringBuilder result = new StringBuilder();
        result.append("A2dpStackEvent {type:" + eventTypeToString(type));
        result.append(", value1:" + valueInt);
        result.append(", device:" + device + "}");
        return result.toString();
    }

    // for debugging only
    private static String eventTypeToString(int type) {
        switch (type) {
            case EVENT_TYPE_NONE:
                return "EVENT_TYPE_NONE";
            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
                return "EVENT_TYPE_CONNECTION_STATE_CHANGED";
            case EVENT_TYPE_AUDIO_STATE_CHANGED:
                return "EVENT_TYPE_AUDIO_STATE_CHANGED";
            default:
                return "EVENT_TYPE_UNKNOWN:" + type;
        }
    }
}
+176 −191

File changed.

Preview size limit exceeded, changes collapsed.

Loading