QSPanel.java revision b5ece9a9d6add01b7a0c392372ff8ba061fcb7e7
1/*
2 * Copyright (C) 2014 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
17package com.android.systemui.qs;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.content.res.Resources;
23import android.os.Handler;
24import android.os.Message;
25import android.util.AttributeSet;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.widget.ImageView;
29import android.widget.LinearLayout;
30import com.android.internal.logging.MetricsLogger;
31import com.android.internal.logging.MetricsProto.MetricsEvent;
32import com.android.systemui.R;
33import com.android.systemui.qs.QSTile.DetailAdapter;
34import com.android.systemui.qs.QSTile.Host.Callback;
35import com.android.systemui.qs.customize.QSCustomizer;
36import com.android.systemui.qs.external.CustomTile;
37import com.android.systemui.settings.BrightnessController;
38import com.android.systemui.settings.ToggleSlider;
39import com.android.systemui.statusbar.phone.QSTileHost;
40import com.android.systemui.statusbar.policy.BrightnessMirrorController;
41import com.android.systemui.tuner.TunerService;
42import com.android.systemui.tuner.TunerService.Tunable;
43
44import java.util.ArrayList;
45import java.util.Collection;
46
47/** View that represents the quick settings tile panel. **/
48public class QSPanel extends LinearLayout implements Tunable, Callback {
49
50    public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
51
52    protected final Context mContext;
53    protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
54    protected final View mBrightnessView;
55    private final H mHandler = new H();
56
57    private int mPanelPaddingBottom;
58    private int mBrightnessPaddingTop;
59    protected boolean mExpanded;
60    protected boolean mListening;
61
62    private Callback mCallback;
63    private BrightnessController mBrightnessController;
64    protected QSTileHost mHost;
65
66    protected QSFooter mFooter;
67    private boolean mGridContentVisible = true;
68
69    protected QSTileLayout mTileLayout;
70
71    private QSCustomizer mCustomizePanel;
72    private Record mDetailRecord;
73
74    private BrightnessMirrorController mBrightnessMirrorController;
75
76    public QSPanel(Context context) {
77        this(context, null);
78    }
79
80    public QSPanel(Context context, AttributeSet attrs) {
81        super(context, attrs);
82        mContext = context;
83
84        setOrientation(VERTICAL);
85
86        mBrightnessView = LayoutInflater.from(context).inflate(
87                R.layout.quick_settings_brightness_dialog, this, false);
88        addView(mBrightnessView);
89
90        setupTileLayout();
91
92        mFooter = new QSFooter(this, context);
93        addView(mFooter.getView());
94
95        updateResources();
96
97        mBrightnessController = new BrightnessController(getContext(),
98                (ImageView) findViewById(R.id.brightness_icon),
99                (ToggleSlider) findViewById(R.id.brightness_slider));
100
101    }
102
103    protected void setupTileLayout() {
104        mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
105                R.layout.qs_paged_tile_layout, this, false);
106        mTileLayout.setListening(mListening);
107        addView((View) mTileLayout);
108    }
109
110    public boolean isShowingCustomize() {
111        return mCustomizePanel != null && mCustomizePanel.isCustomizing();
112    }
113
114    @Override
115    protected void onAttachedToWindow() {
116        super.onAttachedToWindow();
117        TunerService.get(mContext).addTunable(this, QS_SHOW_BRIGHTNESS);
118        if (mHost != null) {
119            setTiles(mHost.getTiles());
120        }
121    }
122
123    @Override
124    protected void onDetachedFromWindow() {
125        TunerService.get(mContext).removeTunable(this);
126        mHost.removeCallback(this);
127        for (TileRecord record : mRecords) {
128            record.tile.removeCallbacks();
129        }
130        super.onDetachedFromWindow();
131    }
132
133    @Override
134    public void onTilesChanged() {
135        setTiles(mHost.getTiles());
136    }
137
138    @Override
139    public void onTuningChanged(String key, String newValue) {
140        if (QS_SHOW_BRIGHTNESS.equals(key)) {
141            mBrightnessView.setVisibility(newValue == null || Integer.parseInt(newValue) != 0
142                    ? VISIBLE : GONE);
143        }
144    }
145
146    public void openDetails(String subPanel) {
147        QSTile<?> tile = getTile(subPanel);
148        showDetailAdapter(true, tile.getDetailAdapter(), new int[] {getWidth() / 2, 0});
149    }
150
151    private QSTile<?> getTile(String subPanel) {
152        for (int i = 0; i < mRecords.size(); i++) {
153            if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) {
154                return mRecords.get(i).tile;
155            }
156        }
157        return mHost.createTile(subPanel);
158    }
159
160    public void setBrightnessMirror(BrightnessMirrorController c) {
161        mBrightnessMirrorController = c;
162        ToggleSlider brightnessSlider = (ToggleSlider) findViewById(R.id.brightness_slider);
163        ToggleSlider mirror = (ToggleSlider) c.getMirror().findViewById(R.id.brightness_slider);
164        brightnessSlider.setMirror(mirror);
165        brightnessSlider.setMirrorController(c);
166    }
167
168    View getBrightnessView() {
169        return mBrightnessView;
170    }
171
172    public void setCallback(Callback callback) {
173        mCallback = callback;
174    }
175
176    public void setHost(QSTileHost host, QSCustomizer customizer) {
177        mHost = host;
178        mHost.addCallback(this);
179        setTiles(mHost.getTiles());
180        mFooter.setHost(host);
181        mCustomizePanel = customizer;
182        if (mCustomizePanel != null) {
183            mCustomizePanel.setHost(mHost);
184        }
185    }
186
187    public QSTileHost getHost() {
188        return mHost;
189    }
190
191    public void updateResources() {
192        final Resources res = mContext.getResources();
193        mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
194        mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
195        setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
196        for (TileRecord r : mRecords) {
197            r.tile.clearState();
198        }
199        if (mListening) {
200            refreshAllTiles();
201        }
202        if (mTileLayout != null) {
203            mTileLayout.updateResources();
204        }
205    }
206
207    @Override
208    protected void onConfigurationChanged(Configuration newConfig) {
209        super.onConfigurationChanged(newConfig);
210        mFooter.onConfigurationChanged();
211
212        if (mBrightnessMirrorController != null) {
213            // Reload the mirror in case it got reinflated but we didn't.
214            setBrightnessMirror(mBrightnessMirrorController);
215        }
216    }
217
218    public void onCollapse() {
219        if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
220            mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
221        }
222    }
223
224    public void setExpanded(boolean expanded) {
225        if (mExpanded == expanded) return;
226        mExpanded = expanded;
227        if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
228            ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
229        }
230        MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
231        if (!mExpanded) {
232            closeDetail();
233        } else {
234            logTiles();
235        }
236    }
237
238    public void setListening(boolean listening) {
239        if (mListening == listening) return;
240        mListening = listening;
241        if (mTileLayout != null) {
242            mTileLayout.setListening(listening);
243        }
244        mFooter.setListening(mListening);
245        if (mListening) {
246            refreshAllTiles();
247        }
248        if (mBrightnessView.getVisibility() == View.VISIBLE) {
249            if (listening) {
250                mBrightnessController.registerCallbacks();
251            } else {
252                mBrightnessController.unregisterCallbacks();
253            }
254        }
255    }
256
257    public void refreshAllTiles() {
258        for (TileRecord r : mRecords) {
259            r.tile.refreshState();
260        }
261        mFooter.refreshState();
262    }
263
264    public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
265        int xInWindow = locationInWindow[0];
266        int yInWindow = locationInWindow[1];
267        ((View) getParent()).getLocationInWindow(locationInWindow);
268
269        Record r = new Record();
270        r.detailAdapter = adapter;
271        r.x = xInWindow - locationInWindow[0];
272        r.y = yInWindow - locationInWindow[1];
273
274        locationInWindow[0] = xInWindow;
275        locationInWindow[1] = yInWindow;
276
277        showDetail(show, r);
278    }
279
280    protected void showDetail(boolean show, Record r) {
281        mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
282    }
283
284    public void setTiles(Collection<QSTile<?>> tiles) {
285        setTiles(tiles, false);
286    }
287
288    public void setTiles(Collection<QSTile<?>> tiles, boolean collapsedView) {
289        for (TileRecord record : mRecords) {
290            mTileLayout.removeTile(record);
291            record.tile.removeCallback(record.callback);
292        }
293        mRecords.clear();
294        for (QSTile<?> tile : tiles) {
295            addTile(tile, collapsedView);
296        }
297    }
298
299    protected void drawTile(TileRecord r, QSTile.State state) {
300        r.tileView.onStateChanged(state);
301    }
302
303    protected QSTileBaseView createTileView(QSTile<?> tile, boolean collapsedView) {
304        return new QSTileView(mContext, tile.createTileView(mContext), collapsedView);
305    }
306
307    protected boolean shouldShowDetail() {
308        return mExpanded;
309    }
310
311    protected void addTile(final QSTile<?> tile, boolean collapsedView) {
312        final TileRecord r = new TileRecord();
313        r.tile = tile;
314        r.tileView = createTileView(tile, collapsedView);
315        final QSTile.Callback callback = new QSTile.Callback() {
316            @Override
317            public void onStateChanged(QSTile.State state) {
318                drawTile(r, state);
319            }
320
321            @Override
322            public void onShowDetail(boolean show) {
323                // Both the collapsed and full QS panels get this callback, this check determines
324                // which one should handle showing the detail.
325                if (shouldShowDetail()) {
326                    QSPanel.this.showDetail(show, r);
327                }
328            }
329
330            @Override
331            public void onToggleStateChanged(boolean state) {
332                if (mDetailRecord == r) {
333                    fireToggleStateChanged(state);
334                }
335            }
336
337            @Override
338            public void onScanStateChanged(boolean state) {
339                r.scanState = state;
340                if (mDetailRecord == r) {
341                    fireScanStateChanged(r.scanState);
342                }
343            }
344
345            @Override
346            public void onAnnouncementRequested(CharSequence announcement) {
347                announceForAccessibility(announcement);
348            }
349        };
350        r.tile.addCallback(callback);
351        r.callback = callback;
352        final View.OnClickListener click = new View.OnClickListener() {
353            @Override
354            public void onClick(View v) {
355                onTileClick(r.tile);
356            }
357        };
358        final View.OnLongClickListener longClick = new View.OnLongClickListener() {
359            @Override
360            public boolean onLongClick(View v) {
361                r.tile.longClick();
362                return true;
363            }
364        };
365        r.tileView.init(click, longClick);
366        r.tile.refreshState();
367        mRecords.add(r);
368
369        if (mTileLayout != null) {
370            mTileLayout.addTile(r);
371        }
372    }
373
374
375    public void showEdit(final View v) {
376        v.post(new Runnable() {
377            @Override
378            public void run() {
379                if (mCustomizePanel != null) {
380                    if (!mCustomizePanel.isCustomizing()) {
381                        int[] loc = new int[2];
382                        v.getLocationInWindow(loc);
383                        int x = loc[0] + v.getWidth() / 2;
384                        int y = loc[1] + v.getHeight() / 2;
385                        mCustomizePanel.show(x, y);
386                    }
387                }
388
389            }
390        });
391    }
392
393    protected void onTileClick(QSTile<?> tile) {
394        tile.click();
395    }
396
397    public void closeDetail() {
398        if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
399            // Treat this as a detail panel for now, to make things easy.
400            mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
401            return;
402        }
403        showDetail(false, mDetailRecord);
404    }
405
406    public int getGridHeight() {
407        return getMeasuredHeight();
408    }
409
410    protected void handleShowDetail(Record r, boolean show) {
411        if (r instanceof TileRecord) {
412            handleShowDetailTile((TileRecord) r, show);
413        } else {
414            int x = 0;
415            int y = 0;
416            if (r != null) {
417                x = r.x;
418                y = r.y;
419            }
420            handleShowDetailImpl(r, show, x, y);
421        }
422    }
423
424    private void handleShowDetailTile(TileRecord r, boolean show) {
425        if ((mDetailRecord != null) == show && mDetailRecord == r) return;
426
427        if (show) {
428            r.detailAdapter = r.tile.getDetailAdapter();
429            if (r.detailAdapter == null) return;
430        }
431        r.tile.setDetailListening(show);
432        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
433        int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + r.tileView.getHeight() / 2
434                + getTop();
435        handleShowDetailImpl(r, show, x, y);
436    }
437
438    private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
439        setDetailRecord(show ? r : null);
440        fireShowingDetail(show ? r.detailAdapter : null, x, y);
441    }
442
443    private void setDetailRecord(Record r) {
444        if (r == mDetailRecord) return;
445        mDetailRecord = r;
446        final boolean scanState = mDetailRecord instanceof TileRecord
447                && ((TileRecord) mDetailRecord).scanState;
448        fireScanStateChanged(scanState);
449    }
450
451    void setGridContentVisibility(boolean visible) {
452        int newVis = visible ? VISIBLE : INVISIBLE;
453        setVisibility(newVis);
454        if (mGridContentVisible != visible) {
455            MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
456        }
457        mGridContentVisible = visible;
458    }
459
460    private void logTiles() {
461        for (int i = 0; i < mRecords.size(); i++) {
462            TileRecord tileRecord = mRecords.get(i);
463            MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
464        }
465    }
466
467    private void fireShowingDetail(DetailAdapter detail, int x, int y) {
468        if (mCallback != null) {
469            mCallback.onShowingDetail(detail, x, y);
470        }
471    }
472
473    private void fireToggleStateChanged(boolean state) {
474        if (mCallback != null) {
475            mCallback.onToggleStateChanged(state);
476        }
477    }
478
479    private void fireScanStateChanged(boolean state) {
480        if (mCallback != null) {
481            mCallback.onScanStateChanged(state);
482        }
483    }
484
485    public void clickTile(ComponentName tile) {
486        final String spec = CustomTile.toSpec(tile);
487        final int N = mRecords.size();
488        for (int i = 0; i < N; i++) {
489            if (mRecords.get(i).tile.getTileSpec().equals(spec)) {
490                mRecords.get(i).tile.click();
491                break;
492            }
493        }
494    }
495
496    QSTileLayout getTileLayout() {
497        return mTileLayout;
498    }
499
500    QSTileBaseView getTileView(QSTile<?> tile) {
501        for (TileRecord r : mRecords) {
502            if (r.tile == tile) {
503                return r.tileView;
504            }
505        }
506        return null;
507    }
508
509    public QSFooter getFooter() {
510        return mFooter;
511    }
512
513    private class H extends Handler {
514        private static final int SHOW_DETAIL = 1;
515        private static final int SET_TILE_VISIBILITY = 2;
516        @Override
517        public void handleMessage(Message msg) {
518            if (msg.what == SHOW_DETAIL) {
519                handleShowDetail((Record)msg.obj, msg.arg1 != 0);
520            }
521        }
522    }
523
524    protected static class Record {
525        DetailAdapter detailAdapter;
526        int x;
527        int y;
528    }
529
530    public static final class TileRecord extends Record {
531        public QSTile<?> tile;
532        public QSTileBaseView tileView;
533        public boolean scanState;
534        public QSTile.Callback callback;
535    }
536
537    public interface Callback {
538        void onShowingDetail(DetailAdapter detail, int x, int y);
539        void onToggleStateChanged(boolean state);
540        void onScanStateChanged(boolean state);
541    }
542
543    public interface QSTileLayout {
544        void addTile(TileRecord tile);
545        void removeTile(TileRecord tile);
546        int getOffsetTop(TileRecord tile);
547        boolean updateResources();
548
549        void setListening(boolean listening);
550    }
551}
552