diff options
3 files changed, 192 insertions, 5 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e1c71fae7559..42e19aace0c3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -761,6 +761,8 @@ <string name="quick_settings_cast_device_default_description">Ready to cast</string> <!-- QuickSettings: Cast detail panel, text when there are no items [CHAR LIMIT=NONE] --> <string name="quick_settings_cast_detail_empty_text">No devices available</string> + <!-- QuickSettings: Cast unavailable, text when not connected to WiFi [CHAR LIMIT=NONE] --> + <string name="quick_settings_cast_no_wifi">Wi\u2011Fi not connected</string> <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Brightness dialog auto brightness button [CHAR LIMIT=NONE] --> @@ -1999,6 +2001,9 @@ <!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_open_details">Open details.</string> + <!-- accessibility label for quick settings items that are currently disabled. Must have a reason [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_not_available">Unvailable due to <xliff:g name="reason" id="reason" example="Wifi not available">%s</xliff:g></string> + <!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string> 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 ed78048c8746..921db6901626 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.KeyguardMonitor; +import com.android.systemui.statusbar.policy.NetworkController; import java.util.LinkedHashMap; import java.util.Set; @@ -58,16 +59,18 @@ public class CastTile extends QSTileImpl<BooleanState> { private final CastController mController; private final CastDetailAdapter mDetailAdapter; private final KeyguardMonitor mKeyguard; + private final NetworkController mNetworkController; private final Callback mCallback = new Callback(); private final ActivityStarter mActivityStarter; private Dialog mDialog; - private boolean mRegistered; + private boolean mWifiConnected; public CastTile(QSHost host) { super(host); mController = Dependency.get(CastController.class); mDetailAdapter = new CastDetailAdapter(); mKeyguard = Dependency.get(KeyguardMonitor.class); + mNetworkController = Dependency.get(NetworkController.class); mActivityStarter = Dependency.get(ActivityStarter.class); } @@ -87,10 +90,12 @@ public class CastTile extends QSTileImpl<BooleanState> { if (listening) { mController.addCallback(mCallback); mKeyguard.addCallback(mCallback); + mNetworkController.addCallback(mSignalCallback); } else { mController.setDiscovering(false); mController.removeCallback(mCallback); mKeyguard.removeCallback(mCallback); + mNetworkController.removeCallback(mSignalCallback); } } @@ -112,6 +117,9 @@ public class CastTile extends QSTileImpl<BooleanState> { @Override protected void handleClick() { + if (getState().state == Tile.STATE_UNAVAILABLE) { + return; + } if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { showDetail(true); @@ -164,13 +172,22 @@ public class CastTile extends QSTileImpl<BooleanState> { if (!state.value && connecting) { state.label = mContext.getString(R.string.quick_settings_connecting); } - state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on : R.drawable.ic_qs_cast_off); + if (mWifiConnected) { + state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.secondaryLabel = ""; + state.contentDescription = state.contentDescription + "," + + mContext.getString(R.string.accessibility_quick_settings_open_details); + state.expandedAccessibilityClassName = Button.class.getName(); + } else { + state.state = Tile.STATE_UNAVAILABLE; + String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); + state.secondaryLabel = noWifi; + state.contentDescription = state.contentDescription + ", " + mContext.getString( + R.string.accessibility_quick_settings_not_available, noWifi); + } mDetailAdapter.updateItems(devices); - state.expandedAccessibilityClassName = Button.class.getName(); - state.contentDescription = state.contentDescription + "," - + mContext.getString(R.string.accessibility_quick_settings_open_details); } @Override @@ -192,6 +209,22 @@ public class CastTile extends QSTileImpl<BooleanState> { : mContext.getString(R.string.quick_settings_cast_device_default_name); } + private final NetworkController.SignalCallback mSignalCallback = + new NetworkController.SignalCallback() { + @Override + public void setWifiIndicators(boolean enabled, + NetworkController.IconState statusIcon, + NetworkController.IconState qsIcon, boolean activityIn, boolean activityOut, + String description, boolean isTransient, String statusLabel) { + // statusIcon.visible has the connected status information + boolean enabledAndConnected = enabled && qsIcon.visible; + if (enabledAndConnected != mWifiConnected) { + mWifiConnected = enabledAndConnected; + refreshState(); + } + } + }; + private final class Callback implements CastController.Callback, KeyguardMonitor.Callback { @Override public void onCastDevicesChanged() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java new file mode 100644 index 000000000000..d9412ecd9a3a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.service.quicksettings.Tile; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.statusbar.policy.CastController; +import com.android.systemui.statusbar.policy.KeyguardMonitor; +import com.android.systemui.statusbar.policy.NetworkController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashSet; +import java.util.Set; + + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class CastTileTest extends SysuiTestCase { + + @Mock + private CastController mController; + @Mock + private ActivityStarter mActivityStarter; + @Mock + private KeyguardMonitor mKeyguard; + @Mock + private NetworkController mNetworkController; + @Mock + private QSTileHost mHost; + @Mock + NetworkController.SignalCallback mCallback; + + private TestableLooper mTestableLooper; + private CastTile mCastTile; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + + mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); + mController = mDependency.injectMockDependency(CastController.class); + mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class); + mKeyguard = mDependency.injectMockDependency(KeyguardMonitor.class); + mNetworkController = mDependency.injectMockDependency(NetworkController.class); + + when(mHost.getContext()).thenReturn(mContext); + + mCastTile = new CastTile(mHost); + + // We are not setting the mocks to listening, so we trigger a first refresh state to + // set the initial state + mCastTile.refreshState(); + + mCastTile.handleSetListening(true); + ArgumentCaptor<NetworkController.SignalCallback> signalCallbackArgumentCaptor = + ArgumentCaptor.forClass(NetworkController.SignalCallback.class); + verify(mNetworkController).addCallback(signalCallbackArgumentCaptor.capture()); + mCallback = signalCallbackArgumentCaptor.getValue(); + + } + + @Test + public void testStateUnavailable_wifiDisabled() { + NetworkController.IconState qsIcon = + new NetworkController.IconState(false, 0, ""); + mCallback.setWifiIndicators(false, mock(NetworkController.IconState.class), + qsIcon, false,false, "", + false, ""); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state); + } + + @Test + public void testStateUnavailable_wifiNotConnected() { + NetworkController.IconState qsIcon = + new NetworkController.IconState(false, 0, ""); + mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class), + qsIcon, false,false, "", + false, ""); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state); + } + + @Test + public void testStateActive_wifiEnabledAndCasting() { + CastController.CastDevice device = mock(CastController.CastDevice.class); + device.state = CastController.CastDevice.STATE_CONNECTED; + Set<CastController.CastDevice> devices = new HashSet<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + NetworkController.IconState qsIcon = + new NetworkController.IconState(true, 0, ""); + mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class), + qsIcon, false,false, "", + false, ""); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); + } + + @Test + public void testStateInactive_wifiEnabledNotCasting() { + NetworkController.IconState qsIcon = + new NetworkController.IconState(true, 0, ""); + mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class), + qsIcon, false,false, "", + false, ""); + mTestableLooper.processAllMessages(); + + assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); + } +} |