blob: 36677a46ed9f9f52c286605aa5d78e468fa03ad6 [file] [log] [blame]
John Spurlockaf8d6c42014-05-07 17:49:08 -04001/*
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
Jason Monkfac25382016-07-19 14:13:37 -040019import android.bluetooth.BluetoothAdapter;
Jason Monkbe3c5db2015-02-04 13:00:55 -050020import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothProfile;
John Spurlock486b78e2014-07-07 08:37:56 -040022import android.content.Context;
John Spurlockaf8d6c42014-05-07 17:49:08 -040023import android.content.Intent;
24import android.provider.Settings;
Jason Monk32508852017-01-18 09:17:13 -050025import android.service.quicksettings.Tile;
John Spurlockd1c86e22014-06-01 00:04:53 -040026import android.text.TextUtils;
John Spurlock486b78e2014-07-07 08:37:56 -040027import android.view.View;
28import android.view.ViewGroup;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040029import android.widget.Switch;
Chris Wrenf6e9228b2016-01-26 18:04:35 -050030
Chris Wren457a21c2015-05-06 17:50:34 -040031import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010032import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Jason Monkbe3c5db2015-02-04 13:00:55 -050033import com.android.settingslib.bluetooth.CachedBluetoothDevice;
Jason Monk9c7844c2017-01-18 15:21:53 -050034import com.android.systemui.Dependency;
John Spurlockaf8d6c42014-05-07 17:49:08 -040035import com.android.systemui.R;
Jason Monkec34da82017-02-24 15:57:05 -050036import com.android.systemui.plugins.ActivityStarter;
Jason Monke5b770e2017-03-03 21:49:29 -050037import com.android.systemui.plugins.qs.DetailAdapter;
Jason Monkbe3235a2017-04-05 09:29:53 -040038import com.android.systemui.plugins.qs.QSTile.BooleanState;
John Spurlock486b78e2014-07-07 08:37:56 -040039import com.android.systemui.qs.QSDetailItems;
40import com.android.systemui.qs.QSDetailItems.Item;
Jason Monk702e2eb2017-03-03 16:53:44 -050041import com.android.systemui.qs.QSHost;
Jason Monk702e2eb2017-03-03 16:53:44 -050042import com.android.systemui.qs.tileimpl.QSTileImpl;
John Spurlockaf8d6c42014-05-07 17:49:08 -040043import com.android.systemui.statusbar.policy.BluetoothController;
John Spurlock486b78e2014-07-07 08:37:56 -040044
Jason Monkc17d3292016-01-30 12:49:41 -050045import java.util.ArrayList;
Jason Monkbe3c5db2015-02-04 13:00:55 -050046import java.util.Collection;
John Spurlockaf8d6c42014-05-07 17:49:08 -040047
48/** Quick settings tile: Bluetooth **/
Jason Monk702e2eb2017-03-03 16:53:44 -050049public class BluetoothTile extends QSTileImpl<BooleanState> {
John Spurlockaf8d6c42014-05-07 17:49:08 -040050 private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
51
52 private final BluetoothController mController;
John Spurlock486b78e2014-07-07 08:37:56 -040053 private final BluetoothDetailAdapter mDetailAdapter;
Jason Monk9c7844c2017-01-18 15:21:53 -050054 private final ActivityStarter mActivityStarter;
John Spurlockaf8d6c42014-05-07 17:49:08 -040055
Jason Monk702e2eb2017-03-03 16:53:44 -050056 public BluetoothTile(QSHost host) {
John Spurlockaf8d6c42014-05-07 17:49:08 -040057 super(host);
Jason Monk9c7844c2017-01-18 15:21:53 -050058 mController = Dependency.get(BluetoothController.class);
59 mActivityStarter = Dependency.get(ActivityStarter.class);
Muyuan Li0e9f5382016-04-27 15:51:15 -070060 mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter();
John Spurlockccb6b9a2014-05-17 15:54:40 -040061 }
62
63 @Override
John Spurlock486b78e2014-07-07 08:37:56 -040064 public DetailAdapter getDetailAdapter() {
65 return mDetailAdapter;
66 }
67
68 @Override
Jason Monk62b63a02016-02-02 15:15:31 -050069 public BooleanState newTileState() {
John Spurlockaf8d6c42014-05-07 17:49:08 -040070 return new BooleanState();
71 }
72
73 @Override
John Spurlockccb6b9a2014-05-17 15:54:40 -040074 public void setListening(boolean listening) {
75 if (listening) {
Jason Monk88529052016-11-04 13:29:58 -040076 mController.addCallback(mCallback);
John Spurlockccb6b9a2014-05-17 15:54:40 -040077 } else {
Jason Monk88529052016-11-04 13:29:58 -040078 mController.removeCallback(mCallback);
John Spurlockccb6b9a2014-05-17 15:54:40 -040079 }
John Spurlockaf8d6c42014-05-07 17:49:08 -040080 }
81
82 @Override
Jason Monk32508852017-01-18 09:17:13 -050083 protected void handleClick() {
Jason Monkdc35dcb2015-12-04 16:36:15 -050084 // Secondary clicks are header clicks, just toggle.
John Spurlockaf8d6c42014-05-07 17:49:08 -040085 final boolean isEnabled = (Boolean)mState.value;
86 mController.setBluetoothEnabled(!isEnabled);
87 }
88
89 @Override
Jason Monk76c67aa2016-02-19 14:49:42 -050090 public Intent getLongClickIntent() {
91 return new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
92 }
93
94 @Override
Jason Monk32508852017-01-18 09:17:13 -050095 protected void handleSecondaryClick() {
Sudheer Shankab6fc9312016-01-27 19:59:03 +000096 if (!mController.canConfigBluetooth()) {
Jason Monk9c7844c2017-01-18 15:21:53 -050097 mActivityStarter.postStartActivityDismissingKeyguard(
98 new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0);
Sudheer Shankab6fc9312016-01-27 19:59:03 +000099 return;
100 }
Jason Monk48e093e2016-07-15 10:37:07 -0400101 showDetail(true);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400102 }
103
104 @Override
Jason Monk39c98e62016-03-16 09:18:35 -0400105 public CharSequence getTileLabel() {
106 return mContext.getString(R.string.quick_settings_bluetooth_label);
107 }
108
109 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400110 protected void handleUpdateState(BooleanState state, Object arg) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400111 final boolean enabled = mController.isBluetoothEnabled();
112 final boolean connected = mController.isBluetoothConnected();
Jason Monk110e5f62017-03-31 13:45:12 -0400113 state.isTransient = mController.isBluetoothConnecting()
114 || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
Jason Monk2db46192017-02-01 17:18:22 -0500115 state.dualTarget = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400116 state.value = enabled;
Jason Monk395617f2017-05-05 14:07:58 -0400117 if (state.slash == null) {
118 state.slash = new SlashState();
119 }
120 state.slash.isSlashed = !enabled;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400121 if (enabled) {
John Spurlockd1c86e22014-06-01 00:04:53 -0400122 state.label = null;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400123 if (connected) {
John Spurlock2d695812014-10-30 13:25:21 -0400124 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);
John Spurlockd1c86e22014-06-01 00:04:53 -0400125 state.label = mController.getLastDeviceName();
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400126 state.contentDescription = mContext.getString(
127 R.string.accessibility_bluetooth_name, state.label);
Jason Monk110e5f62017-03-31 13:45:12 -0400128 } else if (state.isTransient) {
129 state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200130 state.contentDescription = mContext.getString(
131 R.string.accessibility_quick_settings_bluetooth_connecting);
John Spurlocka625b742014-06-05 09:55:37 -0400132 state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400133 } else {
John Spurlock2d695812014-10-30 13:25:21 -0400134 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200135 state.contentDescription = mContext.getString(
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400136 R.string.accessibility_quick_settings_bluetooth_on) + ","
137 + mContext.getString(R.string.accessibility_not_connected);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400138 }
John Spurlockd1c86e22014-06-01 00:04:53 -0400139 if (TextUtils.isEmpty(state.label)) {
140 state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
141 }
Jason Monk32508852017-01-18 09:17:13 -0500142 state.state = Tile.STATE_ACTIVE;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400143 } else {
Jason Monk395617f2017-05-05 14:07:58 -0400144 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
John Spurlock012d4a22014-06-04 00:40:18 -0400145 state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200146 state.contentDescription = mContext.getString(
147 R.string.accessibility_quick_settings_bluetooth_off);
Jason Monk32508852017-01-18 09:17:13 -0500148 state.state = Tile.STATE_INACTIVE;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400149 }
Selim Cinek4fda7b22014-08-18 22:07:25 +0200150
Jason Monk28df4ed2017-03-23 10:25:37 -0400151 state.dualLabelContentDescription = mContext.getResources().getString(
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400152 R.string.accessibility_quick_settings_open_settings, getTileLabel());
Jason Monk702e2eb2017-03-03 16:53:44 -0500153 state.expandedAccessibilityClassName = Switch.class.getName();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400154 }
155
Selim Cinek4fda7b22014-08-18 22:07:25 +0200156 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400157 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500158 return MetricsEvent.QS_BLUETOOTH;
Chris Wren457a21c2015-05-06 17:50:34 -0400159 }
160
161 @Override
Selim Cinek4fda7b22014-08-18 22:07:25 +0200162 protected String composeChangeAnnouncement() {
163 if (mState.value) {
164 return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_on);
165 } else {
166 return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_off);
167 }
168 }
169
Jason Monkc3f42c12016-02-05 12:33:13 -0500170 @Override
171 public boolean isAvailable() {
172 return mController.isBluetoothSupported();
Jason Monkba2318e2015-12-08 09:04:23 -0500173 }
174
John Spurlockd1c86e22014-06-01 00:04:53 -0400175 private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400176 @Override
Jason Monka7d92b62015-05-27 10:20:37 -0400177 public void onBluetoothStateChange(boolean enabled) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400178 refreshState();
Jason Monkbe3235a2017-04-05 09:29:53 -0400179 if (isShowingDetail()) {
180 mDetailAdapter.updateItems();
181 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400182 }
Jason Monka7d92b62015-05-27 10:20:37 -0400183
John Spurlock486b78e2014-07-07 08:37:56 -0400184 @Override
Jason Monkbe3c5db2015-02-04 13:00:55 -0500185 public void onBluetoothDevicesChanged() {
PauloftheWest6cdeaa02014-10-06 09:27:08 -0700186 refreshState();
Jason Monkbe3235a2017-04-05 09:29:53 -0400187 if (isShowingDetail()) {
188 mDetailAdapter.updateItems();
189 }
John Spurlock486b78e2014-07-07 08:37:56 -0400190 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400191 };
John Spurlock486b78e2014-07-07 08:37:56 -0400192
Muyuan Li0e9f5382016-04-27 15:51:15 -0700193 @Override
194 protected DetailAdapter createDetailAdapter() {
195 return new BluetoothDetailAdapter();
196 }
197
198 protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
Jason Monk9ef03b42017-06-13 12:49:55 -0400199 // We probably won't ever have space in the UI for more than 20 devices, so don't
200 // get info for them.
201 private static final int MAX_DEVICES = 20;
John Spurlock486b78e2014-07-07 08:37:56 -0400202 private QSDetailItems mItems;
203
204 @Override
Jason Monkc06fbb12016-01-08 14:12:18 -0500205 public CharSequence getTitle() {
206 return mContext.getString(R.string.quick_settings_bluetooth_label);
John Spurlock486b78e2014-07-07 08:37:56 -0400207 }
208
209 @Override
210 public Boolean getToggleState() {
211 return mState.value;
212 }
213
214 @Override
Jason Monkfac25382016-07-19 14:13:37 -0400215 public boolean getToggleEnabled() {
216 return mController.getBluetoothState() == BluetoothAdapter.STATE_OFF
217 || mController.getBluetoothState() == BluetoothAdapter.STATE_ON;
218 }
219
220 @Override
John Spurlock486b78e2014-07-07 08:37:56 -0400221 public Intent getSettingsIntent() {
222 return BLUETOOTH_SETTINGS;
223 }
224
225 @Override
226 public void setToggleState(boolean state) {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500227 MetricsLogger.action(mContext, MetricsEvent.QS_BLUETOOTH_TOGGLE, state);
John Spurlock486b78e2014-07-07 08:37:56 -0400228 mController.setBluetoothEnabled(state);
John Spurlock486b78e2014-07-07 08:37:56 -0400229 }
230
231 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400232 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500233 return MetricsEvent.QS_BLUETOOTH_DETAILS;
Chris Wren457a21c2015-05-06 17:50:34 -0400234 }
235
236 @Override
John Spurlock486b78e2014-07-07 08:37:56 -0400237 public View createDetailView(Context context, View convertView, ViewGroup parent) {
238 mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
239 mItems.setTagSuffix("Bluetooth");
John Spurlock486b78e2014-07-07 08:37:56 -0400240 mItems.setCallback(this);
241 updateItems();
242 setItemsVisible(mState.value);
243 return mItems;
244 }
245
246 public void setItemsVisible(boolean visible) {
247 if (mItems == null) return;
248 mItems.setItemsVisible(visible);
249 }
250
251 private void updateItems() {
252 if (mItems == null) return;
Jason Monkbe3235a2017-04-05 09:29:53 -0400253 if (mController.isBluetoothEnabled()) {
254 mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
255 R.string.quick_settings_bluetooth_detail_empty_text);
256 } else {
257 mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
258 R.string.bt_is_off);
259 }
Jason Monkc17d3292016-01-30 12:49:41 -0500260 ArrayList<Item> items = new ArrayList<Item>();
Jason Monkbe3c5db2015-02-04 13:00:55 -0500261 final Collection<CachedBluetoothDevice> devices = mController.getDevices();
John Spurlock486b78e2014-07-07 08:37:56 -0400262 if (devices != null) {
tturney6f991c22017-04-18 13:59:40 -0700263 int connectedDevices = 0;
Jason Monk9ef03b42017-06-13 12:49:55 -0400264 int count = 0;
Jason Monkbe3c5db2015-02-04 13:00:55 -0500265 for (CachedBluetoothDevice device : devices) {
Jason Monk9ef03b42017-06-13 12:49:55 -0400266 if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
John Spurlock486b78e2014-07-07 08:37:56 -0400267 final Item item = new Item();
268 item.icon = R.drawable.ic_qs_bluetooth_on;
Jason Monkbe3c5db2015-02-04 13:00:55 -0500269 item.line1 = device.getName();
tturney6f991c22017-04-18 13:59:40 -0700270 item.tag = device;
Jason Monk9ef03b42017-06-13 12:49:55 -0400271 int state = mController.getMaxConnectionState(device);
Jason Monkbe3c5db2015-02-04 13:00:55 -0500272 if (state == BluetoothProfile.STATE_CONNECTED) {
John Spurlock486b78e2014-07-07 08:37:56 -0400273 item.icon = R.drawable.ic_qs_bluetooth_connected;
274 item.line2 = mContext.getString(R.string.quick_settings_connected);
275 item.canDisconnect = true;
tturney6f991c22017-04-18 13:59:40 -0700276 items.add(connectedDevices, item);
277 connectedDevices++;
Jason Monkbe3c5db2015-02-04 13:00:55 -0500278 } else if (state == BluetoothProfile.STATE_CONNECTING) {
John Spurlock486b78e2014-07-07 08:37:56 -0400279 item.icon = R.drawable.ic_qs_bluetooth_connecting;
280 item.line2 = mContext.getString(R.string.quick_settings_connecting);
tturney6f991c22017-04-18 13:59:40 -0700281 items.add(connectedDevices, item);
282 } else {
283 items.add(item);
John Spurlock486b78e2014-07-07 08:37:56 -0400284 }
Jason Monk9ef03b42017-06-13 12:49:55 -0400285 if (++count == MAX_DEVICES) {
286 break;
287 }
John Spurlock486b78e2014-07-07 08:37:56 -0400288 }
289 }
Jason Monkc17d3292016-01-30 12:49:41 -0500290 mItems.setItems(items.toArray(new Item[items.size()]));
Jason Monkbe3c5db2015-02-04 13:00:55 -0500291 }
292
John Spurlock486b78e2014-07-07 08:37:56 -0400293 @Override
294 public void onDetailItemClick(Item item) {
295 if (item == null || item.tag == null) return;
Jason Monkbe3c5db2015-02-04 13:00:55 -0500296 final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
297 if (device != null && device.getMaxConnectionState()
298 == BluetoothProfile.STATE_DISCONNECTED) {
John Spurlock486b78e2014-07-07 08:37:56 -0400299 mController.connect(device);
300 }
301 }
302
303 @Override
304 public void onDetailItemDisconnect(Item item) {
305 if (item == null || item.tag == null) return;
Jason Monkbe3c5db2015-02-04 13:00:55 -0500306 final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
John Spurlock486b78e2014-07-07 08:37:56 -0400307 if (device != null) {
308 mController.disconnect(device);
309 }
310 }
311 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400312}