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