1/*
2 * Copyright (C) 2014 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.systemui.qs.tiles;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothProfile;
22import android.content.Context;
23import android.content.Intent;
24import android.provider.Settings;
25import android.service.quicksettings.Tile;
26import android.text.TextUtils;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.Switch;
30
31import com.android.internal.logging.MetricsLogger;
32import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
33import com.android.settingslib.bluetooth.CachedBluetoothDevice;
34import com.android.systemui.Dependency;
35import com.android.systemui.R;
36import com.android.systemui.plugins.ActivityStarter;
37import com.android.systemui.plugins.qs.DetailAdapter;
38import com.android.systemui.plugins.qs.QSTile.BooleanState;
39import com.android.systemui.qs.QSDetailItems;
40import com.android.systemui.qs.QSDetailItems.Item;
41import com.android.systemui.qs.QSHost;
42import com.android.systemui.qs.tileimpl.QSTileImpl;
43import com.android.systemui.statusbar.policy.BluetoothController;
44
45import java.util.ArrayList;
46import java.util.Collection;
47
48/** Quick settings tile: Bluetooth **/
49public class BluetoothTile extends QSTileImpl<BooleanState> {
50    private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
51
52    private final BluetoothController mController;
53    private final BluetoothDetailAdapter mDetailAdapter;
54    private final ActivityStarter mActivityStarter;
55
56    public BluetoothTile(QSHost host) {
57        super(host);
58        mController = Dependency.get(BluetoothController.class);
59        mActivityStarter = Dependency.get(ActivityStarter.class);
60        mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter();
61    }
62
63    @Override
64    public DetailAdapter getDetailAdapter() {
65        return mDetailAdapter;
66    }
67
68    @Override
69    public BooleanState newTileState() {
70        return new BooleanState();
71    }
72
73    @Override
74    public void setListening(boolean listening) {
75        if (listening) {
76            mController.addCallback(mCallback);
77        } else {
78            mController.removeCallback(mCallback);
79        }
80    }
81
82    @Override
83    protected void handleClick() {
84        // Secondary clicks are header clicks, just toggle.
85        final boolean isEnabled = (Boolean)mState.value;
86        mController.setBluetoothEnabled(!isEnabled);
87    }
88
89    @Override
90    public Intent getLongClickIntent() {
91        return new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
92    }
93
94    @Override
95    protected void handleSecondaryClick() {
96        if (!mController.canConfigBluetooth()) {
97            mActivityStarter.postStartActivityDismissingKeyguard(
98                    new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0);
99            return;
100        }
101        showDetail(true);
102    }
103
104    @Override
105    public CharSequence getTileLabel() {
106        return mContext.getString(R.string.quick_settings_bluetooth_label);
107    }
108
109    @Override
110    protected void handleUpdateState(BooleanState state, Object arg) {
111        final boolean enabled = mController.isBluetoothEnabled();
112        final boolean connected = mController.isBluetoothConnected();
113        state.isTransient = mController.isBluetoothConnecting()
114                || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
115        state.dualTarget = true;
116        state.value = enabled;
117        if (enabled) {
118            state.label = null;
119            if (connected) {
120                state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);
121                state.label = mController.getLastDeviceName();
122                state.contentDescription = mContext.getString(
123                        R.string.accessibility_bluetooth_name, state.label);
124            } else if (state.isTransient) {
125                state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation);
126                state.contentDescription = mContext.getString(
127                        R.string.accessibility_quick_settings_bluetooth_connecting);
128                state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
129            } else {
130                state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
131                state.contentDescription = mContext.getString(
132                        R.string.accessibility_quick_settings_bluetooth_on) + ","
133                        + mContext.getString(R.string.accessibility_not_connected);
134            }
135            if (TextUtils.isEmpty(state.label)) {
136                state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
137            }
138            state.state = Tile.STATE_ACTIVE;
139        } else {
140            state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_off);
141            state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
142            state.contentDescription = mContext.getString(
143                    R.string.accessibility_quick_settings_bluetooth_off);
144            state.state = Tile.STATE_INACTIVE;
145        }
146
147        state.dualLabelContentDescription = mContext.getResources().getString(
148                R.string.accessibility_quick_settings_open_settings, getTileLabel());
149        state.expandedAccessibilityClassName = Switch.class.getName();
150    }
151
152    @Override
153    public int getMetricsCategory() {
154        return MetricsEvent.QS_BLUETOOTH;
155    }
156
157    @Override
158    protected String composeChangeAnnouncement() {
159        if (mState.value) {
160            return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_on);
161        } else {
162            return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_off);
163        }
164    }
165
166    @Override
167    public boolean isAvailable() {
168        return mController.isBluetoothSupported();
169    }
170
171    private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
172        @Override
173        public void onBluetoothStateChange(boolean enabled) {
174            refreshState();
175            if (isShowingDetail()) {
176                mDetailAdapter.updateItems();
177            }
178        }
179
180        @Override
181        public void onBluetoothDevicesChanged() {
182            refreshState();
183            if (isShowingDetail()) {
184                mDetailAdapter.updateItems();
185            }
186        }
187    };
188
189    @Override
190    protected DetailAdapter createDetailAdapter() {
191        return new BluetoothDetailAdapter();
192    }
193
194    protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
195        // We probably won't ever have space in the UI for more than 20 devices, so don't
196        // get info for them.
197        private static final int MAX_DEVICES = 20;
198        private QSDetailItems mItems;
199
200        @Override
201        public CharSequence getTitle() {
202            return mContext.getString(R.string.quick_settings_bluetooth_label);
203        }
204
205        @Override
206        public Boolean getToggleState() {
207            return mState.value;
208        }
209
210        @Override
211        public boolean getToggleEnabled() {
212            return mController.getBluetoothState() == BluetoothAdapter.STATE_OFF
213                    || mController.getBluetoothState() == BluetoothAdapter.STATE_ON;
214        }
215
216        @Override
217        public Intent getSettingsIntent() {
218            return BLUETOOTH_SETTINGS;
219        }
220
221        @Override
222        public void setToggleState(boolean state) {
223            MetricsLogger.action(mContext, MetricsEvent.QS_BLUETOOTH_TOGGLE, state);
224            mController.setBluetoothEnabled(state);
225        }
226
227        @Override
228        public int getMetricsCategory() {
229            return MetricsEvent.QS_BLUETOOTH_DETAILS;
230        }
231
232        @Override
233        public View createDetailView(Context context, View convertView, ViewGroup parent) {
234            mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
235            mItems.setTagSuffix("Bluetooth");
236            mItems.setCallback(this);
237            updateItems();
238            setItemsVisible(mState.value);
239            return mItems;
240        }
241
242        public void setItemsVisible(boolean visible) {
243            if (mItems == null) return;
244            mItems.setItemsVisible(visible);
245        }
246
247        private void updateItems() {
248            if (mItems == null) return;
249            if (mController.isBluetoothEnabled()) {
250                mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
251                        R.string.quick_settings_bluetooth_detail_empty_text);
252            } else {
253                mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
254                        R.string.bt_is_off);
255            }
256            ArrayList<Item> items = new ArrayList<Item>();
257            final Collection<CachedBluetoothDevice> devices = mController.getDevices();
258            if (devices != null) {
259                int connectedDevices = 0;
260                int count = 0;
261                for (CachedBluetoothDevice device : devices) {
262                    if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
263                    final Item item = new Item();
264                    item.icon = R.drawable.ic_qs_bluetooth_on;
265                    item.line1 = device.getName();
266                    item.tag = device;
267                    int state = mController.getMaxConnectionState(device);
268                    if (state == BluetoothProfile.STATE_CONNECTED) {
269                        item.icon = R.drawable.ic_qs_bluetooth_connected;
270                        item.line2 = mContext.getString(R.string.quick_settings_connected);
271                        item.canDisconnect = true;
272                        items.add(connectedDevices, item);
273                        connectedDevices++;
274                    } else if (state == BluetoothProfile.STATE_CONNECTING) {
275                        item.icon = R.drawable.ic_qs_bluetooth_connecting;
276                        item.line2 = mContext.getString(R.string.quick_settings_connecting);
277                        items.add(connectedDevices, item);
278                    } else {
279                        items.add(item);
280                    }
281                    if (++count == MAX_DEVICES) {
282                        break;
283                    }
284                }
285            }
286            mItems.setItems(items.toArray(new Item[items.size()]));
287        }
288
289        @Override
290        public void onDetailItemClick(Item item) {
291            if (item == null || item.tag == null) return;
292            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
293            if (device != null && device.getMaxConnectionState()
294                    == BluetoothProfile.STATE_DISCONNECTED) {
295                mController.connect(device);
296            }
297        }
298
299        @Override
300        public void onDetailItemDisconnect(Item item) {
301            if (item == null || item.tag == null) return;
302            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
303            if (device != null) {
304                mController.disconnect(device);
305            }
306        }
307    }
308}
309