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 17 package com.android.systemui.qs; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.util.AttributeSet; 22 import android.view.Gravity; 23 import android.view.View; 24 import android.widget.LinearLayout; 25 import android.widget.Space; 26 27 import com.android.systemui.Dependency; 28 import com.android.systemui.R; 29 import com.android.systemui.plugins.qs.QSTile; 30 import com.android.systemui.plugins.qs.QSTile.SignalState; 31 import com.android.systemui.plugins.qs.QSTile.State; 32 import com.android.systemui.plugins.qs.QSTileView; 33 import com.android.systemui.qs.customize.QSCustomizer; 34 import com.android.systemui.tuner.TunerService; 35 import com.android.systemui.tuner.TunerService.Tunable; 36 37 import java.util.ArrayList; 38 import java.util.Collection; 39 40 /** 41 * Version of QSPanel that only shows N Quick Tiles in the QS Header. 42 */ 43 public class QuickQSPanel extends QSPanel { 44 45 public static final String NUM_QUICK_TILES = "sysui_qqs_count"; 46 47 private boolean mDisabledByPolicy; 48 private int mMaxTiles; 49 protected QSPanel mFullPanel; 50 51 public QuickQSPanel(Context context, AttributeSet attrs) { 52 super(context, attrs); 53 if (mFooter != null) { 54 removeView(mFooter.getView()); 55 } 56 if (mTileLayout != null) { 57 for (int i = 0; i < mRecords.size(); i++) { 58 mTileLayout.removeTile(mRecords.get(i)); 59 } 60 removeView((View) mTileLayout); 61 } 62 mTileLayout = new HeaderTileLayout(context); 63 mTileLayout.setListening(mListening); 64 addView((View) mTileLayout, 0 /* Between brightness and footer */); 65 super.setPadding(0, 0, 0, 0); 66 } 67 68 @Override 69 public void setPadding(int left, int top, int right, int bottom) { 70 // Always have no padding. 71 } 72 73 @Override 74 protected void addDivider() { 75 } 76 77 @Override 78 protected void onAttachedToWindow() { 79 super.onAttachedToWindow(); 80 Dependency.get(TunerService.class).addTunable(mNumTiles, NUM_QUICK_TILES); 81 } 82 83 @Override 84 protected void onDetachedFromWindow() { 85 super.onDetachedFromWindow(); 86 Dependency.get(TunerService.class).removeTunable(mNumTiles); 87 } 88 89 public void setQSPanelAndHeader(QSPanel fullPanel, View header) { 90 mFullPanel = fullPanel; 91 } 92 93 @Override 94 protected boolean shouldShowDetail() { 95 return !mExpanded; 96 } 97 98 @Override 99 protected void drawTile(TileRecord r, State state) { 100 if (state instanceof SignalState) { 101 SignalState copy = new SignalState(); 102 state.copyTo(copy); 103 // No activity shown in the quick panel. 104 copy.activityIn = false; 105 copy.activityOut = false; 106 state = copy; 107 } 108 super.drawTile(r, state); 109 } 110 111 @Override 112 public void setHost(QSTileHost host, QSCustomizer customizer) { 113 super.setHost(host, customizer); 114 setTiles(mHost.getTiles()); 115 } 116 117 public void setMaxTiles(int maxTiles) { 118 mMaxTiles = maxTiles; 119 if (mHost != null) { 120 setTiles(mHost.getTiles()); 121 } 122 } 123 124 @Override 125 public void onTuningChanged(String key, String newValue) { 126 if (QS_SHOW_BRIGHTNESS.equals(key)) { 127 // No Brightness or Tooltip for you! 128 super.onTuningChanged(key, "0"); 129 } 130 } 131 132 @Override 133 public void setTiles(Collection<QSTile> tiles) { 134 ArrayList<QSTile> quickTiles = new ArrayList<>(); 135 for (QSTile tile : tiles) { 136 quickTiles.add(tile); 137 if (quickTiles.size() == mMaxTiles) { 138 break; 139 } 140 } 141 super.setTiles(quickTiles, true); 142 } 143 144 private final Tunable mNumTiles = new Tunable() { 145 @Override 146 public void onTuningChanged(String key, String newValue) { 147 setMaxTiles(getNumQuickTiles(mContext)); 148 } 149 }; 150 151 public static int getNumQuickTiles(Context context) { 152 return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6); 153 } 154 155 void setDisabledByPolicy(boolean disabled) { 156 if (disabled != mDisabledByPolicy) { 157 mDisabledByPolicy = disabled; 158 setVisibility(disabled ? View.GONE : View.VISIBLE); 159 } 160 } 161 162 /** 163 * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel 164 * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the 165 * visibility will always be {@link View#GONE}. This method is called externally by 166 * {@link QSAnimator} only. 167 */ 168 @Override 169 public void setVisibility(int visibility) { 170 if (mDisabledByPolicy) { 171 if (getVisibility() == View.GONE) { 172 return; 173 } 174 visibility = View.GONE; 175 } 176 super.setVisibility(visibility); 177 } 178 179 private static class HeaderTileLayout extends LinearLayout implements QSTileLayout { 180 181 protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); 182 private boolean mListening; 183 /** Size of the QS tile (width & height). */ 184 private int mTileDimensionSize; 185 186 public HeaderTileLayout(Context context) { 187 super(context); 188 setClipChildren(false); 189 setClipToPadding(false); 190 191 mTileDimensionSize = mContext.getResources().getDimensionPixelSize( 192 R.dimen.qs_quick_tile_size); 193 194 setGravity(Gravity.CENTER); 195 setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 196 } 197 198 @Override 199 protected void onConfigurationChanged(Configuration newConfig) { 200 super.onConfigurationChanged(newConfig); 201 202 setGravity(Gravity.CENTER); 203 LayoutParams staticSpaceLayoutParams = generateSpaceLayoutParams( 204 mContext.getResources().getDimensionPixelSize( 205 R.dimen.qs_quick_tile_space_width)); 206 207 // Update space params since they fill any open space in portrait orientation and have 208 // a static width in landscape orientation. 209 final int childViewCount = getChildCount(); 210 for (int i = 0; i < childViewCount; i++) { 211 View childView = getChildAt(i); 212 if (childView instanceof Space) { 213 childView.setLayoutParams(staticSpaceLayoutParams); 214 } 215 } 216 } 217 218 /** 219 * Returns {@link LayoutParams} based on the given {@code spaceWidth}. If the width is 0, 220 * then we're going to have the space expand to take up as much space as possible. If the 221 * width is non-zero, we want the inter-tile spacers to be fixed. 222 */ 223 private LayoutParams generateSpaceLayoutParams(int spaceWidth) { 224 LayoutParams lp = new LayoutParams(spaceWidth, mTileDimensionSize); 225 if (spaceWidth == 0) { 226 lp.weight = 1; 227 } 228 lp.gravity = Gravity.CENTER; 229 return lp; 230 } 231 232 @Override 233 public void setListening(boolean listening) { 234 if (mListening == listening) return; 235 mListening = listening; 236 for (TileRecord record : mRecords) { 237 record.tile.setListening(this, mListening); 238 } 239 } 240 241 @Override 242 public void addTile(TileRecord tile) { 243 if (getChildCount() != 0) { 244 // Add a spacer between tiles. We want static-width spaces if we're in landscape to 245 // keep the tiles close. For portrait, we stick with spaces that fill up any 246 // available space. 247 LayoutParams spaceLayoutParams = generateSpaceLayoutParams( 248 mContext.getResources().getDimensionPixelSize( 249 R.dimen.qs_quick_tile_space_width)); 250 addView(new Space(mContext), getChildCount(), spaceLayoutParams); 251 } 252 253 addView(tile.tileView, getChildCount(), generateTileLayoutParams()); 254 mRecords.add(tile); 255 tile.tile.setListening(this, mListening); 256 } 257 258 private LayoutParams generateTileLayoutParams() { 259 LayoutParams lp = new LayoutParams(mTileDimensionSize, mTileDimensionSize); 260 lp.gravity = Gravity.CENTER; 261 return lp; 262 } 263 264 @Override 265 public void removeTile(TileRecord tile) { 266 int childIndex = getChildIndex(tile.tileView); 267 // Remove the tile. 268 removeViewAt(childIndex); 269 if (getChildCount() != 0) { 270 // Remove its spacer as well. 271 removeViewAt(childIndex); 272 } 273 mRecords.remove(tile); 274 tile.tile.setListening(this, false); 275 } 276 277 private int getChildIndex(QSTileView tileView) { 278 final int childViewCount = getChildCount(); 279 for (int i = 0; i < childViewCount; i++) { 280 if (getChildAt(i) == tileView) { 281 return i; 282 } 283 } 284 return -1; 285 } 286 287 @Override 288 public int getOffsetTop(TileRecord tile) { 289 return 0; 290 } 291 292 @Override 293 public boolean updateResources() { 294 // No resources here. 295 return false; 296 } 297 298 @Override 299 public boolean hasOverlappingRendering() { 300 return false; 301 } 302 303 @Override 304 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 305 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 306 if (mRecords != null && mRecords.size() > 0) { 307 View previousView = this; 308 for (TileRecord record : mRecords) { 309 if (record.tileView.getVisibility() == GONE) continue; 310 previousView = record.tileView.updateAccessibilityOrder(previousView); 311 } 312 mRecords.get(0).tileView.setAccessibilityTraversalAfter( 313 R.id.alarm_status_collapsed); 314 mRecords.get(mRecords.size() - 1).tileView.setAccessibilityTraversalBefore( 315 R.id.expand_indicator); 316 } 317 } 318 } 319 } 320