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 com.android.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothProfile;
22import android.bluetooth.le.BluetoothLeScanner;
23import android.content.Context;
24import android.os.ParcelUuid;
25import android.util.Log;
26
27import java.util.Set;
28
29/**
30 * LocalBluetoothAdapter provides an interface between the Settings app
31 * and the functionality of the local {@link BluetoothAdapter}, specifically
32 * those related to state transitions of the adapter itself.
33 *
34 * <p>Connection and bonding state changes affecting specific devices
35 * are handled by {@link CachedBluetoothDeviceManager},
36 * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}.
37 */
38public final class LocalBluetoothAdapter {
39    private static final String TAG = "LocalBluetoothAdapter";
40
41    /** This class does not allow direct access to the BluetoothAdapter. */
42    private final BluetoothAdapter mAdapter;
43
44    private LocalBluetoothProfileManager mProfileManager;
45
46    private static LocalBluetoothAdapter sInstance;
47
48    private int mState = BluetoothAdapter.ERROR;
49
50    private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
51
52    private long mLastScan;
53
54    private LocalBluetoothAdapter(BluetoothAdapter adapter) {
55        mAdapter = adapter;
56    }
57
58    void setProfileManager(LocalBluetoothProfileManager manager) {
59        mProfileManager = manager;
60    }
61
62    /**
63     * Get the singleton instance of the LocalBluetoothAdapter. If this device
64     * doesn't support Bluetooth, then null will be returned. Callers must be
65     * prepared to handle a null return value.
66     * @return the LocalBluetoothAdapter object, or null if not supported
67     */
68    static synchronized LocalBluetoothAdapter getInstance() {
69        if (sInstance == null) {
70            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
71            if (adapter != null) {
72                sInstance = new LocalBluetoothAdapter(adapter);
73            }
74        }
75
76        return sInstance;
77    }
78
79    // Pass-through BluetoothAdapter methods that we can intercept if necessary
80
81    public void cancelDiscovery() {
82        mAdapter.cancelDiscovery();
83    }
84
85    public boolean enable() {
86        return mAdapter.enable();
87    }
88
89    public boolean disable() {
90        return mAdapter.disable();
91    }
92
93    void getProfileProxy(Context context,
94            BluetoothProfile.ServiceListener listener, int profile) {
95        mAdapter.getProfileProxy(context, listener, profile);
96    }
97
98    public Set<BluetoothDevice> getBondedDevices() {
99        return mAdapter.getBondedDevices();
100    }
101
102    public String getName() {
103        return mAdapter.getName();
104    }
105
106    public int getScanMode() {
107        return mAdapter.getScanMode();
108    }
109
110    public BluetoothLeScanner getBluetoothLeScanner() {
111        return mAdapter.getBluetoothLeScanner();
112    }
113
114    public int getState() {
115        return mAdapter.getState();
116    }
117
118    public ParcelUuid[] getUuids() {
119        return mAdapter.getUuids();
120    }
121
122    public boolean isDiscovering() {
123        return mAdapter.isDiscovering();
124    }
125
126    public boolean isEnabled() {
127        return mAdapter.isEnabled();
128    }
129
130    public int getConnectionState() {
131        return mAdapter.getConnectionState();
132    }
133
134    public void setDiscoverableTimeout(int timeout) {
135        mAdapter.setDiscoverableTimeout(timeout);
136    }
137
138    public void setName(String name) {
139        mAdapter.setName(name);
140    }
141
142    public void setScanMode(int mode) {
143        mAdapter.setScanMode(mode);
144    }
145
146    public boolean setScanMode(int mode, int duration) {
147        return mAdapter.setScanMode(mode, duration);
148    }
149
150    public void startScanning(boolean force) {
151        // Only start if we're not already scanning
152        if (!mAdapter.isDiscovering()) {
153            if (!force) {
154                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
155                // unless forced
156                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
157                    return;
158                }
159
160                // If we are playing music, don't scan unless forced.
161                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
162                if (a2dp != null && a2dp.isA2dpPlaying()) {
163                    return;
164                }
165                A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
166                if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
167                    return;
168                }
169            }
170
171            if (mAdapter.startDiscovery()) {
172                mLastScan = System.currentTimeMillis();
173            }
174        }
175    }
176
177    public void stopScanning() {
178        if (mAdapter.isDiscovering()) {
179            mAdapter.cancelDiscovery();
180        }
181    }
182
183    public synchronized int getBluetoothState() {
184        // Always sync state, in case it changed while paused
185        syncBluetoothState();
186        return mState;
187    }
188
189    synchronized void setBluetoothStateInt(int state) {
190        mState = state;
191
192        if (state == BluetoothAdapter.STATE_ON) {
193            // if mProfileManager hasn't been constructed yet, it will
194            // get the adapter UUIDs in its constructor when it is.
195            if (mProfileManager != null) {
196                mProfileManager.setBluetoothStateOn();
197            }
198        }
199    }
200
201    // Returns true if the state changed; false otherwise.
202    boolean syncBluetoothState() {
203        int currentState = mAdapter.getState();
204        if (currentState != mState) {
205            setBluetoothStateInt(mAdapter.getState());
206            return true;
207        }
208        return false;
209    }
210
211    public boolean setBluetoothEnabled(boolean enabled) {
212        boolean success = enabled
213                ? mAdapter.enable()
214                : mAdapter.disable();
215
216        if (success) {
217            setBluetoothStateInt(enabled
218                ? BluetoothAdapter.STATE_TURNING_ON
219                : BluetoothAdapter.STATE_TURNING_OFF);
220        } else {
221            if (Utils.V) {
222                Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
223                        "success for enabled: " + enabled);
224            }
225
226            syncBluetoothState();
227        }
228        return success;
229    }
230
231    public BluetoothDevice getRemoteDevice(String address) {
232        return mAdapter.getRemoteDevice(address);
233    }
234}
235