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