2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.qs;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 import com.android.internal.logging.MetricsLogger;
32 import com.android.internal.logging.MetricsProto.MetricsEvent;
33 import com.android.systemui.R;
34 import com.android.systemui.qs.QSTile.DetailAdapter;
35 import com.android.systemui.qs.QSTile.Host.Callback;
36 import com.android.systemui.qs.customize.QSCustomizer;
37 import com.android.systemui.qs.external.CustomTile;
38 import com.android.systemui.settings.BrightnessController;
39 import com.android.systemui.settings.ToggleSlider;
40 import com.android.systemui.statusbar.phone.QSTileHost;
41 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
42 import com.android.systemui.tuner.TunerService;
43 import com.android.systemui.tuner.TunerService.Tunable;
45 import java.util.ArrayList;
46 import java.util.Collection;
48 /** View that represents the quick settings tile panel. **/
49 public class QSPanel extends LinearLayout implements Tunable, Callback {
51 public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
53 protected final Context mContext;
54 protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
55 protected final View mBrightnessView;
56 private final H mHandler = new H();
58 private int mPanelPaddingBottom;
59 private int mBrightnessPaddingTop;
60 protected boolean mExpanded;
61 protected boolean mListening;
63 private Callback mCallback;
64 private BrightnessController mBrightnessController;
65 protected QSTileHost mHost;
67 protected QSFooter mFooter;
68 private boolean mGridContentVisible = true;
70 protected QSTileLayout mTileLayout;
72 private QSCustomizer mCustomizePanel;
73 private Record mDetailRecord;
75 private BrightnessMirrorController mBrightnessMirrorController;
77 public QSPanel(Context context) {
81 public QSPanel(Context context, AttributeSet attrs) {
82 super(context, attrs);
85 setOrientation(VERTICAL);
87 mBrightnessView = LayoutInflater.from(context).inflate(
88 R.layout.quick_settings_brightness_dialog, this, false);
89 addView(mBrightnessView);
93 mFooter = new QSFooter(this, context);
94 addView(mFooter.getView());
98 mBrightnessController = new BrightnessController(getContext(),
99 (ImageView) findViewById(R.id.brightness_icon),
100 (ToggleSlider) findViewById(R.id.brightness_slider));
104 protected void setupTileLayout() {
105 mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
106 R.layout.qs_paged_tile_layout, this, false);
107 mTileLayout.setListening(mListening);
108 addView((View) mTileLayout);
109 findViewById(android.R.id.edit).setOnClickListener(view ->
110 mHost.startRunnableDismissingKeyguard(() -> showEdit(view)));
113 public boolean isShowingCustomize() {
114 return mCustomizePanel != null && mCustomizePanel.isCustomizing();
118 protected void onAttachedToWindow() {
119 super.onAttachedToWindow();
120 TunerService.get(mContext).addTunable(this, QS_SHOW_BRIGHTNESS);
122 setTiles(mHost.getTiles());
127 protected void onDetachedFromWindow() {
128 TunerService.get(mContext).removeTunable(this);
129 mHost.removeCallback(this);
130 for (TileRecord record : mRecords) {
131 record.tile.removeCallbacks();
133 super.onDetachedFromWindow();
137 public void onTilesChanged() {
138 setTiles(mHost.getTiles());
142 public void onTuningChanged(String key, String newValue) {
143 if (QS_SHOW_BRIGHTNESS.equals(key)) {
144 mBrightnessView.setVisibility(newValue == null || Integer.parseInt(newValue) != 0
149 public void openDetails(String subPanel) {
150 QSTile<?> tile = getTile(subPanel);
151 showDetailAdapter(true, tile.getDetailAdapter(), new int[] {getWidth() / 2, 0});
154 private QSTile<?> getTile(String subPanel) {
155 for (int i = 0; i < mRecords.size(); i++) {
156 if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) {
157 return mRecords.get(i).tile;
160 return mHost.createTile(subPanel);
163 public void setBrightnessMirror(BrightnessMirrorController c) {
164 mBrightnessMirrorController = c;
165 ToggleSlider brightnessSlider = (ToggleSlider) findViewById(R.id.brightness_slider);
166 ToggleSlider mirror = (ToggleSlider) c.getMirror().findViewById(R.id.brightness_slider);
167 brightnessSlider.setMirror(mirror);
168 brightnessSlider.setMirrorController(c);
171 public void setCallback(Callback callback) {
172 mCallback = callback;
175 public void setHost(QSTileHost host, QSCustomizer customizer) {
177 mHost.addCallback(this);
178 setTiles(mHost.getTiles());
179 mFooter.setHost(host);
180 mCustomizePanel = customizer;
181 if (mCustomizePanel != null) {
182 mCustomizePanel.setHost(mHost);
186 public QSTileHost getHost() {
190 public void updateResources() {
191 final Resources res = mContext.getResources();
192 mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
193 mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
194 setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
195 for (TileRecord r : mRecords) {
201 if (mTileLayout != null) {
202 mTileLayout.updateResources();
207 protected void onConfigurationChanged(Configuration newConfig) {
208 super.onConfigurationChanged(newConfig);
209 mFooter.onConfigurationChanged();
211 if (mBrightnessMirrorController != null) {
212 // Reload the mirror in case it got reinflated but we didn't.
213 setBrightnessMirror(mBrightnessMirrorController);
217 public void onCollapse() {
218 if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
219 mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
223 public void setExpanded(boolean expanded) {
224 if (mExpanded == expanded) return;
225 mExpanded = expanded;
226 if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
227 ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
229 MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
237 public void setListening(boolean listening) {
238 if (mListening == listening) return;
239 mListening = listening;
240 if (mTileLayout != null) {
241 mTileLayout.setListening(listening);
243 mFooter.setListening(mListening);
248 mBrightnessController.registerCallbacks();
250 mBrightnessController.unregisterCallbacks();
254 public void refreshAllTiles() {
255 for (TileRecord r : mRecords) {
256 r.tile.refreshState();
258 mFooter.refreshState();
261 public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
262 int xInWindow = locationInWindow[0];
263 int yInWindow = locationInWindow[1];
264 ((View) getParent()).getLocationInWindow(locationInWindow);
266 Record r = new Record();
267 r.detailAdapter = adapter;
268 r.x = xInWindow - locationInWindow[0];
269 r.y = yInWindow - locationInWindow[1];
271 locationInWindow[0] = xInWindow;
272 locationInWindow[1] = yInWindow;
277 protected void showDetail(boolean show, Record r) {
278 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
281 public void setTiles(Collection<QSTile<?>> tiles) {
282 setTiles(tiles, false);
285 public void setTiles(Collection<QSTile<?>> tiles, boolean collapsedView) {
286 for (TileRecord record : mRecords) {
287 mTileLayout.removeTile(record);
288 record.tile.removeCallback(record.callback);
291 for (QSTile<?> tile : tiles) {
292 addTile(tile, collapsedView);
296 protected void drawTile(TileRecord r, QSTile.State state) {
297 r.tileView.onStateChanged(state);
300 protected QSTileBaseView createTileView(QSTile<?> tile, boolean collapsedView) {
301 return new QSTileView(mContext, tile.createTileView(mContext), collapsedView);
304 protected boolean shouldShowDetail() {
308 protected void addTile(final QSTile<?> tile, boolean collapsedView) {
309 final TileRecord r = new TileRecord();
311 r.tileView = createTileView(tile, collapsedView);
312 final QSTile.Callback callback = new QSTile.Callback() {
314 public void onStateChanged(QSTile.State state) {
319 public void onShowDetail(boolean show) {
320 // Both the collapsed and full QS panels get this callback, this check determines
321 // which one should handle showing the detail.
322 if (shouldShowDetail()) {
323 QSPanel.this.showDetail(show, r);
328 public void onToggleStateChanged(boolean state) {
329 if (mDetailRecord == r) {
330 fireToggleStateChanged(state);
335 public void onScanStateChanged(boolean state) {
337 if (mDetailRecord == r) {
338 fireScanStateChanged(r.scanState);
343 public void onAnnouncementRequested(CharSequence announcement) {
344 announceForAccessibility(announcement);
347 r.tile.addCallback(callback);
348 r.callback = callback;
349 final View.OnClickListener click = new View.OnClickListener() {
351 public void onClick(View v) {
355 final View.OnLongClickListener longClick = new View.OnLongClickListener() {
357 public boolean onLongClick(View v) {
362 r.tileView.init(click, longClick);
363 r.tile.refreshState();
366 if (mTileLayout != null) {
367 mTileLayout.addTile(r);
372 private void showEdit(final View v) {
373 v.post(new Runnable() {
376 if (mCustomizePanel != null) {
377 if (!mCustomizePanel.isCustomizing()) {
378 int[] loc = new int[2];
379 v.getLocationInWindow(loc);
382 mCustomizePanel.show(x, y);
390 protected void onTileClick(QSTile<?> tile) {
394 public void closeDetail() {
395 if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
396 // Treat this as a detail panel for now, to make things easy.
397 mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
400 showDetail(false, mDetailRecord);
403 public int getGridHeight() {
404 return getMeasuredHeight();
407 protected void handleShowDetail(Record r, boolean show) {
408 if (r instanceof TileRecord) {
409 handleShowDetailTile((TileRecord) r, show);
417 handleShowDetailImpl(r, show, x, y);
421 private void handleShowDetailTile(TileRecord r, boolean show) {
422 if ((mDetailRecord != null) == show && mDetailRecord == r) return;
425 r.detailAdapter = r.tile.getDetailAdapter();
426 if (r.detailAdapter == null) return;
428 r.tile.setDetailListening(show);
429 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
430 int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + r.tileView.getHeight() / 2
432 handleShowDetailImpl(r, show, x, y);
435 private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
436 setDetailRecord(show ? r : null);
437 fireShowingDetail(show ? r.detailAdapter : null, x, y);
440 private void setDetailRecord(Record r) {
441 if (r == mDetailRecord) return;
443 final boolean scanState = mDetailRecord instanceof TileRecord
444 && ((TileRecord) mDetailRecord).scanState;
445 fireScanStateChanged(scanState);
448 void setGridContentVisibility(boolean visible) {
449 int newVis = visible ? VISIBLE : INVISIBLE;
450 setVisibility(newVis);
451 if (mGridContentVisible != visible) {
452 MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
454 mGridContentVisible = visible;
457 private void logTiles() {
458 for (int i = 0; i < mRecords.size(); i++) {
459 TileRecord tileRecord = mRecords.get(i);
460 MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
464 private void fireShowingDetail(DetailAdapter detail, int x, int y) {
465 if (mCallback != null) {
466 mCallback.onShowingDetail(detail, x, y);
470 private void fireToggleStateChanged(boolean state) {
471 if (mCallback != null) {
472 mCallback.onToggleStateChanged(state);
476 private void fireScanStateChanged(boolean state) {
477 if (mCallback != null) {
478 mCallback.onScanStateChanged(state);
482 public void clickTile(ComponentName tile) {
483 final String spec = CustomTile.toSpec(tile);
484 final int N = mRecords.size();
485 for (int i = 0; i < N; i++) {
486 if (mRecords.get(i).tile.getTileSpec().equals(spec)) {
487 mRecords.get(i).tile.click();
493 QSTileLayout getTileLayout() {
497 QSTileBaseView getTileView(QSTile<?> tile) {
498 for (TileRecord r : mRecords) {
499 if (r.tile == tile) {
506 private class H extends Handler {
507 private static final int SHOW_DETAIL = 1;
508 private static final int SET_TILE_VISIBILITY = 2;
510 public void handleMessage(Message msg) {
511 if (msg.what == SHOW_DETAIL) {
512 handleShowDetail((Record)msg.obj, msg.arg1 != 0);
517 protected static class Record {
518 DetailAdapter detailAdapter;
523 public static final class TileRecord extends Record {
524 public QSTile<?> tile;
525 public QSTileBaseView tileView;
526 public boolean scanState;
527 public QSTile.Callback callback;
530 public interface Callback {
531 void onShowingDetail(DetailAdapter detail, int x, int y);
532 void onToggleStateChanged(boolean state);
533 void onScanStateChanged(boolean state);
536 public interface QSTileLayout {
537 void addTile(TileRecord tile);
538 void removeTile(TileRecord tile);
539 int getOffsetTop(TileRecord tile);
540 boolean updateResources();
542 void setListening(boolean listening);