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 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 long getDiscoveryEndMillis() {
139        return mAdapter.getDiscoveryEndMillis();
140    }
141
142    public void setName(String name) {
143        mAdapter.setName(name);
144    }
145
146    public void setScanMode(int mode) {
147        mAdapter.setScanMode(mode);
148    }
149
150    public boolean setScanMode(int mode, int duration) {
151        return mAdapter.setScanMode(mode, duration);
152    }
153
154    public void startScanning(boolean force) {
155        // Only start if we're not already scanning
156        if (!mAdapter.isDiscovering()) {
157            if (!force) {
158                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
159                // unless forced
160                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
161                    return;
162                }
163
164                // If we are playing music, don't scan unless forced.
165                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
166                if (a2dp != null && a2dp.isA2dpPlaying()) {
167                    return;
168                }
169                A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
170                if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
171                    return;
172                }
173            }
174
175            if (mAdapter.startDiscovery()) {
176                mLastScan = System.currentTimeMillis();
177            }
178        }
179    }
180
181    public void stopScanning() {
182        if (mAdapter.isDiscovering()) {
183            mAdapter.cancelDiscovery();
184        }
185    }
186
187    public synchronized int getBluetoothState() {
188        // Always sync state, in case it changed while paused
189        syncBluetoothState();
190        return mState;
191    }
192
193    synchronized void setBluetoothStateInt(int state) {
194        mState = state;
195
196        if (state == BluetoothAdapter.STATE_ON) {
197            // if mProfileManager hasn't been constructed yet, it will
198            // get the adapter UUIDs in its constructor when it is.
199            if (mProfileManager != null) {
200                mProfileManager.setBluetoothStateOn();
201            }
202        }
203    }
204
205    // Returns true if the state changed; false otherwise.
206    boolean syncBluetoothState() {
207        int currentState = mAdapter.getState();
208        if (currentState != mState) {
209            setBluetoothStateInt(mAdapter.getState());
210            return true;
211        }
212        return false;
213    }
214
215    public boolean setBluetoothEnabled(boolean enabled) {
216        boolean success = enabled
217                ? mAdapter.enable()
218                : mAdapter.disable();
219
220        if (success) {
221            setBluetoothStateInt(enabled
222                ? BluetoothAdapter.STATE_TURNING_ON
223                : BluetoothAdapter.STATE_TURNING_OFF);
224        } else {
225            if (Utils.V) {
226                Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
227                        "success for enabled: " + enabled);
228            }
229
230            syncBluetoothState();
231        }
232        return success;
233    }
234
235    public BluetoothDevice getRemoteDevice(String address) {
236        return mAdapter.getRemoteDevice(address);
237    }
238}
239