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