[go: nahoru, domu]

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