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 static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; 20 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.graphics.Rect; 24 import android.util.AttributeSet; 25 import android.view.Gravity; 26 import android.view.View; 27 import android.widget.LinearLayout; 28 29 import com.android.systemui.Dependency; 30 import com.android.systemui.DumpController; 31 import com.android.systemui.R; 32 import com.android.systemui.plugins.qs.QSTile; 33 import com.android.systemui.plugins.qs.QSTile.SignalState; 34 import com.android.systemui.plugins.qs.QSTile.State; 35 import com.android.systemui.qs.customize.QSCustomizer; 36 import com.android.systemui.tuner.TunerService; 37 import com.android.systemui.tuner.TunerService.Tunable; 38 39 import java.util.ArrayList; 40 import java.util.Collection; 41 42 import javax.inject.Inject; 43 import javax.inject.Named; 44 45 /** 46 * Version of QSPanel that only shows N Quick Tiles in the QS Header. 47 */ 48 public class QuickQSPanel extends QSPanel { 49 50 public static final String NUM_QUICK_TILES = "sysui_qqs_count"; 51 private static final String TAG = "QuickQSPanel"; 52 53 private boolean mDisabledByPolicy; 54 private static int mDefaultMaxTiles; 55 private int mMaxTiles; 56 protected QSPanel mFullPanel; 57 58 @Inject QuickQSPanel(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, DumpController dumpController)59 public QuickQSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, 60 DumpController dumpController) { 61 super(context, attrs, dumpController); 62 if (mFooter != null) { 63 removeView(mFooter.getView()); 64 } 65 if (mTileLayout != null) { 66 for (int i = 0; i < mRecords.size(); i++) { 67 mTileLayout.removeTile(mRecords.get(i)); 68 } 69 removeView((View) mTileLayout); 70 } 71 mDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); 72 mTileLayout = new HeaderTileLayout(context); 73 mTileLayout.setListening(mListening); 74 addView((View) mTileLayout, 0 /* Between brightness and footer */); 75 super.setPadding(0, 0, 0, 0); 76 } 77 78 @Override setPadding(int left, int top, int right, int bottom)79 public void setPadding(int left, int top, int right, int bottom) { 80 // Always have no padding. 81 } 82 83 @Override addDivider()84 protected void addDivider() { 85 } 86 87 @Override onAttachedToWindow()88 protected void onAttachedToWindow() { 89 super.onAttachedToWindow(); 90 Dependency.get(TunerService.class).addTunable(mNumTiles, NUM_QUICK_TILES); 91 } 92 93 @Override onDetachedFromWindow()94 protected void onDetachedFromWindow() { 95 super.onDetachedFromWindow(); 96 Dependency.get(TunerService.class).removeTunable(mNumTiles); 97 } 98 setQSPanelAndHeader(QSPanel fullPanel, View header)99 public void setQSPanelAndHeader(QSPanel fullPanel, View header) { 100 mFullPanel = fullPanel; 101 } 102 103 @Override shouldShowDetail()104 protected boolean shouldShowDetail() { 105 return !mExpanded; 106 } 107 108 @Override drawTile(TileRecord r, State state)109 protected void drawTile(TileRecord r, State state) { 110 if (state instanceof SignalState) { 111 SignalState copy = new SignalState(); 112 state.copyTo(copy); 113 // No activity shown in the quick panel. 114 copy.activityIn = false; 115 copy.activityOut = false; 116 state = copy; 117 } 118 super.drawTile(r, state); 119 } 120 121 @Override setHost(QSTileHost host, QSCustomizer customizer)122 public void setHost(QSTileHost host, QSCustomizer customizer) { 123 super.setHost(host, customizer); 124 setTiles(mHost.getTiles()); 125 } 126 setMaxTiles(int maxTiles)127 public void setMaxTiles(int maxTiles) { 128 mMaxTiles = maxTiles; 129 if (mHost != null) { 130 setTiles(mHost.getTiles()); 131 } 132 } 133 134 @Override onTuningChanged(String key, String newValue)135 public void onTuningChanged(String key, String newValue) { 136 if (QS_SHOW_BRIGHTNESS.equals(key)) { 137 // No Brightness or Tooltip for you! 138 super.onTuningChanged(key, "0"); 139 } 140 } 141 142 @Override setTiles(Collection<QSTile> tiles)143 public void setTiles(Collection<QSTile> tiles) { 144 ArrayList<QSTile> quickTiles = new ArrayList<>(); 145 for (QSTile tile : tiles) { 146 quickTiles.add(tile); 147 if (quickTiles.size() == mMaxTiles) { 148 break; 149 } 150 } 151 super.setTiles(quickTiles, true); 152 } 153 154 private final Tunable mNumTiles = new Tunable() { 155 @Override 156 public void onTuningChanged(String key, String newValue) { 157 setMaxTiles(getNumQuickTiles(mContext)); 158 } 159 }; 160 getNumQuickTiles(Context context)161 public static int getNumQuickTiles(Context context) { 162 return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, mDefaultMaxTiles); 163 } 164 setDisabledByPolicy(boolean disabled)165 void setDisabledByPolicy(boolean disabled) { 166 if (disabled != mDisabledByPolicy) { 167 mDisabledByPolicy = disabled; 168 setVisibility(disabled ? View.GONE : View.VISIBLE); 169 } 170 } 171 172 /** 173 * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel 174 * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the 175 * visibility will always be {@link View#GONE}. This method is called externally by 176 * {@link QSAnimator} only. 177 */ 178 @Override setVisibility(int visibility)179 public void setVisibility(int visibility) { 180 if (mDisabledByPolicy) { 181 if (getVisibility() == View.GONE) { 182 return; 183 } 184 visibility = View.GONE; 185 } 186 super.setVisibility(visibility); 187 } 188 189 private static class HeaderTileLayout extends TileLayout { 190 191 private boolean mListening; 192 private Rect mClippingBounds = new Rect(); 193 HeaderTileLayout(Context context)194 public HeaderTileLayout(Context context) { 195 super(context); 196 setClipChildren(false); 197 setClipToPadding(false); 198 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 199 LayoutParams.MATCH_PARENT); 200 lp.gravity = Gravity.CENTER_HORIZONTAL; 201 setLayoutParams(lp); 202 } 203 204 @Override onConfigurationChanged(Configuration newConfig)205 protected void onConfigurationChanged(Configuration newConfig) { 206 super.onConfigurationChanged(newConfig); 207 updateResources(); 208 } 209 210 @Override onFinishInflate()211 public void onFinishInflate(){ 212 updateResources(); 213 } 214 generateTileLayoutParams()215 private LayoutParams generateTileLayoutParams() { 216 LayoutParams lp = new LayoutParams(mCellWidth, mCellHeight); 217 return lp; 218 } 219 220 @Override addTileView(TileRecord tile)221 protected void addTileView(TileRecord tile) { 222 addView(tile.tileView, getChildCount(), generateTileLayoutParams()); 223 } 224 225 @Override onLayout(boolean changed, int l, int t, int r, int b)226 protected void onLayout(boolean changed, int l, int t, int r, int b) { 227 // We only care about clipping on the right side 228 mClippingBounds.set(0, 0, r - l, 10000); 229 setClipBounds(mClippingBounds); 230 231 calculateColumns(); 232 233 for (int i = 0; i < mRecords.size(); i++) { 234 mRecords.get(i).tileView.setVisibility( i < mColumns ? View.VISIBLE : View.GONE); 235 } 236 237 setAccessibilityOrder(); 238 layoutTileRecords(mColumns); 239 } 240 241 @Override 242 public boolean updateResources() { 243 mCellWidth = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); 244 mCellHeight = mCellWidth; 245 246 return false; 247 } 248 249 private boolean calculateColumns() { 250 int prevNumColumns = mColumns; 251 int maxTiles = mRecords.size(); 252 253 if (maxTiles == 0){ // Early return during setup 254 mColumns = 0; 255 return true; 256 } 257 258 final int availableWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); 259 final int leftoverWhitespace = availableWidth - maxTiles * mCellWidth; 260 final int smallestHorizontalMarginNeeded; 261 smallestHorizontalMarginNeeded = leftoverWhitespace / Math.max(1, maxTiles - 1); 262 263 if (smallestHorizontalMarginNeeded > 0){ 264 mCellMarginHorizontal = smallestHorizontalMarginNeeded; 265 mColumns = maxTiles; 266 } else{ 267 mColumns = mCellWidth == 0 ? 1 : 268 Math.min(maxTiles, availableWidth / mCellWidth ); 269 mCellMarginHorizontal = (availableWidth - mColumns * mCellWidth) / (mColumns - 1); 270 } 271 return mColumns != prevNumColumns; 272 } 273 setAccessibilityOrder()274 private void setAccessibilityOrder() { 275 if (mRecords != null && mRecords.size() > 0) { 276 View previousView = this; 277 for (TileRecord record : mRecords) { 278 if (record.tileView.getVisibility() == GONE) continue; 279 previousView = record.tileView.updateAccessibilityOrder(previousView); 280 } 281 mRecords.get(mRecords.size() - 1).tileView.setAccessibilityTraversalBefore( 282 R.id.expand_indicator); 283 } 284 } 285 286 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)287 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 288 // Measure each QS tile. 289 for (TileRecord record : mRecords) { 290 if (record.tileView.getVisibility() == GONE) continue; 291 record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight)); 292 } 293 294 int height = mCellHeight; 295 if (height < 0) height = 0; 296 297 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height); 298 } 299 300 @Override getNumVisibleTiles()301 public int getNumVisibleTiles() { 302 return mColumns; 303 } 304 305 @Override getColumnStart(int column)306 protected int getColumnStart(int column) { 307 return getPaddingStart() + column * (mCellWidth + mCellMarginHorizontal); 308 } 309 } 310 } 311