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 */ 16 package com.android.systemui.qs; 17 18 import android.content.Context; 19 import android.content.res.TypedArray; 20 import android.graphics.drawable.Drawable; 21 import android.graphics.drawable.RippleDrawable; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.text.TextUtils; 26 import android.view.View; 27 import android.view.accessibility.AccessibilityEvent; 28 import android.view.accessibility.AccessibilityNodeInfo; 29 import android.widget.LinearLayout; 30 import android.widget.Switch; 31 32 import com.android.systemui.R; 33 34 public 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 setFocusable(true); 67 } 68 69 private Drawable newTileBackground() { 70 final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless }; 71 final TypedArray ta = mContext.obtainStyledAttributes(attrs); 72 final Drawable d = ta.getDrawable(0); 73 ta.recycle(); 74 return d; 75 } 76 77 private void setRipple(RippleDrawable tileBackground) { 78 mRipple = tileBackground; 79 if (getWidth() != 0) { 80 updateRippleSize(getWidth(), getHeight()); 81 } 82 } 83 84 private void updateRippleSize(int width, int height) { 85 // center the touch feedback on the center of the icon, and dial it down a bit 86 final int cx = width / 2; 87 final int cy = height / 2; 88 final int rad = (int)(mIcon.getHeight() * .85f); 89 mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad); 90 } 91 92 public void init(OnClickListener click, OnLongClickListener longClick) { 93 setClickable(true); 94 setOnClickListener(click); 95 setOnLongClickListener(longClick); 96 } 97 98 @Override 99 protected void onLayout(boolean changed, int l, int t, int r, int b) { 100 super.onLayout(changed, l, t, r, b); 101 final int w = getMeasuredWidth(); 102 final int h = getMeasuredHeight(); 103 104 if (mRipple != null) { 105 updateRippleSize(w, h); 106 } 107 } 108 109 @Override 110 public boolean hasOverlappingRendering() { 111 // Avoid layers for this layout - we don't need them. 112 return false; 113 } 114 115 /** 116 * Update the accessibility order for this view. 117 * 118 * @param previousView the view which should be before this one 119 * @return the last view in this view which is accessible 120 */ 121 public View updateAccessibilityOrder(View previousView) { 122 setAccessibilityTraversalAfter(previousView.getId()); 123 return this; 124 } 125 126 public void onStateChanged(QSTile.State state) { 127 mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget(); 128 } 129 130 protected void handleStateChanged(QSTile.State state) { 131 mIcon.setIcon(state); 132 if (mCollapsedView && !TextUtils.isEmpty(state.minimalContentDescription)) { 133 setContentDescription(state.minimalContentDescription); 134 } else { 135 setContentDescription(state.contentDescription); 136 } 137 if (mCollapsedView) { 138 mAccessibilityClass = state.minimalAccessibilityClassName; 139 } else { 140 mAccessibilityClass = state.expandedAccessibilityClassName; 141 } 142 if (state instanceof QSTile.BooleanState) { 143 mTileState = ((QSTile.BooleanState) state).value; 144 } 145 } 146 147 public QSIconView getIcon() { 148 return mIcon; 149 } 150 151 @Override 152 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 153 super.onInitializeAccessibilityEvent(event); 154 if (!TextUtils.isEmpty(mAccessibilityClass)) { 155 event.setClassName(mAccessibilityClass); 156 if (Switch.class.getName().equals(mAccessibilityClass)) { 157 String label = getResources() 158 .getString(!mTileState ? R.string.switch_bar_on : R.string.switch_bar_off); 159 event.setContentDescription(label); 160 event.setChecked(!mTileState); 161 } 162 } 163 } 164 165 @Override 166 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 167 super.onInitializeAccessibilityNodeInfo(info); 168 if (!TextUtils.isEmpty(mAccessibilityClass)) { 169 info.setClassName(mAccessibilityClass); 170 if (Switch.class.getName().equals(mAccessibilityClass)) { 171 String label = getResources() 172 .getString(mTileState ? R.string.switch_bar_on : R.string.switch_bar_off); 173 info.setText(label); 174 info.setChecked(mTileState); 175 info.setCheckable(true); 176 } 177 } 178 } 179 180 private class H extends Handler { 181 private static final int STATE_CHANGED = 1; 182 public H() { 183 super(Looper.getMainLooper()); 184 } 185 186 @Override 187 public void handleMessage(Message msg) { 188 if (msg.what == STATE_CHANGED) { 189 handleStateChanged((QSTile.State) msg.obj); 190 } 191 } 192 } 193 } 194