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