diff --git a/packages/SystemUI/docs/QS-QQS.png b/packages/SystemUI/docs/QS-QQS.png index 02de479cb8c02..3ed8e24c6a8d3 100644 Binary files a/packages/SystemUI/docs/QS-QQS.png and b/packages/SystemUI/docs/QS-QQS.png differ diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md index b48ba6708313e..521620935ddcb 100644 --- a/packages/SystemUI/docs/qs-tiles.md +++ b/packages/SystemUI/docs/qs-tiles.md @@ -8,7 +8,7 @@ This document is a more or less comprehensive summary of the state and infrastru ## What are Quick Settings Tiles? -Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no text. +Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no secondary line. Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages. @@ -69,13 +69,13 @@ For more information on how to implement a tile in SystemUI, see [Implementing a Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`. -* **[`com.android.systemui.plugins.qs.QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles. -* **[`QSTileBaseView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java)**: Implementation of `QSTileView` used in QQS that takes care of most of the features of the view: +* **[`QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles. +* **[`QSTileViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.java)**: Implementation of `QSTileView`. It takes care of the following: * Holding the icon * Background color and shape * Ripple * Click listening -* **[`QSTileView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java)**: Extends `QSTileBaseView`to add label support. Used in QS. + * Labels * **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)** * **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)** @@ -103,7 +103,7 @@ When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, t This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`. 1. User clicks on tile. The following calls happen in sequence: - 1. `QSTileBaseView#onClickListener`. + 1. `QSTileViewImpl#onClickListener`. 2. `QSTile#click`. 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller. 2. State in the device changes. This is normally outside of SystemUI's control. @@ -113,9 +113,9 @@ This is a brief run-down of what happens when a user clicks on a tile. Internal 4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. 5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. 6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view: - * The tile is rippled and the color changes to match the new state. + * The tile colors change to match the new state. * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view. - * If the tile is a `QSTileView` (in expanded QS), the labels are changed. + * The tile labels change to match the new state. ## Third party tiles (TileService) diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index f8812ba894faa..1d93f5d69da83 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -1,6 +1,6 @@ - - + + - - - - - - - - + android:gravity="start" + android:textDirection="locale" + android:ellipsize="marquee" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.QS.TileLabel"/> - - - + diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index 5bf691928300e..241f0495c8a4c 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -52,7 +52,6 @@ android:layout_below="@id/quick_qs_status_icons" android:layout_marginTop="8dp" android:accessibilityTraversalAfter="@id/quick_qs_status_icons" - android:accessibilityTraversalBefore="@id/expand_indicator" android:clipChildren="false" android:clipToPadding="false" android:focusable="true" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index edd8486379a20..78cd22dae2a07 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -555,7 +555,8 @@ 20dp 10dp 60dp - 12dp + 12dp + 16dp 16dp 18dp 16dp @@ -564,8 +565,6 @@ Scaled @dimen/qs_page_indicator-width by .4f. --> 6.4dp - 12dp - 24dp 14sp 1dp 16dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d9cc24fa38ef7..955a9b5224cad 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2187,6 +2187,9 @@ Unavailable + + Disabled + Navigation bar diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 40967ede057e1..01aa996feafd8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -45,7 +45,6 @@ import com.android.settingslib.Utils; import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.drawable.UserIconDrawable; import com.android.systemui.R; -import com.android.systemui.R.dimen; import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.statusbar.phone.MultiUserSwitch; import com.android.systemui.statusbar.phone.SettingsButton; @@ -140,7 +139,7 @@ public class QSFooterView extends FrameLayout { void updateAnimator(int width, int numTiles) { int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size) - - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding); + - mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_padding); int remaining = (width - numTiles * size) / (numTiles - 1); int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 63733b3926318..c3458feb5dfef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -285,8 +285,6 @@ public class QuickQSPanel extends QSPanel { if (record.tileView.getVisibility() == GONE) continue; previousView = record.tileView.updateAccessibilityOrder(previousView); } - mRecords.get(mRecords.size() - 1).tileView.setAccessibilityTraversalBefore( - R.id.expand_indicator); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java deleted file mode 100644 index ce8f6c1737d7a..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016 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.customize; - -import android.content.Context; -import android.view.View; - -import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.qs.tileimpl.QSTileView; - -public class CustomizeTileView extends QSTileView implements TileAdapter.CustomizeView { - private boolean mShowAppLabel; - - public CustomizeTileView(Context context, QSIconView icon) { - super(context, icon); - } - - @Override - public void setShowAppLabel(boolean showAppLabel) { - mShowAppLabel = showAppLabel; - mSecondLine.setVisibility(showAppLabel ? View.VISIBLE : View.GONE); - mLabel.setSingleLine(showAppLabel); - } - - @Override - protected void handleStateChanged(QSTile.State state) { - super.handleStateChanged(state); - mSecondLine.setVisibility(mShowAppLabel ? View.VISIBLE : View.GONE); - } - - @Override - protected boolean animationsEnabled() { - return false; - } - - @Override - public boolean isLongClickable() { - return false; - } - - @Override - public void changeState(QSTile.State state) { - handleStateChanged(state); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt new file mode 100644 index 0000000000000..287d815c2e4ab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt @@ -0,0 +1,64 @@ +/* + * 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.customize + +import android.content.Context +import android.text.TextUtils +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.QSTileViewImpl + +/** + * Class for displaying tiles in [QSCustomizer] with the new design (labels on the side). + */ +class CustomizeTileView( + context: Context, + icon: QSIconView +) : QSTileViewImpl(context, icon, collapsed = false) { + + var showAppLabel = false + set(value) { + field = value + secondaryLabel.visibility = getVisibilityState(secondaryLabel.text) + } + + override fun handleStateChanged(state: QSTile.State) { + super.handleStateChanged(state) + showRippleEffect = false + secondaryLabel.visibility = getVisibilityState(state.secondaryLabel) + } + + private fun getVisibilityState(text: CharSequence?): Int { + return if (showAppLabel && !TextUtils.isEmpty(text)) { + VISIBLE + } else { + GONE + } + } + + override fun animationsEnabled(): Boolean { + return false + } + + override fun isLongClickable(): Boolean { + return false + } + + fun changeState(state: QSTile.State) { + handleStateChanged(state) + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt deleted file mode 100644 index 7977b4904a7da..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.android.systemui.qs.customize - -import android.content.Context -import android.view.View -import com.android.systemui.plugins.qs.QSIconView -import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.tileimpl.QSTileViewHorizontal - -/** - * Class for displaying tiles in [QSCustomizer] with the new design (labels on the side). - * - * This is a class parallel to [CustomizeTileView], but inheriting from [QSTileViewHorizontal]. - */ -class CustomizeTileViewHorizontal( - context: Context, - icon: QSIconView -) : QSTileViewHorizontal(context, icon, collapsed = false), - TileAdapter.CustomizeView { - - private var showAppLabel = false - - override fun setShowAppLabel(showAppLabel: Boolean) { - this.showAppLabel = showAppLabel - mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE - mLabel.isSingleLine = showAppLabel - } - - override fun handleStateChanged(state: QSTile.State) { - super.handleStateChanged(state) - mShowRippleEffect = false - mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE - } - - override fun animationsEnabled(): Boolean { - return false - } - - override fun isLongClickable(): Boolean { - return false - } - - override fun changeState(state: QSTile.State) { - handleStateChanged(state) - } -} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 50805330cf1f8..505f5866c105e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -43,7 +43,6 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; -import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.customize.TileAdapter.Holder; @@ -53,7 +52,7 @@ import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.dagger.QSThemedContext; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; -import com.android.systemui.qs.tileimpl.QSTileView; +import com.android.systemui.qs.tileimpl.QSTileViewImpl; import java.util.ArrayList; import java.util.List; @@ -280,7 +279,7 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta } FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent, false); - View view = new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context)); + View view = new CustomizeTileView(context, new QSIconViewImpl(context)); frame.addView(view); return new Holder(frame); } @@ -545,15 +544,12 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta } public class Holder extends ViewHolder { - private QSTileView mTileView; + private QSTileViewImpl mTileView; public Holder(View itemView) { super(itemView); if (itemView instanceof FrameLayout) { - mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0); - if (mTileView instanceof CustomizeTileView) { - mTileView.setBackground(null); - } + mTileView = (QSTileViewImpl) ((FrameLayout) itemView).getChildAt(0); mTileView.getIcon().disableAnimation(); mTileView.setTag(this); ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate); @@ -561,8 +557,8 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta } @Nullable - public CustomizeView getTileAsCustomizeView() { - return (CustomizeView) mTileView; + public CustomizeTileView getTileAsCustomizeView() { + return (CustomizeTileView) mTileView; } public void clearDrag() { @@ -570,8 +566,8 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta if (mTileView instanceof CustomizeTileView) { mTileView.findViewById(R.id.tile_label).clearAnimation(); mTileView.findViewById(R.id.tile_label).setAlpha(1); - mTileView.getAppLabel().clearAnimation(); - mTileView.getAppLabel().setAlpha(.6f); + mTileView.getSecondaryLabel().clearAnimation(); + mTileView.getSecondaryLabel().setAlpha(.6f); } } @@ -584,7 +580,7 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta mTileView.findViewById(R.id.tile_label).animate() .setDuration(DRAG_LENGTH) .alpha(0); - mTileView.getAppLabel().animate() + mTileView.getSecondaryLabel().animate() .setDuration(DRAG_LENGTH) .alpha(0); } @@ -599,7 +595,7 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta mTileView.findViewById(R.id.tile_label).animate() .setDuration(DRAG_LENGTH) .alpha(1); - mTileView.getAppLabel().animate() + mTileView.getSecondaryLabel().animate() .setDuration(DRAG_LENGTH) .alpha(.6f); } @@ -765,7 +761,7 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta int position = mCurrentDrag.getAdapterPosition(); if (position == RecyclerView.NO_POSITION) return; TileInfo info = mTiles.get(position); - ((CustomizeView) mCurrentDrag.mTileView).setShowAppLabel( + ((CustomizeTileView) mCurrentDrag.mTileView).setShowAppLabel( position > mEditIndex && !info.isSystem); mCurrentDrag.stopDrag(); mCurrentDrag = null; @@ -825,9 +821,4 @@ public class TileAdapter extends RecyclerView.Adapter implements TileSta public void onSwiped(ViewHolder viewHolder, int direction) { } }; - - interface CustomizeView { - void setShowAppLabel(boolean showAppLabel); - void changeState(@NonNull QSTile.State state); - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java deleted file mode 100644 index 962537a01fe20..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2017 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.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.Button; -import android.widget.RelativeLayout; - -/** - * Used for QS tile labels - */ -public class ButtonRelativeLayout extends RelativeLayout { - - private View mIgnoredView; - - public ButtonRelativeLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public CharSequence getAccessibilityClassName() { - return Button.class.getName(); - } - - /** - * Set a view to be ignored for measure. - * - * The view will be measured and laid out, but its size will be subtracted from the total size - * of this view. It assumes that this view only contributes vertical height. - */ - public void setIgnoredView(View view) { - if (mIgnoredView == null || mIgnoredView.getParent() == this) { - mIgnoredView = view; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mIgnoredView != null && mIgnoredView.getVisibility() != GONE) { - int height = mIgnoredView.getMeasuredHeight(); - MarginLayoutParams lp = (MarginLayoutParams) mIgnoredView.getLayoutParams(); - height = height - lp.bottomMargin - lp.topMargin; - setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() - height); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt new file mode 100644 index 0000000000000..705576059b581 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt @@ -0,0 +1,58 @@ +/* + * 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.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout + +/** + * [LinearLayout] that can ignore the last child for measuring. + * + * The view is measured as regularlt, then if [ignoreLastView] is true: + * * In [LinearLayout.VERTICAL] orientation, the height of the last view is subtracted from the + * final measured height. + * * In [LinearLayout.HORIZONTAL] orientation, the width of the last view is subtracted from the + * final measured width. + * + * This allows to measure the view and position it where it should, without it amounting to the + * total size (only in the direction of layout). + */ +class IgnorableChildLinearLayout @JvmOverloads constructor( + context: Context, + attributeSet: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : LinearLayout(context, attributeSet, defStyleAttr, defStyleRes) { + + var ignoreLastView = false + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (ignoreLastView && childCount > 0) { + val lastView = getChildAt(childCount - 1) + val lp = lastView.layoutParams as MarginLayoutParams + if (orientation == VERTICAL) { + val height = lastView.measuredHeight + lp.bottomMargin + lp.topMargin + setMeasuredDimension(measuredWidth, measuredHeight - height) + } else { + val width = lastView.measuredWidth + lp.leftMargin + lp.rightMargin + setMeasuredDimension(measuredWidth - width, measuredHeight) + } + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 3437dd595152a..8f7c493417ecd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -243,6 +243,6 @@ public class QSFactoryImpl implements QSFactory { @Override public QSTileView createTileView(Context context, QSTile tile, boolean collapsedView) { QSIconView icon = tile.createTileView(context); - return new QSTileViewHorizontal(context, icon, collapsedView); + return new QSTileViewImpl(context, icon, collapsedView); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index 31526bf8f5e12..8280f98114e2b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -14,22 +14,24 @@ package com.android.systemui.qs.tileimpl; -import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Animatable2; import android.graphics.drawable.Animatable2.AnimationCallback; import android.graphics.drawable.Drawable; +import android.service.quicksettings.Tile; +import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.ImageView.ScaleType; +import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; @@ -43,7 +45,7 @@ public class QSIconViewImpl extends QSIconView { public static final long QS_ANIM_LENGTH = 350; protected final View mIcon; - protected final int mIconSizePx; + protected int mIconSizePx; private boolean mAnimationEnabled = true; private int mState = -1; private int mTint; @@ -53,12 +55,18 @@ public class QSIconViewImpl extends QSIconView { super(context); final Resources res = context.getResources(); - mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size); + mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_icon_size); mIcon = createIcon(); addView(mIcon); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mIconSizePx = getContext().getResources().getDimensionPixelSize(R.dimen.qs_icon_size); + } + public void disableAnimation() { mAnimationEnabled = false; } @@ -169,7 +177,7 @@ public class QSIconViewImpl extends QSIconView { } protected int getColor(int state) { - return getColorForState(getContext(), state); + return getIconColorForState(getContext(), state); } private void animateGrayScale(int fromColor, int toColor, ImageView iv, @@ -229,4 +237,21 @@ public class QSIconViewImpl extends QSIconView { protected final void layout(View child, int left, int top) { child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); } + + /** + * Color to tint the tile icon based on state + */ + public static int getIconColorForState(Context context, int state) { + switch (state) { + case Tile.STATE_UNAVAILABLE: + return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorTertiary); + case Tile.STATE_INACTIVE: + return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); + case Tile.STATE_ACTIVE: + return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary); + default: + Log.e("QSIconView", "Invalid state " + state); + return 0; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java deleted file mode 100644 index c973e8c6fc2c4..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (C) 2017 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 static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION; - -import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.RippleDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.PathShape; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.service.quicksettings.Tile; -import android.text.TextUtils; -import android.util.Log; -import android.util.PathParser; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.Switch; - -import com.android.settingslib.Utils; -import com.android.systemui.R; -import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.plugins.qs.QSTile.BooleanState; - -public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { - - private static final String TAG = "QSTileBaseView"; - private static final int ICON_MASK_ID = com.android.internal.R.string.config_icon_mask; - protected final Handler mHandler = new H(); - private final int[] mLocInScreen = new int[2]; - protected final FrameLayout mIconFrame; - protected QSIconView mIcon; - protected RippleDrawable mRipple; - protected Drawable mTileBackground; - private String mAccessibilityClass; - private boolean mTileState; - protected boolean mCollapsedView; - protected boolean mShowRippleEffect = true; - private float mStrokeWidthActive; - private float mStrokeWidthInactive; - - protected final ImageView mBg; - private final int mColorActive; - private final int mColorInactive; - private int mCircleColor; - private int mBgSize; - - private static final int INVALID = -1; - private CharSequence mStateDescriptionDeltas = null; - private CharSequence mLastStateDescription; - private int mLastState = INVALID; - - public QSTileBaseView(Context context, QSIconView icon) { - this(context, icon, false); - } - - public QSTileBaseView(Context context, QSIconView icon, boolean collapsedView) { - super(context); - // Default to Quick Tile padding, and QSTileView will specify its own padding. - int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding); - mIconFrame = new FrameLayout(context); - mStrokeWidthActive = context.getResources() - .getDimension(com.android.internal.R.dimen.config_qsTileStrokeWidthActive); - mStrokeWidthInactive = context.getResources() - .getDimension(com.android.internal.R.dimen.config_qsTileStrokeWidthInactive); - int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); - addView(mIconFrame, new LayoutParams(size, size)); - mBg = new ImageView(getContext()); - Path path = new Path(PathParser.createPathFromPathData( - context.getResources().getString(ICON_MASK_ID))); - float pathSize = AdaptiveIconDrawable.MASK_SIZE; - PathShape p = new PathShape(path, pathSize, pathSize); - ShapeDrawable d = new ShapeDrawable(p); - d.setTintList(ColorStateList.valueOf(Color.TRANSPARENT)); - float backgroundStrokeWidth = context.getResources() - .getDimension(R.dimen.qs_tile_icon_background_stroke_width); - if (backgroundStrokeWidth > 0) { - d.getPaint().setStyle(Paint.Style.STROKE); - d.getPaint().setStrokeWidth(backgroundStrokeWidth); - } - int bgSize = context.getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size); - d.setIntrinsicHeight(bgSize); - d.setIntrinsicWidth(bgSize); - mBg.setImageDrawable(d); - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(bgSize, bgSize, Gravity.CENTER); - mIconFrame.addView(mBg, lp); - mBg.setLayoutParams(lp); - mIcon = icon; - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, - Gravity.CENTER); - mIconFrame.addView(mIcon, params); - mIconFrame.setClipChildren(false); - mIconFrame.setClipToPadding(false); - - mTileBackground = newTileBackground(); - if (mTileBackground instanceof RippleDrawable) { - setRipple((RippleDrawable) mTileBackground); - } - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - setBackground(mTileBackground); - - mColorActive = Utils.getColorAttrDefaultColor(context, - com.android.internal.R.attr.colorAccentPrimary); - mColorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor); - - setPadding(0, 0, 0, 0); - setClipChildren(false); - setClipToPadding(false); - mCollapsedView = collapsedView; - setFocusable(true); - } - - public View getBgCircle() { - return mBg; - } - - protected Drawable newTileBackground() { - final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless}; - final TypedArray ta = getContext().obtainStyledAttributes(attrs); - final Drawable d = ta.getDrawable(0); - ta.recycle(); - return d; - } - - private void setRipple(RippleDrawable tileBackground) { - mRipple = tileBackground; - if (getWidth() != 0) { - updateRippleSize(); - } - } - - protected void updateRippleSize() { - // center the touch feedback on the center of the icon, and dial it down a bit - final int cx = mIconFrame.getMeasuredWidth() / 2 + mIconFrame.getLeft(); - final int cy = mIconFrame.getMeasuredHeight() / 2 + mIconFrame.getTop(); - final int rad = (int) (mIcon.getHeight() * .85f); - mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad); - } - - @Override - public void init(QSTile tile) { - init(v -> tile.click(this), v -> tile.secondaryClick(this), view -> { - tile.longClick(this); - return true; - }); - } - - public void init(OnClickListener click, OnClickListener secondaryClick, - OnLongClickListener longClick) { - setOnClickListener(click); - setOnLongClickListener(longClick); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - if (mRipple != null) { - updateRippleSize(); - } - } - - @Override - public boolean hasOverlappingRendering() { - // Avoid layers for this layout - we don't need them. - return false; - } - - /** - * Update the accessibility order for this view. - * - * @param previousView the view which should be before this one - * @return the last view in this view which is accessible - */ - public View updateAccessibilityOrder(View previousView) { - setAccessibilityTraversalAfter(previousView.getId()); - return this; - } - - public void onStateChanged(QSTile.State state) { - mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget(); - } - - private void updateStrokeShapeWidth(QSTile.State state) { - Resources resources = getContext().getResources(); - if (!(mBg.getDrawable() instanceof ShapeDrawable)) { - return; - } - ShapeDrawable d = (ShapeDrawable) mBg.getDrawable(); - d.getPaint().setStyle(Paint.Style.FILL); - switch (state.state) { - case Tile.STATE_INACTIVE: - if (mStrokeWidthInactive >= 0) { - d.getPaint().setStyle(Paint.Style.STROKE); - d.getPaint().setStrokeWidth(mStrokeWidthInactive); - } - break; - case Tile.STATE_ACTIVE: - if (mStrokeWidthActive >= 0) { - d.getPaint().setStyle(Paint.Style.STROKE); - d.getPaint().setStrokeWidth(mStrokeWidthActive); - } - break; - } - } - - protected void handleStateChanged(QSTile.State state) { - updateStrokeShapeWidth(state); - int circleColor = getCircleColor(state.state); - boolean allowAnimations = animationsEnabled(); - if (circleColor != mCircleColor) { - if (allowAnimations) { - ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor) - .setDuration(QS_ANIM_LENGTH); - animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf( - (Integer) animation.getAnimatedValue()))); - animator.start(); - } else { - QSIconViewImpl.setTint(mBg, circleColor); - } - mCircleColor = circleColor; - } - - mShowRippleEffect = state.showRippleEffect; - setClickable(state.state != Tile.STATE_UNAVAILABLE); - setLongClickable(state.handlesLongClick); - mIcon.setIcon(state, allowAnimations); - setContentDescription(state.contentDescription); - final StringBuilder stateDescription = new StringBuilder(); - String text = ""; - switch (state.state) { - case Tile.STATE_UNAVAILABLE: - text = mContext.getString(R.string.tile_unavailable); - break; - case Tile.STATE_INACTIVE: - if (state instanceof QSTile.BooleanState) { - text = mContext.getString(R.string.switch_bar_off); - } - break; - case Tile.STATE_ACTIVE: - if (state instanceof QSTile.BooleanState) { - text = mContext.getString(R.string.switch_bar_on); - } - break; - default: - break; - } - if (!TextUtils.isEmpty(text)) { - stateDescription.append(text); - if (TextUtils.isEmpty(state.secondaryLabel)) { - state.secondaryLabel = text; - } - } - if (!TextUtils.isEmpty(state.stateDescription)) { - stateDescription.append(", "); - stateDescription.append(state.stateDescription); - if (mLastState != INVALID && state.state == mLastState - && !state.stateDescription.equals(mLastStateDescription)) { - mStateDescriptionDeltas = state.stateDescription; - } - } - setStateDescription(stateDescription.toString()); - mLastState = state.state; - mLastStateDescription = state.stateDescription; - - mAccessibilityClass = - state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName; - if (state instanceof QSTile.BooleanState) { - boolean newState = ((BooleanState) state).value; - if (mTileState != newState) { - mTileState = newState; - } - } - } - - /* The view should not be animated if it's not on screen and no part of it is visible. - */ - protected boolean animationsEnabled() { - if (!isShown()) { - return false; - } - if (getAlpha() != 1f) { - return false; - } - getLocationOnScreen(mLocInScreen); - return mLocInScreen[1] >= -getHeight(); - } - - protected int getCircleColor(int state) { - switch (state) { - case Tile.STATE_ACTIVE: - return mColorActive; - case Tile.STATE_INACTIVE: - case Tile.STATE_UNAVAILABLE: - return mColorInactive; - default: - Log.e(TAG, "Invalid state " + state); - return 0; - } - } - - @Override - public void setClickable(boolean clickable) { - super.setClickable(clickable); - setBackground(clickable && mShowRippleEffect ? mRipple : null); - } - - @Override - public int getDetailY() { - return getTop() + getHeight() / 2; - } - - public QSIconView getIcon() { - return mIcon; - } - - public View getIconWithBackground() { - return mIconFrame; - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - if (!TextUtils.isEmpty(mAccessibilityClass)) { - event.setClassName(mAccessibilityClass); - } - if (event.getContentChangeTypes() == CONTENT_CHANGE_TYPE_STATE_DESCRIPTION - && mStateDescriptionDeltas != null) { - event.getText().add(mStateDescriptionDeltas); - mStateDescriptionDeltas = null; - } - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - // Clear selected state so it is not announce by talkback. - info.setSelected(false); - if (!TextUtils.isEmpty(mAccessibilityClass)) { - info.setClassName(mAccessibilityClass); - if (Switch.class.getName().equals(mAccessibilityClass)) { - String label = getResources().getString( - mTileState ? R.string.switch_bar_on : R.string.switch_bar_off); - // Set the text here for tests in - // android.platform.test.scenario.sysui.quicksettings. Can be removed when - // UiObject2 has a new getStateDescription() API and tests are updated. - info.setText(label); - info.setChecked(mTileState); - info.setCheckable(true); - if (isLongClickable()) { - info.addAction( - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfo.AccessibilityAction - .ACTION_LONG_CLICK.getId(), - getResources().getString( - R.string.accessibility_long_click_tile))); - } - } - } - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); - sb.append("locInScreen=(" + mLocInScreen[0] + ", " + mLocInScreen[1] + ")"); - sb.append(", iconView=" + mIcon.toString()); - sb.append(", tileState=" + mTileState); - sb.append("]"); - return sb.toString(); - } - - private class H extends Handler { - private static final int STATE_CHANGED = 1; - - public H() { - super(Looper.getMainLooper()); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == STATE_CHANGED) { - handleStateChanged((QSTile.State) msg.obj); - } - } - } -} \ 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 5ff785dc32d97..47d80bb0d16fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -38,7 +38,6 @@ import android.metrics.LogMaker; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.service.quicksettings.Tile; import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; @@ -56,7 +55,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; -import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; @@ -563,21 +561,6 @@ public abstract class QSTileImpl implements QSTile, Lifecy return mReadyState == READY_STATE_READY; } - public static int getColorForState(Context context, int state) { - switch (state) { - case Tile.STATE_UNAVAILABLE: - return Utils.getDisabled(context, - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary)); - case Tile.STATE_INACTIVE: - return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary); - case Tile.STATE_ACTIVE: - return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary); - default: - Log.e("QSTile", "Invalid state " + state); - return 0; - } - } - protected final class H extends Handler { private static final int ADD_CALLBACK = 1; private static final int CLICK = 2; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java deleted file mode 100644 index 50417e9ab2592..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2017 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.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.service.quicksettings.Tile; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.settingslib.Utils; -import com.android.systemui.FontSizeUtils; -import com.android.systemui.R; -import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile; - -import java.util.Objects; - -/** View that represents a standard quick settings tile. **/ -public class QSTileView extends QSTileBaseView { - protected int mMaxLabelLines = 2; - private View mDivider; - protected TextView mLabel; - protected TextView mSecondLine; - private ImageView mPadLock; - protected int mState; - protected ButtonRelativeLayout mLabelContainer; - private View mExpandIndicator; - private View mExpandSpace; - protected ColorStateList mColorLabelActive; - protected ColorStateList mColorLabelInactive; - private ColorStateList mColorLabelUnavailable; - protected boolean mDualTargetAllowed = false; - - public QSTileView(Context context, QSIconView icon) { - this(context, icon, false); - } - - public QSTileView(Context context, QSIconView icon, boolean collapsedView) { - super(context, icon, collapsedView); - - setClipChildren(false); - setClipToPadding(false); - - setClickable(true); - setId(View.generateViewId()); - createLabel(); - setOrientation(VERTICAL); - setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); - mColorLabelActive = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary); - mColorLabelInactive = mColorLabelActive; - // The text color for unavailable tiles is textColorSecondary, same as secondaryLabel for - // contrast purposes - mColorLabelUnavailable = Utils.getColorAttr(getContext(), - android.R.attr.textColorSecondary); - } - - TextView getLabel() { - return mLabel; - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size); - FontSizeUtils.updateFontSize(mSecondLine, R.dimen.qs_tile_text_size); - } - - @Override - public int getDetailY() { - return getTop() + mLabelContainer.getTop() + mLabelContainer.getHeight() / 2; - } - - protected void createLabel() { - mLabelContainer = (ButtonRelativeLayout) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_tile_label, this, false); - mLabelContainer.setClipChildren(false); - mLabelContainer.setClipToPadding(false); - mLabel = mLabelContainer.findViewById(R.id.tile_label); - mPadLock = mLabelContainer.findViewById(R.id.restricted_padlock); - mDivider = mLabelContainer.findViewById(R.id.underline); - mExpandIndicator = mLabelContainer.findViewById(R.id.expand_indicator); - mExpandSpace = mLabelContainer.findViewById(R.id.expand_space); - mSecondLine = mLabelContainer.findViewById(R.id.app_label); - addView(mLabelContainer); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mLabel.setSingleLine(false); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // Remeasure view if the primary label requires more than mMaxLabelLines lines or the - // secondary label text will be cut off. - if (shouldLabelBeSingleLine()) { - mLabel.setSingleLine(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - protected boolean shouldLabelBeSingleLine() { - if (mCollapsedView) return true; - if (mLabel.getLineCount() > mMaxLabelLines) { - return true; - } else if (!TextUtils.isEmpty(mSecondLine.getText()) - && mLabel.getLineCount() > mMaxLabelLines - 1) { - return true; - } - return false; - } - - @Override - protected void handleStateChanged(QSTile.State state) { - super.handleStateChanged(state); - if (!Objects.equals(mLabel.getText(), state.label) || mState != state.state) { - ColorStateList labelColor = getLabelColor(state.state); - changeLabelColor(labelColor); - mState = state.state; - mLabel.setText(state.label); - } - if (!Objects.equals(mSecondLine.getText(), state.secondaryLabel)) { - mSecondLine.setText(state.secondaryLabel); - mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) - ? View.GONE : View.VISIBLE); - } - boolean dualTarget = mDualTargetAllowed && state.dualTarget; - handleExpand(dualTarget); - mLabelContainer.setContentDescription(dualTarget ? state.dualLabelContentDescription - : null); - if (dualTarget != mLabelContainer.isClickable()) { - mLabelContainer.setClickable(dualTarget); - mLabelContainer.setLongClickable(dualTarget); - mLabelContainer.setBackground(dualTarget ? newTileBackground() : null); - } - mLabel.setEnabled(!state.disabledByPolicy); - mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE); - } - - protected final ColorStateList getLabelColor(int state) { - if (state == Tile.STATE_ACTIVE) { - return mColorLabelActive; - } else if (state == Tile.STATE_INACTIVE) { - return mColorLabelInactive; - } - return mColorLabelUnavailable; - } - - protected void changeLabelColor(ColorStateList color) { - mLabel.setTextColor(color); - } - - protected void handleExpand(boolean dualTarget) { - mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE); - mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE); - } - - @Override - public void init(OnClickListener click, OnClickListener secondaryClick, - OnLongClickListener longClick) { - super.init(click, secondaryClick, longClick); - mLabelContainer.setOnClickListener(secondaryClick); - mLabelContainer.setOnLongClickListener(longClick); - mLabelContainer.setClickable(false); - mLabelContainer.setLongClickable(false); - } - - public TextView getAppLabel() { - return mSecondLine; - } - - @Nullable - @Override - public View getLabelContainer() { - return mLabelContainer; - } - - @Override - public View getSecondaryLabel() { - return mSecondLine; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt deleted file mode 100644 index 70d51ee1c516b..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2020 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.animation.ValueAnimator -import android.content.Context -import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.drawable.Drawable -import android.graphics.drawable.RippleDrawable -import android.service.quicksettings.Tile.STATE_ACTIVE -import android.view.Gravity -import android.view.View -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.RelativeLayout -import com.android.systemui.R -import com.android.systemui.plugins.qs.QSIconView -import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState - -open class QSTileViewHorizontal( - context: Context, - icon: QSIconView, - collapsed: Boolean -) : QSTileView(context, icon, collapsed), HeightOverrideable { - - protected var colorBackgroundDrawable: Drawable? = null - private var paintColor = Color.WHITE - private var paintAnimator: ValueAnimator? = null - private var labelAnimator: ValueAnimator? = null - private var sideView: ImageView = ImageView(mContext) - override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE - - init { - orientation = HORIZONTAL - gravity = Gravity.CENTER_VERTICAL or Gravity.START - mDualTargetAllowed = false - val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding) - setPadding(padding, paddingTop, padding, paddingBottom) - - mBg.setImageDrawable(null) - mIconFrame.removeAllViews() - removeView(mIconFrame) - val iconSize = context.resources.getDimensionPixelSize(R.dimen.qs_icon_size) - addView(mIcon, 0, LayoutParams(iconSize, iconSize)) - - sideView.visibility = View.GONE - val sideViewLayoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - gravity = Gravity.CENTER_VERTICAL - marginStart = context.resources.getDimensionPixelSize(R.dimen.qs_label_container_margin) - } - addView(sideView, -1, sideViewLayoutParams) - sideView.adjustViewBounds = true - sideView.scaleType = ImageView.ScaleType.FIT_CENTER - - mColorLabelActive = ColorStateList.valueOf(getColorForState(getContext(), STATE_ACTIVE)) - changeLabelColor(getLabelColor(mState)) // Matches the default state of the tile - } - - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - super.onLayout(changed, l, t, r, b) - if (heightOverride != HeightOverrideable.NO_OVERRIDE) { - bottom = top + heightOverride - } - } - - override fun createLabel() { - super.createLabel() - findViewById(R.id.label_group)?.apply { - gravity = Gravity.START - (layoutParams as? RelativeLayout.LayoutParams)?.apply { - removeRule(RelativeLayout.ALIGN_PARENT_TOP) - } - } - mLabelContainer.setPadding(0, 0, 0, 0) - (mLabelContainer.layoutParams as MarginLayoutParams).apply { - marginStart = context.resources.getDimensionPixelSize(R.dimen.qs_label_container_margin) - marginEnd = 0 - gravity = Gravity.CENTER_VERTICAL or Gravity.START - } - mLabel.gravity = Gravity.START - mLabel.textDirection = TEXT_DIRECTION_LOCALE - mSecondLine.gravity = Gravity.START - mSecondLine.textDirection = TEXT_DIRECTION_LOCALE - - if (mCollapsedView) { - mSecondLine.alpha = 0f - mLabelContainer.setIgnoredView(mSecondLine) - } - } - - override fun shouldLabelBeSingleLine(): Boolean { - return true - } - - override fun updateRippleSize() { - } - - override fun newTileBackground(): Drawable? { - val ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable - colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background) - return ripple - } - - override fun setClickable(clickable: Boolean) { - super.setClickable(clickable) - background = if (clickable && mShowRippleEffect) { - mRipple?.also { - // In case that the colorBackgroundDrawable was used as the background, make sure - // it has the correct callback instead of null - colorBackgroundDrawable?.callback = it - } - } else { - colorBackgroundDrawable - } - } - - override fun handleStateChanged(state: QSTile.State) { - super.handleStateChanged(state) - mLabelContainer.background = null - - val allowAnimations = animationsEnabled() && paintColor != Color.WHITE - val newColor = getCircleColor(state.state) - if (allowAnimations) { - animateBackground(newColor) - } else { - clearBackgroundAnimator() - colorBackgroundDrawable?.setTintList(ColorStateList.valueOf(newColor))?.also { - paintColor = newColor - } - paintColor = newColor - } - loadSideViewDrawableIfNecessary(state) - } - - private fun animateBackground(newBackgroundColor: Int) { - if (newBackgroundColor != paintColor) { - clearBackgroundAnimator() - paintAnimator = ValueAnimator.ofArgb(paintColor, newBackgroundColor) - .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { - addUpdateListener { animation: ValueAnimator -> - val c = animation.animatedValue as Int - colorBackgroundDrawable?.setTintList(ColorStateList.valueOf(c))?.also { - paintColor = c - } - } - start() - } - } - } - - override fun changeLabelColor(color: ColorStateList) { - val allowAnimations = animationsEnabled() - val currentColor = mLabel.textColors.defaultColor - if (currentColor != color.defaultColor) { - clearLabelAnimator() - if (allowAnimations) { - labelAnimator = ValueAnimator.ofArgb(currentColor, color.defaultColor) - .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { - addUpdateListener { - setLabelsColor(ColorStateList.valueOf(it.animatedValue as Int)) - } - start() - } - } else { - setLabelsColor(color) - } - } - } - - private fun setLabelsColor(color: ColorStateList) { - mLabel.setTextColor(color) - mSecondLine.setTextColor(color) - } - - private fun clearBackgroundAnimator() { - paintAnimator?.cancel()?.also { paintAnimator = null } - } - - private fun clearLabelAnimator() { - labelAnimator?.cancel()?.also { labelAnimator = null } - } - - private fun loadSideViewDrawableIfNecessary(state: QSTile.State) { - if (state.sideViewDrawable != null) { - sideView.setImageDrawable(state.sideViewDrawable) - sideView.visibility = View.VISIBLE - } else { - sideView.setImageDrawable(null) - sideView.visibility = GONE - } - } - - override fun handleExpand(dualTarget: Boolean) {} - - override fun getSecondaryIcon(): View { - return sideView - } -} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt new file mode 100644 index 0000000000000..954321e50587c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -0,0 +1,516 @@ +/* + * 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.animation.ValueAnimator +import android.content.Context +import android.content.res.ColorStateList +import android.content.res.Configuration +import android.content.res.Resources.ID_NULL +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.RippleDrawable +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.util.Log +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.Switch +import android.widget.TextView +import com.android.settingslib.Utils +import com.android.systemui.FontSizeUtils +import com.android.systemui.R +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.QSTile.BooleanState +import com.android.systemui.plugins.qs.QSTileView +import java.util.Objects + +private const val TAG = "QSTileViewImpl" +open class QSTileViewImpl @JvmOverloads constructor( + context: Context, + private val _icon: QSIconView, + private val collapsed: Boolean = false +) : QSTileView(context), HeightOverrideable { + + companion object { + private const val INVALID = -1 + } + + override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE + + private val colorActive = Utils.getColorAttrDefaultColor(context, + com.android.internal.R.attr.colorAccentPrimary) + private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor) + private val colorUnavailable = + Utils.getColorAttrDefaultColor(context, android.R.attr.colorBackground) + + private val colorLabelActive = + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse) + private val colorLabelInactive = + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + private val colorLabelUnavailable = + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorTertiary) + + private val sideView = ImageView(context) + private lateinit var label: TextView + protected lateinit var secondaryLabel: TextView + private lateinit var labelContainer: IgnorableChildLinearLayout + + protected var showRippleEffect = true + + private lateinit var ripple: RippleDrawable + private lateinit var colorBackgroundDrawable: Drawable + private var paintColor = Color.WHITE + private var paintAnimator: ValueAnimator? = null + private var labelAnimator: ValueAnimator? = null + private var secondaryLabelAnimator: ValueAnimator? = null + + private var accessibilityClass: String? = null + private var stateDescriptionDeltas: CharSequence? = null + private var lastStateDescription: CharSequence? = null + private var tileState = false + private var lastState = INVALID + + private val locInScreen = IntArray(2) + + init { + setId(generateViewId()) + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL or Gravity.START + importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_YES + clipChildren = false + clipToPadding = false + isFocusable = true + background = createTileBackground() + + val padding = resources.getDimensionPixelSize(R.dimen.qs_tile_padding) + val startPadding = resources.getDimensionPixelSize(R.dimen.qs_tile_start_padding) + setPaddingRelative(startPadding, padding, padding, padding) + + val iconSize = resources.getDimensionPixelSize(R.dimen.qs_icon_size) + addView(_icon, LayoutParams(iconSize, iconSize)) + + createAndAddLabels() + + sideView.visibility = GONE + val sideViewLayoutParams = LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + gravity = Gravity.CENTER_VERTICAL + marginStart = resources.getDimensionPixelSize(R.dimen.qs_label_container_margin) + } + addView(sideView, sideViewLayoutParams) + sideView.adjustViewBounds = true + sideView.scaleType = ImageView.ScaleType.FIT_CENTER + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + super.onConfigurationChanged(newConfig) + FontSizeUtils.updateFontSize(label, R.dimen.qs_tile_text_size) + FontSizeUtils.updateFontSize(secondaryLabel, R.dimen.qs_tile_text_size) + + val iconSize = context.resources.getDimensionPixelSize(R.dimen.qs_icon_size) + _icon.layoutParams.apply { + height = iconSize + width = iconSize + } + + val padding = resources.getDimensionPixelSize(R.dimen.qs_tile_padding) + val startPadding = resources.getDimensionPixelSize(R.dimen.qs_tile_start_padding) + setPaddingRelative(startPadding, padding, padding, padding) + + val labelMargin = context.resources.getDimensionPixelSize(R.dimen.qs_label_container_margin) + (labelContainer.layoutParams as MarginLayoutParams).apply { + marginStart = labelMargin + } + (sideView.layoutParams as MarginLayoutParams).apply { + marginStart = labelMargin + } + } + + private fun createAndAddLabels() { + labelContainer = LayoutInflater.from(context) + .inflate(R.layout.qs_tile_label, this, false) as IgnorableChildLinearLayout + label = labelContainer.requireViewById(R.id.tile_label) + secondaryLabel = labelContainer.requireViewById(R.id.app_label) + if (collapsed) { + labelContainer.ignoreLastView = true + secondaryLabel.alpha = 0f + } + label.setTextColor(getLabelColor(0)) // Default state + secondaryLabel.setTextColor(getSecondaryLabelColor(0)) + addView(labelContainer) + } + + fun createTileBackground(): Drawable { + ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable + colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background) + return ripple + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + super.onLayout(changed, l, t, r, b) + if (heightOverride != HeightOverrideable.NO_OVERRIDE) { + bottom = top + heightOverride + } + } + + override fun updateAccessibilityOrder(previousView: View?): View { + accessibilityTraversalAfter = previousView?.id ?: ID_NULL + return this + } + + override fun getIcon(): QSIconView { + return _icon + } + + override fun getIconWithBackground(): View { + return icon + } + + override fun init(tile: QSTile) { + init( + { v: View? -> tile.click(this) }, + { view: View? -> + tile.longClick(this) + true + } + ) + } + + private fun init( + click: OnClickListener?, + longClick: OnLongClickListener? + ) { + setOnClickListener(click) + onLongClickListener = longClick + } + + override fun onStateChanged(state: QSTile.State) { + post { + handleStateChanged(state) + } + } + + override fun getDetailY(): Int { + return top + height / 2 + } + + override fun hasOverlappingRendering(): Boolean { + // Avoid layers for this layout - we don't need them. + return false + } + + override fun setClickable(clickable: Boolean) { + super.setClickable(clickable) + background = if (clickable && showRippleEffect) { + ripple.also { + // In case that the colorBackgroundDrawable was used as the background, make sure + // it has the correct callback instead of null + colorBackgroundDrawable.callback = it + } + } else { + colorBackgroundDrawable + } + } + + override fun getLabelContainer(): View { + return labelContainer + } + + override fun getSecondaryLabel(): View { + return secondaryLabel + } + + override fun getSecondaryIcon(): View { + return sideView + } + + // Accessibility + + override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) { + super.onInitializeAccessibilityEvent(event) + if (!TextUtils.isEmpty(accessibilityClass)) { + event.className = accessibilityClass + } + if (event.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION && + stateDescriptionDeltas != null) { + event.text.add(stateDescriptionDeltas) + stateDescriptionDeltas = null + } + } + + override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) { + super.onInitializeAccessibilityNodeInfo(info) + // Clear selected state so it is not announce by talkback. + info.isSelected = false + if (!TextUtils.isEmpty(accessibilityClass)) { + info.className = accessibilityClass + if (Switch::class.java.name == accessibilityClass) { + val label = resources.getString( + if (tileState) R.string.switch_bar_on else R.string.switch_bar_off) + // Set the text here for tests in + // android.platform.test.scenario.sysui.quicksettings. Can be removed when + // UiObject2 has a new getStateDescription() API and tests are updated. + info.text = label + info.isChecked = tileState + info.isCheckable = true + if (isLongClickable) { + info.addAction( + AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id, + resources.getString( + R.string.accessibility_long_click_tile))) + } + } + } + } + + override fun toString(): String { + val sb = StringBuilder(javaClass.simpleName).append('[') + sb.append("locInScreen=(${locInScreen[0]}, ${locInScreen[1]})") + sb.append(", iconView=$_icon") + sb.append(", tileState=$tileState") + sb.append("]") + return sb.toString() + } + + // HANDLE STATE CHANGES RELATED METHODS + + protected open fun handleStateChanged(state: QSTile.State) { + val allowAnimations = animationsEnabled() && paintColor != Color.WHITE + showRippleEffect = state.showRippleEffect + isClickable = state.state != Tile.STATE_UNAVAILABLE + isLongClickable = state.handlesLongClick + icon.setIcon(state, allowAnimations) + contentDescription = state.contentDescription + + // Background color animation + val newColor = getCircleColor(state.state) + if (allowAnimations) { + animateBackground(newColor) + } else { + clearBackgroundAnimator() + colorBackgroundDrawable.setTintList(ColorStateList.valueOf(newColor)).also { + paintColor = newColor + } + paintColor = newColor + } + // + + // State handling and description + val stateDescription = StringBuilder() + val stateText = getStateText(state) + if (!TextUtils.isEmpty(stateText)) { + stateDescription.append(stateText) + if (TextUtils.isEmpty(state.secondaryLabel)) { + state.secondaryLabel = stateText + } + } + if (!TextUtils.isEmpty(state.stateDescription)) { + stateDescription.append(", ") + stateDescription.append(state.stateDescription) + if (lastState != INVALID && state.state == lastState && + state.stateDescription != lastStateDescription) { + stateDescriptionDeltas = state.stateDescription + } + } + + setStateDescription(stateDescription.toString()) + lastStateDescription = state.stateDescription + + accessibilityClass = if (state.state == Tile.STATE_UNAVAILABLE) { + null + } else { + state.expandedAccessibilityClassName + } + + if (state is BooleanState) { + val newState = state.value + if (tileState != newState) { + tileState = newState + } + } + // + + // Labels + if (!Objects.equals(label.text, state.label)) { + label.text = state.label + } + if (!Objects.equals(secondaryLabel.text, state.secondaryLabel)) { + secondaryLabel.text = state.secondaryLabel + secondaryLabel.visibility = if (TextUtils.isEmpty(state.secondaryLabel)) { + GONE + } else { + VISIBLE + } + } + + if (allowAnimations) { + animateLabelColor(getLabelColor(state.state)) + animateSecondaryLabelColor(getSecondaryLabelColor(state.state)) + } else { + label.setTextColor(getLabelColor(state.state)) + secondaryLabel.setTextColor(getSecondaryLabelColor(state.state)) + } + + label.isEnabled = !state.disabledByPolicy + + loadSideViewDrawableIfNecessary(state) + + lastState = state.state + } + + private fun loadSideViewDrawableIfNecessary(state: QSTile.State) { + if (state.sideViewDrawable != null) { + sideView.setImageDrawable(state.sideViewDrawable) + sideView.visibility = View.VISIBLE + } else { + sideView.setImageDrawable(null) + sideView.visibility = GONE + } + } + + 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) + } + } else { + "" + } + } + + /* + * The view should not be animated if it's not on screen and no part of it is visible. + */ + protected open fun animationsEnabled(): Boolean { + if (!isShown) { + return false + } + if (alpha != 1f) { + return false + } + getLocationOnScreen(locInScreen) + return locInScreen.get(1) >= -height + } + + private fun animateBackground(newBackgroundColor: Int) { + if (newBackgroundColor != paintColor) { + clearBackgroundAnimator() + paintAnimator = ValueAnimator.ofArgb(paintColor, newBackgroundColor) + .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { + addUpdateListener { animation: ValueAnimator -> + val c = animation.animatedValue as Int + colorBackgroundDrawable.setTintList(ColorStateList.valueOf(c)).also { + paintColor = c + } + } + start() + } + } + } + + private fun animateLabelColor(color: Int) { + val currentColor = label.textColors.defaultColor + if (currentColor != color) { + clearLabelAnimator() + labelAnimator = ValueAnimator.ofArgb(currentColor, color) + .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { + addUpdateListener { + label.setTextColor(it.animatedValue as Int) + } + start() + } + } + } + + private fun animateSecondaryLabelColor(color: Int) { + val currentColor = secondaryLabel.textColors.defaultColor + if (currentColor != color) { + clearSecondaryLabelAnimator() + secondaryLabelAnimator = ValueAnimator.ofArgb(currentColor, color) + .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { + addUpdateListener { + secondaryLabel.setTextColor(it.animatedValue as Int) + } + start() + } + } + } + + private fun clearBackgroundAnimator() { + paintAnimator?.cancel()?.also { paintAnimator = null } + } + + private fun clearLabelAnimator() { + labelAnimator?.cancel()?.also { labelAnimator = null } + } + + private fun clearSecondaryLabelAnimator() { + secondaryLabelAnimator?.cancel()?.also { secondaryLabelAnimator = null } + } + + private fun getCircleColor(state: Int): Int { + return when (state) { + Tile.STATE_ACTIVE -> colorActive + Tile.STATE_INACTIVE -> colorInactive + Tile.STATE_UNAVAILABLE -> colorUnavailable + else -> { + Log.e(TAG, "Invalid state $state") + 0 + } + } + } + + private fun getLabelColor(state: Int): Int { + return when (state) { + Tile.STATE_ACTIVE -> colorLabelActive + Tile.STATE_INACTIVE -> colorLabelInactive + Tile.STATE_UNAVAILABLE -> colorLabelUnavailable + else -> { + Log.e(TAG, "Invalid state $state") + 0 + } + } + } + + private fun getSecondaryLabelColor(state: Int): Int { + return when (state) { + Tile.STATE_ACTIVE -> colorLabelActive + Tile.STATE_INACTIVE, Tile.STATE_UNAVAILABLE -> colorLabelUnavailable + else -> { + Log.e(TAG, "Invalid state $state") + 0 + } + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index d2bbcd50d0e20..cfee3d87a6f97 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -59,6 +59,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSIconViewImpl; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -298,7 +299,7 @@ public class GarbageMonitor implements Dumpable { MemoryIconDrawable(Context context) { baseIcon = context.getDrawable(R.drawable.ic_memory).mutate(); dp = context.getResources().getDisplayMetrics().density; - paint.setColor(QSTileImpl.getColorForState(context, STATE_ACTIVE)); + paint.setColor(QSIconViewImpl.getIconColorForState(context, STATE_ACTIVE)); } public void setRss(long rss) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java index 54cee847b757a..24e47c5159a00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java @@ -35,7 +35,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; -import com.android.systemui.qs.tileimpl.QSTileView; +import com.android.systemui.qs.tileimpl.QSTileViewImpl; import org.junit.Before; import org.junit.Test; @@ -60,7 +60,7 @@ public class TileLayoutTest extends SysuiTestCase { private QSPanelControllerBase.TileRecord createTileRecord() { QSPanelControllerBase.TileRecord tileRecord = new QSPanelControllerBase.TileRecord(); tileRecord.tile = mock(QSTile.class); - tileRecord.tileView = spy(new QSTileView(mContext, new QSIconViewImpl(mContext))); + tileRecord.tileView = spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext))); return tileRecord; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt similarity index 84% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt rename to packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt index 998e070009cc9..42c89f9eb1834 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileBaseViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tileimpl +import android.content.Context import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner import android.text.TextUtils @@ -33,18 +34,18 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest -class QSTileBaseViewTest : SysuiTestCase() { +class QSTileViewImplTest : SysuiTestCase() { @Mock private lateinit var iconView: QSIconView - private lateinit var tileView: QSTileBaseView + private lateinit var tileView: FakeTileView @Before fun setUp() { MockitoAnnotations.initMocks(this) - tileView = QSTileBaseView(context, iconView, false) + tileView = FakeTileView(context, iconView, false) } @Test @@ -54,7 +55,7 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_UNAVAILABLE state.secondaryLabel = testString - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) } @@ -66,7 +67,7 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_INACTIVE state.secondaryLabel = testString - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) } @@ -78,7 +79,7 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_ACTIVE state.secondaryLabel = testString - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(state.secondaryLabel as CharSequence).isEqualTo(testString) } @@ -89,7 +90,7 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_INACTIVE state.secondaryLabel = "" - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() } @@ -100,7 +101,7 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_ACTIVE state.secondaryLabel = "" - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(TextUtils.isEmpty(state.secondaryLabel)).isTrue() } @@ -111,7 +112,7 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_UNAVAILABLE state.secondaryLabel = "" - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(state.secondaryLabel as CharSequence).isEqualTo( context.getString(R.string.tile_unavailable) @@ -124,7 +125,7 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_INACTIVE state.secondaryLabel = "" - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(state.secondaryLabel as CharSequence).isEqualTo( context.getString(R.string.switch_bar_off) @@ -137,10 +138,20 @@ class QSTileBaseViewTest : SysuiTestCase() { state.state = Tile.STATE_ACTIVE state.secondaryLabel = "" - tileView.handleStateChanged(state) + tileView.changeState(state) assertThat(state.secondaryLabel as CharSequence).isEqualTo( context.getString(R.string.switch_bar_on) ) } + + class FakeTileView( + context: Context, + icon: QSIconView, + collapsed: Boolean + ) : QSTileViewImpl(context, icon, collapsed) { + fun changeState(state: QSTile.State) { + handleStateChanged(state) + } + } } \ No newline at end of file