From 4235b2a80261ea801c5eb71d28c7ef186c38da14 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Thu, 4 Oct 2018 17:46:59 -0400 Subject: [PATCH] CastTile becomes unavailable when not connected to Wifi CastTile registers with NetworkController and shows unavailable status when not connected to WiFi (regardless of internet connectivity). Includes accessibility context for this state. Added tests for behavior. Test: manual && atest Change-Id: I3004ed18e545d8d8c448f01d33eb70bcfd9831b2 Fixes: 78152102 --- packages/SystemUI/res/values/strings.xml | 5 + .../android/systemui/qs/tiles/CastTile.java | 43 ++++- .../systemui/qs/tiles/CastTileTest.java | 149 ++++++++++++++++++ 3 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e1c71fae7559a..42e19aace0c3f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -761,6 +761,8 @@ Ready to cast No devices available + + Wi\u2011Fi not connected Brightness @@ -1999,6 +2001,9 @@ Open details. + + Unvailable due to %s + Open %s settings. 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 ed78048c87464..921db6901626d 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 { 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 { 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 { @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 { 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 { : 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 0000000000000..d9412ecd9a3ac --- /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 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 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); + } +}