blob: 42e5adced68d361a20b852ce65e4bc987f0a5ad6 [file] [log] [blame]
Jason Monkc133d262015-10-27 12:32:45 -04001/*
Jason Monk702e2eb2017-03-03 16:53:44 -05002 * Copyright (C) 2017 The Android Open Source Project
Jason Monkc133d262015-10-27 12:32:45 -04003 *
Jason Monk702e2eb2017-03-03 16:53:44 -05004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
Jason Monkc133d262015-10-27 12:32:45 -04006 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
Jason Monk702e2eb2017-03-03 16:53:44 -05009 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
Jason Monkc133d262015-10-27 12:32:45 -040013 */
Jason Monk702e2eb2017-03-03 16:53:44 -050014package com.android.systemui.qs.tileimpl;
Jason Monkc133d262015-10-27 12:32:45 -040015
Jason Monkcb2307c2018-01-15 14:19:45 -050016import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
17
Jason Monkcb2307c2018-01-15 14:19:45 -050018import android.animation.ValueAnimator;
Jason Monkc133d262015-10-27 12:32:45 -040019import android.content.Context;
Jason Monkcb2307c2018-01-15 14:19:45 -050020import android.content.res.ColorStateList;
Jason Monkdc35dcb2015-12-04 16:36:15 -050021import android.content.res.TypedArray;
22import android.graphics.drawable.Drawable;
23import android.graphics.drawable.RippleDrawable;
Jason Monkc133d262015-10-27 12:32:45 -040024import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
Jason Monke2f05d22017-03-23 10:45:37 -040027import android.service.quicksettings.Tile;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040028import android.text.TextUtils;
Jason Monkcb2307c2018-01-15 14:19:45 -050029import android.util.Log;
Jason Monk32508852017-01-18 09:17:13 -050030import android.view.Gravity;
Jason Monkc133d262015-10-27 12:32:45 -040031import android.view.View;
Jason Monk32508852017-01-18 09:17:13 -050032import android.view.ViewGroup;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040033import android.view.accessibility.AccessibilityEvent;
34import android.view.accessibility.AccessibilityNodeInfo;
Jason Monk32508852017-01-18 09:17:13 -050035import android.widget.FrameLayout;
Jason Monkcb2307c2018-01-15 14:19:45 -050036import android.widget.ImageView;
37import android.widget.ImageView.ScaleType;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040038import android.widget.Switch;
Winsonc0d70582016-01-29 10:24:39 -080039
Jason Monkcb2307c2018-01-15 14:19:45 -050040import com.android.settingslib.Utils;
Jason Monkdc35dcb2015-12-04 16:36:15 -050041import com.android.systemui.R;
Jason Monkcb2307c2018-01-15 14:19:45 -050042import com.android.systemui.plugins.qs.QSIconView;
43import com.android.systemui.plugins.qs.QSTile;
Jason Monk90950d92017-06-20 13:16:05 -040044import com.android.systemui.plugins.qs.QSTile.BooleanState;
Jason Monkc133d262015-10-27 12:32:45 -040045
Jason Monk702e2eb2017-03-03 16:53:44 -050046public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
Jason Monkc133d262015-10-27 12:32:45 -040047
Jason Monk32508852017-01-18 09:17:13 -050048 private static final String TAG = "QSTileBaseView";
Jason Monkc133d262015-10-27 12:32:45 -040049 private final H mHandler = new H();
Jason Monkf13413e2017-02-15 15:49:32 -050050 private final FrameLayout mIconFrame;
Jiaquan He074ae462016-05-13 08:56:19 -070051 protected QSIconView mIcon;
52 protected RippleDrawable mRipple;
Jason Monkdc35dcb2015-12-04 16:36:15 -050053 private Drawable mTileBackground;
Julia Reynolds20aef8a2016-05-04 16:44:08 -040054 private String mAccessibilityClass;
55 private boolean mTileState;
56 private boolean mCollapsedView;
Jason Monk90950d92017-06-20 13:16:05 -040057 private boolean mClicked;
Jason Monkc133d262015-10-27 12:32:45 -040058
Jason Monkcb2307c2018-01-15 14:19:45 -050059 private final ImageView mBg;
60 private final int mColorActive;
61 private final int mColorInactive;
62 private final int mColorDisabled;
63 private int mCircleColor;
64
Jason Monkdc35dcb2015-12-04 16:36:15 -050065 public QSTileBaseView(Context context, QSIconView icon) {
Julia Reynolds20aef8a2016-05-04 16:44:08 -040066 this(context, icon, false);
67 }
68
69 public QSTileBaseView(Context context, QSIconView icon, boolean collapsedView) {
Jason Monkc133d262015-10-27 12:32:45 -040070 super(context);
Jason Monk32508852017-01-18 09:17:13 -050071 // Default to Quick Tile padding, and QSTileView will specify its own padding.
72 int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
73
Jason Monkf13413e2017-02-15 15:49:32 -050074 mIconFrame = new FrameLayout(context);
75 mIconFrame.setForegroundGravity(Gravity.CENTER);
Jason Monk32508852017-01-18 09:17:13 -050076 int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
Jason Monkf13413e2017-02-15 15:49:32 -050077 addView(mIconFrame, new LayoutParams(size, size));
Jason Monkcb2307c2018-01-15 14:19:45 -050078 mBg = new ImageView(getContext());
79 mBg.setScaleType(ScaleType.FIT_CENTER);
80 mBg.setImageResource(R.drawable.ic_qs_circle);
81 mIconFrame.addView(mBg);
Jason Monkdc35dcb2015-12-04 16:36:15 -050082 mIcon = icon;
Jason Monk32508852017-01-18 09:17:13 -050083 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
84 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
85 params.setMargins(0, padding, 0, padding);
Jason Monkf13413e2017-02-15 15:49:32 -050086 mIconFrame.addView(mIcon, params);
Rohan Shah28191fc2018-01-16 14:28:55 -080087 mIconFrame.setClipChildren(false);
88 mIconFrame.setClipToPadding(false);
Jason Monkdc35dcb2015-12-04 16:36:15 -050089
90 mTileBackground = newTileBackground();
91 if (mTileBackground instanceof RippleDrawable) {
92 setRipple((RippleDrawable) mTileBackground);
93 }
94 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
95 setBackground(mTileBackground);
96
Jason Monkcb2307c2018-01-15 14:19:45 -050097 mColorActive = Utils.getColorAttr(context, android.R.attr.colorAccent);
98 mColorDisabled = Utils.getDisabled(context,
99 Utils.getColorAttr(context, android.R.attr.textColorTertiary));
100 mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary);
101
Jason Monk32508852017-01-18 09:17:13 -0500102 setPadding(0, 0, 0, 0);
Jason Monk162011e2016-02-19 08:11:55 -0500103 setClipChildren(false);
104 setClipToPadding(false);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400105 mCollapsedView = collapsedView;
Min Yunf07f1702016-06-27 13:55:15 -0700106 setFocusable(true);
Jason Monkc133d262015-10-27 12:32:45 -0400107 }
108
Jason Monkcb2307c2018-01-15 14:19:45 -0500109 public View getBgCicle() {
110 return mBg;
111 }
112
Jiaquan He074ae462016-05-13 08:56:19 -0700113 protected Drawable newTileBackground() {
Jason Monk32508852017-01-18 09:17:13 -0500114 final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
115 final TypedArray ta = getContext().obtainStyledAttributes(attrs);
Jason Monkdc35dcb2015-12-04 16:36:15 -0500116 final Drawable d = ta.getDrawable(0);
117 ta.recycle();
118 return d;
Jason Monkc133d262015-10-27 12:32:45 -0400119 }
120
Jason Monkdc35dcb2015-12-04 16:36:15 -0500121 private void setRipple(RippleDrawable tileBackground) {
122 mRipple = tileBackground;
123 if (getWidth() != 0) {
Amin Shaikh30330992018-03-26 17:50:42 -0400124 updateRippleSize();
Jason Monkdc35dcb2015-12-04 16:36:15 -0500125 }
126 }
127
Amin Shaikh30330992018-03-26 17:50:42 -0400128 private void updateRippleSize() {
Jason Monkdc35dcb2015-12-04 16:36:15 -0500129 // center the touch feedback on the center of the icon, and dial it down a bit
Amin Shaikh30330992018-03-26 17:50:42 -0400130 final int cx = mIconFrame.getMeasuredWidth() / 2 + mIconFrame.getLeft();
131 final int cy = mIconFrame.getMeasuredHeight() / 2 + mIconFrame.getTop();
Jason Monk32508852017-01-18 09:17:13 -0500132 final int rad = (int) (mIcon.getHeight() * .85f);
Jason Monkdc35dcb2015-12-04 16:36:15 -0500133 mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
134 }
135
Jason Monk702e2eb2017-03-03 16:53:44 -0500136 @Override
137 public void init(QSTile tile) {
138 init(v -> tile.click(), v -> tile.secondaryClick(), view -> {
139 tile.longClick();
140 return true;
141 });
142 }
143
Jason Monk32508852017-01-18 09:17:13 -0500144 public void init(OnClickListener click, OnClickListener secondaryClick,
145 OnLongClickListener longClick) {
Jason Monkdc35dcb2015-12-04 16:36:15 -0500146 setOnClickListener(click);
Jason Monk76c67aa2016-02-19 14:49:42 -0500147 setOnLongClickListener(longClick);
Jason Monkdc35dcb2015-12-04 16:36:15 -0500148 }
149
150 @Override
151 protected void onLayout(boolean changed, int l, int t, int r, int b) {
152 super.onLayout(changed, l, t, r, b);
Jason Monkdc35dcb2015-12-04 16:36:15 -0500153 if (mRipple != null) {
Amin Shaikh30330992018-03-26 17:50:42 -0400154 updateRippleSize();
Jason Monkdc35dcb2015-12-04 16:36:15 -0500155 }
156 }
157
Jorim Jaggic3081172016-04-08 23:56:37 -0700158 @Override
159 public boolean hasOverlappingRendering() {
160 // Avoid layers for this layout - we don't need them.
161 return false;
162 }
163
Jason Monkdc35dcb2015-12-04 16:36:15 -0500164 /**
165 * Update the accessibility order for this view.
166 *
167 * @param previousView the view which should be before this one
168 * @return the last view in this view which is accessible
169 */
170 public View updateAccessibilityOrder(View previousView) {
171 setAccessibilityTraversalAfter(previousView.getId());
172 return this;
173 }
Jason Monkc133d262015-10-27 12:32:45 -0400174
175 public void onStateChanged(QSTile.State state) {
176 mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
177 }
178
Jason Monkdc35dcb2015-12-04 16:36:15 -0500179 protected void handleStateChanged(QSTile.State state) {
Jason Monkcb2307c2018-01-15 14:19:45 -0500180 int circleColor = getCircleColor(state.state);
181 if (circleColor != mCircleColor) {
Amin Shaikh5df1a752018-04-03 13:39:53 -0400182 if (mBg.isShown() && animationsEnabled()) {
Jason Monkcb2307c2018-01-15 14:19:45 -0500183 ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor)
184 .setDuration(QS_ANIM_LENGTH);
185 animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf(
186 (Integer) animation.getAnimatedValue())));
187 animator.start();
188 } else {
189 QSIconViewImpl.setTint(mBg, circleColor);
190 }
191 mCircleColor = circleColor;
192 }
193
Jason Monke2f05d22017-03-23 10:45:37 -0400194 setClickable(state.state != Tile.STATE_UNAVAILABLE);
Jason Monkdc35dcb2015-12-04 16:36:15 -0500195 mIcon.setIcon(state);
Jason Monk702e2eb2017-03-03 16:53:44 -0500196 setContentDescription(state.contentDescription);
Rohan Shah185f6612018-03-29 17:35:00 -0700197
Jason Monk702e2eb2017-03-03 16:53:44 -0500198 mAccessibilityClass = state.expandedAccessibilityClassName;
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400199 if (state instanceof QSTile.BooleanState) {
Jason Monk90950d92017-06-20 13:16:05 -0400200 boolean newState = ((BooleanState) state).value;
201 if (mTileState != newState) {
202 mClicked = false;
203 mTileState = newState;
204 }
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400205 }
Jason Monkc133d262015-10-27 12:32:45 -0400206 }
207
Amin Shaikh5df1a752018-04-03 13:39:53 -0400208 protected boolean animationsEnabled() {
209 return true;
210 }
211
Jason Monkcb2307c2018-01-15 14:19:45 -0500212 private int getCircleColor(int state) {
213 switch (state) {
214 case Tile.STATE_ACTIVE:
215 return mColorActive;
216 case Tile.STATE_INACTIVE:
217 case Tile.STATE_UNAVAILABLE:
218 return mColorDisabled;
219 default:
220 Log.e(TAG, "Invalid state " + state);
221 return 0;
222 }
223 }
224
Jason Monk702e2eb2017-03-03 16:53:44 -0500225 @Override
Jason Monke2f05d22017-03-23 10:45:37 -0400226 public void setClickable(boolean clickable) {
227 super.setClickable(clickable);
228 setBackground(clickable ? mRipple : null);
229 }
230
231 @Override
Jason Monk702e2eb2017-03-03 16:53:44 -0500232 public int getDetailY() {
233 return getTop() + getHeight() / 2;
234 }
235
Jason Monk1aec93f2016-03-01 09:39:30 -0500236 public QSIconView getIcon() {
Jason Monk162011e2016-02-19 08:11:55 -0500237 return mIcon;
238 }
239
Rohan Shaha8401992018-01-25 18:22:44 -0800240 public View getIconWithBackground() {
241 return mIconFrame;
242 }
243
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400244 @Override
Jason Monk90950d92017-06-20 13:16:05 -0400245 public boolean performClick() {
246 mClicked = true;
247 return super.performClick();
248 }
249
250 @Override
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400251 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
252 super.onInitializeAccessibilityEvent(event);
253 if (!TextUtils.isEmpty(mAccessibilityClass)) {
254 event.setClassName(mAccessibilityClass);
255 if (Switch.class.getName().equals(mAccessibilityClass)) {
Jason Monk90950d92017-06-20 13:16:05 -0400256 boolean b = mClicked ? !mTileState : mTileState;
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400257 String label = getResources()
Jason Monk90950d92017-06-20 13:16:05 -0400258 .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400259 event.setContentDescription(label);
Jason Monk90950d92017-06-20 13:16:05 -0400260 event.setChecked(b);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400261 }
262 }
263 }
264
265 @Override
266 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
267 super.onInitializeAccessibilityNodeInfo(info);
Amin Shaikh34044ac2018-06-12 18:25:43 -0400268 // Clear selected state so it is not announce by talkback.
269 info.setSelected(false);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400270 if (!TextUtils.isEmpty(mAccessibilityClass)) {
271 info.setClassName(mAccessibilityClass);
272 if (Switch.class.getName().equals(mAccessibilityClass)) {
Jason Monk90950d92017-06-20 13:16:05 -0400273 boolean b = mClicked ? !mTileState : mTileState;
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400274 String label = getResources()
Jason Monk90950d92017-06-20 13:16:05 -0400275 .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400276 info.setText(label);
Jason Monk90950d92017-06-20 13:16:05 -0400277 info.setChecked(b);
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400278 info.setCheckable(true);
Rohan Shah185f6612018-03-29 17:35:00 -0700279 info.addAction(
280 new AccessibilityNodeInfo.AccessibilityAction(
281 AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.getId(),
282 getResources().getString(R.string.accessibility_long_click_tile)));
Julia Reynolds20aef8a2016-05-04 16:44:08 -0400283 }
284 }
285 }
286
Jason Monkc133d262015-10-27 12:32:45 -0400287 private class H extends Handler {
288 private static final int STATE_CHANGED = 1;
289 public H() {
290 super(Looper.getMainLooper());
291 }
292
293 @Override
294 public void handleMessage(Message msg) {
295 if (msg.what == STATE_CHANGED) {
296 handleStateChanged((QSTile.State) msg.obj);
297 }
298 }
299 }
300}