• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
85         mRecyclerView = findViewById(android.R.id.list);
86         mTransparentView = findViewById(R.id.customizer_transparent_view);
87         DefaultItemAnimator animator = new DefaultItemAnimator();
88         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
89         mRecyclerView.setItemAnimator(animator);
90 
91         updateTransparentViewHeight();
92     }
93 
applyBottomNavBarToPadding(int padding)94     void applyBottomNavBarToPadding(int padding) {
95         mRecyclerView.setPadding(
96                 /* left= */ mRecyclerView.getPaddingLeft(),
97                 /* top= */ mRecyclerView.getPaddingTop(),
98                 /* right= */ mRecyclerView.getPaddingRight(),
99                 /* bottom= */ padding
100         );
101     }
102 
setSceneContainerEnabled(boolean enabled)103     void setSceneContainerEnabled(boolean enabled) {
104         if (enabled != mSceneContainerEnabled) {
105             mSceneContainerEnabled = enabled;
106             updateTransparentViewHeight();
107             if (mSceneContainerEnabled) {
108                 findViewById(R.id.nav_bar_background).setVisibility(View.GONE);
109             } else {
110                 findViewById(R.id.nav_bar_background)
111                         .setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
112             }
113         }
114     }
115 
updateResources()116     void updateResources() {
117         updateTransparentViewHeight();
118         mRecyclerView.getAdapter().notifyItemChanged(0);
119     }
120 
updateNavBackDrop(Configuration newConfig, LightBarController lightBarController)121     void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) {
122         View navBackdrop = findViewById(R.id.nav_bar_background);
123         mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
124                 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
125         if (navBackdrop != null) {
126             navBackdrop.setVisibility(
127                     mIsShowingNavBackdrop && !mSceneContainerEnabled ? View.VISIBLE : View.GONE);
128         }
129         updateNavColors(lightBarController);
130     }
131 
updateNavColors(LightBarController lightBarController)132     void updateNavColors(LightBarController lightBarController) {
133         lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
134     }
135 
setContainerController(QSContainerController controller)136     public void setContainerController(QSContainerController controller) {
137         mQsContainerController = controller;
138     }
139 
setQs(@ullable QS qs)140     public void setQs(@Nullable QS qs) {
141         mQs = qs;
142     }
143 
reloadAdapterTileHeight(@ullable RecyclerView.Adapter adapter)144     private void reloadAdapterTileHeight(@Nullable RecyclerView.Adapter adapter) {
145         if (adapter instanceof TileAdapter) {
146             ((TileAdapter) adapter).reloadTileHeight();
147         }
148     }
149 
150     /** Animate and show QSCustomizer panel.
151      * @param x,y Location on screen of {@code edit} button to determine center of animation.
152      */
show(int x, int y, TileAdapter tileAdapter)153     void show(int x, int y, TileAdapter tileAdapter) {
154         if (!isShown) {
155             reloadAdapterTileHeight(tileAdapter);
156             mRecyclerView.getLayoutManager().scrollToPosition(0);
157             int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
158             mX = x - containerLocation[0];
159             mY = y - containerLocation[1];
160             isShown = true;
161             mOpening = true;
162             setVisibility(View.VISIBLE);
163             long duration = mClipper.animateCircularClip(
164                     mX, mY, true, new ExpandAnimatorListener(tileAdapter));
165             if (mQsContainerController != null) {
166                 mQsContainerController.setCustomizerAnimating(true);
167                 mQsContainerController.setCustomizerShowing(true, duration);
168             }
169         }
170     }
171 
172 
showImmediately()173     void showImmediately() {
174         if (!isShown) {
175             reloadAdapterTileHeight(mRecyclerView.getAdapter());
176             mRecyclerView.getLayoutManager().scrollToPosition(0);
177             setVisibility(VISIBLE);
178             mClipper.cancelAnimator();
179             mClipper.showBackground();
180             isShown = true;
181             setCustomizing(true);
182             if (mQsContainerController != null) {
183                 mQsContainerController.setCustomizerAnimating(false);
184                 mQsContainerController.setCustomizerShowing(true);
185             }
186         }
187     }
188 
189     /** Hide the customizer. */
hide(boolean animate)190     public void hide(boolean animate) {
191         if (isShown) {
192             isShown = false;
193             mClipper.cancelAnimator();
194             // Make sure we're not opening (because we're closing). Nobody can think we are
195             // customizing after the next two lines.
196             mOpening = false;
197             long duration = 0;
198             if (animate) {
199                 duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
200             } else {
201                 setVisibility(View.GONE);
202             }
203             if (mQsContainerController != null) {
204                 mQsContainerController.setCustomizerAnimating(animate);
205                 mQsContainerController.setCustomizerShowing(false, duration);
206             }
207         }
208     }
209 
isShown()210     public boolean isShown() {
211         return isShown;
212     }
213 
214     @Override
onConfigurationChanged(Configuration newConfig)215     protected void onConfigurationChanged(Configuration newConfig) {
216         super.onConfigurationChanged(newConfig);
217         mToolbar.setTitleTextAppearance(mContext,
218                 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
219         updateToolbarMenuFontSize();
220     }
221 
setCustomizing(boolean customizing)222     void setCustomizing(boolean customizing) {
223         mCustomizing = customizing;
224         if (mQs != null) {
225             mQs.notifyCustomizeChanged();
226         }
227     }
228 
isCustomizing()229     public boolean isCustomizing() {
230         return mCustomizing || mOpening;
231     }
232 
233     /** @param x,y Location on screen of animation center.
234      */
setEditLocation(int x, int y)235     public void setEditLocation(int x, int y) {
236         int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
237         mX = x - containerLocation[0];
238         mY = y - containerLocation[1];
239     }
240 
241     class ExpandAnimatorListener extends AnimatorListenerAdapter {
242         private final TileAdapter mTileAdapter;
243 
ExpandAnimatorListener(TileAdapter tileAdapter)244         ExpandAnimatorListener(TileAdapter tileAdapter) {
245             mTileAdapter = tileAdapter;
246         }
247 
248         @Override
onAnimationEnd(Animator animation)249         public void onAnimationEnd(Animator animation) {
250             if (isShown) {
251                 setCustomizing(true);
252             }
253             mOpening = false;
254             if (mQsContainerController != null) {
255                 mQsContainerController.setCustomizerAnimating(false);
256             }
257             mRecyclerView.setAdapter(mTileAdapter);
258         }
259 
260         @Override
onAnimationCancel(Animator animation)261         public void onAnimationCancel(Animator animation) {
262             mOpening = false;
263             if (mQs != null) {
264                 mQs.notifyCustomizeChanged();
265             }
266             if (mQsContainerController != null) {
267                 mQsContainerController.setCustomizerAnimating(false);
268             }
269         }
270     }
271 
272     private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
273         @Override
274         public void onAnimationEnd(Animator animation) {
275             if (!isShown) {
276                 setVisibility(View.GONE);
277             }
278             if (mQsContainerController != null) {
279                 mQsContainerController.setCustomizerAnimating(false);
280             }
281         }
282 
283         @Override
284         public void onAnimationCancel(Animator animation) {
285             if (!isShown) {
286                 setVisibility(View.GONE);
287             }
288             if (mQsContainerController != null) {
289                 mQsContainerController.setCustomizerAnimating(false);
290             }
291         }
292     };
293 
getRecyclerView()294     public RecyclerView getRecyclerView() {
295         return mRecyclerView;
296     }
297 
isOpening()298     public boolean isOpening() {
299         return mOpening;
300     }
301 
updateTransparentViewHeight()302     private void updateTransparentViewHeight() {
303         LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
304         lp.height = mSceneContainerEnabled ? 0 : QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
305         mTransparentView.setLayoutParams(lp);
306     }
307 
updateToolbarMenuFontSize()308     private void updateToolbarMenuFontSize() {
309         // Clearing and re-adding the toolbar action force updates the font size
310         mToolbar.getMenu().clear();
311         mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
312                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
313     }
314 }