1/*
2 * Copyright (C) 2016 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.mapclient;
18
19import android.Manifest;
20import android.app.PendingIntent;
21import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothProfile;
24import android.bluetooth.IBluetoothMapClient;
25import android.net.Uri;
26import android.provider.Settings;
27import android.util.Log;
28
29import com.android.bluetooth.Utils;
30import com.android.bluetooth.btservice.ProfileService;
31
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.List;
35import java.util.Set;
36
37public class MapClientService extends ProfileService {
38    private static final String TAG = "MapClientService";
39
40    static final boolean DBG = false;
41    static final boolean VDBG = false;
42
43    private static final int MAXIMUM_CONNECTED_DEVICES = 1;
44
45    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
46
47    MceStateMachine mMceStateMachine;
48    private MnsService mMnsServer;
49    private BluetoothAdapter mAdapter;
50    private static MapClientService sMapClientService;
51
52
53    public static synchronized MapClientService getMapClientService() {
54        if (sMapClientService != null && sMapClientService.isAvailable()) {
55            if (DBG) Log.d(TAG, "getMapClientService(): returning " + sMapClientService);
56            return sMapClientService;
57        }
58        if (DBG) {
59            if (sMapClientService == null) {
60                Log.d(TAG, "getMapClientService(): service is NULL");
61            } else if (!(sMapClientService.isAvailable())) {
62                Log.d(TAG, "getMapClientService(): service is not available");
63            }
64        }
65        return null;
66    }
67
68    private static synchronized void setService(MapClientService instance) {
69        if (instance != null && instance.isAvailable()) {
70            if (DBG) Log.d(TAG, "setMapMceService(): replacing old instance: " + sMapClientService);
71            sMapClientService = instance;
72        } else {
73            if (DBG) {
74                if (sMapClientService == null) {
75                    Log.d(TAG, "setA2dpService(): service not available");
76                } else if (!sMapClientService.isAvailable()) {
77                    Log.d(TAG, "setA2dpService(): service is cleaning up");
78                }
79            }
80        }
81    }
82
83    public synchronized boolean connect(BluetoothDevice device) {
84        Log.d(TAG, "MAP Mce connect " + device.toString());
85        return mMceStateMachine.connect(device);
86    }
87
88    public synchronized boolean disconnect(BluetoothDevice device) {
89        Log.d(TAG, "MAP Mce disconnect " + device.toString());
90        return mMceStateMachine.disconnect(device);
91    }
92
93    public List<BluetoothDevice> getConnectedDevices() {
94        return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
95    }
96
97    public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
98        Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
99        List<BluetoothDevice> deviceList = new ArrayList<>();
100        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
101        int connectionState;
102        for (BluetoothDevice device : bondedDevices) {
103            connectionState = getConnectionState(device);
104            Log.d(TAG, "Device: " + device + "State: " + connectionState);
105            for (int i = 0; i < states.length; i++) {
106                if (connectionState == states[i]) {
107                    deviceList.add(device);
108                }
109            }
110        }
111        Log.d(TAG, deviceList.toString());
112        return deviceList;
113    }
114
115    public synchronized int getConnectionState(BluetoothDevice device) {
116        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
117            return mMceStateMachine.getState();
118        } else {
119            return BluetoothProfile.STATE_DISCONNECTED;
120        }
121    }
122
123    public boolean setPriority(BluetoothDevice device, int priority) {
124        Settings.Global.putInt(getContentResolver(),
125                Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()),
126                priority);
127        if (VDBG) Log.v(TAG, "Saved priority " + device + " = " + priority);
128        return true;
129    }
130
131    public int getPriority(BluetoothDevice device) {
132        int priority = Settings.Global.getInt(getContentResolver(),
133                Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()),
134                BluetoothProfile.PRIORITY_UNDEFINED);
135        return priority;
136    }
137
138    public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
139            PendingIntent sentIntent, PendingIntent deliveredIntent) {
140        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
141            return mMceStateMachine.sendMapMessage(contacts, message, sentIntent, deliveredIntent);
142        } else {
143            return false;
144        }
145
146    }
147
148    @Override
149    protected IProfileServiceBinder initBinder() {
150        return new Binder(this);
151    }
152
153    @Override
154    protected boolean start() {
155        if (DBG) Log.d(TAG, "start()");
156        setService(this);
157
158        if (mMnsServer == null) {
159            mMnsServer = new MnsService(this);
160        }
161        if (mMceStateMachine == null) {
162            mMceStateMachine = new MceStateMachine(this);
163        }
164
165        mAdapter = BluetoothAdapter.getDefaultAdapter();
166        mStartError = false;
167        return !mStartError;
168    }
169
170    @Override
171    protected synchronized boolean stop() {
172        if (DBG) Log.d(TAG, "stop()");
173        if (mMnsServer != null) {
174            mMnsServer.stop();
175        }
176        if (mMceStateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) {
177            mMceStateMachine.disconnect(mMceStateMachine.getDevice());
178        }
179        mMceStateMachine.doQuit();
180        return true;
181    }
182
183    public boolean cleanup() {
184        if (DBG) Log.d(TAG, "cleanup()");
185        return true;
186    }
187
188    public synchronized boolean getUnreadMessages(BluetoothDevice device) {
189        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
190            return mMceStateMachine.getUnreadMessages();
191        } else {
192            return false;
193        }
194    }
195
196    @Override
197    public synchronized void dump(StringBuilder sb) {
198        super.dump(sb);
199        println(sb, "StateMachine: " + mMceStateMachine.toString());
200    }
201
202    //Binder object: Must be static class or memory leak may occur
203    /**
204     * This class implements the IClient interface - or actually it validates the
205     * preconditions for calling the actual functionality in the MapClientService, and calls it.
206     */
207    private static class Binder extends IBluetoothMapClient.Stub
208            implements IProfileServiceBinder {
209        private MapClientService mService;
210
211        Binder(MapClientService service) {
212            if (VDBG) Log.v(TAG, "Binder()");
213            mService = service;
214        }
215
216        private MapClientService getService() {
217            if (!Utils.checkCaller()) {
218                Log.w(TAG, "MAP call not allowed for non-active user");
219                return null;
220            }
221
222            if (mService != null && mService.isAvailable()) {
223                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
224                        "Need BLUETOOTH permission");
225                return mService;
226            }
227            return null;
228        }
229
230        public boolean cleanup() {
231            mService = null;
232            return true;
233        }
234
235        public boolean isConnected(BluetoothDevice device) {
236            if (VDBG) Log.v(TAG, "isConnected()");
237            MapClientService service = getService();
238            if (service == null) return false;
239            return service.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
240        }
241
242        public boolean connect(BluetoothDevice device) {
243            if (VDBG) Log.v(TAG, "connect()");
244            MapClientService service = getService();
245            if (service == null) return false;
246            return service.connect(device);
247        }
248
249        public boolean disconnect(BluetoothDevice device) {
250            if (VDBG) Log.v(TAG, "disconnect()");
251            MapClientService service = getService();
252            if (service == null) return false;
253            return service.disconnect(device);
254        }
255
256        public List<BluetoothDevice> getConnectedDevices() {
257            if (VDBG) Log.v(TAG, "getConnectedDevices()");
258            MapClientService service = getService();
259            if (service == null) return new ArrayList<BluetoothDevice>(0);
260            return service.getConnectedDevices();
261        }
262
263        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
264            if (VDBG) Log.v(TAG, "getDevicesMatchingConnectionStates()");
265            MapClientService service = getService();
266            if (service == null) return new ArrayList<BluetoothDevice>(0);
267            return service.getDevicesMatchingConnectionStates(states);
268        }
269
270        public int getConnectionState(BluetoothDevice device) {
271            if (VDBG) Log.v(TAG, "getConnectionState()");
272            MapClientService service = getService();
273            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
274            return service.getConnectionState(device);
275        }
276
277        public boolean setPriority(BluetoothDevice device, int priority) {
278            MapClientService service = getService();
279            if (service == null) return false;
280            return service.setPriority(device, priority);
281        }
282
283        public int getPriority(BluetoothDevice device) {
284            MapClientService service = getService();
285            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
286            return service.getPriority(device);
287        }
288
289        public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
290                PendingIntent sentIntent, PendingIntent deliveredIntent) {
291            MapClientService service = getService();
292            if (service == null) return false;
293            Log.d(TAG, "Checking Permission of sendMessage");
294            mService.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS,
295                    "Need SEND_SMS permission");
296
297            return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
298        }
299
300        public boolean getUnreadMessages(BluetoothDevice device) {
301            MapClientService service = getService();
302            if (service == null) return false;
303            mService.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS,
304                    "Need READ_SMS permission");
305            return service.getUnreadMessages(device);
306        }
307    }
308}
309