diff options
9 files changed, 205 insertions, 4 deletions
diff --git a/api/current.txt b/api/current.txt index 40106b3a5724..4174b6a53cc9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -41917,6 +41917,7 @@ package android.service.quicksettings { field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE"; field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES"; field public static final String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE"; + field public static final String META_DATA_BOOLEAN_TILE = "android.service.quicksettings.BOOLEAN_TILE"; } } diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java index f1c870d96065..dd2586cd58ad 100644 --- a/core/java/android/service/quicksettings/TileService.java +++ b/core/java/android/service/quicksettings/TileService.java @@ -126,11 +126,29 @@ public class TileService extends Service { = "android.service.quicksettings.ACTIVE_TILE"; /** + * Meta-data for a tile to support {@code BooleanState}. + * <p> + * BooleanState is for tiles that should support switch tile behavior in accessibility. This is + * the behavior of most of the framework tiles. + * + * To make a TileService support BooleanState, set this meta-data to true on the TileService's + * manifest declaration. + * <pre class="prettyprint"> + * {@literal + * <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE" + * android:value="true" /> + * } + * </pre> + */ + public static final String META_DATA_BOOLEAN_TILE = + "android.service.quicksettings.BOOLEAN_TILE"; + + /** * Used to notify SysUI that Listening has be requested. * @hide */ - public static final String ACTION_REQUEST_LISTENING - = "android.service.quicksettings.action.REQUEST_LISTENING"; + public static final String ACTION_REQUEST_LISTENING = + "android.service.quicksettings.action.REQUEST_LISTENING"; /** * @hide diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index db026cad6ef5..6518924ca0c2 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -186,6 +186,8 @@ public interface QSTile { return toStringBuilder().toString(); } + // Used in dumps to determine current state of a tile. + // This string may be used for CTS testing of tiles, so removing elements is discouraged. protected StringBuilder toStringBuilder() { final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); sb.append(",icon=").append(icon); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 466c8082f0b9..411980b399bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -39,6 +39,7 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.IWindowManager; import android.view.WindowManagerGlobal; +import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; @@ -82,6 +83,11 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile = new Tile(); updateDefaultTileAndIcon(); mServiceManager = host.getTileServices().getTileWrapper(this); + if (mServiceManager.isBooleanTile()) { + // Replace states with BooleanState + resetStates(); + } + mService = mServiceManager.getTileService(); mServiceManager.setTileChangeListener(this); mUser = ActivityManager.getCurrentUser(); @@ -246,8 +252,10 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Override public State newTileState() { - State state = new State(); - return state; + if (mServiceManager != null && mServiceManager.isBooleanTile()) { + return new BooleanState(); + } + return new State(); } @Override @@ -336,6 +344,12 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } else { state.contentDescription = state.label; } + + if (state instanceof BooleanState) { + state.expandedAccessibilityClassName = Switch.class.getName(); + ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE); + } + } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index effea6a877b8..f59e0c2d9bc2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -131,6 +131,24 @@ public class TileLifecycleManager extends BroadcastReceiver implements } /** + * Determines whether the associated TileService is a Boolean Tile. + * + * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this + * tile + * @see TileService#META_DATA_BOOLEAN_TILE + */ + public boolean isBooleanTile() { + try { + ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), + PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); + return info.metaData != null + && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + /** * Binds just long enough to send any queued messages, then unbinds. */ public void flushMessagesAndUnbind() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 2a7e55fe6f8f..0b4e6485551c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -123,6 +123,10 @@ public class TileServiceManager { return mStateManager.isActiveTile(); } + public boolean isBooleanTile() { + return mStateManager.isBooleanTile(); + } + public void setShowingDialog(boolean dialog) { mShowingDialog = dialog; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 681de378ff57..e0f26cd1a267 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -139,6 +139,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption(); } + protected final void resetStates() { + mState = newTileState(); + mTmpState = newTileState(); + } + @NonNull @Override public Lifecycle getLifecycle() { @@ -629,6 +634,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } } + /** + * Dumps the state of this tile along with its name. + * + * This may be used for CTS testing of tiles. + */ @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(this.getClass().getSimpleName() + ":"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt new file mode 100644 index 000000000000..4becd522ebd6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 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.external + +import android.content.ComponentName +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ServiceInfo +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.service.quicksettings.IQSTileService +import android.service.quicksettings.Tile +import android.test.suitebuilder.annotation.SmallTest +import android.view.IWindowManager +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.QSTileHost +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CustomTileTest : SysuiTestCase() { + + companion object { + const val packageName = "test_package" + const val className = "test_class" + val componentName = ComponentName(packageName, className) + val TILE_SPEC = CustomTile.toSpec(componentName) + } + + @Mock private lateinit var mTileHost: QSTileHost + @Mock private lateinit var mTileService: IQSTileService + @Mock private lateinit var mTileServices: TileServices + @Mock private lateinit var mTileServiceManager: TileServiceManager + @Mock private lateinit var mWindowService: IWindowManager + @Mock private lateinit var mPackageManager: PackageManager + @Mock private lateinit var mApplicationInfo: ApplicationInfo + @Mock private lateinit var mServiceInfo: ServiceInfo + + private lateinit var customTile: CustomTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mContext.addMockSystemService("window", mWindowService) + mContext.setMockPackageManager(mPackageManager) + `when`(mTileHost.tileServices).thenReturn(mTileServices) + `when`(mTileHost.context).thenReturn(mContext) + `when`(mTileServices.getTileWrapper(any(CustomTile::class.java))) + .thenReturn(mTileServiceManager) + `when`(mTileServiceManager.tileService).thenReturn(mTileService) + `when`(mPackageManager.getApplicationInfo(anyString(), anyInt())) + .thenReturn(mApplicationInfo) + + `when`(mPackageManager.getServiceInfo(any(ComponentName::class.java), anyInt())) + .thenReturn(mServiceInfo) + mServiceInfo.applicationInfo = mApplicationInfo + + customTile = CustomTile.create(mTileHost, TILE_SPEC) + } + + @Test + fun testBooleanTileHasBooleanState() { + `when`(mTileServiceManager.isBooleanTile).thenReturn(true) + customTile = CustomTile.create(mTileHost, TILE_SPEC) + + assertTrue(customTile.state is QSTile.BooleanState) + assertTrue(customTile.newTileState() is QSTile.BooleanState) + } + + @Test + fun testRegularTileHasNotBooleanState() { + assertFalse(customTile.state is QSTile.BooleanState) + assertFalse(customTile.newTileState() is QSTile.BooleanState) + } + + @Test + fun testValueUpdatedInBooleanTile() { + `when`(mTileServiceManager.isBooleanTile).thenReturn(true) + customTile = CustomTile.create(mTileHost, TILE_SPEC) + customTile.qsTile.icon = mock(Icon::class.java) + `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java))) + .thenReturn(mock(Drawable::class.java)) + + val state = customTile.newTileState() + assertTrue(state is QSTile.BooleanState) + + customTile.qsTile.state = Tile.STATE_INACTIVE + customTile.handleUpdateState(state, null) + assertFalse((state as QSTile.BooleanState).value) + + customTile.qsTile.state = Tile.STATE_ACTIVE + customTile.handleUpdateState(state, null) + assertTrue(state.value) + + customTile.qsTile.state = Tile.STATE_UNAVAILABLE + customTile.handleUpdateState(state, null) + assertFalse(state.value) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index f35295cf6f99..11b0c69e8a41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -101,6 +101,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { defaultServiceInfo = new ServiceInfo(); defaultServiceInfo.metaData = new Bundle(); defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true); + defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_BOOLEAN_TILE, true); } when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt())) .thenReturn(defaultServiceInfo); @@ -237,4 +238,9 @@ public class TileLifecycleManagerTest extends SysuiTestCase { verifyBind(2); verify(mMockTileService, times(2)).onStartListening(); } + + @Test + public void testBooleanTile() throws Exception { + assertTrue(mStateManager.isBooleanTile()); + } } |