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.content.Context; 20 import android.content.Intent; 21 import android.provider.Settings; 22 import android.util.Log; 23 import android.view.View; 24 import android.view.View.OnAttachStateChangeListener; 25 import android.view.ViewGroup; 26 27 import com.android.internal.logging.MetricsLogger; 28 import com.android.systemui.R; 29 import com.android.systemui.qs.QSDetailItems; 30 import com.android.systemui.qs.QSDetailItems.Item; 31 import com.android.systemui.qs.QSTile; 32 import com.android.systemui.statusbar.policy.CastController; 33 import com.android.systemui.statusbar.policy.CastController.CastDevice; 34 import com.android.systemui.statusbar.policy.KeyguardMonitor; 35 36 import java.util.LinkedHashMap; 37 import java.util.Set; 38 39 /** Quick settings tile: Cast **/ 40 public class CastTile extends QSTile<QSTile.BooleanState> { 41 private static final Intent CAST_SETTINGS = 42 new Intent(Settings.ACTION_CAST_SETTINGS); 43 44 private final CastController mController; 45 private final CastDetailAdapter mDetailAdapter; 46 private final KeyguardMonitor mKeyguard; 47 private final Callback mCallback = new Callback(); 48 49 public CastTile(Host host) { 50 super(host); 51 mController = host.getCastController(); 52 mDetailAdapter = new CastDetailAdapter(); 53 mKeyguard = host.getKeyguardMonitor(); 54 } 55 56 @Override 57 public DetailAdapter getDetailAdapter() { 58 return mDetailAdapter; 59 } 60 61 @Override 62 protected BooleanState newTileState() { 63 return new BooleanState(); 64 } 65 66 @Override 67 public void setListening(boolean listening) { 68 if (mController == null) return; 69 if (DEBUG) Log.d(TAG, "setListening " + listening); 70 if (listening) { 71 mController.addCallback(mCallback); 72 mKeyguard.addCallback(mCallback); 73 } else { 74 mController.setDiscovering(false); 75 mController.removeCallback(mCallback); 76 mKeyguard.removeCallback(mCallback); 77 } 78 } 79 80 @Override 81 protected void handleUserSwitch(int newUserId) { 82 super.handleUserSwitch(newUserId); 83 if (mController == null) return; 84 mController.setCurrentUserId(newUserId); 85 } 86 87 @Override 88 protected void handleClick() { 89 MetricsLogger.action(mContext, getMetricsCategory()); 90 showDetail(true); 91 } 92 93 @Override 94 protected void handleUpdateState(BooleanState state, Object arg) { 95 state.visible = !mKeyguard.isSecure() || !mKeyguard.isShowing() 96 || mKeyguard.canSkipBouncer(); 97 state.label = mContext.getString(R.string.quick_settings_cast_title); 98 state.value = false; 99 state.autoMirrorDrawable = false; 100 final Set<CastDevice> devices = mController.getCastDevices(); 101 boolean connecting = false; 102 for (CastDevice device : devices) { 103 if (device.state == CastDevice.STATE_CONNECTED) { 104 state.value = true; 105 state.label = getDeviceName(device); 106 } else if (device.state == CastDevice.STATE_CONNECTING) { 107 connecting = true; 108 } 109 } 110 if (!state.value && connecting) { 111 state.label = mContext.getString(R.string.quick_settings_connecting); 112 } 113 state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on 114 : R.drawable.ic_qs_cast_off); 115 mDetailAdapter.updateItems(devices); 116 } 117 118 @Override 119 public int getMetricsCategory() { 120 return MetricsLogger.QS_CAST; 121 } 122 123 @Override 124 protected String composeChangeAnnouncement() { 125 if (!mState.value) { 126 // We only announce when it's turned off to avoid vocal overflow. 127 return mContext.getString(R.string.accessibility_casting_turned_off); 128 } 129 return null; 130 } 131 132 private String getDeviceName(CastDevice device) { 133 return device.name != null ? device.name 134 : mContext.getString(R.string.quick_settings_cast_device_default_name); 135 } 136 137 private final class Callback implements CastController.Callback, KeyguardMonitor.Callback { 138 @Override 139 public void onCastDevicesChanged() { 140 refreshState(); 141 } 142 143 @Override 144 public void onKeyguardChanged() { 145 refreshState(); 146 } 147 }; 148 149 private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback { 150 private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>(); 151 152 private QSDetailItems mItems; 153 154 @Override 155 public int getTitle() { 156 return R.string.quick_settings_cast_title; 157 } 158 159 @Override 160 public Boolean getToggleState() { 161 return null; 162 } 163 164 @Override 165 public Intent getSettingsIntent() { 166 return CAST_SETTINGS; 167 } 168 169 @Override 170 public void setToggleState(boolean state) { 171 // noop 172 } 173 174 @Override 175 public int getMetricsCategory() { 176 return MetricsLogger.QS_CAST_DETAILS; 177 } 178 179 @Override 180 public View createDetailView(Context context, View convertView, ViewGroup parent) { 181 mItems = QSDetailItems.convertOrInflate(context, convertView, parent); 182 mItems.setTagSuffix("Cast"); 183 if (convertView == null) { 184 if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener"); 185 mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { 186 @Override 187 public void onViewAttachedToWindow(View v) { 188 if (DEBUG) Log.d(TAG, "onViewAttachedToWindow"); 189 } 190 191 @Override 192 public void onViewDetachedFromWindow(View v) { 193 if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow"); 194 mVisibleOrder.clear(); 195 } 196 }); 197 } 198 mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty, 199 R.string.quick_settings_cast_detail_empty_text); 200 mItems.setCallback(this); 201 updateItems(mController.getCastDevices()); 202 mController.setDiscovering(true); 203 return mItems; 204 } 205 206 private void updateItems(Set<CastDevice> devices) { 207 if (mItems == null) return; 208 Item[] items = null; 209 if (devices != null && !devices.isEmpty()) { 210 // if we are connected, simply show that device 211 for (CastDevice device : devices) { 212 if (device.state == CastDevice.STATE_CONNECTED) { 213 final Item item = new Item(); 214 item.icon = R.drawable.ic_qs_cast_on; 215 item.line1 = getDeviceName(device); 216 item.line2 = mContext.getString(R.string.quick_settings_connected); 217 item.tag = device; 218 item.canDisconnect = true; 219 items = new Item[] { item }; 220 break; 221 } 222 } 223 // otherwise list all available devices, and don't move them around 224 if (items == null) { 225 for (CastDevice device : devices) { 226 mVisibleOrder.put(device.id, device); 227 } 228 items = new Item[devices.size()]; 229 int i = 0; 230 for (String id : mVisibleOrder.keySet()) { 231 final CastDevice device = mVisibleOrder.get(id); 232 if (!devices.contains(device)) continue; 233 final Item item = new Item(); 234 item.icon = R.drawable.ic_qs_cast_off; 235 item.line1 = getDeviceName(device); 236 if (device.state == CastDevice.STATE_CONNECTING) { 237 item.line2 = mContext.getString(R.string.quick_settings_connecting); 238 } 239 item.tag = device; 240 items[i++] = item; 241 } 242 } 243 } 244 mItems.setItems(items); 245 } 246 247 @Override 248 public void onDetailItemClick(Item item) { 249 if (item == null || item.tag == null) return; 250 MetricsLogger.action(mContext, MetricsLogger.QS_CAST_SELECT); 251 final CastDevice device = (CastDevice) item.tag; 252 mController.startCasting(device); 253 } 254 255 @Override 256 public void onDetailItemDisconnect(Item item) { 257 if (item == null || item.tag == null) return; 258 MetricsLogger.action(mContext, MetricsLogger.QS_CAST_DISCONNECT); 259 final CastDevice device = (CastDevice) item.tag; 260 mController.stopCasting(device); 261 } 262 } 263 } 264