1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.systemui.qs;
17
18import android.content.Context;
19import android.content.res.TypedArray;
20import android.graphics.drawable.Drawable;
21import android.graphics.drawable.RippleDrawable;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.Message;
25import android.text.TextUtils;
26import android.view.View;
27import android.view.accessibility.AccessibilityEvent;
28import android.view.accessibility.AccessibilityNodeInfo;
29import android.widget.LinearLayout;
30import android.widget.Switch;
31
32import com.android.systemui.R;
33
34public class QSTileBaseView extends LinearLayout {
35
36    private final H mHandler = new H();
37    private QSIconView mIcon;
38    private RippleDrawable mRipple;
39    private Drawable mTileBackground;
40    private String mAccessibilityClass;
41    private boolean mTileState;
42    private boolean mCollapsedView;
43
44    public QSTileBaseView(Context context, QSIconView icon) {
45        this(context, icon, false);
46    }
47
48    public QSTileBaseView(Context context, QSIconView icon, boolean collapsedView) {
49        super(context);
50        mIcon = icon;
51        addView(mIcon);
52
53        mTileBackground = newTileBackground();
54        if (mTileBackground instanceof RippleDrawable) {
55            setRipple((RippleDrawable) mTileBackground);
56        }
57        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
58        setBackground(mTileBackground);
59
60        // Default to Quick Tile padding, and QSTileView will specify its own padding.
61        int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
62        setPadding(0, padding, 0, padding);
63        setClipChildren(false);
64        setClipToPadding(false);
65        mCollapsedView = collapsedView;
66    }
67
68    private Drawable newTileBackground() {
69        final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
70        final TypedArray ta = mContext.obtainStyledAttributes(attrs);
71        final Drawable d = ta.getDrawable(0);
72        ta.recycle();
73        return d;
74    }
75
76    private void setRipple(RippleDrawable tileBackground) {
77        mRipple = tileBackground;
78        if (getWidth() != 0) {
79            updateRippleSize(getWidth(), getHeight());
80        }
81    }
82
83    private void updateRippleSize(int width, int height) {
84        // center the touch feedback on the center of the icon, and dial it down a bit
85        final int cx = width / 2;
86        final int cy = height / 2;
87        final int rad = (int)(mIcon.getHeight() * .85f);
88        mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
89    }
90
91    public void init(OnClickListener click, OnLongClickListener longClick) {
92        setClickable(true);
93        setOnClickListener(click);
94        setOnLongClickListener(longClick);
95    }
96
97    @Override
98    protected void onLayout(boolean changed, int l, int t, int r, int b) {
99        super.onLayout(changed, l, t, r, b);
100        final int w = getMeasuredWidth();
101        final int h = getMeasuredHeight();
102
103        if (mRipple != null) {
104            updateRippleSize(w, h);
105        }
106    }
107
108    @Override
109    public boolean hasOverlappingRendering() {
110        // Avoid layers for this layout - we don't need them.
111        return false;
112    }
113
114    /**
115     * Update the accessibility order for this view.
116     *
117     * @param previousView the view which should be before this one
118     * @return the last view in this view which is accessible
119     */
120    public View updateAccessibilityOrder(View previousView) {
121        setAccessibilityTraversalAfter(previousView.getId());
122        return this;
123    }
124
125    public void onStateChanged(QSTile.State state) {
126        mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
127    }
128
129    protected void handleStateChanged(QSTile.State state) {
130        mIcon.setIcon(state);
131        if (mCollapsedView && !TextUtils.isEmpty(state.minimalContentDescription)) {
132            setContentDescription(state.minimalContentDescription);
133        } else {
134            setContentDescription(state.contentDescription);
135        }
136        if (mCollapsedView) {
137            mAccessibilityClass = state.minimalAccessibilityClassName;
138        } else {
139            mAccessibilityClass = state.expandedAccessibilityClassName;
140        }
141        if (state instanceof QSTile.BooleanState) {
142            mTileState = ((QSTile.BooleanState) state).value;
143        }
144    }
145
146    public QSIconView getIcon() {
147        return mIcon;
148    }
149
150    @Override
151    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
152        super.onInitializeAccessibilityEvent(event);
153        if (!TextUtils.isEmpty(mAccessibilityClass)) {
154            event.setClassName(mAccessibilityClass);
155            if (Switch.class.getName().equals(mAccessibilityClass)) {
156                String label = getResources()
157                        .getString(!mTileState ? R.string.switch_bar_on : R.string.switch_bar_off);
158                event.setContentDescription(label);
159                event.setChecked(!mTileState);
160            }
161        }
162    }
163
164    @Override
165    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
166        super.onInitializeAccessibilityNodeInfo(info);
167        if (!TextUtils.isEmpty(mAccessibilityClass)) {
168            info.setClassName(mAccessibilityClass);
169            if (Switch.class.getName().equals(mAccessibilityClass)) {
170                String label = getResources()
171                        .getString(mTileState ? R.string.switch_bar_on : R.string.switch_bar_off);
172                info.setText(label);
173                info.setChecked(mTileState);
174                info.setCheckable(true);
175            }
176        }
177    }
178
179    private class H extends Handler {
180        private static final int STATE_CHANGED = 1;
181        public H() {
182            super(Looper.getMainLooper());
183        }
184
185        @Override
186        public void handleMessage(Message msg) {
187            if (msg.what == STATE_CHANGED) {
188                handleStateChanged((QSTile.State) msg.obj);
189            }
190        }
191    }
192}
193