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.iconId = state.value ? R.drawable.ic_qs_cast_on : R.drawable.ic_qs_cast_off; 111 mDetailAdapter.updateItems(devices); 112 } 113 114 @Override 115 protected String composeChangeAnnouncement() { 116 if (!mState.value) { 117 // We only announce when it's turned off to avoid vocal overflow. 118 return mContext.getString(R.string.accessibility_casting_turned_off); 119 } 120 return null; 121 } 122 123 private String getDeviceName(CastDevice device) { 124 return device.name != null ? device.name 125 : mContext.getString(R.string.quick_settings_cast_device_default_name); 126 } 127 128 private final class Callback implements CastController.Callback, KeyguardMonitor.Callback { 129 @Override 130 public void onCastDevicesChanged() { 131 refreshState(); 132 } 133 134 @Override 135 public void onKeyguardChanged() { 136 refreshState(); 137 } 138 }; 139 140 private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback { 141 private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>(); 142 143 private QSDetailItems mItems; 144 145 @Override 146 public int getTitle() { 147 return R.string.quick_settings_cast_title; 148 } 149 150 @Override 151 public Boolean getToggleState() { 152 return null; 153 } 154 155 @Override 156 public Intent getSettingsIntent() { 157 return CAST_SETTINGS; 158 } 159 160 @Override 161 public void setToggleState(boolean state) { 162 // noop 163 } 164 165 @Override 166 public View createDetailView(Context context, View convertView, ViewGroup parent) { 167 mItems = QSDetailItems.convertOrInflate(context, convertView, parent); 168 mItems.setTagSuffix("Cast"); 169 if (convertView == null) { 170 if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener"); 171 mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { 172 @Override 173 public void onViewAttachedToWindow(View v) { 174 if (DEBUG) Log.d(TAG, "onViewAttachedToWindow"); 175 } 176 177 @Override 178 public void onViewDetachedFromWindow(View v) { 179 if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow"); 180 mVisibleOrder.clear(); 181 } 182 }); 183 } 184 mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty, 185 R.string.quick_settings_cast_detail_empty_text); 186 mItems.setCallback(this); 187 updateItems(mController.getCastDevices()); 188 mController.setDiscovering(true); 189 return mItems; 190 } 191 192 private void updateItems(Set<CastDevice> devices) { 193 if (mItems == null) return; 194 Item[] items = null; 195 if (devices != null && !devices.isEmpty()) { 196 // if we are connected, simply show that device 197 for (CastDevice device : devices) { 198 if (device.state == CastDevice.STATE_CONNECTED) { 199 final Item item = new Item(); 200 item.icon = R.drawable.ic_qs_cast_on; 201 item.line1 = getDeviceName(device); 202 item.line2 = mContext.getString(R.string.quick_settings_connected); 203 item.tag = device; 204 item.canDisconnect = true; 205 items = new Item[] { item }; 206 break; 207 } 208 } 209 // otherwise list all available devices, and don't move them around 210 if (items == null) { 211 for (CastDevice device : devices) { 212 mVisibleOrder.put(device.id, device); 213 } 214 items = new Item[devices.size()]; 215 int i = 0; 216 for (String id : mVisibleOrder.keySet()) { 217 final CastDevice device = mVisibleOrder.get(id); 218 if (!devices.contains(device)) continue; 219 final Item item = new Item(); 220 item.icon = R.drawable.ic_qs_cast_off; 221 item.line1 = getDeviceName(device); 222 if (device.state == CastDevice.STATE_CONNECTING) { 223 item.line2 = mContext.getString(R.string.quick_settings_connecting); 224 } 225 item.tag = device; 226 items[i++] = item; 227 } 228 } 229 } 230 mItems.setItems(items); 231 } 232 233 @Override 234 public void onDetailItemClick(Item item) { 235 if (item == null || item.tag == null) return; 236 final CastDevice device = (CastDevice) item.tag; 237 mController.startCasting(device); 238 } 239 240 @Override 241 public void onDetailItemDisconnect(Item item) { 242 if (item == null || item.tag == null) return; 243 final CastDevice device = (CastDevice) item.tag; 244 mController.stopCasting(device); 245 } 246 } 247 } 248