blob: 0b1e9c399410382d34d6a864814dfedbfc22ee31 [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 Monkf6d31a02017-05-24 13:29:19 -040019import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
20
Jason Monkee1f6e22017-04-21 12:51:18 -040021import android.app.Dialog;
John Spurlock1e6eb172014-07-13 11:59:50 -040022import android.content.Context;
John Spurlockaf8d6c42014-05-07 17:49:08 -040023import android.content.Intent;
Narayan Kamathf60a6272019-02-27 14:25:59 +000024import android.media.projection.MediaProjectionInfo;
John Spurlockaf8d6c42014-05-07 17:49:08 -040025import android.provider.Settings;
Jason Monk32508852017-01-18 09:17:13 -050026import android.service.quicksettings.Tile;
John Spurlock1e6eb172014-07-13 11:59:50 -040027import android.util.Log;
John Spurlockaf8d6c42014-05-07 17:49:08 -040028import android.view.View;
John Spurlock1e6eb172014-07-13 11:59:50 -040029import android.view.View.OnAttachStateChangeListener;
30import android.view.ViewGroup;
Jason Monk9ca18482017-08-01 10:38:02 -040031import android.view.WindowManager.LayoutParams;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040032import android.widget.Button;
Chris Wrenf6e9228b2016-01-26 18:04:35 -050033
Jason Monkf6d31a02017-05-24 13:29:19 -040034import com.android.internal.app.MediaRouteDialogPresenter;
Chris Wren457a21c2015-05-06 17:50:34 -040035import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010036import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
John Spurlockaf8d6c42014-05-07 17:49:08 -040037import com.android.systemui.R;
Jason Monkec34da82017-02-24 15:57:05 -050038import com.android.systemui.plugins.ActivityStarter;
Jason Monke5b770e2017-03-03 21:49:29 -050039import com.android.systemui.plugins.qs.DetailAdapter;
Jason Monk1cbf5fb2017-03-30 15:23:43 -040040import com.android.systemui.plugins.qs.QSTile.BooleanState;
John Spurlock1e6eb172014-07-13 11:59:50 -040041import com.android.systemui.qs.QSDetailItems;
42import com.android.systemui.qs.QSDetailItems.Item;
Jason Monk702e2eb2017-03-03 16:53:44 -050043import com.android.systemui.qs.QSHost;
Jason Monk702e2eb2017-03-03 16:53:44 -050044import com.android.systemui.qs.tileimpl.QSTileImpl;
Jason Monk9ca18482017-08-01 10:38:02 -040045import com.android.systemui.statusbar.phone.SystemUIDialog;
John Spurlockaf8d6c42014-05-07 17:49:08 -040046import com.android.systemui.statusbar.policy.CastController;
John Spurlock1e6eb172014-07-13 11:59:50 -040047import com.android.systemui.statusbar.policy.CastController.CastDevice;
John Spurlock657c62c2014-07-22 12:18:09 -040048import com.android.systemui.statusbar.policy.KeyguardMonitor;
Fabian Kozynski4235b2a2018-10-04 17:46:59 -040049import com.android.systemui.statusbar.policy.NetworkController;
John Spurlock1e6eb172014-07-13 11:59:50 -040050
51import java.util.LinkedHashMap;
52import java.util.Set;
John Spurlockaf8d6c42014-05-07 17:49:08 -040053
Jason Monk5d577202018-12-26 15:43:06 -050054import javax.inject.Inject;
55
John Spurlockaf8d6c42014-05-07 17:49:08 -040056/** Quick settings tile: Cast **/
Jason Monk702e2eb2017-03-03 16:53:44 -050057public class CastTile extends QSTileImpl<BooleanState> {
Jeff Brown18492622014-08-19 17:51:47 -070058 private static final Intent CAST_SETTINGS =
59 new Intent(Settings.ACTION_CAST_SETTINGS);
John Spurlockaf8d6c42014-05-07 17:49:08 -040060
61 private final CastController mController;
John Spurlock1e6eb172014-07-13 11:59:50 -040062 private final CastDetailAdapter mDetailAdapter;
John Spurlock657c62c2014-07-22 12:18:09 -040063 private final KeyguardMonitor mKeyguard;
Fabian Kozynski4235b2a2018-10-04 17:46:59 -040064 private final NetworkController mNetworkController;
John Spurlock657c62c2014-07-22 12:18:09 -040065 private final Callback mCallback = new Callback();
Jason Monk9c7844c2017-01-18 15:21:53 -050066 private final ActivityStarter mActivityStarter;
Jason Monkee1f6e22017-04-21 12:51:18 -040067 private Dialog mDialog;
Fabian Kozynski4235b2a2018-10-04 17:46:59 -040068 private boolean mWifiConnected;
John Spurlockaf8d6c42014-05-07 17:49:08 -040069
Jason Monk5d577202018-12-26 15:43:06 -050070 @Inject
Jason Monkc0e0e2b2018-12-26 16:47:41 -050071 public CastTile(QSHost host, CastController castController, KeyguardMonitor keyguardMonitor,
72 NetworkController networkController, ActivityStarter activityStarter) {
John Spurlockaf8d6c42014-05-07 17:49:08 -040073 super(host);
Jason Monkc0e0e2b2018-12-26 16:47:41 -050074 mController = castController;
John Spurlock1e6eb172014-07-13 11:59:50 -040075 mDetailAdapter = new CastDetailAdapter();
Jason Monkc0e0e2b2018-12-26 16:47:41 -050076 mKeyguard = keyguardMonitor;
77 mNetworkController = networkController;
78 mActivityStarter = activityStarter;
Jason Monkfa452ef2018-12-26 17:26:10 -050079 mController.observe(this, mCallback);
80 mKeyguard.observe(this, mCallback);
81 mNetworkController.observe(this, mSignalCallback);
John Spurlock1e6eb172014-07-13 11:59:50 -040082 }
83
84 @Override
85 public DetailAdapter getDetailAdapter() {
86 return mDetailAdapter;
John Spurlockaf8d6c42014-05-07 17:49:08 -040087 }
88
89 @Override
Jason Monk62b63a02016-02-02 15:15:31 -050090 public BooleanState newTileState() {
John Spurlockaf8d6c42014-05-07 17:49:08 -040091 return new BooleanState();
92 }
93
94 @Override
Jason Monk1c6116c2017-09-06 17:33:01 -040095 public void handleSetListening(boolean listening) {
Jason Monk1c6116c2017-09-06 17:33:01 -040096 if (DEBUG) Log.d(TAG, "handleSetListening " + listening);
Jason Monkfa452ef2018-12-26 17:26:10 -050097 if (!listening) {
John Spurlock1e6eb172014-07-13 11:59:50 -040098 mController.setDiscovering(false);
John Spurlockccb6b9a2014-05-17 15:54:40 -040099 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400100 }
101
102 @Override
103 protected void handleUserSwitch(int newUserId) {
104 super.handleUserSwitch(newUserId);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400105 mController.setCurrentUserId(newUserId);
106 }
107
108 @Override
Jason Monk76c67aa2016-02-19 14:49:42 -0500109 public Intent getLongClickIntent() {
110 return new Intent(Settings.ACTION_CAST_SETTINGS);
111 }
112
113 @Override
Jason Monk32508852017-01-18 09:17:13 -0500114 protected void handleSecondaryClick() {
115 handleClick();
116 }
117
118 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400119 protected void handleClick() {
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400120 if (getState().state == Tile.STATE_UNAVAILABLE) {
121 return;
122 }
Jason Monkba2318e2015-12-08 09:04:23 -0500123 if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
Jason Monk9c7844c2017-01-18 15:21:53 -0500124 mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
Jason Monk9c7844c2017-01-18 15:21:53 -0500125 showDetail(true);
Jason Monkba2318e2015-12-08 09:04:23 -0500126 });
127 return;
128 }
Narayan Kamathf60a6272019-02-27 14:25:59 +0000129
130 CastDevice activeProjection = getActiveDeviceMediaProjection();
131 if (activeProjection == null) {
132 showDetail(true);
133 } else {
134 mController.stopCasting(activeProjection);
135 }
136 }
137
138 private CastDevice getActiveDeviceMediaProjection() {
139 CastDevice activeDevice = null;
140 for (CastDevice device : mController.getCastDevices()) {
141 if (device.state == CastDevice.STATE_CONNECTED
142 || device.state == CastDevice.STATE_CONNECTING) {
143 activeDevice = device;
144 break;
145 }
146 }
147
148 if (activeDevice != null && activeDevice.tag instanceof MediaProjectionInfo) {
149 return activeDevice;
150 }
151
152 return null;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400153 }
154
155 @Override
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400156 public void showDetail(boolean show) {
157 mUiHandler.post(() -> {
Jason Monkf6d31a02017-05-24 13:29:19 -0400158 mDialog = MediaRouteDialogPresenter.createDialog(mContext, ROUTE_TYPE_REMOTE_DISPLAY,
159 v -> {
160 mDialog.dismiss();
Jason Monkc0e0e2b2018-12-26 16:47:41 -0500161 mActivityStarter
Jason Monkf6d31a02017-05-24 13:29:19 -0400162 .postStartActivityDismissingKeyguard(getLongClickIntent(), 0);
163 });
Jason Monk9ca18482017-08-01 10:38:02 -0400164 mDialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
165 SystemUIDialog.setShowForAllUsers(mDialog, true);
166 SystemUIDialog.registerDismissListener(mDialog);
167 SystemUIDialog.setWindowOnTop(mDialog);
Jason Monkfa3478a2017-05-08 10:32:03 -0400168 mUiHandler.post(() -> mDialog.show());
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400169 mHost.collapsePanels();
170 });
171 }
172
173 @Override
Jason Monk39c98e62016-03-16 09:18:35 -0400174 public CharSequence getTileLabel() {
175 return mContext.getString(R.string.quick_settings_cast_title);
176 }
177
178 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400179 protected void handleUpdateState(BooleanState state, Object arg) {
John Spurlock1e6eb172014-07-13 11:59:50 -0400180 state.label = mContext.getString(R.string.quick_settings_cast_title);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400181 state.contentDescription = state.label;
John Spurlock1e6eb172014-07-13 11:59:50 -0400182 state.value = false;
183 final Set<CastDevice> devices = mController.getCastDevices();
184 boolean connecting = false;
185 for (CastDevice device : devices) {
186 if (device.state == CastDevice.STATE_CONNECTED) {
187 state.value = true;
Narayan Kamathb040df72019-02-27 18:27:59 +0000188 state.secondaryLabel = getDeviceName(device);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400189 state.contentDescription = state.contentDescription + "," +
190 mContext.getString(R.string.accessibility_cast_name, state.label);
John Spurlock1e6eb172014-07-13 11:59:50 -0400191 } else if (device.state == CastDevice.STATE_CONNECTING) {
192 connecting = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400193 }
194 }
Narayan Kamathb040df72019-02-27 18:27:59 +0000195 if (connecting && !state.value) {
196 state.secondaryLabel = mContext.getString(R.string.quick_settings_connecting);
John Spurlock1e6eb172014-07-13 11:59:50 -0400197 }
John Spurlock2d695812014-10-30 13:25:21 -0400198 state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on
199 : R.drawable.ic_qs_cast_off);
Narayan Kamathb040df72019-02-27 18:27:59 +0000200 if (mWifiConnected || state.value) {
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400201 state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
Narayan Kamathb040df72019-02-27 18:27:59 +0000202 if (!state.value) {
203 state.secondaryLabel = "";
204 }
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400205 state.contentDescription = state.contentDescription + ","
206 + mContext.getString(R.string.accessibility_quick_settings_open_details);
207 state.expandedAccessibilityClassName = Button.class.getName();
208 } else {
209 state.state = Tile.STATE_UNAVAILABLE;
210 String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
211 state.secondaryLabel = noWifi;
212 state.contentDescription = state.contentDescription + ", " + mContext.getString(
213 R.string.accessibility_quick_settings_not_available, noWifi);
214 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400215 mDetailAdapter.updateItems(devices);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400216 }
217
Selim Cinek4fda7b22014-08-18 22:07:25 +0200218 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400219 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500220 return MetricsEvent.QS_CAST;
Chris Wren457a21c2015-05-06 17:50:34 -0400221 }
222
223 @Override
Selim Cinek4fda7b22014-08-18 22:07:25 +0200224 protected String composeChangeAnnouncement() {
225 if (!mState.value) {
226 // We only announce when it's turned off to avoid vocal overflow.
227 return mContext.getString(R.string.accessibility_casting_turned_off);
228 }
229 return null;
230 }
231
John Spurlock1e6eb172014-07-13 11:59:50 -0400232 private String getDeviceName(CastDevice device) {
233 return device.name != null ? device.name
234 : mContext.getString(R.string.quick_settings_cast_device_default_name);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400235 }
236
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400237 private final NetworkController.SignalCallback mSignalCallback =
238 new NetworkController.SignalCallback() {
239 @Override
240 public void setWifiIndicators(boolean enabled,
241 NetworkController.IconState statusIcon,
242 NetworkController.IconState qsIcon, boolean activityIn, boolean activityOut,
243 String description, boolean isTransient, String statusLabel) {
244 // statusIcon.visible has the connected status information
245 boolean enabledAndConnected = enabled && qsIcon.visible;
246 if (enabledAndConnected != mWifiConnected) {
247 mWifiConnected = enabledAndConnected;
248 refreshState();
249 }
250 }
251 };
252
John Spurlock657c62c2014-07-22 12:18:09 -0400253 private final class Callback implements CastController.Callback, KeyguardMonitor.Callback {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400254 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400255 public void onCastDevicesChanged() {
256 refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400257 }
John Spurlock657c62c2014-07-22 12:18:09 -0400258
259 @Override
Jason Monkaa573e92017-01-27 17:00:29 -0500260 public void onKeyguardShowingChanged() {
John Spurlock657c62c2014-07-22 12:18:09 -0400261 refreshState();
262 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400263 };
John Spurlock76c43b92014-05-13 21:10:51 -0400264
John Spurlock1e6eb172014-07-13 11:59:50 -0400265 private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
266 private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>();
267
268 private QSDetailItems mItems;
269
John Spurlock76c43b92014-05-13 21:10:51 -0400270 @Override
Jason Monkc06fbb12016-01-08 14:12:18 -0500271 public CharSequence getTitle() {
272 return mContext.getString(R.string.quick_settings_cast_title);
John Spurlock76c43b92014-05-13 21:10:51 -0400273 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400274
275 @Override
276 public Boolean getToggleState() {
277 return null;
278 }
279
280 @Override
281 public Intent getSettingsIntent() {
Jeff Brown18492622014-08-19 17:51:47 -0700282 return CAST_SETTINGS;
John Spurlock1e6eb172014-07-13 11:59:50 -0400283 }
284
285 @Override
286 public void setToggleState(boolean state) {
287 // noop
288 }
289
290 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400291 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500292 return MetricsEvent.QS_CAST_DETAILS;
Chris Wren457a21c2015-05-06 17:50:34 -0400293 }
294
295 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400296 public View createDetailView(Context context, View convertView, ViewGroup parent) {
297 mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
298 mItems.setTagSuffix("Cast");
299 if (convertView == null) {
300 if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
301 mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
302 @Override
303 public void onViewAttachedToWindow(View v) {
304 if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
305 }
306
307 @Override
308 public void onViewDetachedFromWindow(View v) {
309 if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
310 mVisibleOrder.clear();
311 }
312 });
313 }
314 mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
315 R.string.quick_settings_cast_detail_empty_text);
316 mItems.setCallback(this);
317 updateItems(mController.getCastDevices());
318 mController.setDiscovering(true);
319 return mItems;
320 }
321
322 private void updateItems(Set<CastDevice> devices) {
323 if (mItems == null) return;
324 Item[] items = null;
325 if (devices != null && !devices.isEmpty()) {
326 // if we are connected, simply show that device
327 for (CastDevice device : devices) {
328 if (device.state == CastDevice.STATE_CONNECTED) {
329 final Item item = new Item();
jackqdyulei786a4902017-10-24 13:27:53 -0700330 item.iconResId = R.drawable.ic_qs_cast_on;
John Spurlock1e6eb172014-07-13 11:59:50 -0400331 item.line1 = getDeviceName(device);
332 item.line2 = mContext.getString(R.string.quick_settings_connected);
333 item.tag = device;
334 item.canDisconnect = true;
335 items = new Item[] { item };
336 break;
337 }
338 }
339 // otherwise list all available devices, and don't move them around
340 if (items == null) {
341 for (CastDevice device : devices) {
342 mVisibleOrder.put(device.id, device);
343 }
344 items = new Item[devices.size()];
345 int i = 0;
346 for (String id : mVisibleOrder.keySet()) {
347 final CastDevice device = mVisibleOrder.get(id);
348 if (!devices.contains(device)) continue;
349 final Item item = new Item();
jackqdyulei786a4902017-10-24 13:27:53 -0700350 item.iconResId = R.drawable.ic_qs_cast_off;
John Spurlock1e6eb172014-07-13 11:59:50 -0400351 item.line1 = getDeviceName(device);
352 if (device.state == CastDevice.STATE_CONNECTING) {
353 item.line2 = mContext.getString(R.string.quick_settings_connecting);
354 }
355 item.tag = device;
356 items[i++] = item;
357 }
358 }
359 }
360 mItems.setItems(items);
361 }
362
363 @Override
364 public void onDetailItemClick(Item item) {
365 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500366 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT);
John Spurlock1e6eb172014-07-13 11:59:50 -0400367 final CastDevice device = (CastDevice) item.tag;
368 mController.startCasting(device);
369 }
370
371 @Override
372 public void onDetailItemDisconnect(Item item) {
373 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500374 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400375 final CastDevice device = (CastDevice) item.tag;
376 mController.stopCasting(device);
John Spurlock1e6eb172014-07-13 11:59:50 -0400377 }
378 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400379}