1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.bluetooth.a2dp;
18
19import android.bluetooth.BluetoothAudioConfig;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothProfile;
22import android.bluetooth.IBluetoothA2dpSink;
23import android.util.Log;
24import com.android.bluetooth.btservice.ProfileService;
25import com.android.bluetooth.Utils;
26import java.util.ArrayList;
27import java.util.List;
28import java.util.Map;
29
30/**
31 * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
32 * @hide
33 */
34public class A2dpSinkService extends ProfileService {
35    private static final boolean DBG = false;
36    private static final String TAG = "A2dpSinkService";
37
38    private A2dpSinkStateMachine mStateMachine;
39    private static A2dpSinkService sA2dpSinkService;
40
41    protected String getName() {
42        return TAG;
43    }
44
45    protected IProfileServiceBinder initBinder() {
46        return new BluetoothA2dpSinkBinder(this);
47    }
48
49    protected boolean start() {
50        mStateMachine = A2dpSinkStateMachine.make(this, this);
51        setA2dpSinkService(this);
52        return true;
53    }
54
55    protected boolean stop() {
56        mStateMachine.doQuit();
57        return true;
58    }
59
60    protected boolean cleanup() {
61        if (mStateMachine!= null) {
62            mStateMachine.cleanup();
63        }
64        clearA2dpSinkService();
65        return true;
66    }
67
68    //API Methods
69
70    public static synchronized A2dpSinkService getA2dpSinkService(){
71        if (sA2dpSinkService != null && sA2dpSinkService.isAvailable()) {
72            if (DBG) Log.d(TAG, "getA2dpSinkService(): returning " + sA2dpSinkService);
73            return sA2dpSinkService;
74        }
75        if (DBG)  {
76            if (sA2dpSinkService == null) {
77                Log.d(TAG, "getA2dpSinkService(): service is NULL");
78            } else if (!(sA2dpSinkService.isAvailable())) {
79                Log.d(TAG,"getA2dpSinkService(): service is not available");
80            }
81        }
82        return null;
83    }
84
85    private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
86        if (instance != null && instance.isAvailable()) {
87            if (DBG) Log.d(TAG, "setA2dpSinkService(): set to: " + sA2dpSinkService);
88            sA2dpSinkService = instance;
89        } else {
90            if (DBG)  {
91                if (sA2dpSinkService == null) {
92                    Log.d(TAG, "setA2dpSinkService(): service not available");
93                } else if (!sA2dpSinkService.isAvailable()) {
94                    Log.d(TAG,"setA2dpSinkService(): service is cleaning up");
95                }
96            }
97        }
98    }
99
100    private static synchronized void clearA2dpSinkService() {
101        sA2dpSinkService = null;
102    }
103
104    public boolean connect(BluetoothDevice device) {
105        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
106                                       "Need BLUETOOTH ADMIN permission");
107
108        int connectionState = mStateMachine.getConnectionState(device);
109        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
110            connectionState == BluetoothProfile.STATE_CONNECTING) {
111            return false;
112        }
113
114        mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device);
115        return true;
116    }
117
118    boolean disconnect(BluetoothDevice device) {
119        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
120                                       "Need BLUETOOTH ADMIN permission");
121        int connectionState = mStateMachine.getConnectionState(device);
122        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
123            connectionState != BluetoothProfile.STATE_CONNECTING) {
124            return false;
125        }
126
127        mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
128        return true;
129    }
130
131    public List<BluetoothDevice> getConnectedDevices() {
132        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
133        return mStateMachine.getConnectedDevices();
134    }
135
136    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
137        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
138        return mStateMachine.getDevicesMatchingConnectionStates(states);
139    }
140
141    int getConnectionState(BluetoothDevice device) {
142        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
143        return mStateMachine.getConnectionState(device);
144    }
145
146    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
147        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
148        return mStateMachine.getAudioConfig(device);
149    }
150
151    //Binder object: Must be static class or memory leak may occur
152    private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
153        implements IProfileServiceBinder {
154        private A2dpSinkService mService;
155
156        private A2dpSinkService getService() {
157            if (!Utils.checkCaller()) {
158                Log.w(TAG,"A2dp call not allowed for non-active user");
159                return null;
160            }
161
162            if (mService != null && mService.isAvailable()) {
163                return mService;
164            }
165            return null;
166        }
167
168        BluetoothA2dpSinkBinder(A2dpSinkService svc) {
169            mService = svc;
170        }
171
172        public boolean cleanup()  {
173            mService = null;
174            return true;
175        }
176
177        public boolean connect(BluetoothDevice device) {
178            A2dpSinkService service = getService();
179            if (service == null) return false;
180            return service.connect(device);
181        }
182
183        public boolean disconnect(BluetoothDevice device) {
184            A2dpSinkService service = getService();
185            if (service == null) return false;
186            return service.disconnect(device);
187        }
188
189        public List<BluetoothDevice> getConnectedDevices() {
190            A2dpSinkService service = getService();
191            if (service == null) return new ArrayList<BluetoothDevice>(0);
192            return service.getConnectedDevices();
193        }
194
195        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
196            A2dpSinkService service = getService();
197            if (service == null) return new ArrayList<BluetoothDevice>(0);
198            return service.getDevicesMatchingConnectionStates(states);
199        }
200
201        public int getConnectionState(BluetoothDevice device) {
202            A2dpSinkService service = getService();
203            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
204            return service.getConnectionState(device);
205        }
206
207        public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
208            A2dpSinkService service = getService();
209            if (service == null) return null;
210            return service.getAudioConfig(device);
211        }
212    };
213}
214