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.content.Context;
22 import android.content.res.Configuration;
23 import android.util.AttributeSet;
24 import android.util.TypedValue;
25 import android.view.LayoutInflater;
26 import android.view.Menu;
27 import android.view.View;
28 import android.widget.LinearLayout;
29 import android.widget.Toolbar;
30 
31 import androidx.recyclerview.widget.DefaultItemAnimator;
32 import androidx.recyclerview.widget.RecyclerView;
33 
34 import com.android.systemui.R;
35 import com.android.systemui.plugins.qs.QS;
36 import com.android.systemui.plugins.qs.QSContainerController;
37 import com.android.systemui.qs.QSDetailClipper;
38 import com.android.systemui.statusbar.phone.LightBarController;
39 import com.android.systemui.util.Utils;
40 
41 /**
42  * Allows full-screen customization of QS, through show() and hide().
43  *
44  * This adds itself to the status bar window, so it can appear on top of quick settings and
45  * *someday* do fancy animations to get into/out of it.
46  */
47 public class QSCustomizer extends LinearLayout {
48 
49     static final int MENU_RESET = Menu.FIRST;
50     static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
51 
52     private final QSDetailClipper mClipper;
53     private final View mTransparentView;
54 
55     private boolean isShown;
56     private final RecyclerView mRecyclerView;
57     private boolean mCustomizing;
58     private QSContainerController mQsContainerController;
59     private QS mQs;
60     private int mX;
61     private int mY;
62     private boolean mOpening;
63     private boolean mIsShowingNavBackdrop;
64 
QSCustomizer(Context context, AttributeSet attrs)65     public QSCustomizer(Context context, AttributeSet attrs) {
66         super(context, attrs);
67 
68         LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
69         mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
70         Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar);
71         TypedValue value = new TypedValue();
72         mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
73         toolbar.setNavigationIcon(
74                 getResources().getDrawable(value.resourceId, mContext.getTheme()));
75 
76         toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
77                 mContext.getString(com.android.internal.R.string.reset));
78         toolbar.setTitle(R.string.qs_edit);
79         mRecyclerView = findViewById(android.R.id.list);
80         mTransparentView = findViewById(R.id.customizer_transparent_view);
81         DefaultItemAnimator animator = new DefaultItemAnimator();
82         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
83         mRecyclerView.setItemAnimator(animator);
84     }
85 
updateResources()86     void updateResources() {
87         LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
88         lp.height = Utils.getQsHeaderSystemIconsAreaHeight(mContext);
89         mTransparentView.setLayoutParams(lp);
90     }
91 
updateNavBackDrop(Configuration newConfig, LightBarController lightBarController)92     void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) {
93         View navBackdrop = findViewById(R.id.nav_bar_background);
94         mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
95                 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
96         if (navBackdrop != null) {
97             navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
98         }
99         updateNavColors(lightBarController);
100     }
101 
updateNavColors(LightBarController lightBarController)102     void updateNavColors(LightBarController lightBarController) {
103         lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
104     }
105 
setContainerController(QSContainerController controller)106     public void setContainerController(QSContainerController controller) {
107         mQsContainerController = controller;
108     }
109 
setQs(QS qs)110     public void setQs(QS qs) {
111         mQs = qs;
112     }
113 
114     /** Animate and show QSCustomizer panel.
115      * @param x,y Location on screen of {@code edit} button to determine center of animation.
116      */
show(int x, int y, TileAdapter tileAdapter)117     void show(int x, int y, TileAdapter tileAdapter) {
118         if (!isShown) {
119             int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
120             mX = x - containerLocation[0];
121             mY = y - containerLocation[1];
122             isShown = true;
123             mOpening = true;
124             setVisibility(View.VISIBLE);
125             mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter));
126             mQsContainerController.setCustomizerAnimating(true);
127             mQsContainerController.setCustomizerShowing(true);
128         }
129     }
130 
131 
showImmediately()132     void showImmediately() {
133         if (!isShown) {
134             setVisibility(VISIBLE);
135             mClipper.cancelAnimator();
136             mClipper.showBackground();
137             isShown = true;
138             setCustomizing(true);
139             mQsContainerController.setCustomizerAnimating(false);
140             mQsContainerController.setCustomizerShowing(true);
141         }
142     }
143 
144     /** Hide the customizer. */
hide(boolean animate)145     public void hide(boolean animate) {
146         if (isShown) {
147             isShown = false;
148             mClipper.cancelAnimator();
149             // Make sure we're not opening (because we're closing). Nobody can think we are
150             // customizing after the next two lines.
151             mOpening = false;
152             if (animate) {
153                 mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
154             } else {
155                 setVisibility(View.GONE);
156             }
157             mQsContainerController.setCustomizerAnimating(animate);
158             mQsContainerController.setCustomizerShowing(false);
159         }
160     }
161 
isShown()162     public boolean isShown() {
163         return isShown;
164     }
165 
setCustomizing(boolean customizing)166     void setCustomizing(boolean customizing) {
167         mCustomizing = customizing;
168         mQs.notifyCustomizeChanged();
169     }
170 
isCustomizing()171     public boolean isCustomizing() {
172         return mCustomizing || mOpening;
173     }
174 
175     /** @param x,y Location on screen of animation center.
176      */
setEditLocation(int x, int y)177     public void setEditLocation(int x, int y) {
178         int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
179         mX = x - containerLocation[0];
180         mY = y - containerLocation[1];
181     }
182 
183     class ExpandAnimatorListener extends AnimatorListenerAdapter {
184         private final TileAdapter mTileAdapter;
185 
ExpandAnimatorListener(TileAdapter tileAdapter)186         ExpandAnimatorListener(TileAdapter tileAdapter) {
187             mTileAdapter = tileAdapter;
188         }
189 
190         @Override
onAnimationEnd(Animator animation)191         public void onAnimationEnd(Animator animation) {
192             if (isShown) {
193                 setCustomizing(true);
194             }
195             mOpening = false;
196             mQsContainerController.setCustomizerAnimating(false);
197             mRecyclerView.setAdapter(mTileAdapter);
198         }
199 
200         @Override
onAnimationCancel(Animator animation)201         public void onAnimationCancel(Animator animation) {
202             mOpening = false;
203             mQs.notifyCustomizeChanged();
204             mQsContainerController.setCustomizerAnimating(false);
205         }
206     }
207 
208     private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
209         @Override
210         public void onAnimationEnd(Animator animation) {
211             if (!isShown) {
212                 setVisibility(View.GONE);
213             }
214             mQsContainerController.setCustomizerAnimating(false);
215         }
216 
217         @Override
218         public void onAnimationCancel(Animator animation) {
219             if (!isShown) {
220                 setVisibility(View.GONE);
221             }
222             mQsContainerController.setCustomizerAnimating(false);
223         }
224     };
225 
getRecyclerView()226     public RecyclerView getRecyclerView() {
227         return mRecyclerView;
228     }
229 
isOpening()230     public boolean isOpening() {
231         return mOpening;
232     }
233 }