Home | History | Annotate | Download | only in qs
      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 
     17 package com.android.systemui.qs;
     18 
     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.view.LayoutInflater;
     27 import android.view.View;
     28 import android.widget.ImageView;
     29 import android.widget.LinearLayout;
     30 import com.android.internal.logging.MetricsLogger;
     31 import com.android.internal.logging.MetricsProto.MetricsEvent;
     32 import com.android.systemui.R;
     33 import com.android.systemui.qs.QSTile.DetailAdapter;
     34 import com.android.systemui.qs.QSTile.Host.Callback;
     35 import com.android.systemui.qs.customize.QSCustomizer;
     36 import com.android.systemui.qs.external.CustomTile;
     37 import com.android.systemui.settings.BrightnessController;
     38 import com.android.systemui.settings.ToggleSlider;
     39 import com.android.systemui.statusbar.phone.QSTileHost;
     40 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
     41 import com.android.systemui.tuner.TunerService;
     42 import com.android.systemui.tuner.TunerService.Tunable;
     43 
     44 import java.util.ArrayList;
     45 import java.util.Collection;
     46 
     47 /** View that represents the quick settings tile panel. **/
     48 public 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         mBrightnessController.setBackgroundLooper(host.getLooper());
    186     }
    187 
    188     public QSTileHost getHost() {
    189         return mHost;
    190     }
    191 
    192     public void updateResources() {
    193         final Resources res = mContext.getResources();
    194         mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
    195         mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
    196         setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
    197         for (TileRecord r : mRecords) {
    198             r.tile.clearState();
    199         }
    200         if (mListening) {
    201             refreshAllTiles();
    202         }
    203         if (mTileLayout != null) {
    204             mTileLayout.updateResources();
    205         }
    206     }
    207 
    208     @Override
    209     protected void onConfigurationChanged(Configuration newConfig) {
    210         super.onConfigurationChanged(newConfig);
    211         mFooter.onConfigurationChanged();
    212 
    213         if (mBrightnessMirrorController != null) {
    214             // Reload the mirror in case it got reinflated but we didn't.
    215             setBrightnessMirror(mBrightnessMirrorController);
    216         }
    217     }
    218 
    219     public void onCollapse() {
    220         if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
    221             mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
    222         }
    223     }
    224 
    225     public void setExpanded(boolean expanded) {
    226         if (mExpanded == expanded) return;
    227         mExpanded = expanded;
    228         if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
    229             ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
    230         }
    231         MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
    232         if (!mExpanded) {
    233             closeDetail();
    234         } else {
    235             logTiles();
    236         }
    237     }
    238 
    239     public void setListening(boolean listening) {
    240         if (mListening == listening) return;
    241         mListening = listening;
    242         if (mTileLayout != null) {
    243             mTileLayout.setListening(listening);
    244         }
    245         mFooter.setListening(mListening);
    246         if (mListening) {
    247             refreshAllTiles();
    248         }
    249         if (mBrightnessView.getVisibility() == View.VISIBLE) {
    250             if (listening) {
    251                 mBrightnessController.registerCallbacks();
    252             } else {
    253                 mBrightnessController.unregisterCallbacks();
    254             }
    255         }
    256     }
    257 
    258     public void refreshAllTiles() {
    259         for (TileRecord r : mRecords) {
    260             r.tile.refreshState();
    261         }
    262         mFooter.refreshState();
    263     }
    264 
    265     public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
    266         int xInWindow = locationInWindow[0];
    267         int yInWindow = locationInWindow[1];
    268         ((View) getParent()).getLocationInWindow(locationInWindow);
    269 
    270         Record r = new Record();
    271         r.detailAdapter = adapter;
    272         r.x = xInWindow - locationInWindow[0];
    273         r.y = yInWindow - locationInWindow[1];
    274 
    275         locationInWindow[0] = xInWindow;
    276         locationInWindow[1] = yInWindow;
    277 
    278         showDetail(show, r);
    279     }
    280 
    281     protected void showDetail(boolean show, Record r) {
    282         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
    283     }
    284 
    285     public void setTiles(Collection<QSTile<?>> tiles) {
    286         setTiles(tiles, false);
    287     }
    288 
    289     public void setTiles(Collection<QSTile<?>> tiles, boolean collapsedView) {
    290         for (TileRecord record : mRecords) {
    291             mTileLayout.removeTile(record);
    292             record.tile.removeCallback(record.callback);
    293         }
    294         mRecords.clear();
    295         for (QSTile<?> tile : tiles) {
    296             addTile(tile, collapsedView);
    297         }
    298     }
    299 
    300     protected void drawTile(TileRecord r, QSTile.State state) {
    301         r.tileView.onStateChanged(state);
    302     }
    303 
    304     protected QSTileBaseView createTileView(QSTile<?> tile, boolean collapsedView) {
    305         return new QSTileView(mContext, tile.createTileView(mContext), collapsedView);
    306     }
    307 
    308     protected boolean shouldShowDetail() {
    309         return mExpanded;
    310     }
    311 
    312     protected void addTile(final QSTile<?> tile, boolean collapsedView) {
    313         final TileRecord r = new TileRecord();
    314         r.tile = tile;
    315         r.tileView = createTileView(tile, collapsedView);
    316         final QSTile.Callback callback = new QSTile.Callback() {
    317             @Override
    318             public void onStateChanged(QSTile.State state) {
    319                 drawTile(r, state);
    320             }
    321 
    322             @Override
    323             public void onShowDetail(boolean show) {
    324                 // Both the collapsed and full QS panels get this callback, this check determines
    325                 // which one should handle showing the detail.
    326                 if (shouldShowDetail()) {
    327                     QSPanel.this.showDetail(show, r);
    328                 }
    329             }
    330 
    331             @Override
    332             public void onToggleStateChanged(boolean state) {
    333                 if (mDetailRecord == r) {
    334                     fireToggleStateChanged(state);
    335                 }
    336             }
    337 
    338             @Override
    339             public void onScanStateChanged(boolean state) {
    340                 r.scanState = state;
    341                 if (mDetailRecord == r) {
    342                     fireScanStateChanged(r.scanState);
    343                 }
    344             }
    345 
    346             @Override
    347             public void onAnnouncementRequested(CharSequence announcement) {
    348                 announceForAccessibility(announcement);
    349             }
    350         };
    351         r.tile.addCallback(callback);
    352         r.callback = callback;
    353         final View.OnClickListener click = new View.OnClickListener() {
    354             @Override
    355             public void onClick(View v) {
    356                 onTileClick(r.tile);
    357             }
    358         };
    359         final View.OnLongClickListener longClick = new View.OnLongClickListener() {
    360             @Override
    361             public boolean onLongClick(View v) {
    362                 r.tile.longClick();
    363                 return true;
    364             }
    365         };
    366         r.tileView.init(click, longClick);
    367         r.tile.refreshState();
    368         mRecords.add(r);
    369 
    370         if (mTileLayout != null) {
    371             mTileLayout.addTile(r);
    372         }
    373     }
    374 
    375 
    376     public void showEdit(final View v) {
    377         v.post(new Runnable() {
    378             @Override
    379             public void run() {
    380                 if (mCustomizePanel != null) {
    381                     if (!mCustomizePanel.isCustomizing()) {
    382                         int[] loc = new int[2];
    383                         v.getLocationInWindow(loc);
    384                         int x = loc[0] + v.getWidth() / 2;
    385                         int y = loc[1] + v.getHeight() / 2;
    386                         mCustomizePanel.show(x, y);
    387                     }
    388                 }
    389 
    390             }
    391         });
    392     }
    393 
    394     protected void onTileClick(QSTile<?> tile) {
    395         tile.click();
    396     }
    397 
    398     public void closeDetail() {
    399         if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
    400             // Treat this as a detail panel for now, to make things easy.
    401             mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
    402             return;
    403         }
    404         showDetail(false, mDetailRecord);
    405     }
    406 
    407     public int getGridHeight() {
    408         return getMeasuredHeight();
    409     }
    410 
    411     protected void handleShowDetail(Record r, boolean show) {
    412         if (r instanceof TileRecord) {
    413             handleShowDetailTile((TileRecord) r, show);
    414         } else {
    415             int x = 0;
    416             int y = 0;
    417             if (r != null) {
    418                 x = r.x;
    419                 y = r.y;
    420             }
    421             handleShowDetailImpl(r, show, x, y);
    422         }
    423     }
    424 
    425     private void handleShowDetailTile(TileRecord r, boolean show) {
    426         if ((mDetailRecord != null) == show && mDetailRecord == r) return;
    427 
    428         if (show) {
    429             r.detailAdapter = r.tile.getDetailAdapter();
    430             if (r.detailAdapter == null) return;
    431         }
    432         r.tile.setDetailListening(show);
    433         int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
    434         int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + r.tileView.getHeight() / 2
    435                 + getTop();
    436         handleShowDetailImpl(r, show, x, y);
    437     }
    438 
    439     private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
    440         setDetailRecord(show ? r : null);
    441         fireShowingDetail(show ? r.detailAdapter : null, x, y);
    442     }
    443 
    444     private void setDetailRecord(Record r) {
    445         if (r == mDetailRecord) return;
    446         mDetailRecord = r;
    447         final boolean scanState = mDetailRecord instanceof TileRecord
    448                 && ((TileRecord) mDetailRecord).scanState;
    449         fireScanStateChanged(scanState);
    450     }
    451 
    452     void setGridContentVisibility(boolean visible) {
    453         int newVis = visible ? VISIBLE : INVISIBLE;
    454         setVisibility(newVis);
    455         if (mGridContentVisible != visible) {
    456             MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
    457         }
    458         mGridContentVisible = visible;
    459     }
    460 
    461     private void logTiles() {
    462         for (int i = 0; i < mRecords.size(); i++) {
    463             TileRecord tileRecord = mRecords.get(i);
    464             MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
    465         }
    466     }
    467 
    468     private void fireShowingDetail(DetailAdapter detail, int x, int y) {
    469         if (mCallback != null) {
    470             mCallback.onShowingDetail(detail, x, y);
    471         }
    472     }
    473 
    474     private void fireToggleStateChanged(boolean state) {
    475         if (mCallback != null) {
    476             mCallback.onToggleStateChanged(state);
    477         }
    478     }
    479 
    480     private void fireScanStateChanged(boolean state) {
    481         if (mCallback != null) {
    482             mCallback.onScanStateChanged(state);
    483         }
    484     }
    485 
    486     public void clickTile(ComponentName tile) {
    487         final String spec = CustomTile.toSpec(tile);
    488         final int N = mRecords.size();
    489         for (int i = 0; i < N; i++) {
    490             if (mRecords.get(i).tile.getTileSpec().equals(spec)) {
    491                 mRecords.get(i).tile.click();
    492                 break;
    493             }
    494         }
    495     }
    496 
    497     QSTileLayout getTileLayout() {
    498         return mTileLayout;
    499     }
    500 
    501     QSTileBaseView getTileView(QSTile<?> tile) {
    502         for (TileRecord r : mRecords) {
    503             if (r.tile == tile) {
    504                 return r.tileView;
    505             }
    506         }
    507         return null;
    508     }
    509 
    510     public QSFooter getFooter() {
    511         return mFooter;
    512     }
    513 
    514     private class H extends Handler {
    515         private static final int SHOW_DETAIL = 1;
    516         private static final int SET_TILE_VISIBILITY = 2;
    517         @Override
    518         public void handleMessage(Message msg) {
    519             if (msg.what == SHOW_DETAIL) {
    520                 handleShowDetail((Record)msg.obj, msg.arg1 != 0);
    521             }
    522         }
    523     }
    524 
    525     protected static class Record {
    526         DetailAdapter detailAdapter;
    527         int x;
    528         int y;
    529     }
    530 
    531     public static final class TileRecord extends Record {
    532         public QSTile<?> tile;
    533         public QSTileBaseView tileView;
    534         public boolean scanState;
    535         public QSTile.Callback callback;
    536     }
    537 
    538     public interface Callback {
    539         void onShowingDetail(DetailAdapter detail, int x, int y);
    540         void onToggleStateChanged(boolean state);
    541         void onScanStateChanged(boolean state);
    542     }
    543 
    544     public interface QSTileLayout {
    545         void addTile(TileRecord tile);
    546         void removeTile(TileRecord tile);
    547         int getOffsetTop(TileRecord tile);
    548         boolean updateResources();
    549 
    550         void setListening(boolean listening);
    551     }
    552 }
    553