diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index 38316ff9de903..150ab7411c8e9 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -86,7 +86,7 @@ public class KeyguardStatusView extends GridLayout {
private void setEnableMarquee(boolean enabled) {
if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
- mOwnerInfo.setSelected(enabled);
+ if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
}
@Override
@@ -131,6 +131,7 @@ public class KeyguardStatusView extends GridLayout {
}
private void updateOwnerInfo() {
+ if (mOwnerInfo == null) return;
String ownerInfo = getOwnerInfo();
if (!TextUtils.isEmpty(ownerInfo)) {
mOwnerInfo.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml
new file mode 100644
index 0000000000000..fbc21d4a43314
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
index 8dacdc9809f0b..159bf65256c96 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
@@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project
+ android:pathData="M2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM38.0,14.0L10.0,14.0l0.0,3.3c7.9,2.6 14.2,8.8 16.7,16.7L38.0,34.0L38.0,14.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0zM42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0z"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b0f2133546e5e..6a7b450a3c812 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -526,8 +526,16 @@
Wi-Fi Off
No saved networks available
-
- Cast screen
+
+ Cast screen
+
+ Casting
+
+ Unnamed device
+
+ Ready to cast
+
+ No devices available
Brightness
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 502713f6f9d4b..ab952c623a92d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -16,17 +16,23 @@
package com.android.systemui.qs.tiles;
-import android.app.Dialog;
+import android.content.Context;
import android.content.Intent;
-import android.media.MediaRouter;
import android.provider.Settings;
+import android.util.Log;
import android.view.View;
-import android.view.WindowManager;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewGroup;
-import com.android.internal.app.MediaRouteDialogPresenter;
import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastController.CastDevice;
+
+import java.util.LinkedHashMap;
+import java.util.Set;
/** Quick settings tile: Cast **/
public class CastTile extends QSTile {
@@ -34,10 +40,17 @@ public class CastTile extends QSTile {
new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS);
private final CastController mController;
+ private final CastDetailAdapter mDetailAdapter;
public CastTile(Host host) {
super(host);
mController = host.getCastController();
+ mDetailAdapter = new CastDetailAdapter();
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
}
@Override
@@ -48,12 +61,13 @@ public class CastTile extends QSTile {
@Override
public void setListening(boolean listening) {
if (mController == null) return;
+ if (DEBUG) Log.d(TAG, "setListening " + listening);
if (listening) {
mController.addCallback(mCallback);
} else {
+ mController.setDiscovering(false);
mController.removeCallback(mCallback);
}
- mController.setDiscovering(listening);
}
@Override
@@ -65,61 +79,147 @@ public class CastTile extends QSTile {
@Override
protected void handleClick() {
- mHandler.post(new Runnable() {
- public void run() {
- mHost.collapsePanels();
- mUiHandler.post(mShowDialog);
- }
- });
+ showDetail(true);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.visible = true;
- state.label = mContext
- .getString(R.string.quick_settings_remote_display_no_connection_label);
- if (arg instanceof CallbackInfo) {
- final CallbackInfo cb = (CallbackInfo) arg;
- if (cb.connectedRouteName != null) {
- state.value = !cb.connecting;
+ state.label = mContext.getString(R.string.quick_settings_cast_title);
+ state.value = false;
+ final Set devices = mController.getCastDevices();
+ boolean connecting = false;
+ for (CastDevice device : devices) {
+ if (device.state == CastDevice.STATE_CONNECTED) {
+ state.value = true;
+ state.label = getDeviceName(device);
+ } else if (device.state == CastDevice.STATE_CONNECTING) {
+ connecting = true;
}
}
+ if (!state.value && connecting) {
+ state.label = mContext.getString(R.string.quick_settings_connecting);
+ }
state.iconId = state.value ? R.drawable.ic_qs_cast_on : R.drawable.ic_qs_cast_off;
+ mDetailAdapter.updateItems(devices);
}
- private static class CallbackInfo {
- boolean enabled;
- boolean connecting;
- String connectedRouteName;
+ private String getDeviceName(CastDevice device) {
+ return device.name != null ? device.name
+ : mContext.getString(R.string.quick_settings_cast_device_default_name);
}
private final CastController.Callback mCallback = new CastController.Callback() {
@Override
- public void onStateChanged(boolean enabled, boolean connecting,
- String connectedRouteName) {
- final CallbackInfo info = new CallbackInfo(); // TODO pool
- info.enabled = enabled;
- info.connecting = connecting;
- info.connectedRouteName = connectedRouteName;
- refreshState(info);
+ public void onCastDevicesChanged() {
+ refreshState();
}
};
- private final Runnable mShowDialog = new Runnable() {
- private Dialog mDialog;
+ private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+ private final LinkedHashMap mVisibleOrder = new LinkedHashMap<>();
+
+ private QSDetailItems mItems;
+
@Override
- public void run() {
- mDialog = MediaRouteDialogPresenter.createDialog(mContext,
- MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mDialog.dismiss();
- mHost.startSettingsActivity(WIFI_DISPLAY_SETTINGS);
- }
- });
- mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- mDialog.show();
+ public int getTitle() {
+ return R.string.quick_settings_cast_title;
}
- };
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return WIFI_DISPLAY_SETTINGS;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ // noop
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
+ mItems.setTagSuffix("Cast");
+ if (convertView == null) {
+ if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener");
+ mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
+ mVisibleOrder.clear();
+ }
+ });
+ }
+ mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty,
+ R.string.quick_settings_cast_detail_empty_text);
+ mItems.setCallback(this);
+ updateItems(mController.getCastDevices());
+ mController.setDiscovering(true);
+ return mItems;
+ }
+
+ private void updateItems(Set devices) {
+ if (mItems == null) return;
+ Item[] items = null;
+ if (devices != null && !devices.isEmpty()) {
+ // if we are connected, simply show that device
+ for (CastDevice device : devices) {
+ if (device.state == CastDevice.STATE_CONNECTED) {
+ final Item item = new Item();
+ item.icon = R.drawable.ic_qs_cast_on;
+ item.line1 = getDeviceName(device);
+ item.line2 = mContext.getString(R.string.quick_settings_connected);
+ item.tag = device;
+ item.canDisconnect = true;
+ items = new Item[] { item };
+ break;
+ }
+ }
+ // otherwise list all available devices, and don't move them around
+ if (items == null) {
+ for (CastDevice device : devices) {
+ mVisibleOrder.put(device.id, device);
+ }
+ items = new Item[devices.size()];
+ int i = 0;
+ for (String id : mVisibleOrder.keySet()) {
+ final CastDevice device = mVisibleOrder.get(id);
+ if (!devices.contains(device)) continue;
+ final Item item = new Item();
+ item.icon = R.drawable.ic_qs_cast_off;
+ item.line1 = getDeviceName(device);
+ if (device.state == CastDevice.STATE_CONNECTING) {
+ item.line2 = mContext.getString(R.string.quick_settings_connecting);
+ }
+ item.tag = device;
+ items[i++] = item;
+ }
+ }
+ }
+ mItems.setItems(items);
+ }
+
+ @Override
+ public void onDetailItemClick(Item item) {
+ if (item == null || item.tag == null) return;
+ final CastDevice device = (CastDevice) item.tag;
+ mController.startCasting(device);
+ }
+
+ @Override
+ public void onDetailItemDisconnect(Item item) {
+ if (item == null || item.tag == null) return;
+ mController.stopCasting();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9d7d933365d22..505af4493868f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2322,6 +2322,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mBluetoothController != null) {
mBluetoothController.dump(fd, pw, args);
}
+ if (mCastController != null) {
+ mCastController.dump(fd, pw, args);
+ }
}
private String hunStateToString(Entry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index 54041e13ae00e..eb5804ac1e56c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -16,13 +16,30 @@
package com.android.systemui.statusbar.policy;
+import java.util.Set;
+
public interface CastController {
void addCallback(Callback callback);
void removeCallback(Callback callback);
void setDiscovering(boolean request);
void setCurrentUserId(int currentUserId);
+ Set getCastDevices();
+ void startCasting(CastDevice device);
+ void stopCasting();
public interface Callback {
- void onStateChanged(boolean enabled, boolean connecting, String connectedRouteName);
+ void onCastDevicesChanged();
+ }
+
+ public static final class CastDevice {
+ public static final int STATE_DISCONNECTED = 0;
+ public static final int STATE_CONNECTING = 1;
+ public static final int STATE_CONNECTED = 2;
+
+ public String id;
+ public String name;
+ public String description;
+ public int state = STATE_DISCONNECTED;
+ public Object tag;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index bcd865c350413..22179e0542787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -16,30 +16,55 @@
package com.android.systemui.statusbar.policy;
+import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+
import android.content.Context;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Set;
+import java.util.UUID;
/** Platform implementation of the cast controller. **/
public class CastControllerImpl implements CastController {
+ private static final String TAG = "CastController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final Context mContext;
private final ArrayList mCallbacks = new ArrayList();
private final MediaRouter mMediaRouter;
+ private final ArrayMap mRoutes = new ArrayMap<>();
+ private final Object mDiscoveringLock = new Object();
- private boolean mEnabled;
- private boolean mConnecting;
- private String mConnectedRouteName;
+ private boolean mDiscovering;
public CastControllerImpl(Context context) {
+ mContext = context;
mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ if (DEBUG) Log.d(TAG, "new CastController()");
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("CastController state:");
+ pw.print(" mDiscovering="); pw.println(mDiscovering);
+ pw.print(" mCallbacks.size="); pw.println(mCallbacks.size());
+ pw.print(" mRoutes.size="); pw.println(mRoutes.size());
+ for (int i = 0; i < mRoutes.size(); i++) {
+ final RouteInfo route = mRoutes.valueAt(i);
+ pw.print(" "); pw.println(routeToString(route));
+ }
}
@Override
public void addCallback(Callback callback) {
mCallbacks.add(callback);
- fireStateChanged(callback);
+ fireOnCastDevicesChanged(callback);
}
@Override
@@ -49,12 +74,16 @@ public class CastControllerImpl implements CastController {
@Override
public void setDiscovering(boolean request) {
- if (request) {
- mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
- mMediaCallback,
- MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
- } else {
- mMediaRouter.removeCallback(mMediaCallback);
+ synchronized (mDiscoveringLock) {
+ if (mDiscovering == request) return;
+ mDiscovering = request;
+ if (DEBUG) Log.d(TAG, "setDiscovering: " + request);
+ if (request) {
+ mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
+ MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ } else {
+ mMediaRouter.removeCallback(mMediaCallback);
+ }
}
}
@@ -63,63 +92,113 @@ public class CastControllerImpl implements CastController {
mMediaRouter.rebindAsUser(currentUserId);
}
+ @Override
+ public Set getCastDevices() {
+ final ArraySet devices = new ArraySet();
+ synchronized(mRoutes) {
+ for (RouteInfo route : mRoutes.values()) {
+ final CastDevice device = new CastDevice();
+ device.id = route.getTag().toString();
+ final CharSequence name = route.getName(mContext);
+ device.name = name != null ? name.toString() : null;
+ final CharSequence description = route.getDescription();
+ device.description = description != null ? description.toString() : null;
+ device.state = route.isConnecting() ? CastDevice.STATE_CONNECTING
+ : route.isSelected() ? CastDevice.STATE_CONNECTED
+ : CastDevice.STATE_DISCONNECTED;
+ device.tag = route;
+ devices.add(device);
+ }
+ }
+ return devices;
+ }
+
+ @Override
+ public void startCasting(CastDevice device) {
+ if (device == null || device.tag == null) return;
+ final RouteInfo route = (RouteInfo) device.tag;
+ if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route));
+ mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route);
+ }
+
+ @Override
+ public void stopCasting() {
+ if (DEBUG) Log.d(TAG, "stopCasting");
+ mMediaRouter.getDefaultRoute().select();
+ }
+
private void updateRemoteDisplays() {
- final MediaRouter.RouteInfo connectedRoute = mMediaRouter.getSelectedRoute(
- MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
- boolean enabled = connectedRoute != null
- && connectedRoute.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
- boolean connecting;
- if (enabled) {
- connecting = connectedRoute.isConnecting();
- } else {
- connecting = false;
- enabled = mMediaRouter.isRouteAvailable(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
- MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
+ synchronized(mRoutes) {
+ mRoutes.clear();
+ final int n = mMediaRouter.getRouteCount();
+ for (int i = 0; i < n; i++) {
+ final RouteInfo route = mMediaRouter.getRouteAt(i);
+ if (!route.isEnabled()) continue;
+ if (!route.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) continue;
+ ensureTagExists(route);
+ mRoutes.put(route.getTag().toString(), route);
+ }
+ final RouteInfo selected = mMediaRouter.getSelectedRoute(ROUTE_TYPE_REMOTE_DISPLAY);
+ if (selected != null && !selected.isDefault()) {
+ ensureTagExists(selected);
+ mRoutes.put(selected.getTag().toString(), selected);
+ }
}
-
- String connectedRouteName = null;
- if (connectedRoute != null) {
- connectedRouteName = connectedRoute.getName().toString();
- }
- synchronized(mCallbacks) {
- mEnabled = enabled;
- mConnecting = connecting;
- mConnectedRouteName = connectedRouteName;
- }
- fireStateChanged();
+ fireOnCastDevicesChanged();
}
- private void fireStateChanged() {
+ private void ensureTagExists(RouteInfo route) {
+ if (route.getTag() == null) {
+ route.setTag(UUID.randomUUID().toString());
+ }
+ }
+
+ private void fireOnCastDevicesChanged() {
for (Callback callback : mCallbacks) {
- fireStateChanged(callback);
+ fireOnCastDevicesChanged(callback);
}
}
- private void fireStateChanged(Callback callback) {
- synchronized(mCallbacks) {
- callback.onStateChanged(mEnabled, mConnecting, mConnectedRouteName);
- }
+ private void fireOnCastDevicesChanged(Callback callback) {
+ callback.onCastDevicesChanged();
+ }
+
+ private static String routeToString(RouteInfo route) {
+ if (route == null) return null;
+ final StringBuilder sb = new StringBuilder().append(route.getName()).append('/')
+ .append(route.getDescription()).append('@').append(route.getDeviceAddress())
+ .append(",status=").append(route.getStatus());
+ if (route.isDefault()) sb.append(",default");
+ if (route.isEnabled()) sb.append(",enabled");
+ if (route.isConnecting()) sb.append(",connecting");
+ if (route.isSelected()) sb.append(",selected");
+ return sb.append(",id=").append(route.getTag()).toString();
}
private final MediaRouter.SimpleCallback mMediaCallback = new MediaRouter.SimpleCallback() {
@Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
+ if (DEBUG) Log.d(TAG, "onRouteAdded: " + routeToString(route));
updateRemoteDisplays();
}
@Override
public void onRouteChanged(MediaRouter router, RouteInfo route) {
+ if (DEBUG) Log.d(TAG, "onRouteChanged: " + routeToString(route));
updateRemoteDisplays();
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
+ if (DEBUG) Log.d(TAG, "onRouteRemoved: " + routeToString(route));
updateRemoteDisplays();
}
@Override
public void onRouteSelected(MediaRouter router, int type, RouteInfo route) {
+ if (DEBUG) Log.d(TAG, "onRouteSelected(" + type + "): " + routeToString(route));
updateRemoteDisplays();
}
@Override
public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
+ if (DEBUG) Log.d(TAG, "onRouteUnselected(" + type + "): " + routeToString(route));
updateRemoteDisplays();
}
};