• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.customize;
18 
19 import static com.android.systemui.qs.customize.QSCustomizer.EXTRA_QS_CUSTOMIZING;
20 import static com.android.systemui.qs.customize.QSCustomizer.MENU_RESET;
21 
22 import android.content.res.Configuration;
23 import android.graphics.Rect;
24 import android.os.Bundle;
25 import android.view.MenuItem;
26 import android.view.View;
27 import android.widget.TextView;
28 import android.widget.Toolbar;
29 import android.widget.Toolbar.OnMenuItemClickListener;
30 
31 import androidx.annotation.Nullable;
32 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
33 import androidx.recyclerview.widget.GridLayoutManager;
34 import androidx.recyclerview.widget.RecyclerView;
35 
36 import com.android.internal.logging.UiEventLogger;
37 import com.android.systemui.keyguard.ScreenLifecycle;
38 import com.android.systemui.plugins.qs.QS;
39 import com.android.systemui.plugins.qs.QSContainerController;
40 import com.android.systemui.plugins.qs.QSTile;
41 import com.android.systemui.qs.QSEditEvent;
42 import com.android.systemui.qs.QSHost;
43 import com.android.systemui.qs.dagger.QSScope;
44 import com.android.systemui.res.R;
45 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
46 import com.android.systemui.shade.ShadeDisplayAware;
47 import com.android.systemui.statusbar.phone.LightBarController;
48 import com.android.systemui.statusbar.policy.ConfigurationController;
49 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
50 import com.android.systemui.statusbar.policy.KeyguardStateController;
51 import com.android.systemui.util.ViewController;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 import javax.inject.Inject;
57 
58 /** {@link ViewController} for {@link QSCustomizer}. */
59 @QSScope
60 public class QSCustomizerController extends ViewController<QSCustomizer> {
61     private final TileQueryHelper mTileQueryHelper;
62     private final QSHost mQsHost;
63     private final TileAdapter mTileAdapter;
64     private final ScreenLifecycle mScreenLifecycle;
65     private final KeyguardStateController mKeyguardStateController;
66     private final LightBarController mLightBarController;
67     private final ConfigurationController mConfigurationController;
68     private final UiEventLogger mUiEventLogger;
69     private final Toolbar mToolbar;
70 
71     private final OnMenuItemClickListener mOnMenuItemClickListener = new OnMenuItemClickListener() {
72         @Override
73         public boolean onMenuItemClick(MenuItem item) {
74             if (item.getItemId() == MENU_RESET) {
75                 mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET);
76                 reset();
77             }
78             return false;
79         }
80     };
81 
82     private final KeyguardStateController.Callback mKeyguardCallback =
83             new KeyguardStateController.Callback() {
84         @Override
85         public void onKeyguardShowingChanged() {
86             if (!mView.isAttachedToWindow()) return;
87             if (mKeyguardStateController.isShowing() && !mView.isOpening()) {
88                 hide();
89             }
90         }
91     };
92 
93     private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
94         @Override
95         public void onConfigChanged(Configuration newConfig) {
96             mView.updateNavBackDrop(newConfig, mLightBarController);
97             mView.updateResources();
98             if (mTileAdapter.updateNumColumns()) {
99                 RecyclerView.LayoutManager lm = mView.getRecyclerView().getLayoutManager();
100                 if (lm instanceof GridLayoutManager) {
101                     ((GridLayoutManager) lm).setSpanCount(mTileAdapter.getNumColumns());
102                 }
103             }
104         }
105     };
106 
107     @Inject
QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper, QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle, KeyguardStateController keyguardStateController, LightBarController lightBarController, @ShadeDisplayAware ConfigurationController configurationController, UiEventLogger uiEventLogger)108     protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
109             QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
110             KeyguardStateController keyguardStateController, LightBarController lightBarController,
111             @ShadeDisplayAware ConfigurationController configurationController,
112             UiEventLogger uiEventLogger) {
113         super(view);
114         mTileQueryHelper = tileQueryHelper;
115         mQsHost = qsHost;
116         mTileAdapter = tileAdapter;
117         mScreenLifecycle = screenLifecycle;
118         mKeyguardStateController = keyguardStateController;
119         mLightBarController = lightBarController;
120         mConfigurationController = configurationController;
121         mUiEventLogger = uiEventLogger;
122         view.setSceneContainerEnabled(SceneContainerFlag.isEnabled());
123 
124         mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
125     }
126 
applyBottomNavBarSizeToRecyclerViewPadding(int padding)127     public void applyBottomNavBarSizeToRecyclerViewPadding(int padding) {
128         mView.applyBottomNavBarToPadding(padding);
129     }
130 
131     @Override
onViewAttached()132     protected void onViewAttached() {
133         mView.updateNavBackDrop(getResources().getConfiguration(), mLightBarController);
134 
135         mConfigurationController.addCallback(mConfigurationListener);
136 
137         mTileQueryHelper.setListener(mTileAdapter);
138         int halfMargin =
139                 getResources().getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
140         mTileAdapter.changeHalfMargin(halfMargin);
141 
142         RecyclerView recyclerView = mView.getRecyclerView();
143         recyclerView.setAdapter(mTileAdapter);
144         mTileAdapter.getItemTouchHelper().attachToRecyclerView(recyclerView);
145         GridLayoutManager layout =
146                 new GridLayoutManager(getContext(), mTileAdapter.getNumColumns()) {
147             @Override
148             public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
149                     RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
150                 // Do not read row and column every time it changes.
151             }
152 
153             public void calculateItemDecorationsForChild(View child, Rect outRect) {
154                 // There's only a single item decoration that cares about the itemOffsets, so
155                 // we just call it manually so they are never cached. This way, it's updated as the
156                 // tiles are moved around.
157                 // It only sets the left and right margin and only cares about tiles (not TextView).
158                 if (!(child instanceof TextView)) {
159                     outRect.setEmpty();
160                     mTileAdapter.getMarginItemDecoration().getItemOffsets(outRect, child,
161                             recyclerView, new RecyclerView.State());
162                     ((LayoutParams) child.getLayoutParams()).leftMargin = outRect.left;
163                     ((LayoutParams) child.getLayoutParams()).rightMargin = outRect.right;
164                 }
165             }
166         };
167         layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
168         recyclerView.setLayoutManager(layout);
169         recyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
170         recyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
171 
172         mToolbar.setOnMenuItemClickListener(mOnMenuItemClickListener);
173         mToolbar.setNavigationOnClickListener(v -> hide());
174     }
175 
176     @Override
onViewDetached()177     protected void onViewDetached() {
178         mTileQueryHelper.setListener(null);
179         mToolbar.setOnMenuItemClickListener(null);
180         mConfigurationController.removeCallback(mConfigurationListener);
181     }
182 
183 
reset()184     private void reset() {
185         mTileAdapter.resetTileSpecs(QSHost.getDefaultSpecs(getContext().getResources()));
186     }
187 
isCustomizing()188     public boolean isCustomizing() {
189         return mView.isCustomizing();
190     }
191 
192     /** */
show(int x, int y, boolean immediate)193     public void show(int x, int y, boolean immediate) {
194         if (!mView.isShown()) {
195             setTileSpecs();
196             if (immediate) {
197                 mView.showImmediately();
198             } else {
199                 mView.show(x, y, mTileAdapter);
200                 mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN);
201             }
202             mTileQueryHelper.queryTiles(mQsHost);
203             mKeyguardStateController.addCallback(mKeyguardCallback);
204             mView.updateNavColors(mLightBarController);
205         }
206     }
207 
208     /** */
setQs(@ullable QS qsFragment)209     public void setQs(@Nullable QS qsFragment) {
210         mView.setQs(qsFragment);
211     }
212 
213     /** */
restoreInstanceState(Bundle savedInstanceState)214     public void restoreInstanceState(Bundle savedInstanceState) {
215         boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING);
216         if (customizing) {
217             mView.setVisibility(View.VISIBLE);
218             mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
219                 @Override
220                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
221                         int oldLeft,
222                         int oldTop, int oldRight, int oldBottom) {
223                     mView.removeOnLayoutChangeListener(this);
224                     show(0, 0, true);
225                 }
226             });
227         }
228     }
229 
230     /** */
saveInstanceState(Bundle outState)231     public void saveInstanceState(Bundle outState) {
232         if (mView.isShown()) {
233             mKeyguardStateController.removeCallback(mKeyguardCallback);
234         }
235         outState.putBoolean(EXTRA_QS_CUSTOMIZING, mView.isCustomizing());
236     }
237 
238     /** */
setEditLocation(int x, int y)239     public void setEditLocation(int x, int y) {
240         mView.setEditLocation(x, y);
241     }
242 
243     /** */
setContainerController(QSContainerController controller)244     public void setContainerController(QSContainerController controller) {
245         mView.setContainerController(controller);
246     }
247 
isShown()248     public boolean isShown() {
249         return mView.isShown();
250     }
251 
252     /** Hice the customizer. */
hide()253     public void hide() {
254         hide(true);
255     }
256 
hide(boolean animated)257     public void hide(boolean animated) {
258         final boolean animate = animated
259                 && mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
260         if (mView.isShown()) {
261             mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
262             mToolbar.dismissPopupMenus();
263             mView.setCustomizing(false);
264             save();
265             mView.hide(animate);
266             mView.updateNavColors(mLightBarController);
267             mKeyguardStateController.removeCallback(mKeyguardCallback);
268         }
269     }
270 
save()271     private void save() {
272         if (mTileQueryHelper.isFinished()) {
273             mTileAdapter.saveSpecs(mQsHost);
274         }
275     }
276 
setTileSpecs()277     private void setTileSpecs() {
278         List<String> specs = new ArrayList<>();
279         for (QSTile tile : mQsHost.getTiles()) {
280             specs.add(tile.getTileSpec());
281         }
282         mTileAdapter.setTileSpecs(specs);
283     }
284 }
285