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