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