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