blob: 6f2883850c5b8409361d3b3678bbe07371f887d5 [file] [log] [blame]
John Spurlockaf8d6c42014-05-07 17:49:08 -04001/*
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
17package com.android.systemui.qs.tiles;
18
Jason Monkee1f6e22017-04-21 12:51:18 -040019import android.app.Dialog;
20import android.content.BroadcastReceiver;
John Spurlock1e6eb172014-07-13 11:59:50 -040021import android.content.Context;
Jason Monkee1f6e22017-04-21 12:51:18 -040022import android.content.DialogInterface;
23import android.content.DialogInterface.OnDismissListener;
John Spurlockaf8d6c42014-05-07 17:49:08 -040024import android.content.Intent;
Jason Monkee1f6e22017-04-21 12:51:18 -040025import android.content.IntentFilter;
26import android.os.UserHandle;
John Spurlockaf8d6c42014-05-07 17:49:08 -040027import android.provider.Settings;
Jason Monk32508852017-01-18 09:17:13 -050028import android.service.quicksettings.Tile;
Jason Monk1cbf5fb2017-03-30 15:23:43 -040029import android.support.v7.app.MediaRouteChooserDialog;
30import android.support.v7.app.MediaRouteControllerDialog;
31import android.support.v7.media.MediaControlIntent;
32import android.support.v7.media.MediaRouteSelector;
John Spurlock1e6eb172014-07-13 11:59:50 -040033import android.util.Log;
Jason Monk1cbf5fb2017-03-30 15:23:43 -040034import android.view.ContextThemeWrapper;
John Spurlockaf8d6c42014-05-07 17:49:08 -040035import android.view.View;
John Spurlock1e6eb172014-07-13 11:59:50 -040036import android.view.View.OnAttachStateChangeListener;
37import android.view.ViewGroup;
Jason Monk1cbf5fb2017-03-30 15:23:43 -040038import android.view.WindowManager;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040039import android.widget.Button;
Chris Wrenf6e9228b2016-01-26 18:04:35 -050040
Chris Wren457a21c2015-05-06 17:50:34 -040041import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010042import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Jason Monk9c7844c2017-01-18 15:21:53 -050043import com.android.systemui.Dependency;
John Spurlockaf8d6c42014-05-07 17:49:08 -040044import com.android.systemui.R;
Jason Monk1cbf5fb2017-03-30 15:23:43 -040045import com.android.systemui.R.style;
Jason Monkec34da82017-02-24 15:57:05 -050046import com.android.systemui.plugins.ActivityStarter;
Jason Monke5b770e2017-03-03 21:49:29 -050047import com.android.systemui.plugins.qs.DetailAdapter;
Jason Monk1cbf5fb2017-03-30 15:23:43 -040048import com.android.systemui.plugins.qs.QSTile.BooleanState;
John Spurlock1e6eb172014-07-13 11:59:50 -040049import com.android.systemui.qs.QSDetailItems;
50import com.android.systemui.qs.QSDetailItems.Item;
Jason Monk702e2eb2017-03-03 16:53:44 -050051import com.android.systemui.qs.QSHost;
Jason Monk702e2eb2017-03-03 16:53:44 -050052import com.android.systemui.qs.tileimpl.QSTileImpl;
John Spurlockaf8d6c42014-05-07 17:49:08 -040053import com.android.systemui.statusbar.policy.CastController;
John Spurlock1e6eb172014-07-13 11:59:50 -040054import com.android.systemui.statusbar.policy.CastController.CastDevice;
John Spurlock657c62c2014-07-22 12:18:09 -040055import com.android.systemui.statusbar.policy.KeyguardMonitor;
John Spurlock1e6eb172014-07-13 11:59:50 -040056
57import java.util.LinkedHashMap;
58import java.util.Set;
John Spurlockaf8d6c42014-05-07 17:49:08 -040059
60/** Quick settings tile: Cast **/
Jason Monk702e2eb2017-03-03 16:53:44 -050061public class CastTile extends QSTileImpl<BooleanState> {
Jeff Brown18492622014-08-19 17:51:47 -070062 private static final Intent CAST_SETTINGS =
63 new Intent(Settings.ACTION_CAST_SETTINGS);
John Spurlockaf8d6c42014-05-07 17:49:08 -040064
65 private final CastController mController;
John Spurlock1e6eb172014-07-13 11:59:50 -040066 private final CastDetailAdapter mDetailAdapter;
John Spurlock657c62c2014-07-22 12:18:09 -040067 private final KeyguardMonitor mKeyguard;
68 private final Callback mCallback = new Callback();
Jason Monk9c7844c2017-01-18 15:21:53 -050069 private final ActivityStarter mActivityStarter;
Jason Monkee1f6e22017-04-21 12:51:18 -040070 private Dialog mDialog;
Jason Monkfa3478a2017-05-08 10:32:03 -040071 private boolean mRegistered;
John Spurlockaf8d6c42014-05-07 17:49:08 -040072
Jason Monk702e2eb2017-03-03 16:53:44 -050073 public CastTile(QSHost host) {
John Spurlockaf8d6c42014-05-07 17:49:08 -040074 super(host);
Jason Monk9c7844c2017-01-18 15:21:53 -050075 mController = Dependency.get(CastController.class);
John Spurlock1e6eb172014-07-13 11:59:50 -040076 mDetailAdapter = new CastDetailAdapter();
Jason Monk9c7844c2017-01-18 15:21:53 -050077 mKeyguard = Dependency.get(KeyguardMonitor.class);
78 mActivityStarter = Dependency.get(ActivityStarter.class);
John Spurlock1e6eb172014-07-13 11:59:50 -040079 }
80
81 @Override
82 public DetailAdapter getDetailAdapter() {
83 return mDetailAdapter;
John Spurlockaf8d6c42014-05-07 17:49:08 -040084 }
85
86 @Override
Jason Monk62b63a02016-02-02 15:15:31 -050087 public BooleanState newTileState() {
John Spurlockaf8d6c42014-05-07 17:49:08 -040088 return new BooleanState();
89 }
90
91 @Override
John Spurlockccb6b9a2014-05-17 15:54:40 -040092 public void setListening(boolean listening) {
John Spurlockaf8d6c42014-05-07 17:49:08 -040093 if (mController == null) return;
John Spurlock1e6eb172014-07-13 11:59:50 -040094 if (DEBUG) Log.d(TAG, "setListening " + listening);
John Spurlockccb6b9a2014-05-17 15:54:40 -040095 if (listening) {
96 mController.addCallback(mCallback);
John Spurlock657c62c2014-07-22 12:18:09 -040097 mKeyguard.addCallback(mCallback);
John Spurlockccb6b9a2014-05-17 15:54:40 -040098 } else {
John Spurlock1e6eb172014-07-13 11:59:50 -040099 mController.setDiscovering(false);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400100 mController.removeCallback(mCallback);
John Spurlock657c62c2014-07-22 12:18:09 -0400101 mKeyguard.removeCallback(mCallback);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400102 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400103 }
104
105 @Override
106 protected void handleUserSwitch(int newUserId) {
107 super.handleUserSwitch(newUserId);
108 if (mController == null) return;
109 mController.setCurrentUserId(newUserId);
110 }
111
112 @Override
Jason Monk76c67aa2016-02-19 14:49:42 -0500113 public Intent getLongClickIntent() {
114 return new Intent(Settings.ACTION_CAST_SETTINGS);
115 }
116
117 @Override
Jason Monk32508852017-01-18 09:17:13 -0500118 protected void handleSecondaryClick() {
119 handleClick();
120 }
121
122 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400123 protected void handleClick() {
Jason Monkba2318e2015-12-08 09:04:23 -0500124 if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
Jason Monk9c7844c2017-01-18 15:21:53 -0500125 mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
Jason Monk9c7844c2017-01-18 15:21:53 -0500126 showDetail(true);
Jason Monkba2318e2015-12-08 09:04:23 -0500127 });
128 return;
129 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400130 showDetail(true);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400131 }
132
133 @Override
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400134 public void showDetail(boolean show) {
135 mUiHandler.post(() -> {
136 Context context = new ContextThemeWrapper(mContext,
137 R.style.Theme_AppCompat_Light_Dialog_Alert);
138 if (mState.value) {
Jason Monkee1f6e22017-04-21 12:51:18 -0400139 mDialog = new MediaRouteControllerDialog(context);
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400140 } else {
141 // Instead of showing detail, show standard media routing UI.
142 MediaRouteChooserDialog dialog = new MediaRouteChooserDialog(context);
143 MediaRouteSelector selector = new MediaRouteSelector.Builder()
144 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
145 .build();
146 dialog.setRouteSelector(selector);
Jason Monkee1f6e22017-04-21 12:51:18 -0400147 mDialog = dialog;
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400148 }
Jason Monkee1f6e22017-04-21 12:51:18 -0400149 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
Jason Monkfa3478a2017-05-08 10:32:03 -0400150 mUiHandler.post(() -> mDialog.show());
Jason Monkee1f6e22017-04-21 12:51:18 -0400151 registerReceiver();
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400152 mHost.collapsePanels();
153 });
154 }
155
Jason Monkee1f6e22017-04-21 12:51:18 -0400156 private void registerReceiver() {
157 mContext.registerReceiverAsUser(mReceiver, UserHandle.CURRENT,
158 new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null, null);
Jason Monkfa3478a2017-05-08 10:32:03 -0400159 mRegistered = true;
160 mDialog.setOnDismissListener(dialog -> {
161 if (mRegistered) {
162 mContext.unregisterReceiver(mReceiver);
163 mRegistered = false;
164 }
165 });
Jason Monkee1f6e22017-04-21 12:51:18 -0400166 }
167
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400168 @Override
Jason Monk39c98e62016-03-16 09:18:35 -0400169 public CharSequence getTileLabel() {
170 return mContext.getString(R.string.quick_settings_cast_title);
171 }
172
173 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400174 protected void handleUpdateState(BooleanState state, Object arg) {
John Spurlock1e6eb172014-07-13 11:59:50 -0400175 state.label = mContext.getString(R.string.quick_settings_cast_title);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400176 state.contentDescription = state.label;
John Spurlock1e6eb172014-07-13 11:59:50 -0400177 state.value = false;
178 final Set<CastDevice> devices = mController.getCastDevices();
179 boolean connecting = false;
180 for (CastDevice device : devices) {
181 if (device.state == CastDevice.STATE_CONNECTED) {
182 state.value = true;
183 state.label = getDeviceName(device);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400184 state.contentDescription = state.contentDescription + "," +
185 mContext.getString(R.string.accessibility_cast_name, state.label);
John Spurlock1e6eb172014-07-13 11:59:50 -0400186 } else if (device.state == CastDevice.STATE_CONNECTING) {
187 connecting = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400188 }
189 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400190 if (!state.value && connecting) {
191 state.label = mContext.getString(R.string.quick_settings_connecting);
192 }
Jason Monk32508852017-01-18 09:17:13 -0500193 state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
John Spurlock2d695812014-10-30 13:25:21 -0400194 state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on
195 : R.drawable.ic_qs_cast_off);
John Spurlock1e6eb172014-07-13 11:59:50 -0400196 mDetailAdapter.updateItems(devices);
Jason Monk702e2eb2017-03-03 16:53:44 -0500197 state.expandedAccessibilityClassName = Button.class.getName();
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400198 state.contentDescription = state.contentDescription + ","
199 + mContext.getString(R.string.accessibility_quick_settings_open_details);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400200 }
201
Selim Cinek4fda7b22014-08-18 22:07:25 +0200202 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400203 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500204 return MetricsEvent.QS_CAST;
Chris Wren457a21c2015-05-06 17:50:34 -0400205 }
206
207 @Override
Selim Cinek4fda7b22014-08-18 22:07:25 +0200208 protected String composeChangeAnnouncement() {
209 if (!mState.value) {
210 // We only announce when it's turned off to avoid vocal overflow.
211 return mContext.getString(R.string.accessibility_casting_turned_off);
212 }
213 return null;
214 }
215
John Spurlock1e6eb172014-07-13 11:59:50 -0400216 private String getDeviceName(CastDevice device) {
217 return device.name != null ? device.name
218 : mContext.getString(R.string.quick_settings_cast_device_default_name);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400219 }
220
John Spurlock657c62c2014-07-22 12:18:09 -0400221 private final class Callback implements CastController.Callback, KeyguardMonitor.Callback {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400222 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400223 public void onCastDevicesChanged() {
224 refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400225 }
John Spurlock657c62c2014-07-22 12:18:09 -0400226
227 @Override
Jason Monkaa573e92017-01-27 17:00:29 -0500228 public void onKeyguardShowingChanged() {
John Spurlock657c62c2014-07-22 12:18:09 -0400229 refreshState();
230 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400231 };
John Spurlock76c43b92014-05-13 21:10:51 -0400232
Jason Monkee1f6e22017-04-21 12:51:18 -0400233 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
234 @Override
235 public void onReceive(Context context, Intent intent) {
236 if (mDialog != null) {
237 mDialog.dismiss();
238 }
239 }
240 };
241
John Spurlock1e6eb172014-07-13 11:59:50 -0400242 private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
243 private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>();
244
245 private QSDetailItems mItems;
246
John Spurlock76c43b92014-05-13 21:10:51 -0400247 @Override
Jason Monkc06fbb12016-01-08 14:12:18 -0500248 public CharSequence getTitle() {
249 return mContext.getString(R.string.quick_settings_cast_title);
John Spurlock76c43b92014-05-13 21:10:51 -0400250 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400251
252 @Override
253 public Boolean getToggleState() {
254 return null;
255 }
256
257 @Override
258 public Intent getSettingsIntent() {
Jeff Brown18492622014-08-19 17:51:47 -0700259 return CAST_SETTINGS;
John Spurlock1e6eb172014-07-13 11:59:50 -0400260 }
261
262 @Override
263 public void setToggleState(boolean state) {
264 // noop
265 }
266
267 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400268 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500269 return MetricsEvent.QS_CAST_DETAILS;
Chris Wren457a21c2015-05-06 17:50:34 -0400270 }
271
272 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400273 public View createDetailView(Context context, View convertView, ViewGroup parent) {
274 mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
275 mItems.setTagSuffix("Cast");
276 if (convertView == null) {
277 if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
278 mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
279 @Override
280 public void onViewAttachedToWindow(View v) {
281 if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
282 }
283
284 @Override
285 public void onViewDetachedFromWindow(View v) {
286 if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
287 mVisibleOrder.clear();
288 }
289 });
290 }
291 mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
292 R.string.quick_settings_cast_detail_empty_text);
293 mItems.setCallback(this);
294 updateItems(mController.getCastDevices());
295 mController.setDiscovering(true);
296 return mItems;
297 }
298
299 private void updateItems(Set<CastDevice> devices) {
300 if (mItems == null) return;
301 Item[] items = null;
302 if (devices != null && !devices.isEmpty()) {
303 // if we are connected, simply show that device
304 for (CastDevice device : devices) {
305 if (device.state == CastDevice.STATE_CONNECTED) {
306 final Item item = new Item();
307 item.icon = R.drawable.ic_qs_cast_on;
308 item.line1 = getDeviceName(device);
309 item.line2 = mContext.getString(R.string.quick_settings_connected);
310 item.tag = device;
311 item.canDisconnect = true;
312 items = new Item[] { item };
313 break;
314 }
315 }
316 // otherwise list all available devices, and don't move them around
317 if (items == null) {
318 for (CastDevice device : devices) {
319 mVisibleOrder.put(device.id, device);
320 }
321 items = new Item[devices.size()];
322 int i = 0;
323 for (String id : mVisibleOrder.keySet()) {
324 final CastDevice device = mVisibleOrder.get(id);
325 if (!devices.contains(device)) continue;
326 final Item item = new Item();
327 item.icon = R.drawable.ic_qs_cast_off;
328 item.line1 = getDeviceName(device);
329 if (device.state == CastDevice.STATE_CONNECTING) {
330 item.line2 = mContext.getString(R.string.quick_settings_connecting);
331 }
332 item.tag = device;
333 items[i++] = item;
334 }
335 }
336 }
337 mItems.setItems(items);
338 }
339
340 @Override
341 public void onDetailItemClick(Item item) {
342 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500343 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT);
John Spurlock1e6eb172014-07-13 11:59:50 -0400344 final CastDevice device = (CastDevice) item.tag;
345 mController.startCasting(device);
346 }
347
348 @Override
349 public void onDetailItemDisconnect(Item item) {
350 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500351 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400352 final CastDevice device = (CastDevice) item.tag;
353 mController.stopCasting(device);
John Spurlock1e6eb172014-07-13 11:59:50 -0400354 }
355 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400356}