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