From 803e5452592b7f1268ad1200d9065d1ab9d9ebba Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Wed, 19 May 2021 12:21:57 -0400 Subject: [PATCH] Create state strings for every stock tile For stock tiles state (filled in subtitle when there is not one set by the tiles), we specify values for each of the tiles states. That way, they can be translated with that specific tile name in mind for grammatical matching. Custom tiles will use the default. New stock tiles must add a new array to pass tests. Test: QSTileViewImplTest TilesStatesTextTest Fixes: 188163204 Change-Id: Idd4da01994e37cb4778dcdd3080711179cb884c7 --- packages/SystemUI/docs/qs-tiles.md | 1 + .../android/systemui/plugins/qs/QSTile.java | 6 +- .../res/values/tiles_states_strings.xml | 284 ++++++++++++++++++ .../systemui/qs/tileimpl/QSTileImpl.java | 7 +- .../systemui/qs/tileimpl/QSTileViewImpl.kt | 23 +- .../systemui/qs/tiles/InternetTile.java | 2 - .../android/systemui/qs/tiles/WifiTile.java | 1 + .../qs/tileimpl/QSTileViewImplTest.kt | 43 ++- .../qs/tileimpl/TilesStatesTextTest.kt | 54 ++++ 9 files changed, 404 insertions(+), 17 deletions(-) create mode 100644 packages/SystemUI/res/values/tiles_states_strings.xml create mode 100644 packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md index 521620935ddcb..89c28a074c76e 100644 --- a/packages/SystemUI/docs/qs-tiles.md +++ b/packages/SystemUI/docs/qs-tiles.md @@ -305,6 +305,7 @@ This section describes necessary and recommended steps when implementing a Quick * Inject a `Provider` for the tile created before. * Add a case to the `switch` with a unique String spec for the chosen tile. 5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. +6. In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml), add a new array for your tile. The name has to be `tile_states_`. Use a good description to help the translators. #### Abstract methods in QSTileImpl 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 f1c1477c5b0a0..77018d736163a 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 @@ -166,11 +166,13 @@ public interface QSTile { public boolean handlesLongClick = true; public boolean showRippleEffect = true; public Drawable sideViewCustomDrawable; + public String spec; public boolean copyTo(State other) { if (other == null) throw new IllegalArgumentException(); if (!other.getClass().equals(getClass())) throw new IllegalArgumentException(); - final boolean changed = !Objects.equals(other.icon, icon) + final boolean changed = !Objects.equals(other.spec, spec) + || !Objects.equals(other.icon, icon) || !Objects.equals(other.iconSupplier, iconSupplier) || !Objects.equals(other.label, label) || !Objects.equals(other.secondaryLabel, secondaryLabel) @@ -188,6 +190,7 @@ public interface QSTile { || !Objects.equals(other.handlesLongClick, handlesLongClick) || !Objects.equals(other.showRippleEffect, showRippleEffect) || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable); + other.spec = spec; other.icon = icon; other.iconSupplier = iconSupplier; other.label = label; @@ -216,6 +219,7 @@ public interface QSTile { // 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("spec=").append(spec); sb.append(",icon=").append(icon); sb.append(",iconSupplier=").append(iconSupplier); sb.append(",label=").append(label); diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml new file mode 100644 index 0000000000000..5ac7c1d3c65ba --- /dev/null +++ b/packages/SystemUI/res/values/tiles_states_strings.xml @@ -0,0 +1,284 @@ + + + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + + + + @string/tile_unavailable + @string/switch_bar_off + @string/switch_bar_on + + \ No newline at end of file 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 47d80bb0d16fb..a938821a343fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -181,14 +181,15 @@ public abstract class QSTileImpl implements QSTile, Lifecy mStatusBarStateController = statusBarStateController; mActivityStarter = activityStarter; - mState = newTileState(); - mTmpState = newTileState(); + resetStates(); mUiHandler.post(() -> mLifecycle.setCurrentState(CREATED)); } protected final void resetStates() { mState = newTileState(); mTmpState = newTileState(); + mState.spec = mTileSpec; + mTmpState.spec = mTileSpec; } @NonNull @@ -225,6 +226,8 @@ public abstract class QSTileImpl implements QSTile, Lifecy public void setTileSpec(String tileSpec) { mTileSpec = tileSpec; + mState.spec = tileSpec; + mTmpState.spec = tileSpec; } public QSHost getHost() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index cd76b4df3c923..8aed0a88fa35d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -38,6 +38,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.Switch import android.widget.TextView +import androidx.annotation.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils import com.android.systemui.R @@ -62,6 +63,8 @@ open class QSTileViewImpl @JvmOverloads constructor( private const val SECONDARY_LABEL_NAME = "secondaryLabel" private const val CHEVRON_NAME = "chevron" const val UNAVAILABLE_ALPHA = 0.3f + @VisibleForTesting + internal const val TILE_STATE_RES_PREFIX = "tile_states_" } override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE @@ -484,16 +487,18 @@ open class QSTileViewImpl @JvmOverloads constructor( } private fun getStateText(state: QSTile.State): String { - return if (state.disabledByPolicy) { - context.getString(R.string.tile_disabled) - } else if (state.state == Tile.STATE_UNAVAILABLE) { - context.getString(R.string.tile_unavailable) - } else if (state is BooleanState) { - if (state.state == Tile.STATE_INACTIVE) { - context.getString(R.string.switch_bar_off) - } else { - context.getString(R.string.switch_bar_on) + if (state.disabledByPolicy) { + return context.getString(R.string.tile_disabled) + } + + return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) { + val resName = "$TILE_STATE_RES_PREFIX${state.spec}" + var arrayResId = resources.getIdentifier(resName, "array", context.packageName) + if (arrayResId == 0) { + arrayResId = R.array.tile_states_default } + val array = resources.getStringArray(arrayResId) + array[state.state] } else { "" } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index b7f2cd0da6425..b2b88e6827931 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -45,7 +45,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.AlphaControlledSignalTileView; @@ -72,7 +71,6 @@ public class InternetTile extends QSTileImpl { protected final NetworkController mController; private final DataUsageController mDataController; - private final QSTile.SignalState mStateBeforeClick = newTileState(); // The last updated tile state, 0: mobile, 1: wifi, 2: ethernet. private int mLastTileState = -1; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index efac1414aeb65..41a3020191a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -94,6 +94,7 @@ public class WifiTile extends QSTileImpl { mWifiController = accessPointController; mDetailAdapter = (WifiDetailAdapter) createDetailAdapter(); mController.observe(getLifecycle(), mSignalCallback); + mStateBeforeClick.spec = "wifi"; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt index 126dca550cdbd..a2b5013c8c2cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt @@ -23,6 +23,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.text.TextUtils import android.view.View +import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -52,6 +53,7 @@ class QSTileViewImplTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + context.ensureTestableResources() tileView = FakeTileView(context, iconView, false) customDrawableView = tileView.requireViewById(R.id.customDrawable) @@ -117,7 +119,7 @@ class QSTileViewImplTest : SysuiTestCase() { } @Test - fun testSecondaryLabelDescription_unavailable() { + fun testSecondaryLabelDescription_unavailable_default() { val state = QSTile.State() state.state = Tile.STATE_UNAVAILABLE state.secondaryLabel = "" @@ -130,7 +132,7 @@ class QSTileViewImplTest : SysuiTestCase() { } @Test - fun testSecondaryLabelDescription_booleanInactive() { + fun testSecondaryLabelDescription_booleanInactive_default() { val state = QSTile.BooleanState() state.state = Tile.STATE_INACTIVE state.secondaryLabel = "" @@ -143,7 +145,7 @@ class QSTileViewImplTest : SysuiTestCase() { } @Test - fun testSecondaryLabelDescription_booleanActive() { + fun testSecondaryLabelDescription_booleanActive_default() { val state = QSTile.BooleanState() state.state = Tile.STATE_ACTIVE state.secondaryLabel = "" @@ -220,6 +222,41 @@ class QSTileViewImplTest : SysuiTestCase() { assertThat(chevronView.visibility).isEqualTo(View.GONE) } + @Test + fun testUseStateStringsForKnownSpec_Boolean() { + val state = QSTile.BooleanState() + val spec = "internet" + state.spec = spec + + val unavailableString = "${spec}_unavailable" + val offString = "${spec}_off" + val onString = "${spec}_on" + + context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf( + unavailableString, + offString, + onString + )) + + // State UNAVAILABLE + state.secondaryLabel = "" + state.state = Tile.STATE_UNAVAILABLE + tileView.changeState(state) + assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(unavailableString) + + // State INACTIVE + state.secondaryLabel = "" + state.state = Tile.STATE_INACTIVE + tileView.changeState(state) + assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(offString) + + // State ACTIVE + state.secondaryLabel = "" + state.state = Tile.STATE_ACTIVE + tileView.changeState(state) + assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(onString) + } + class FakeTileView( context: Context, icon: QSIconView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt new file mode 100644 index 0000000000000..19ffa4991eaf4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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.tileimpl + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TilesStatesTextTest : SysuiTestCase() { + + @Test + fun testStockTilesHaveStatesArray() { + val tiles = mContext.getString(R.string.quick_settings_tiles_stock).split(",") + tiles.forEach { spec -> + val resName = "${QSTileViewImpl.TILE_STATE_RES_PREFIX}$spec" + val resId = mContext.resources.getIdentifier(resName, "array", mContext.packageName) + + assertNotEquals("Missing resource for $resName", 0, resId) + + val array = mContext.resources.getStringArray(resId) + + assertEquals("Array for $spec is of wrong size", 3, array.size) + } + } + + @Test + fun testDefaultArray() { + val array = mContext.resources.getStringArray(R.array.tile_states_default) + + assertThat(array.size).isEqualTo(3) + } +} \ No newline at end of file