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