1/*
2 * Copyright (C) 2011 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 android.server;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothDeviceProfileState;
22import android.bluetooth.BluetoothInputDevice;
23import android.bluetooth.BluetoothProfile;
24import android.bluetooth.BluetoothProfileState;
25import android.content.Context;
26import android.content.Intent;
27import android.os.Message;
28import android.provider.Settings;
29import android.util.Log;
30
31import java.util.ArrayList;
32import java.util.HashMap;
33import java.util.List;
34
35/**
36 * This handles all the operations on the HID profile.
37 * All functions are called by BluetoothService, as Bluetooth Service
38 * is the Service handler for the HID profile.
39 */
40final class BluetoothInputProfileHandler {
41    private static final String TAG = "BluetoothInputProfileHandler";
42    private static final boolean DBG = true;
43
44    public static BluetoothInputProfileHandler sInstance;
45    private Context mContext;
46    private BluetoothService mBluetoothService;
47    private final HashMap<BluetoothDevice, Integer> mInputDevices;
48    private final BluetoothProfileState mHidProfileState;
49
50    private BluetoothInputProfileHandler(Context context, BluetoothService service) {
51        mContext = context;
52        mBluetoothService = service;
53        mInputDevices = new HashMap<BluetoothDevice, Integer>();
54        mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
55        mHidProfileState.start();
56    }
57
58    static synchronized BluetoothInputProfileHandler getInstance(Context context,
59            BluetoothService service) {
60        if (sInstance == null) sInstance = new BluetoothInputProfileHandler(context, service);
61        return sInstance;
62    }
63
64    boolean connectInputDevice(BluetoothDevice device,
65                                            BluetoothDeviceProfileState state) {
66        String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
67        if (objectPath == null ||
68            getInputDeviceConnectionState(device) != BluetoothInputDevice.STATE_DISCONNECTED ||
69            getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_OFF) {
70            return false;
71        }
72        if (state != null) {
73            Message msg = new Message();
74            msg.arg1 = BluetoothDeviceProfileState.CONNECT_HID_OUTGOING;
75            msg.obj = state;
76            mHidProfileState.sendMessage(msg);
77            return true;
78        }
79        return false;
80    }
81
82    boolean connectInputDeviceInternal(BluetoothDevice device) {
83        String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
84        handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
85        if (!mBluetoothService.connectInputDeviceNative(objectPath)) {
86            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTED);
87            return false;
88        }
89        return true;
90    }
91
92    boolean disconnectInputDevice(BluetoothDevice device,
93                                               BluetoothDeviceProfileState state) {
94        String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
95        if (objectPath == null ||
96                getInputDeviceConnectionState(device) == BluetoothInputDevice.STATE_DISCONNECTED) {
97            return false;
98        }
99        if (state != null) {
100            Message msg = new Message();
101            msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HID_OUTGOING;
102            msg.obj = state;
103            mHidProfileState.sendMessage(msg);
104            return true;
105        }
106        return false;
107    }
108
109    boolean disconnectInputDeviceInternal(BluetoothDevice device) {
110        String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
111        handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
112        if (!mBluetoothService.disconnectInputDeviceNative(objectPath)) {
113            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTED);
114            return false;
115        }
116        return true;
117    }
118
119    int getInputDeviceConnectionState(BluetoothDevice device) {
120        if (mInputDevices.get(device) == null) {
121            return BluetoothInputDevice.STATE_DISCONNECTED;
122        }
123        return mInputDevices.get(device);
124    }
125
126    List<BluetoothDevice> getConnectedInputDevices() {
127        List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(
128            new int[] {BluetoothInputDevice.STATE_CONNECTED});
129        return devices;
130    }
131
132    List<BluetoothDevice> getInputDevicesMatchingConnectionStates(int[] states) {
133        List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(states);
134        return devices;
135    }
136
137    int getInputDevicePriority(BluetoothDevice device) {
138        return Settings.Secure.getInt(mContext.getContentResolver(),
139                Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()),
140                BluetoothInputDevice.PRIORITY_UNDEFINED);
141    }
142
143    boolean setInputDevicePriority(BluetoothDevice device, int priority) {
144        if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
145            return false;
146        }
147        return Settings.Secure.putInt(mContext.getContentResolver(),
148                Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()),
149                priority);
150    }
151
152    List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
153        List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
154
155        for (BluetoothDevice device: mInputDevices.keySet()) {
156            int inputDeviceState = getInputDeviceConnectionState(device);
157            for (int state : states) {
158                if (state == inputDeviceState) {
159                    inputDevices.add(device);
160                    break;
161                }
162            }
163        }
164        return inputDevices;
165    }
166
167    private void handleInputDeviceStateChange(BluetoothDevice device, int state) {
168        int prevState;
169        if (mInputDevices.get(device) == null) {
170            prevState = BluetoothInputDevice.STATE_DISCONNECTED;
171        } else {
172            prevState = mInputDevices.get(device);
173        }
174        if (prevState == state) return;
175
176        mInputDevices.put(device, state);
177
178        if (getInputDevicePriority(device) >
179              BluetoothInputDevice.PRIORITY_OFF &&
180            state == BluetoothInputDevice.STATE_CONNECTING ||
181            state == BluetoothInputDevice.STATE_CONNECTED) {
182            // We have connected or attempting to connect.
183            // Bump priority
184            setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_AUTO_CONNECT);
185        }
186
187        Intent intent = new Intent(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
188        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
189        intent.putExtra(BluetoothInputDevice.EXTRA_PREVIOUS_STATE, prevState);
190        intent.putExtra(BluetoothInputDevice.EXTRA_STATE, state);
191        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
192        mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
193
194        debugLog("InputDevice state : device: " + device + " State:" + prevState + "->" + state);
195        mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.INPUT_DEVICE, state,
196                                                    prevState);
197    }
198
199    void handleInputDevicePropertyChange(String address, boolean connected) {
200        int state = connected ? BluetoothInputDevice.STATE_CONNECTED :
201            BluetoothInputDevice.STATE_DISCONNECTED;
202        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
203        BluetoothDevice device = adapter.getRemoteDevice(address);
204        handleInputDeviceStateChange(device, state);
205    }
206
207    void setInitialInputDevicePriority(BluetoothDevice device, int state) {
208        switch (state) {
209            case BluetoothDevice.BOND_BONDED:
210                if (getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_UNDEFINED) {
211                    setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_ON);
212                }
213                break;
214            case BluetoothDevice.BOND_NONE:
215                setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_UNDEFINED);
216                break;
217        }
218    }
219
220    private static void debugLog(String msg) {
221        if (DBG) Log.d(TAG, msg);
222    }
223
224    private static void errorLog(String msg) {
225        Log.e(TAG, msg);
226    }
227}
228