blob: 53503c7544c2349fc56dcdf14fa2506f577ab6bc [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;
188 state.label = 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 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400195 if (!state.value && connecting) {
196 state.label = mContext.getString(R.string.quick_settings_connecting);
197 }
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);
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400200 if (mWifiConnected) {
201 state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
202 state.secondaryLabel = "";
203 state.contentDescription = state.contentDescription + ","
204 + mContext.getString(R.string.accessibility_quick_settings_open_details);
205 state.expandedAccessibilityClassName = Button.class.getName();
206 } else {
207 state.state = Tile.STATE_UNAVAILABLE;
208 String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
209 state.secondaryLabel = noWifi;
210 state.contentDescription = state.contentDescription + ", " + mContext.getString(
211 R.string.accessibility_quick_settings_not_available, noWifi);
212 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400213 mDetailAdapter.updateItems(devices);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400214 }
215
Selim Cinek4fda7b22014-08-18 22:07:25 +0200216 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400217 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500218 return MetricsEvent.QS_CAST;
Chris Wren457a21c2015-05-06 17:50:34 -0400219 }
220
221 @Override
Selim Cinek4fda7b22014-08-18 22:07:25 +0200222 protected String composeChangeAnnouncement() {
223 if (!mState.value) {
224 // We only announce when it's turned off to avoid vocal overflow.
225 return mContext.getString(R.string.accessibility_casting_turned_off);
226 }
227 return null;
228 }
229
John Spurlock1e6eb172014-07-13 11:59:50 -0400230 private String getDeviceName(CastDevice device) {
231 return device.name != null ? device.name
232 : mContext.getString(R.string.quick_settings_cast_device_default_name);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400233 }
234
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400235 private final NetworkController.SignalCallback mSignalCallback =
236 new NetworkController.SignalCallback() {
237 @Override
238 public void setWifiIndicators(boolean enabled,
239 NetworkController.IconState statusIcon,
240 NetworkController.IconState qsIcon, boolean activityIn, boolean activityOut,
241 String description, boolean isTransient, String statusLabel) {
242 // statusIcon.visible has the connected status information
243 boolean enabledAndConnected = enabled && qsIcon.visible;
244 if (enabledAndConnected != mWifiConnected) {
245 mWifiConnected = enabledAndConnected;
246 refreshState();
247 }
248 }
249 };
250
John Spurlock657c62c2014-07-22 12:18:09 -0400251 private final class Callback implements CastController.Callback, KeyguardMonitor.Callback {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400252 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400253 public void onCastDevicesChanged() {
254 refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400255 }
John Spurlock657c62c2014-07-22 12:18:09 -0400256
257 @Override
Jason Monkaa573e92017-01-27 17:00:29 -0500258 public void onKeyguardShowingChanged() {
John Spurlock657c62c2014-07-22 12:18:09 -0400259 refreshState();
260 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400261 };
John Spurlock76c43b92014-05-13 21:10:51 -0400262
John Spurlock1e6eb172014-07-13 11:59:50 -0400263 private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
264 private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>();
265
266 private QSDetailItems mItems;
267
John Spurlock76c43b92014-05-13 21:10:51 -0400268 @Override
Jason Monkc06fbb12016-01-08 14:12:18 -0500269 public CharSequence getTitle() {
270 return mContext.getString(R.string.quick_settings_cast_title);
John Spurlock76c43b92014-05-13 21:10:51 -0400271 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400272
273 @Override
274 public Boolean getToggleState() {
275 return null;
276 }
277
278 @Override
279 public Intent getSettingsIntent() {
Jeff Brown18492622014-08-19 17:51:47 -0700280 return CAST_SETTINGS;
John Spurlock1e6eb172014-07-13 11:59:50 -0400281 }
282
283 @Override
284 public void setToggleState(boolean state) {
285 // noop
286 }
287
288 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400289 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500290 return MetricsEvent.QS_CAST_DETAILS;
Chris Wren457a21c2015-05-06 17:50:34 -0400291 }
292
293 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400294 public View createDetailView(Context context, View convertView, ViewGroup parent) {
295 mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
296 mItems.setTagSuffix("Cast");
297 if (convertView == null) {
298 if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
299 mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
300 @Override
301 public void onViewAttachedToWindow(View v) {
302 if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
303 }
304
305 @Override
306 public void onViewDetachedFromWindow(View v) {
307 if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
308 mVisibleOrder.clear();
309 }
310 });
311 }
312 mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
313 R.string.quick_settings_cast_detail_empty_text);
314 mItems.setCallback(this);
315 updateItems(mController.getCastDevices());
316 mController.setDiscovering(true);
317 return mItems;
318 }
319
320 private void updateItems(Set<CastDevice> devices) {
321 if (mItems == null) return;
322 Item[] items = null;
323 if (devices != null && !devices.isEmpty()) {
324 // if we are connected, simply show that device
325 for (CastDevice device : devices) {
326 if (device.state == CastDevice.STATE_CONNECTED) {
327 final Item item = new Item();
jackqdyulei786a4902017-10-24 13:27:53 -0700328 item.iconResId = R.drawable.ic_qs_cast_on;
John Spurlock1e6eb172014-07-13 11:59:50 -0400329 item.line1 = getDeviceName(device);
330 item.line2 = mContext.getString(R.string.quick_settings_connected);
331 item.tag = device;
332 item.canDisconnect = true;
333 items = new Item[] { item };
334 break;
335 }
336 }
337 // otherwise list all available devices, and don't move them around
338 if (items == null) {
339 for (CastDevice device : devices) {
340 mVisibleOrder.put(device.id, device);
341 }
342 items = new Item[devices.size()];
343 int i = 0;
344 for (String id : mVisibleOrder.keySet()) {
345 final CastDevice device = mVisibleOrder.get(id);
346 if (!devices.contains(device)) continue;
347 final Item item = new Item();
jackqdyulei786a4902017-10-24 13:27:53 -0700348 item.iconResId = R.drawable.ic_qs_cast_off;
John Spurlock1e6eb172014-07-13 11:59:50 -0400349 item.line1 = getDeviceName(device);
350 if (device.state == CastDevice.STATE_CONNECTING) {
351 item.line2 = mContext.getString(R.string.quick_settings_connecting);
352 }
353 item.tag = device;
354 items[i++] = item;
355 }
356 }
357 }
358 mItems.setItems(items);
359 }
360
361 @Override
362 public void onDetailItemClick(Item item) {
363 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500364 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT);
John Spurlock1e6eb172014-07-13 11:59:50 -0400365 final CastDevice device = (CastDevice) item.tag;
366 mController.startCasting(device);
367 }
368
369 @Override
370 public void onDetailItemDisconnect(Item item) {
371 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500372 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400373 final CastDevice device = (CastDevice) item.tag;
374 mController.stopCasting(device);
John Spurlock1e6eb172014-07-13 11:59:50 -0400375 }
376 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400377}