blob: f05ac4c107eb966dd44282165eb280f6e6c6f466 [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;
John Spurlockaf8d6c42014-05-07 17:49:08 -040024import android.provider.Settings;
Jason Monk32508852017-01-18 09:17:13 -050025import android.service.quicksettings.Tile;
John Spurlock1e6eb172014-07-13 11:59:50 -040026import android.util.Log;
John Spurlockaf8d6c42014-05-07 17:49:08 -040027import android.view.View;
John Spurlock1e6eb172014-07-13 11:59:50 -040028import android.view.View.OnAttachStateChangeListener;
29import android.view.ViewGroup;
Jason Monk9ca18482017-08-01 10:38:02 -040030import android.view.WindowManager.LayoutParams;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040031import android.widget.Button;
Chris Wrenf6e9228b2016-01-26 18:04:35 -050032
Jason Monkf6d31a02017-05-24 13:29:19 -040033import com.android.internal.app.MediaRouteDialogPresenter;
Chris Wren457a21c2015-05-06 17:50:34 -040034import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010035import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
John Spurlockaf8d6c42014-05-07 17:49:08 -040036import com.android.systemui.R;
Jason Monkec34da82017-02-24 15:57:05 -050037import com.android.systemui.plugins.ActivityStarter;
Jason Monke5b770e2017-03-03 21:49:29 -050038import com.android.systemui.plugins.qs.DetailAdapter;
Jason Monk1cbf5fb2017-03-30 15:23:43 -040039import com.android.systemui.plugins.qs.QSTile.BooleanState;
John Spurlock1e6eb172014-07-13 11:59:50 -040040import com.android.systemui.qs.QSDetailItems;
41import com.android.systemui.qs.QSDetailItems.Item;
Jason Monk702e2eb2017-03-03 16:53:44 -050042import com.android.systemui.qs.QSHost;
Jason Monk702e2eb2017-03-03 16:53:44 -050043import com.android.systemui.qs.tileimpl.QSTileImpl;
Jason Monk9ca18482017-08-01 10:38:02 -040044import com.android.systemui.statusbar.phone.SystemUIDialog;
John Spurlockaf8d6c42014-05-07 17:49:08 -040045import com.android.systemui.statusbar.policy.CastController;
John Spurlock1e6eb172014-07-13 11:59:50 -040046import com.android.systemui.statusbar.policy.CastController.CastDevice;
John Spurlock657c62c2014-07-22 12:18:09 -040047import com.android.systemui.statusbar.policy.KeyguardMonitor;
Fabian Kozynski4235b2a2018-10-04 17:46:59 -040048import com.android.systemui.statusbar.policy.NetworkController;
John Spurlock1e6eb172014-07-13 11:59:50 -040049
50import java.util.LinkedHashMap;
51import java.util.Set;
John Spurlockaf8d6c42014-05-07 17:49:08 -040052
Jason Monk5d577202018-12-26 15:43:06 -050053import javax.inject.Inject;
54
John Spurlockaf8d6c42014-05-07 17:49:08 -040055/** Quick settings tile: Cast **/
Jason Monk702e2eb2017-03-03 16:53:44 -050056public class CastTile extends QSTileImpl<BooleanState> {
Jeff Brown18492622014-08-19 17:51:47 -070057 private static final Intent CAST_SETTINGS =
58 new Intent(Settings.ACTION_CAST_SETTINGS);
John Spurlockaf8d6c42014-05-07 17:49:08 -040059
60 private final CastController mController;
John Spurlock1e6eb172014-07-13 11:59:50 -040061 private final CastDetailAdapter mDetailAdapter;
John Spurlock657c62c2014-07-22 12:18:09 -040062 private final KeyguardMonitor mKeyguard;
Fabian Kozynski4235b2a2018-10-04 17:46:59 -040063 private final NetworkController mNetworkController;
John Spurlock657c62c2014-07-22 12:18:09 -040064 private final Callback mCallback = new Callback();
Jason Monk9c7844c2017-01-18 15:21:53 -050065 private final ActivityStarter mActivityStarter;
Jason Monkee1f6e22017-04-21 12:51:18 -040066 private Dialog mDialog;
Fabian Kozynski4235b2a2018-10-04 17:46:59 -040067 private boolean mWifiConnected;
John Spurlockaf8d6c42014-05-07 17:49:08 -040068
Jason Monk5d577202018-12-26 15:43:06 -050069 @Inject
Jason Monkc0e0e2b2018-12-26 16:47:41 -050070 public CastTile(QSHost host, CastController castController, KeyguardMonitor keyguardMonitor,
71 NetworkController networkController, ActivityStarter activityStarter) {
John Spurlockaf8d6c42014-05-07 17:49:08 -040072 super(host);
Jason Monkc0e0e2b2018-12-26 16:47:41 -050073 mController = castController;
John Spurlock1e6eb172014-07-13 11:59:50 -040074 mDetailAdapter = new CastDetailAdapter();
Jason Monkc0e0e2b2018-12-26 16:47:41 -050075 mKeyguard = keyguardMonitor;
76 mNetworkController = networkController;
77 mActivityStarter = activityStarter;
Jason Monkfa452ef2018-12-26 17:26:10 -050078 mController.observe(this, mCallback);
79 mKeyguard.observe(this, mCallback);
80 mNetworkController.observe(this, mSignalCallback);
John Spurlock1e6eb172014-07-13 11:59:50 -040081 }
82
83 @Override
84 public DetailAdapter getDetailAdapter() {
85 return mDetailAdapter;
John Spurlockaf8d6c42014-05-07 17:49:08 -040086 }
87
88 @Override
Jason Monk62b63a02016-02-02 15:15:31 -050089 public BooleanState newTileState() {
John Spurlockaf8d6c42014-05-07 17:49:08 -040090 return new BooleanState();
91 }
92
93 @Override
Jason Monk1c6116c2017-09-06 17:33:01 -040094 public void handleSetListening(boolean listening) {
Jason Monk1c6116c2017-09-06 17:33:01 -040095 if (DEBUG) Log.d(TAG, "handleSetListening " + listening);
Jason Monkfa452ef2018-12-26 17:26:10 -050096 if (!listening) {
John Spurlock1e6eb172014-07-13 11:59:50 -040097 mController.setDiscovering(false);
John Spurlockccb6b9a2014-05-17 15:54:40 -040098 }
John Spurlockaf8d6c42014-05-07 17:49:08 -040099 }
100
101 @Override
102 protected void handleUserSwitch(int newUserId) {
103 super.handleUserSwitch(newUserId);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400104 mController.setCurrentUserId(newUserId);
105 }
106
107 @Override
Jason Monk76c67aa2016-02-19 14:49:42 -0500108 public Intent getLongClickIntent() {
109 return new Intent(Settings.ACTION_CAST_SETTINGS);
110 }
111
112 @Override
Jason Monk32508852017-01-18 09:17:13 -0500113 protected void handleSecondaryClick() {
114 handleClick();
115 }
116
117 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400118 protected void handleClick() {
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400119 if (getState().state == Tile.STATE_UNAVAILABLE) {
120 return;
121 }
Jason Monkba2318e2015-12-08 09:04:23 -0500122 if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
Jason Monk9c7844c2017-01-18 15:21:53 -0500123 mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
Jason Monk9c7844c2017-01-18 15:21:53 -0500124 showDetail(true);
Jason Monkba2318e2015-12-08 09:04:23 -0500125 });
126 return;
127 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400128 showDetail(true);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400129 }
130
131 @Override
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400132 public void showDetail(boolean show) {
133 mUiHandler.post(() -> {
Jason Monkf6d31a02017-05-24 13:29:19 -0400134 mDialog = MediaRouteDialogPresenter.createDialog(mContext, ROUTE_TYPE_REMOTE_DISPLAY,
135 v -> {
136 mDialog.dismiss();
Jason Monkc0e0e2b2018-12-26 16:47:41 -0500137 mActivityStarter
Jason Monkf6d31a02017-05-24 13:29:19 -0400138 .postStartActivityDismissingKeyguard(getLongClickIntent(), 0);
139 });
Jason Monk9ca18482017-08-01 10:38:02 -0400140 mDialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
141 SystemUIDialog.setShowForAllUsers(mDialog, true);
142 SystemUIDialog.registerDismissListener(mDialog);
143 SystemUIDialog.setWindowOnTop(mDialog);
Jason Monkfa3478a2017-05-08 10:32:03 -0400144 mUiHandler.post(() -> mDialog.show());
Jason Monk1cbf5fb2017-03-30 15:23:43 -0400145 mHost.collapsePanels();
146 });
147 }
148
149 @Override
Jason Monk39c98e62016-03-16 09:18:35 -0400150 public CharSequence getTileLabel() {
151 return mContext.getString(R.string.quick_settings_cast_title);
152 }
153
154 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400155 protected void handleUpdateState(BooleanState state, Object arg) {
John Spurlock1e6eb172014-07-13 11:59:50 -0400156 state.label = mContext.getString(R.string.quick_settings_cast_title);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400157 state.contentDescription = state.label;
John Spurlock1e6eb172014-07-13 11:59:50 -0400158 state.value = false;
159 final Set<CastDevice> devices = mController.getCastDevices();
160 boolean connecting = false;
161 for (CastDevice device : devices) {
162 if (device.state == CastDevice.STATE_CONNECTED) {
163 state.value = true;
164 state.label = getDeviceName(device);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400165 state.contentDescription = state.contentDescription + "," +
166 mContext.getString(R.string.accessibility_cast_name, state.label);
John Spurlock1e6eb172014-07-13 11:59:50 -0400167 } else if (device.state == CastDevice.STATE_CONNECTING) {
168 connecting = true;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400169 }
170 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400171 if (!state.value && connecting) {
172 state.label = mContext.getString(R.string.quick_settings_connecting);
173 }
John Spurlock2d695812014-10-30 13:25:21 -0400174 state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on
175 : R.drawable.ic_qs_cast_off);
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400176 if (mWifiConnected) {
177 state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
178 state.secondaryLabel = "";
179 state.contentDescription = state.contentDescription + ","
180 + mContext.getString(R.string.accessibility_quick_settings_open_details);
181 state.expandedAccessibilityClassName = Button.class.getName();
182 } else {
183 state.state = Tile.STATE_UNAVAILABLE;
184 String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
185 state.secondaryLabel = noWifi;
186 state.contentDescription = state.contentDescription + ", " + mContext.getString(
187 R.string.accessibility_quick_settings_not_available, noWifi);
188 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400189 mDetailAdapter.updateItems(devices);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400190 }
191
Selim Cinek4fda7b22014-08-18 22:07:25 +0200192 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400193 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500194 return MetricsEvent.QS_CAST;
Chris Wren457a21c2015-05-06 17:50:34 -0400195 }
196
197 @Override
Selim Cinek4fda7b22014-08-18 22:07:25 +0200198 protected String composeChangeAnnouncement() {
199 if (!mState.value) {
200 // We only announce when it's turned off to avoid vocal overflow.
201 return mContext.getString(R.string.accessibility_casting_turned_off);
202 }
203 return null;
204 }
205
John Spurlock1e6eb172014-07-13 11:59:50 -0400206 private String getDeviceName(CastDevice device) {
207 return device.name != null ? device.name
208 : mContext.getString(R.string.quick_settings_cast_device_default_name);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400209 }
210
Fabian Kozynski4235b2a2018-10-04 17:46:59 -0400211 private final NetworkController.SignalCallback mSignalCallback =
212 new NetworkController.SignalCallback() {
213 @Override
214 public void setWifiIndicators(boolean enabled,
215 NetworkController.IconState statusIcon,
216 NetworkController.IconState qsIcon, boolean activityIn, boolean activityOut,
217 String description, boolean isTransient, String statusLabel) {
218 // statusIcon.visible has the connected status information
219 boolean enabledAndConnected = enabled && qsIcon.visible;
220 if (enabledAndConnected != mWifiConnected) {
221 mWifiConnected = enabledAndConnected;
222 refreshState();
223 }
224 }
225 };
226
John Spurlock657c62c2014-07-22 12:18:09 -0400227 private final class Callback implements CastController.Callback, KeyguardMonitor.Callback {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400228 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400229 public void onCastDevicesChanged() {
230 refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400231 }
John Spurlock657c62c2014-07-22 12:18:09 -0400232
233 @Override
Jason Monkaa573e92017-01-27 17:00:29 -0500234 public void onKeyguardShowingChanged() {
John Spurlock657c62c2014-07-22 12:18:09 -0400235 refreshState();
236 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400237 };
John Spurlock76c43b92014-05-13 21:10:51 -0400238
John Spurlock1e6eb172014-07-13 11:59:50 -0400239 private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
240 private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>();
241
242 private QSDetailItems mItems;
243
John Spurlock76c43b92014-05-13 21:10:51 -0400244 @Override
Jason Monkc06fbb12016-01-08 14:12:18 -0500245 public CharSequence getTitle() {
246 return mContext.getString(R.string.quick_settings_cast_title);
John Spurlock76c43b92014-05-13 21:10:51 -0400247 }
John Spurlock1e6eb172014-07-13 11:59:50 -0400248
249 @Override
250 public Boolean getToggleState() {
251 return null;
252 }
253
254 @Override
255 public Intent getSettingsIntent() {
Jeff Brown18492622014-08-19 17:51:47 -0700256 return CAST_SETTINGS;
John Spurlock1e6eb172014-07-13 11:59:50 -0400257 }
258
259 @Override
260 public void setToggleState(boolean state) {
261 // noop
262 }
263
264 @Override
Chris Wren457a21c2015-05-06 17:50:34 -0400265 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500266 return MetricsEvent.QS_CAST_DETAILS;
Chris Wren457a21c2015-05-06 17:50:34 -0400267 }
268
269 @Override
John Spurlock1e6eb172014-07-13 11:59:50 -0400270 public View createDetailView(Context context, View convertView, ViewGroup parent) {
271 mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
272 mItems.setTagSuffix("Cast");
273 if (convertView == null) {
274 if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
275 mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
276 @Override
277 public void onViewAttachedToWindow(View v) {
278 if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
279 }
280
281 @Override
282 public void onViewDetachedFromWindow(View v) {
283 if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
284 mVisibleOrder.clear();
285 }
286 });
287 }
288 mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
289 R.string.quick_settings_cast_detail_empty_text);
290 mItems.setCallback(this);
291 updateItems(mController.getCastDevices());
292 mController.setDiscovering(true);
293 return mItems;
294 }
295
296 private void updateItems(Set<CastDevice> devices) {
297 if (mItems == null) return;
298 Item[] items = null;
299 if (devices != null && !devices.isEmpty()) {
300 // if we are connected, simply show that device
301 for (CastDevice device : devices) {
302 if (device.state == CastDevice.STATE_CONNECTED) {
303 final Item item = new Item();
jackqdyulei786a4902017-10-24 13:27:53 -0700304 item.iconResId = R.drawable.ic_qs_cast_on;
John Spurlock1e6eb172014-07-13 11:59:50 -0400305 item.line1 = getDeviceName(device);
306 item.line2 = mContext.getString(R.string.quick_settings_connected);
307 item.tag = device;
308 item.canDisconnect = true;
309 items = new Item[] { item };
310 break;
311 }
312 }
313 // otherwise list all available devices, and don't move them around
314 if (items == null) {
315 for (CastDevice device : devices) {
316 mVisibleOrder.put(device.id, device);
317 }
318 items = new Item[devices.size()];
319 int i = 0;
320 for (String id : mVisibleOrder.keySet()) {
321 final CastDevice device = mVisibleOrder.get(id);
322 if (!devices.contains(device)) continue;
323 final Item item = new Item();
jackqdyulei786a4902017-10-24 13:27:53 -0700324 item.iconResId = R.drawable.ic_qs_cast_off;
John Spurlock1e6eb172014-07-13 11:59:50 -0400325 item.line1 = getDeviceName(device);
326 if (device.state == CastDevice.STATE_CONNECTING) {
327 item.line2 = mContext.getString(R.string.quick_settings_connecting);
328 }
329 item.tag = device;
330 items[i++] = item;
331 }
332 }
333 }
334 mItems.setItems(items);
335 }
336
337 @Override
338 public void onDetailItemClick(Item item) {
339 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500340 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT);
John Spurlock1e6eb172014-07-13 11:59:50 -0400341 final CastDevice device = (CastDevice) item.tag;
342 mController.startCasting(device);
343 }
344
345 @Override
346 public void onDetailItemDisconnect(Item item) {
347 if (item == null || item.tag == null) return;
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500348 MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT);
John Spurlock78b8c8f2014-08-25 17:52:06 -0400349 final CastDevice device = (CastDevice) item.tag;
350 mController.stopCasting(device);
John Spurlock1e6eb172014-07-13 11:59:50 -0400351 }
352 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400353}