Home | History | Annotate | Download | only in customize
      1 /*
      2  * Copyright (C) 2015 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 package com.android.systemui.qs.customize;
     17 
     18 import android.animation.Animator;
     19 import android.animation.Animator.AnimatorListener;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.app.AlertDialog;
     22 import android.content.Context;
     23 import android.content.res.Configuration;
     24 import android.graphics.drawable.Drawable;
     25 import android.graphics.drawable.TransitionDrawable;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.os.Looper;
     29 import android.support.v7.widget.DefaultItemAnimator;
     30 import android.support.v7.widget.GridLayoutManager;
     31 import android.support.v7.widget.RecyclerView;
     32 import android.util.AttributeSet;
     33 import android.util.TypedValue;
     34 import android.view.ContextThemeWrapper;
     35 import android.view.LayoutInflater;
     36 import android.view.Menu;
     37 import android.view.MenuItem;
     38 import android.view.View;
     39 import android.view.WindowManager;
     40 import android.view.WindowManager.LayoutParams;
     41 import android.widget.LinearLayout;
     42 import android.widget.Toolbar;
     43 import android.widget.Toolbar.OnMenuItemClickListener;
     44 
     45 import com.android.internal.logging.MetricsLogger;
     46 import com.android.internal.logging.nano.MetricsProto;
     47 import com.android.systemui.Dependency;
     48 import com.android.systemui.R;
     49 import com.android.systemui.plugins.qs.QS;
     50 import com.android.systemui.plugins.qs.QSTile;
     51 import com.android.systemui.qs.QSContainerImpl;
     52 import com.android.systemui.qs.QSDetailClipper;
     53 import com.android.systemui.qs.QSTileHost;
     54 import com.android.systemui.statusbar.phone.LightBarController;
     55 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
     56 import com.android.systemui.statusbar.phone.SystemUIDialog;
     57 import com.android.systemui.statusbar.policy.KeyguardMonitor;
     58 import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
     59 
     60 import java.util.ArrayList;
     61 import java.util.List;
     62 
     63 /**
     64  * Allows full-screen customization of QS, through show() and hide().
     65  *
     66  * This adds itself to the status bar window, so it can appear on top of quick settings and
     67  * *someday* do fancy animations to get into/out of it.
     68  */
     69 public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener {
     70 
     71     private static final int MENU_RESET = Menu.FIRST;
     72     private static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
     73 
     74     private final QSDetailClipper mClipper;
     75     private final LightBarController mLightBarController;
     76 
     77     private boolean isShown;
     78     private QSTileHost mHost;
     79     private RecyclerView mRecyclerView;
     80     private TileAdapter mTileAdapter;
     81     private Toolbar mToolbar;
     82     private boolean mCustomizing;
     83     private NotificationsQuickSettingsContainer mNotifQsContainer;
     84     private QS mQs;
     85     private boolean mFinishedFetchingTiles = false;
     86     private int mX;
     87     private int mY;
     88     private boolean mOpening;
     89     private boolean mIsShowingNavBackdrop;
     90 
     91     public QSCustomizer(Context context, AttributeSet attrs) {
     92         super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
     93         mClipper = new QSDetailClipper(this);
     94 
     95         LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
     96 
     97         mToolbar = findViewById(com.android.internal.R.id.action_bar);
     98         TypedValue value = new TypedValue();
     99         mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
    100         mToolbar.setNavigationIcon(
    101                 getResources().getDrawable(value.resourceId, mContext.getTheme()));
    102         mToolbar.setNavigationOnClickListener(new OnClickListener() {
    103             @Override
    104             public void onClick(View v) {
    105                 hide((int) v.getX() + v.getWidth() / 2, (int) v.getY() + v.getHeight() / 2);
    106             }
    107         });
    108         mToolbar.setOnMenuItemClickListener(this);
    109         mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
    110                 mContext.getString(com.android.internal.R.string.reset));
    111         mToolbar.setTitle(R.string.qs_edit);
    112 
    113         mRecyclerView = findViewById(android.R.id.list);
    114         mTileAdapter = new TileAdapter(getContext());
    115         mRecyclerView.setAdapter(mTileAdapter);
    116         mTileAdapter.getItemTouchHelper().attachToRecyclerView(mRecyclerView);
    117         GridLayoutManager layout = new GridLayoutManager(getContext(), 3);
    118         layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
    119         mRecyclerView.setLayoutManager(layout);
    120         mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
    121         DefaultItemAnimator animator = new DefaultItemAnimator();
    122         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
    123         mRecyclerView.setItemAnimator(animator);
    124         mLightBarController = Dependency.get(LightBarController.class);
    125         updateNavBackDrop(getResources().getConfiguration());
    126     }
    127 
    128     @Override
    129     protected void onConfigurationChanged(Configuration newConfig) {
    130         super.onConfigurationChanged(newConfig);
    131         updateNavBackDrop(newConfig);
    132     }
    133 
    134     private void updateNavBackDrop(Configuration newConfig) {
    135         View navBackdrop = findViewById(R.id.nav_bar_background);
    136         mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
    137                 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
    138         if (navBackdrop != null) {
    139             navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
    140         }
    141         updateNavColors();
    142     }
    143 
    144     private void updateNavColors() {
    145         mLightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
    146     }
    147 
    148     public void setHost(QSTileHost host) {
    149         mHost = host;
    150         mTileAdapter.setHost(host);
    151     }
    152 
    153     public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) {
    154         mNotifQsContainer = notificationsQsContainer;
    155     }
    156 
    157     public void setQs(QS qs) {
    158         mQs = qs;
    159     }
    160 
    161     public void show(int x, int y) {
    162         if (!isShown) {
    163             mX = x;
    164             mY = y;
    165             MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
    166             isShown = true;
    167             mOpening = true;
    168             setTileSpecs();
    169             setVisibility(View.VISIBLE);
    170             mClipper.animateCircularClip(x, y, true, mExpandAnimationListener);
    171             queryTiles();
    172             mNotifQsContainer.setCustomizerAnimating(true);
    173             mNotifQsContainer.setCustomizerShowing(true);
    174             announceForAccessibility(mContext.getString(
    175                     R.string.accessibility_desc_quick_settings_edit));
    176             Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
    177             updateNavColors();
    178         }
    179     }
    180 
    181 
    182     public void showImmediately() {
    183         if (!isShown) {
    184             setVisibility(VISIBLE);
    185             mClipper.showBackground();
    186             isShown = true;
    187             setTileSpecs();
    188             setCustomizing(true);
    189             queryTiles();
    190             mNotifQsContainer.setCustomizerAnimating(false);
    191             mNotifQsContainer.setCustomizerShowing(true);
    192             Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
    193             updateNavColors();
    194         }
    195     }
    196 
    197     private void queryTiles() {
    198         mFinishedFetchingTiles = false;
    199         Runnable tileQueryFetchCompletion = () -> {
    200             Handler mainHandler = new Handler(Looper.getMainLooper());
    201             mainHandler.post(() -> mFinishedFetchingTiles = true);
    202         };
    203         new TileQueryHelper(mContext, mHost, mTileAdapter, tileQueryFetchCompletion);
    204     }
    205 
    206     public void hide(int x, int y) {
    207         if (isShown) {
    208             MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
    209             isShown = false;
    210             mToolbar.dismissPopupMenus();
    211             setCustomizing(false);
    212             save();
    213             mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
    214             mNotifQsContainer.setCustomizerAnimating(true);
    215             mNotifQsContainer.setCustomizerShowing(false);
    216             announceForAccessibility(mContext.getString(
    217                     R.string.accessibility_desc_quick_settings));
    218             Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
    219             updateNavColors();
    220         }
    221     }
    222 
    223     public boolean isShown() {
    224         return isShown;
    225     }
    226 
    227     private void setCustomizing(boolean customizing) {
    228         mCustomizing = customizing;
    229         mQs.notifyCustomizeChanged();
    230     }
    231 
    232     public boolean isCustomizing() {
    233         return mCustomizing;
    234     }
    235 
    236     @Override
    237     public boolean onMenuItemClick(MenuItem item) {
    238         switch (item.getItemId()) {
    239             case MENU_RESET:
    240                 MetricsLogger.action(getContext(), MetricsProto.MetricsEvent.ACTION_QS_EDIT_RESET);
    241                 reset();
    242                 break;
    243         }
    244         return false;
    245     }
    246 
    247     private void reset() {
    248         ArrayList<String> tiles = new ArrayList<>();
    249         String defTiles = mContext.getString(R.string.quick_settings_tiles_default);
    250         for (String tile : defTiles.split(",")) {
    251             tiles.add(tile);
    252         }
    253         mTileAdapter.resetTileSpecs(mHost, tiles);
    254     }
    255 
    256     private void setTileSpecs() {
    257         List<String> specs = new ArrayList<>();
    258         for (QSTile tile : mHost.getTiles()) {
    259             specs.add(tile.getTileSpec());
    260         }
    261         mTileAdapter.setTileSpecs(specs);
    262         mRecyclerView.setAdapter(mTileAdapter);
    263     }
    264 
    265     private void save() {
    266         if (mFinishedFetchingTiles) {
    267             mTileAdapter.saveSpecs(mHost);
    268         }
    269     }
    270 
    271 
    272     public void saveInstanceState(Bundle outState) {
    273         if (isShown) {
    274             Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
    275         }
    276         outState.putBoolean(EXTRA_QS_CUSTOMIZING, mCustomizing);
    277     }
    278 
    279     public void restoreInstanceState(Bundle savedInstanceState) {
    280         boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING);
    281         if (customizing) {
    282             setVisibility(VISIBLE);
    283             addOnLayoutChangeListener(new OnLayoutChangeListener() {
    284                 @Override
    285                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
    286                         int oldLeft,
    287                         int oldTop, int oldRight, int oldBottom) {
    288                     removeOnLayoutChangeListener(this);
    289                     showImmediately();
    290                 }
    291             });
    292         }
    293     }
    294 
    295     public void setEditLocation(int x, int y) {
    296         mX = x;
    297         mY = y;
    298     }
    299 
    300     private final Callback mKeyguardCallback = () -> {
    301         if (!isAttachedToWindow()) return;
    302         if (Dependency.get(KeyguardMonitor.class).isShowing() && !mOpening) {
    303             hide(0, 0);
    304         }
    305     };
    306 
    307     private final AnimatorListener mExpandAnimationListener = new AnimatorListenerAdapter() {
    308         @Override
    309         public void onAnimationEnd(Animator animation) {
    310             if (isShown) {
    311                 setCustomizing(true);
    312             }
    313             mOpening = false;
    314             mNotifQsContainer.setCustomizerAnimating(false);
    315         }
    316 
    317         @Override
    318         public void onAnimationCancel(Animator animation) {
    319             mOpening = false;
    320             mNotifQsContainer.setCustomizerAnimating(false);
    321         }
    322     };
    323 
    324     private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
    325         @Override
    326         public void onAnimationEnd(Animator animation) {
    327             if (!isShown) {
    328                 setVisibility(View.GONE);
    329             }
    330             mNotifQsContainer.setCustomizerAnimating(false);
    331             mRecyclerView.setAdapter(mTileAdapter);
    332         }
    333 
    334         @Override
    335         public void onAnimationCancel(Animator animation) {
    336             if (!isShown) {
    337                 setVisibility(View.GONE);
    338             }
    339             mNotifQsContainer.setCustomizerAnimating(false);
    340         }
    341     };
    342 }
    343