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