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.settings.bluetooth;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.os.Bundle;
22import android.os.SystemProperties;
23import android.support.annotation.VisibleForTesting;
24import android.support.v7.preference.Preference;
25import android.support.v7.preference.PreferenceCategory;
26import android.support.v7.preference.PreferenceGroup;
27import android.text.BidiFormatter;
28import android.util.Log;
29
30import com.android.settings.R;
31import com.android.settings.dashboard.RestrictedDashboardFragment;
32import com.android.settingslib.bluetooth.BluetoothCallback;
33import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
34import com.android.settingslib.bluetooth.CachedBluetoothDevice;
35import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
36import com.android.settingslib.bluetooth.LocalBluetoothManager;
37
38import java.util.Collection;
39import java.util.WeakHashMap;
40
41/**
42 * Parent class for settings fragments that contain a list of Bluetooth
43 * devices.
44 *
45 * @see DevicePickerFragment
46 */
47// TODO: Refactor this fragment
48public abstract class DeviceListPreferenceFragment extends
49        RestrictedDashboardFragment implements BluetoothCallback {
50
51    private static final String TAG = "DeviceListPreferenceFragment";
52
53    private static final String KEY_BT_SCAN = "bt_scan";
54
55    // Copied from BluetoothDeviceNoNamePreferenceController.java
56    private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
57            "persist.bluetooth.showdeviceswithoutnames";
58
59    private BluetoothDeviceFilter.Filter mFilter;
60
61    @VisibleForTesting
62    boolean mScanEnabled;
63
64    BluetoothDevice mSelectedDevice;
65
66    LocalBluetoothAdapter mLocalAdapter;
67    LocalBluetoothManager mLocalManager;
68
69    @VisibleForTesting
70    PreferenceGroup mDeviceListGroup;
71
72    final WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
73            new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>();
74
75    boolean mShowDevicesWithoutNames;
76
77    DeviceListPreferenceFragment(String restrictedKey) {
78        super(restrictedKey);
79        mFilter = BluetoothDeviceFilter.ALL_FILTER;
80    }
81
82    final void setFilter(BluetoothDeviceFilter.Filter filter) {
83        mFilter = filter;
84    }
85
86    final void setFilter(int filterType) {
87        mFilter = BluetoothDeviceFilter.getFilter(filterType);
88    }
89
90    @Override
91    public void onCreate(Bundle savedInstanceState) {
92        super.onCreate(savedInstanceState);
93
94        mLocalManager = Utils.getLocalBtManager(getActivity());
95        if (mLocalManager == null) {
96            Log.e(TAG, "Bluetooth is not supported on this device");
97            return;
98        }
99        mLocalAdapter = mLocalManager.getBluetoothAdapter();
100        mShowDevicesWithoutNames = SystemProperties.getBoolean(
101                BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
102
103        initPreferencesFromPreferenceScreen();
104
105        mDeviceListGroup = (PreferenceCategory) findPreference(getDeviceListKey());
106    }
107
108    /** find and update preference that already existed in preference screen */
109    abstract void initPreferencesFromPreferenceScreen();
110
111    @Override
112    public void onStart() {
113        super.onStart();
114        if (mLocalManager == null || isUiRestricted()) return;
115
116        mLocalManager.setForegroundActivity(getActivity());
117        mLocalManager.getEventManager().registerCallback(this);
118    }
119
120    @Override
121    public void onStop() {
122        super.onStop();
123        if (mLocalManager == null || isUiRestricted()) {
124            return;
125        }
126
127        removeAllDevices();
128        mLocalManager.setForegroundActivity(null);
129        mLocalManager.getEventManager().unregisterCallback(this);
130    }
131
132    void removeAllDevices() {
133        mDevicePreferenceMap.clear();
134        mDeviceListGroup.removeAll();
135    }
136
137    void addCachedDevices() {
138        Collection<CachedBluetoothDevice> cachedDevices =
139                mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
140        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
141            onDeviceAdded(cachedDevice);
142        }
143    }
144
145    @Override
146    public boolean onPreferenceTreeClick(Preference preference) {
147        if (KEY_BT_SCAN.equals(preference.getKey())) {
148            mLocalAdapter.startScanning(true);
149            return true;
150        }
151
152        if (preference instanceof BluetoothDevicePreference) {
153            BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
154            CachedBluetoothDevice device = btPreference.getCachedDevice();
155            mSelectedDevice = device.getDevice();
156            onDevicePreferenceClick(btPreference);
157            return true;
158        }
159
160        return super.onPreferenceTreeClick(preference);
161    }
162
163    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
164        btPreference.onClicked();
165    }
166
167    @Override
168    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
169        if (mDevicePreferenceMap.get(cachedDevice) != null) {
170            return;
171        }
172
173        // Prevent updates while the list shows one of the state messages
174        if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;
175
176        if (mFilter.matches(cachedDevice.getDevice())) {
177            createDevicePreference(cachedDevice);
178        }
179    }
180
181    void createDevicePreference(CachedBluetoothDevice cachedDevice) {
182        if (mDeviceListGroup == null) {
183            Log.w(TAG, "Trying to create a device preference before the list group/category "
184                    + "exists!");
185            return;
186        }
187
188        String key = cachedDevice.getDevice().getAddress();
189        BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key);
190
191        if (preference == null) {
192            preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice,
193                    mShowDevicesWithoutNames);
194            preference.setKey(key);
195            mDeviceListGroup.addPreference(preference);
196        } else {
197            // Tell the preference it is being re-used in case there is new info in the
198            // cached device.
199            preference.rebind();
200        }
201
202        initDevicePreference(preference);
203        mDevicePreferenceMap.put(cachedDevice, preference);
204    }
205
206    void initDevicePreference(BluetoothDevicePreference preference) {
207        // Does nothing by default
208    }
209
210    @VisibleForTesting
211    void updateFooterPreference(Preference myDevicePreference) {
212        final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
213
214        myDevicePreference.setTitle(getString(
215                R.string.bluetooth_footer_mac_message,
216                bidiFormatter.unicodeWrap(mLocalAdapter.getAddress())));
217    }
218
219    @Override
220    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
221        BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
222        if (preference != null) {
223            mDeviceListGroup.removePreference(preference);
224        }
225    }
226
227    @VisibleForTesting
228    void enableScanning() {
229        // LocalBluetoothAdapter already handles repeated scan requests
230        mLocalAdapter.startScanning(true);
231        mScanEnabled = true;
232    }
233
234    @VisibleForTesting
235    void disableScanning() {
236        mLocalAdapter.stopScanning();
237        mScanEnabled = false;
238    }
239
240    @Override
241    public void onScanningStateChanged(boolean started) {
242        if (!started && mScanEnabled) {
243            mLocalAdapter.startScanning(true);
244        }
245    }
246
247    @Override
248    public void onBluetoothStateChanged(int bluetoothState) {}
249
250    /**
251     * Add bluetooth device preferences to {@code preferenceGroup} which satisfy the {@code filter}
252     *
253     * This method will also (1) set the title for {@code preferenceGroup} and (2) change the
254     * default preferenceGroup and filter
255     * @param preferenceGroup
256     * @param titleId
257     * @param filter
258     * @param addCachedDevices
259     */
260    public void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
261            BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) {
262        cacheRemoveAllPrefs(preferenceGroup);
263        preferenceGroup.setTitle(titleId);
264        mDeviceListGroup = preferenceGroup;
265        setFilter(filter);
266        if (addCachedDevices) {
267            addCachedDevices();
268        }
269        preferenceGroup.setEnabled(true);
270        removeCachedPrefs(preferenceGroup);
271    }
272
273    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { }
274
275    @Override
276    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { }
277
278    @Override
279    public void onAudioModeChanged() { }
280
281    /**
282     * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices
283     */
284    public abstract String getDeviceListKey();
285
286    public boolean shouldShowDevicesWithoutNames() {
287        return mShowDevicesWithoutNames;
288    }
289}
290