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 android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
     20 
     21 import android.app.Dialog;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.provider.Settings;
     25 import android.service.quicksettings.Tile;
     26 import android.util.Log;
     27 import android.view.View;
     28 import android.view.View.OnAttachStateChangeListener;
     29 import android.view.ViewGroup;
     30 import android.view.WindowManager.LayoutParams;
     31 import android.widget.Button;
     32 
     33 import com.android.internal.app.MediaRouteDialogPresenter;
     34 import com.android.internal.logging.MetricsLogger;
     35 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     36 import com.android.systemui.Dependency;
     37 import com.android.systemui.R;
     38 import com.android.systemui.plugins.ActivityStarter;
     39 import com.android.systemui.plugins.qs.DetailAdapter;
     40 import com.android.systemui.plugins.qs.QSTile.BooleanState;
     41 import com.android.systemui.qs.QSDetailItems;
     42 import com.android.systemui.qs.QSDetailItems.Item;
     43 import com.android.systemui.qs.QSHost;
     44 import com.android.systemui.qs.tileimpl.QSTileImpl;
     45 import com.android.systemui.statusbar.phone.SystemUIDialog;
     46 import com.android.systemui.statusbar.policy.CastController;
     47 import com.android.systemui.statusbar.policy.CastController.CastDevice;
     48 import com.android.systemui.statusbar.policy.KeyguardMonitor;
     49 
     50 import java.util.LinkedHashMap;
     51 import java.util.Set;
     52 
     53 /** Quick settings tile: Cast **/
     54 public class CastTile extends QSTileImpl<BooleanState> {
     55     private static final Intent CAST_SETTINGS =
     56             new Intent(Settings.ACTION_CAST_SETTINGS);
     57 
     58     private final CastController mController;
     59     private final CastDetailAdapter mDetailAdapter;
     60     private final KeyguardMonitor mKeyguard;
     61     private final Callback mCallback = new Callback();
     62     private final ActivityStarter mActivityStarter;
     63     private Dialog mDialog;
     64     private boolean mRegistered;
     65 
     66     public CastTile(QSHost host) {
     67         super(host);
     68         mController = Dependency.get(CastController.class);
     69         mDetailAdapter = new CastDetailAdapter();
     70         mKeyguard = Dependency.get(KeyguardMonitor.class);
     71         mActivityStarter = Dependency.get(ActivityStarter.class);
     72     }
     73 
     74     @Override
     75     public DetailAdapter getDetailAdapter() {
     76         return mDetailAdapter;
     77     }
     78 
     79     @Override
     80     public BooleanState newTileState() {
     81         return new BooleanState();
     82     }
     83 
     84     @Override
     85     public void handleSetListening(boolean listening) {
     86         if (DEBUG) Log.d(TAG, "handleSetListening " + listening);
     87         if (listening) {
     88             mController.addCallback(mCallback);
     89             mKeyguard.addCallback(mCallback);
     90         } else {
     91             mController.setDiscovering(false);
     92             mController.removeCallback(mCallback);
     93             mKeyguard.removeCallback(mCallback);
     94         }
     95     }
     96 
     97     @Override
     98     protected void handleUserSwitch(int newUserId) {
     99         super.handleUserSwitch(newUserId);
    100         mController.setCurrentUserId(newUserId);
    101     }
    102 
    103     @Override
    104     public Intent getLongClickIntent() {
    105         return new Intent(Settings.ACTION_CAST_SETTINGS);
    106     }
    107 
    108     @Override
    109     protected void handleSecondaryClick() {
    110         handleClick();
    111     }
    112 
    113     @Override
    114     protected void handleClick() {
    115         if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
    116             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
    117                 showDetail(true);
    118             });
    119             return;
    120         }
    121         showDetail(true);
    122     }
    123 
    124     @Override
    125     public void showDetail(boolean show) {
    126         mUiHandler.post(() -> {
    127             mDialog = MediaRouteDialogPresenter.createDialog(mContext, ROUTE_TYPE_REMOTE_DISPLAY,
    128                     v -> {
    129                         mDialog.dismiss();
    130                         Dependency.get(ActivityStarter.class)
    131                                 .postStartActivityDismissingKeyguard(getLongClickIntent(), 0);
    132                     });
    133             mDialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
    134             SystemUIDialog.setShowForAllUsers(mDialog, true);
    135             SystemUIDialog.registerDismissListener(mDialog);
    136             SystemUIDialog.setWindowOnTop(mDialog);
    137             mUiHandler.post(() -> mDialog.show());
    138             mHost.collapsePanels();
    139         });
    140     }
    141 
    142     @Override
    143     public CharSequence getTileLabel() {
    144         return mContext.getString(R.string.quick_settings_cast_title);
    145     }
    146 
    147     @Override
    148     protected void handleUpdateState(BooleanState state, Object arg) {
    149         state.label = mContext.getString(R.string.quick_settings_cast_title);
    150         state.contentDescription = state.label;
    151         state.value = false;
    152         final Set<CastDevice> devices = mController.getCastDevices();
    153         boolean connecting = false;
    154         for (CastDevice device : devices) {
    155             if (device.state == CastDevice.STATE_CONNECTED) {
    156                 state.value = true;
    157                 state.label = getDeviceName(device);
    158                 state.contentDescription = state.contentDescription + "," +
    159                         mContext.getString(R.string.accessibility_cast_name, state.label);
    160             } else if (device.state == CastDevice.STATE_CONNECTING) {
    161                 connecting = true;
    162             }
    163         }
    164         if (!state.value && connecting) {
    165             state.label = mContext.getString(R.string.quick_settings_connecting);
    166         }
    167         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
    168         state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on
    169                 : R.drawable.ic_qs_cast_off);
    170         mDetailAdapter.updateItems(devices);
    171         state.expandedAccessibilityClassName = Button.class.getName();
    172         state.contentDescription = state.contentDescription + ","
    173                 + mContext.getString(R.string.accessibility_quick_settings_open_details);
    174     }
    175 
    176     @Override
    177     public int getMetricsCategory() {
    178         return MetricsEvent.QS_CAST;
    179     }
    180 
    181     @Override
    182     protected String composeChangeAnnouncement() {
    183         if (!mState.value) {
    184             // We only announce when it's turned off to avoid vocal overflow.
    185             return mContext.getString(R.string.accessibility_casting_turned_off);
    186         }
    187         return null;
    188     }
    189 
    190     private String getDeviceName(CastDevice device) {
    191         return device.name != null ? device.name
    192                 : mContext.getString(R.string.quick_settings_cast_device_default_name);
    193     }
    194 
    195     private final class Callback implements CastController.Callback, KeyguardMonitor.Callback {
    196         @Override
    197         public void onCastDevicesChanged() {
    198             refreshState();
    199         }
    200 
    201         @Override
    202         public void onKeyguardShowingChanged() {
    203             refreshState();
    204         }
    205     };
    206 
    207     private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
    208         private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>();
    209 
    210         private QSDetailItems mItems;
    211 
    212         @Override
    213         public CharSequence getTitle() {
    214             return mContext.getString(R.string.quick_settings_cast_title);
    215         }
    216 
    217         @Override
    218         public Boolean getToggleState() {
    219             return null;
    220         }
    221 
    222         @Override
    223         public Intent getSettingsIntent() {
    224             return CAST_SETTINGS;
    225         }
    226 
    227         @Override
    228         public void setToggleState(boolean state) {
    229             // noop
    230         }
    231 
    232         @Override
    233         public int getMetricsCategory() {
    234             return MetricsEvent.QS_CAST_DETAILS;
    235         }
    236 
    237         @Override
    238         public View createDetailView(Context context, View convertView, ViewGroup parent) {
    239             mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
    240             mItems.setTagSuffix("Cast");
    241             if (convertView == null) {
    242                 if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
    243                 mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
    244                     @Override
    245                     public void onViewAttachedToWindow(View v) {
    246                         if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
    247                     }
    248 
    249                     @Override
    250                     public void onViewDetachedFromWindow(View v) {
    251                         if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
    252                         mVisibleOrder.clear();
    253                     }
    254                 });
    255             }
    256             mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
    257                     R.string.quick_settings_cast_detail_empty_text);
    258             mItems.setCallback(this);
    259             updateItems(mController.getCastDevices());
    260             mController.setDiscovering(true);
    261             return mItems;
    262         }
    263 
    264         private void updateItems(Set<CastDevice> devices) {
    265             if (mItems == null) return;
    266             Item[] items = null;
    267             if (devices != null && !devices.isEmpty()) {
    268                 // if we are connected, simply show that device
    269                 for (CastDevice device : devices) {
    270                     if (device.state == CastDevice.STATE_CONNECTED) {
    271                         final Item item = new Item();
    272                         item.iconResId = R.drawable.ic_qs_cast_on;
    273                         item.line1 = getDeviceName(device);
    274                         item.line2 = mContext.getString(R.string.quick_settings_connected);
    275                         item.tag = device;
    276                         item.canDisconnect = true;
    277                         items = new Item[] { item };
    278                         break;
    279                     }
    280                 }
    281                 // otherwise list all available devices, and don't move them around
    282                 if (items == null) {
    283                     for (CastDevice device : devices) {
    284                         mVisibleOrder.put(device.id, device);
    285                     }
    286                     items = new Item[devices.size()];
    287                     int i = 0;
    288                     for (String id : mVisibleOrder.keySet()) {
    289                         final CastDevice device = mVisibleOrder.get(id);
    290                         if (!devices.contains(device)) continue;
    291                         final Item item = new Item();
    292                         item.iconResId = R.drawable.ic_qs_cast_off;
    293                         item.line1 = getDeviceName(device);
    294                         if (device.state == CastDevice.STATE_CONNECTING) {
    295                             item.line2 = mContext.getString(R.string.quick_settings_connecting);
    296                         }
    297                         item.tag = device;
    298                         items[i++] = item;
    299                     }
    300                 }
    301             }
    302             mItems.setItems(items);
    303         }
    304 
    305         @Override
    306         public void onDetailItemClick(Item item) {
    307             if (item == null || item.tag == null) return;
    308             MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT);
    309             final CastDevice device = (CastDevice) item.tag;
    310             mController.startCasting(device);
    311         }
    312 
    313         @Override
    314         public void onDetailItemDisconnect(Item item) {
    315             if (item == null || item.tag == null) return;
    316             MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT);
    317             final CastDevice device = (CastDevice) item.tag;
    318             mController.stopCasting(device);
    319         }
    320     }
    321 }
    322