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:
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 78 KiB |
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user