Refactor QSTileView hierarchy

This CL collapses the hierarchy QSTileBaseView - QSTileView -
QSTileViewHorizontal into QSTileViewImpl. This can be done because now
there's a single type of view for the tiles. The benefit of this is that
we do not have to hack our way to undo things that are done in parent
classes. As part of this remove a lot of unnecessary files.

As part of this, bring some colors/sizes up to spec and make sure that
the sizes are reloaded in config changes.

Test: manual
Test: atest com.android.systemui.qs
Fixes: 187061459
Bug: 186057842
Change-Id: I63dbe8fa2b44833c11a486e84195e33e170c6f58
This commit is contained in:
Fabian Kozynski
2021-05-03 16:12:57 -04:00
parent 9195a05cc8
commit 9c8de7bdf1
24 changed files with 737 additions and 1131 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -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)

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
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.
@@ -14,84 +14,38 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.systemui.qs.tileimpl.ButtonRelativeLayout
<com.android.systemui.qs.tileimpl.IgnorableChildLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="12dp">
<LinearLayout
android:id="@+id/label_group"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_marginStart="@dimen/qs_label_container_margin"
android:layout_marginEnd="0dp"
android:layout_gravity="center_vertical | start">
<TextView
android:id="@+id/tile_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:gravity="center"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal">
<Space
android:id="@+id/expand_space"
android:layout_width="22dp"
android:layout_height="0dp"
android:visibility="gone" />
<TextView
android:id="@+id/tile_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:padding="0dp"
android:gravity="center"
android:ellipsize="marquee"
android:textAppearance="@style/TextAppearance.QS.TileLabel"/>
<ImageView android:id="@+id/restricted_padlock"
android:layout_width="@dimen/qs_tile_text_size"
android:layout_height="match_parent"
android:paddingBottom="@dimen/qs_tile_text_size"
android:src="@drawable/ic_info"
android:layout_marginLeft="@dimen/restricted_padlock_pading"
android:scaleType="centerInside"
android:visibility="gone" />
<ImageView
android:id="@+id/expand_indicator"
android:layout_marginStart="4dp"
android:layout_width="18dp"
android:layout_height="match_parent"
android:src="@drawable/qs_dual_tile_caret"
android:tint="?android:attr/textColorPrimary"
android:visibility="gone" />
</LinearLayout>
android:gravity="start"
android:textDirection="locale"
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.TileLabel"/>
<TextView
android:id="@+id/app_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@id/label_group"
android:layout_alignEnd="@id/label_group"
android:layout_below="@id/label_group"
android:clickable="false"
android:gravity="start"
android:textDirection="locale"
android:ellipsize="marquee"
android:singleLine="true"
android:padding="0dp"
android:visibility="gone"
android:gravity="center"
android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
android:textColor="?android:attr/textColorSecondary"/>
<View
android:id="@+id/underline"
android:layout_width="30dp"
android:layout_height="1dp"
android:layout_marginTop="2dp"
android:layout_alignStart="@id/label_group"
android:layout_alignEnd="@id/label_group"
android:layout_below="@id/label_group"
android:visibility="gone"
android:alpha="?android:attr/disabledAlpha"
android:background="?android:attr/colorForeground"/>
</com.android.systemui.qs.tileimpl.ButtonRelativeLayout>
</com.android.systemui.qs.tileimpl.IgnorableChildLinearLayout>

View File

@@ -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"

View File

@@ -555,7 +555,8 @@
<dimen name="qs_icon_size">20dp</dimen>
<dimen name="qs_label_container_margin">10dp</dimen>
<dimen name="qs_quick_tile_size">60dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
<dimen name="qs_tile_padding">12dp</dimen>
<dimen name="qs_tile_start_padding">16dp</dimen>
<dimen name="qs_header_gear_translation">16dp</dimen>
<dimen name="qs_header_tile_margin_bottom">18dp</dimen>
<dimen name="qs_page_indicator_width">16dp</dimen>
@@ -564,8 +565,6 @@
Scaled @dimen/qs_page_indicator-width by .4f.
-->
<dimen name="qs_page_indicator_dot_width">6.4dp</dimen>
<dimen name="qs_tile_side_label_padding">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">14sp</dimen>
<dimen name="qs_tile_divider_height">1dp</dimen>
<dimen name="qs_panel_padding">16dp</dimen>

View File

@@ -2187,6 +2187,9 @@
<!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] -->
<string name="tile_unavailable">Unavailable</string>
<!-- The tile in quick settings is disabled by a device administration policy [CHAR LIMIT=32] -->
<string name="tile_disabled">Disabled</string>
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string>

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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<Holder> 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<Holder> 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<Holder> 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<Holder> 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<Holder> 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<Holder> 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<Holder> 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<Holder> implements TileSta
public void onSwiped(ViewHolder viewHolder, int direction) {
}
};
interface CustomizeView {
void setShowAppLabel(boolean showAppLabel);
void changeState(@NonNull QSTile.State state);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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<TState extends State> 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;

View File

@@ -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;
}
}

View File

@@ -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<LinearLayout>(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
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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)
}
}
}