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