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.MenuItem;
28 import android.view.View;
29 import android.widget.LinearLayout;
30 import android.widget.Toolbar;
31 
32 import androidx.annotation.Nullable;
33 import androidx.recyclerview.widget.DefaultItemAnimator;
34 import androidx.recyclerview.widget.RecyclerView;
35 
36 import com.android.systemui.plugins.qs.QS;
37 import com.android.systemui.plugins.qs.QSContainerController;
38 import com.android.systemui.qs.QSDetailClipper;
39 import com.android.systemui.qs.QSUtils;
40 import com.android.systemui.res.R;
41 import com.android.systemui.statusbar.phone.LightBarController;
42 
43 /**
44  * Allows full-screen customization of QS, through show() and hide().
45  *
46  * This adds itself to the status bar window, so it can appear on top of quick settings and
47  * *someday* do fancy animations to get into/out of it.
48  */
49 public class QSCustomizer extends LinearLayout {
50 
51     static final int MENU_RESET = Menu.FIRST;
52     static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
53 
54     private final QSDetailClipper mClipper;
55     private final View mTransparentView;
56 
57     private boolean isShown;
58     private final RecyclerView mRecyclerView;
59     private boolean mCustomizing;
60     private QSContainerController mQsContainerController;
61     private final Toolbar mToolbar;
62     private QS mQs;
63     private int mX;
64     private int mY;
65     private boolean mOpening;
66     private boolean mIsShowingNavBackdrop;
67 
68     private boolean mSceneContainerEnabled;
69 
QSCustomizer(Context context, AttributeSet attrs)70     public QSCustomizer(Context context, AttributeSet attrs) {
71         super(context, attrs);
72 
73         LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
74         mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
75         mToolbar = findViewById(com.android.internal.R.id.action_bar);
76         TypedValue value = new TypedValue();
77         mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
78         mToolbar.setNavigationIcon(
79                 getResources().getDrawable(value.resourceId, mContext.getTheme()));
80 
81         mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
82                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
83         mToolbar.setTitle(R.string.qs_edit);
84         mRecyclerView = findViewById(android.R.id.list);
85         mTransparentView = findViewById(R.id.customizer_transparent_view);
86         DefaultItemAnimator animator = new DefaultItemAnimator();
87         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
88         mRecyclerView.setItemAnimator(animator);
89 
90         updateTransparentViewHeight();
91     }
92 
applyBottomNavBarToPadding(int padding)93     void applyBottomNavBarToPadding(int padding) {
94         mRecyclerView.setPadding(
95                 /* left= */ mRecyclerView.getPaddingLeft(),
96                 /* top= */ mRecyclerView.getPaddingTop(),
97                 /* right= */ mRecyclerView.getPaddingRight(),
98                 /* bottom= */ padding
99         );
100     }
101 
setSceneContainerEnabled(boolean enabled)102     void setSceneContainerEnabled(boolean enabled) {
103         if (enabled != mSceneContainerEnabled) {
104             mSceneContainerEnabled = enabled;
105             updateTransparentViewHeight();
106             if (mSceneContainerEnabled) {
107                 findViewById(R.id.nav_bar_background).setVisibility(View.GONE);
108             } else {
109                 findViewById(R.id.nav_bar_background)
110                         .setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
111             }
112         }
113     }
114 
updateResources()115     void updateResources() {
116         updateTransparentViewHeight();
117         mRecyclerView.getAdapter().notifyItemChanged(0);
118     }
119 
updateNavBackDrop(Configuration newConfig, LightBarController lightBarController)120     void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) {
121         View navBackdrop = findViewById(R.id.nav_bar_background);
122         mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
123                 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
124         if (navBackdrop != null) {
125             navBackdrop.setVisibility(
126                     mIsShowingNavBackdrop && !mSceneContainerEnabled ? View.VISIBLE : View.GONE);
127         }
128         updateNavColors(lightBarController);
129     }
130 
updateNavColors(LightBarController lightBarController)131     void updateNavColors(LightBarController lightBarController) {
132         lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
133     }
134 
setContainerController(QSContainerController controller)135     public void setContainerController(QSContainerController controller) {
136         mQsContainerController = controller;
137     }
138 
setQs(@ullable QS qs)139     public void setQs(@Nullable QS qs) {
140         mQs = qs;
141     }
142 
reloadAdapterTileHeight(@ullable RecyclerView.Adapter adapter)143     private void reloadAdapterTileHeight(@Nullable RecyclerView.Adapter adapter) {
144         if (adapter instanceof TileAdapter) {
145             ((TileAdapter) adapter).reloadTileHeight();
146         }
147     }
148 
149     /** Animate and show QSCustomizer panel.
150      * @param x,y Location on screen of {@code edit} button to determine center of animation.
151      */
show(int x, int y, TileAdapter tileAdapter)152     void show(int x, int y, TileAdapter tileAdapter) {
153         if (!isShown) {
154             reloadAdapterTileHeight(tileAdapter);
155             mRecyclerView.getLayoutManager().scrollToPosition(0);
156             int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
157             mX = x - containerLocation[0];
158             mY = y - containerLocation[1];
159             isShown = true;
160             mOpening = true;
161             setVisibility(View.VISIBLE);
162             long duration = mClipper.animateCircularClip(
163                     mX, mY, true, new ExpandAnimatorListener(tileAdapter));
164             if (mQsContainerController != null) {
165                 mQsContainerController.setCustomizerAnimating(true);
166                 mQsContainerController.setCustomizerShowing(true, duration);
167             }
168         }
169     }
170 
171 
showImmediately()172     void showImmediately() {
173         if (!isShown) {
174             reloadAdapterTileHeight(mRecyclerView.getAdapter());
175             mRecyclerView.getLayoutManager().scrollToPosition(0);
176             setVisibility(VISIBLE);
177             mClipper.cancelAnimator();
178             mClipper.showBackground();
179             isShown = true;
180             setCustomizing(true);
181             if (mQsContainerController != null) {
182                 mQsContainerController.setCustomizerAnimating(false);
183                 mQsContainerController.setCustomizerShowing(true);
184             }
185         }
186     }
187 
188     /** Hide the customizer. */
hide(boolean animate)189     public void hide(boolean animate) {
190         if (isShown) {
191             isShown = false;
192             mClipper.cancelAnimator();
193             // Make sure we're not opening (because we're closing). Nobody can think we are
194             // customizing after the next two lines.
195             mOpening = false;
196             long duration = 0;
197             if (animate) {
198                 duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
199             } else {
200                 setVisibility(View.GONE);
201             }
202             if (mQsContainerController != null) {
203                 mQsContainerController.setCustomizerAnimating(animate);
204                 mQsContainerController.setCustomizerShowing(false, duration);
205             }
206         }
207     }
208 
isShown()209     public boolean isShown() {
210         return isShown;
211     }
212 
213     @Override
onConfigurationChanged(Configuration newConfig)214     protected void onConfigurationChanged(Configuration newConfig) {
215         super.onConfigurationChanged(newConfig);
216         mToolbar.setTitleTextAppearance(mContext,
217                 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
218         updateToolbarMenuFontSize();
219     }
220 
setCustomizing(boolean customizing)221     void setCustomizing(boolean customizing) {
222         mCustomizing = customizing;
223         if (mQs != null) {
224             mQs.notifyCustomizeChanged();
225         }
226     }
227 
isCustomizing()228     public boolean isCustomizing() {
229         return mCustomizing || mOpening;
230     }
231 
232     /** @param x,y Location on screen of animation center.
233      */
setEditLocation(int x, int y)234     public void setEditLocation(int x, int y) {
235         int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
236         mX = x - containerLocation[0];
237         mY = y - containerLocation[1];
238     }
239 
240     class ExpandAnimatorListener extends AnimatorListenerAdapter {
241         private final TileAdapter mTileAdapter;
242 
ExpandAnimatorListener(TileAdapter tileAdapter)243         ExpandAnimatorListener(TileAdapter tileAdapter) {
244             mTileAdapter = tileAdapter;
245         }
246 
247         @Override
onAnimationEnd(Animator animation)248         public void onAnimationEnd(Animator animation) {
249             if (isShown) {
250                 setCustomizing(true);
251             }
252             mOpening = false;
253             if (mQsContainerController != null) {
254                 mQsContainerController.setCustomizerAnimating(false);
255             }
256             mRecyclerView.setAdapter(mTileAdapter);
257         }
258 
259         @Override
onAnimationCancel(Animator animation)260         public void onAnimationCancel(Animator animation) {
261             mOpening = false;
262             if (mQs != null) {
263                 mQs.notifyCustomizeChanged();
264             }
265             if (mQsContainerController != null) {
266                 mQsContainerController.setCustomizerAnimating(false);
267             }
268         }
269     }
270 
271     private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
272         @Override
273         public void onAnimationEnd(Animator animation) {
274             if (!isShown) {
275                 setVisibility(View.GONE);
276             }
277             if (mQsContainerController != null) {
278                 mQsContainerController.setCustomizerAnimating(false);
279             }
280         }
281 
282         @Override
283         public void onAnimationCancel(Animator animation) {
284             if (!isShown) {
285                 setVisibility(View.GONE);
286             }
287             if (mQsContainerController != null) {
288                 mQsContainerController.setCustomizerAnimating(false);
289             }
290         }
291     };
292 
getRecyclerView()293     public RecyclerView getRecyclerView() {
294         return mRecyclerView;
295     }
296 
isOpening()297     public boolean isOpening() {
298         return mOpening;
299     }
300 
updateTransparentViewHeight()301     private void updateTransparentViewHeight() {
302         LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
303         lp.height = mSceneContainerEnabled ? 0 : QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
304         mTransparentView.setLayoutParams(lp);
305     }
306 
updateToolbarMenuFontSize()307     private void updateToolbarMenuFontSize() {
308         // Clearing and re-adding the toolbar action force updates the font size
309         mToolbar.getMenu().clear();
310         mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
311                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
312     }
313 }