Home | History | Annotate | Download | only in tileimpl
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * 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
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * 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.
     13  */
     14 package com.android.systemui.qs.tileimpl;
     15 
     16 import android.content.Context;
     17 import android.content.res.TypedArray;
     18 import android.graphics.drawable.Drawable;
     19 import android.graphics.drawable.RippleDrawable;
     20 import android.os.Handler;
     21 import android.os.Looper;
     22 import android.os.Message;
     23 import android.service.quicksettings.Tile;
     24 import android.text.TextUtils;
     25 import android.view.Gravity;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 import android.view.accessibility.AccessibilityEvent;
     29 import android.view.accessibility.AccessibilityNodeInfo;
     30 import android.widget.FrameLayout;
     31 import android.widget.Switch;
     32 
     33 import com.android.systemui.R;
     34 import com.android.systemui.plugins.qs.*;
     35 import com.android.systemui.plugins.qs.QSTile.BooleanState;
     36 
     37 public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
     38 
     39     private static final String TAG = "QSTileBaseView";
     40     private final H mHandler = new H();
     41     private final FrameLayout mIconFrame;
     42     protected QSIconView mIcon;
     43     protected RippleDrawable mRipple;
     44     private Drawable mTileBackground;
     45     private String mAccessibilityClass;
     46     private boolean mTileState;
     47     private boolean mCollapsedView;
     48     private boolean mClicked;
     49 
     50     public QSTileBaseView(Context context, QSIconView icon) {
     51         this(context, icon, false);
     52     }
     53 
     54     public QSTileBaseView(Context context, QSIconView icon, boolean collapsedView) {
     55         super(context);
     56         // Default to Quick Tile padding, and QSTileView will specify its own padding.
     57         int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
     58 
     59         mIconFrame = new FrameLayout(context);
     60         mIconFrame.setForegroundGravity(Gravity.CENTER);
     61         int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
     62         addView(mIconFrame, new LayoutParams(size, size));
     63         mIcon = icon;
     64         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
     65                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
     66         params.setMargins(0, padding, 0, padding);
     67         mIconFrame.addView(mIcon, params);
     68 
     69         mTileBackground = newTileBackground();
     70         if (mTileBackground instanceof RippleDrawable) {
     71             setRipple((RippleDrawable) mTileBackground);
     72         }
     73         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     74         setBackground(mTileBackground);
     75 
     76         setPadding(0, 0, 0, 0);
     77         setClipChildren(false);
     78         setClipToPadding(false);
     79         mCollapsedView = collapsedView;
     80         setFocusable(true);
     81     }
     82 
     83     protected Drawable newTileBackground() {
     84         final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
     85         final TypedArray ta = getContext().obtainStyledAttributes(attrs);
     86         final Drawable d = ta.getDrawable(0);
     87         ta.recycle();
     88         return d;
     89     }
     90 
     91     private void setRipple(RippleDrawable tileBackground) {
     92         mRipple = tileBackground;
     93         if (getWidth() != 0) {
     94             updateRippleSize(getWidth(), getHeight());
     95         }
     96     }
     97 
     98     private void updateRippleSize(int width, int height) {
     99         // center the touch feedback on the center of the icon, and dial it down a bit
    100         final int cx = width / 2;
    101         final int cy = mIconFrame.getMeasuredHeight() / 2;
    102         final int rad = (int) (mIcon.getHeight() * .85f);
    103         mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
    104     }
    105 
    106     @Override
    107     public void init(QSTile tile) {
    108         init(v -> tile.click(), v -> tile.secondaryClick(), view -> {
    109             tile.longClick();
    110             return true;
    111         });
    112     }
    113 
    114     public void init(OnClickListener click, OnClickListener secondaryClick,
    115             OnLongClickListener longClick) {
    116         setOnClickListener(click);
    117         setOnLongClickListener(longClick);
    118     }
    119 
    120     @Override
    121     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    122         super.onLayout(changed, l, t, r, b);
    123         final int w = getMeasuredWidth();
    124         final int h = getMeasuredHeight();
    125 
    126         if (mRipple != null) {
    127             updateRippleSize(w, h);
    128         }
    129     }
    130 
    131     @Override
    132     public boolean hasOverlappingRendering() {
    133         // Avoid layers for this layout - we don't need them.
    134         return false;
    135     }
    136 
    137     /**
    138      * Update the accessibility order for this view.
    139      *
    140      * @param previousView the view which should be before this one
    141      * @return the last view in this view which is accessible
    142      */
    143     public View updateAccessibilityOrder(View previousView) {
    144         setAccessibilityTraversalAfter(previousView.getId());
    145         return this;
    146     }
    147 
    148     public void onStateChanged(QSTile.State state) {
    149         mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
    150     }
    151 
    152     protected void handleStateChanged(QSTile.State state) {
    153         setClickable(state.state != Tile.STATE_UNAVAILABLE);
    154         mIcon.setIcon(state);
    155         setContentDescription(state.contentDescription);
    156         mAccessibilityClass = state.expandedAccessibilityClassName;
    157         if (state instanceof QSTile.BooleanState) {
    158             boolean newState = ((BooleanState) state).value;
    159             if (mTileState != newState) {
    160                 mClicked = false;
    161                 mTileState = newState;
    162             }
    163         }
    164     }
    165 
    166     @Override
    167     public void setClickable(boolean clickable) {
    168         super.setClickable(clickable);
    169         setBackground(clickable ? mRipple : null);
    170     }
    171 
    172     @Override
    173     public int getDetailY() {
    174         return getTop() + getHeight() / 2;
    175     }
    176 
    177     public QSIconView getIcon() {
    178         return mIcon;
    179     }
    180 
    181     @Override
    182     public boolean performClick() {
    183         mClicked = true;
    184         return super.performClick();
    185     }
    186 
    187     @Override
    188     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    189         super.onInitializeAccessibilityEvent(event);
    190         if (!TextUtils.isEmpty(mAccessibilityClass)) {
    191             event.setClassName(mAccessibilityClass);
    192             if (Switch.class.getName().equals(mAccessibilityClass)) {
    193                 boolean b = mClicked ? !mTileState : mTileState;
    194                 String label = getResources()
    195                         .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
    196                 event.setContentDescription(label);
    197                 event.setChecked(b);
    198             }
    199         }
    200     }
    201 
    202     @Override
    203     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    204         super.onInitializeAccessibilityNodeInfo(info);
    205         if (!TextUtils.isEmpty(mAccessibilityClass)) {
    206             info.setClassName(mAccessibilityClass);
    207             if (Switch.class.getName().equals(mAccessibilityClass)) {
    208                 boolean b = mClicked ? !mTileState : mTileState;
    209                 String label = getResources()
    210                         .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
    211                 info.setText(label);
    212                 info.setChecked(b);
    213                 info.setCheckable(true);
    214             }
    215         }
    216     }
    217 
    218     private class H extends Handler {
    219         private static final int STATE_CHANGED = 1;
    220         public H() {
    221             super(Looper.getMainLooper());
    222         }
    223 
    224         @Override
    225         public void handleMessage(Message msg) {
    226             if (msg.what == STATE_CHANGED) {
    227                 handleStateChanged((QSTile.State) msg.obj);
    228             }
    229         }
    230     }
    231 }
    232