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