blob: 83b000dca83bb41621e4f16a1bc90790de95d64d [file] [log] [blame]
Jason Monkbd6dbb02015-09-03 15:46:25 -04001/*
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 */
16package com.android.systemui.qs.customize;
17
Jason Monkb9c00192015-10-07 11:45:33 -040018import android.animation.Animator;
Jason Monk62b63a02016-02-02 15:15:31 -050019import android.animation.Animator.AnimatorListener;
Jason Monk77a2c5f2016-02-22 15:18:36 -050020import android.animation.AnimatorListenerAdapter;
Jason Monkbd6dbb02015-09-03 15:46:25 -040021import android.content.Context;
Jason Monk931f5462016-05-25 11:19:04 -040022import android.content.res.Configuration;
Selim Cinek707e2072017-06-30 18:32:40 +020023import android.os.Bundle;
Jason Monkbd6dbb02015-09-03 15:46:25 -040024import android.util.AttributeSet;
Jason Monkb53b6c52016-02-24 17:25:49 -050025import android.util.TypedValue;
Jason Monk5db8a412015-10-21 15:16:23 -070026import android.view.ContextThemeWrapper;
Jason Monka8321e62016-03-11 08:56:25 -050027import android.view.LayoutInflater;
Jason Monkb53b6c52016-02-24 17:25:49 -050028import android.view.Menu;
29import android.view.MenuItem;
Jason Monk5db8a412015-10-21 15:16:23 -070030import android.view.View;
Jason Monkbd6dbb02015-09-03 15:46:25 -040031import android.widget.LinearLayout;
Jason Monkb53b6c52016-02-24 17:25:49 -050032import android.widget.Toolbar;
33import android.widget.Toolbar.OnMenuItemClickListener;
Jason Monk46767b72016-08-18 10:58:04 -040034
Gus Prevasab336792018-11-14 13:52:20 -050035import androidx.recyclerview.widget.DefaultItemAnimator;
36import androidx.recyclerview.widget.GridLayoutManager;
37import androidx.recyclerview.widget.RecyclerView;
38
Jason Monk96defbe2016-03-29 16:51:03 -040039import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010040import com.android.internal.logging.nano.MetricsProto;
Jason Monk9c7844c2017-01-18 15:21:53 -050041import com.android.systemui.Dependency;
Jason Monkbd6dbb02015-09-03 15:46:25 -040042import com.android.systemui.R;
Jason Monk0ceef212016-11-02 14:05:23 -040043import com.android.systemui.plugins.qs.QS;
Jason Monk702e2eb2017-03-03 16:53:44 -050044import com.android.systemui.plugins.qs.QSTile;
Jason Monk78a3d602017-03-16 12:12:57 -040045import com.android.systemui.qs.QSDetailClipper;
Jason Monke5b770e2017-03-03 21:49:29 -050046import com.android.systemui.qs.QSTileHost;
Jason Monk73112d32017-09-06 16:53:53 -040047import com.android.systemui.statusbar.phone.LightBarController;
Jason Monk78a3d602017-03-16 12:12:57 -040048import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
Jason Monk9c7844c2017-01-18 15:21:53 -050049import com.android.systemui.statusbar.policy.KeyguardMonitor;
Jason Monk181d3fe2016-06-06 16:18:50 -040050import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
Jason Monkbd6dbb02015-09-03 15:46:25 -040051
52import java.util.ArrayList;
Jason Monk62b63a02016-02-02 15:15:31 -050053import java.util.List;
Jason Monkbd6dbb02015-09-03 15:46:25 -040054
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 */
Jason Monkb53b6c52016-02-24 17:25:49 -050061public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener {
62
63 private static final int MENU_RESET = Menu.FIRST;
Selim Cinek707e2072017-06-30 18:32:40 +020064 private static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
Fabian Kozynski0db39af2018-08-31 15:56:45 -040065 private static final String TAG = "QSCustomizer";
Jason Monkbd6dbb02015-09-03 15:46:25 -040066
Jason Monkb9c00192015-10-07 11:45:33 -040067 private final QSDetailClipper mClipper;
Jason Monk73112d32017-09-06 16:53:53 -040068 private final LightBarController mLightBarController;
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040069 private final TileQueryHelper mTileQueryHelper;
Fabian Kozynski5d5deeb2018-09-18 14:49:37 -040070 private final View mTransparentView;
Jason Monkbd6dbb02015-09-03 15:46:25 -040071
Jason Monkbd6dbb02015-09-03 15:46:25 -040072 private boolean isShown;
Jason Monkbbadff82015-11-06 15:47:26 -050073 private QSTileHost mHost;
Jason Monk62b63a02016-02-02 15:15:31 -050074 private RecyclerView mRecyclerView;
75 private TileAdapter mTileAdapter;
Jason Monkb53b6c52016-02-24 17:25:49 -050076 private Toolbar mToolbar;
Jason Monk8b9d67f2016-03-02 08:59:08 -050077 private boolean mCustomizing;
78 private NotificationsQuickSettingsContainer mNotifQsContainer;
Jason Monk0ceef212016-11-02 14:05:23 -040079 private QS mQs;
Jason Monke5b770e2017-03-03 21:49:29 -050080 private int mX;
81 private int mY;
Jason Monk78a3d602017-03-16 12:12:57 -040082 private boolean mOpening;
Jason Monk73112d32017-09-06 16:53:53 -040083 private boolean mIsShowingNavBackdrop;
Jason Monkbd6dbb02015-09-03 15:46:25 -040084
85 public QSCustomizer(Context context, AttributeSet attrs) {
Jason Monka8321e62016-03-11 08:56:25 -050086 super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
Jason Monkbd6dbb02015-09-03 15:46:25 -040087
Jason Monka8321e62016-03-11 08:56:25 -050088 LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
Amin Shaikh9a6fa082018-03-29 17:27:26 -040089 mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
Alan Viverette51efddb2017-04-05 10:00:01 -040090 mToolbar = findViewById(com.android.internal.R.id.action_bar);
Jason Monkb53b6c52016-02-24 17:25:49 -050091 TypedValue value = new TypedValue();
92 mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
93 mToolbar.setNavigationIcon(
Jason Monk04fd2492016-02-29 14:29:26 -050094 getResources().getDrawable(value.resourceId, mContext.getTheme()));
Jason Monkb53b6c52016-02-24 17:25:49 -050095 mToolbar.setNavigationOnClickListener(new OnClickListener() {
96 @Override
97 public void onClick(View v) {
Fabian Kozynski0db39af2018-08-31 15:56:45 -040098 hide();
Jason Monkb53b6c52016-02-24 17:25:49 -050099 }
100 });
101 mToolbar.setOnMenuItemClickListener(this);
102 mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
103 mContext.getString(com.android.internal.R.string.reset));
Jason Monkf923db52016-03-02 14:11:14 -0500104 mToolbar.setTitle(R.string.qs_edit);
Alan Viverette51efddb2017-04-05 10:00:01 -0400105 mRecyclerView = findViewById(android.R.id.list);
Fabian Kozynski5d5deeb2018-09-18 14:49:37 -0400106 mTransparentView = findViewById(R.id.customizer_transparent_view);
Jason Monk62b63a02016-02-02 15:15:31 -0500107 mTileAdapter = new TileAdapter(getContext());
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400108 mTileQueryHelper = new TileQueryHelper(context, mTileAdapter);
Jason Monk62b63a02016-02-02 15:15:31 -0500109 mRecyclerView.setAdapter(mTileAdapter);
Jason Monkb53b6c52016-02-24 17:25:49 -0500110 mTileAdapter.getItemTouchHelper().attachToRecyclerView(mRecyclerView);
Jason Monk62b63a02016-02-02 15:15:31 -0500111 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);
Jason Monk73112d32017-09-06 16:53:53 -0400118 mLightBarController = Dependency.get(LightBarController.class);
Selim Cinek707e2072017-06-30 18:32:40 +0200119 updateNavBackDrop(getResources().getConfiguration());
Jason Monkbd6dbb02015-09-03 15:46:25 -0400120 }
121
Jason Monk931f5462016-05-25 11:19:04 -0400122 @Override
123 protected void onConfigurationChanged(Configuration newConfig) {
124 super.onConfigurationChanged(newConfig);
Selim Cinek707e2072017-06-30 18:32:40 +0200125 updateNavBackDrop(newConfig);
Fabian Kozynski5d5deeb2018-09-18 14:49:37 -0400126 updateResources();
127 }
128
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);
Selim Cinek707e2072017-06-30 18:32:40 +0200134 }
135
136 private void updateNavBackDrop(Configuration newConfig) {
Jason Monk931f5462016-05-25 11:19:04 -0400137 View navBackdrop = findViewById(R.id.nav_bar_background);
Jason Monk73112d32017-09-06 16:53:53 -0400138 mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
139 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
Jason Monk931f5462016-05-25 11:19:04 -0400140 if (navBackdrop != null) {
Jason Monk73112d32017-09-06 16:53:53 -0400141 navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
Jason Monk931f5462016-05-25 11:19:04 -0400142 }
Jason Monk73112d32017-09-06 16:53:53 -0400143 updateNavColors();
144 }
145
146 private void updateNavColors() {
147 mLightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
Jason Monk931f5462016-05-25 11:19:04 -0400148 }
149
Jason Monka8321e62016-03-11 08:56:25 -0500150 public void setHost(QSTileHost host) {
151 mHost = host;
Jason Monk63554ed2016-04-29 14:58:29 -0400152 mTileAdapter.setHost(host);
Jason Monka8321e62016-03-11 08:56:25 -0500153 }
154
155 public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) {
156 mNotifQsContainer = notificationsQsContainer;
157 }
158
Jason Monk0ceef212016-11-02 14:05:23 -0400159 public void setQs(QS qs) {
160 mQs = qs;
Jason Monka8321e62016-03-11 08:56:25 -0500161 }
162
Fabian Kozynski0db39af2018-08-31 15:56:45 -0400163 /** Animate and show QSCustomizer panel.
164 * @param x,y Location on screen of {@code edit} button to determine center of animation.
165 */
Jason Monkb9c00192015-10-07 11:45:33 -0400166 public void show(int x, int y) {
Jason Monk8c7e47f2016-02-17 10:39:31 -0500167 if (!isShown) {
Fabian Kozynski0db39af2018-08-31 15:56:45 -0400168 int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen();
169 mX = x - containerLocation[0];
170 mY = y - containerLocation[1];
Jason Monk96defbe2016-03-29 16:51:03 -0400171 MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
Jason Monk8c7e47f2016-02-17 10:39:31 -0500172 isShown = true;
Jason Monk78a3d602017-03-16 12:12:57 -0400173 mOpening = true;
Jason Monk8c7e47f2016-02-17 10:39:31 -0500174 setTileSpecs();
Jason Monk8b9d67f2016-03-02 08:59:08 -0500175 setVisibility(View.VISIBLE);
Fabian Kozynski0db39af2018-08-31 15:56:45 -0400176 mClipper.animateCircularClip(mX, mY, true, mExpandAnimationListener);
Evan Laird6b284732017-02-28 17:27:04 -0500177 queryTiles();
Jason Monk8b9d67f2016-03-02 08:59:08 -0500178 mNotifQsContainer.setCustomizerAnimating(true);
Jason Monk428914d2016-03-30 09:35:36 -0400179 mNotifQsContainer.setCustomizerShowing(true);
Jason Monk9c7844c2017-01-18 15:21:53 -0500180 Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
Jason Monk73112d32017-09-06 16:53:53 -0400181 updateNavColors();
Jason Monk8c7e47f2016-02-17 10:39:31 -0500182 }
Jason Monkbd6dbb02015-09-03 15:46:25 -0400183 }
184
Selim Cinek707e2072017-06-30 18:32:40 +0200185
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);
Jason Monk73112d32017-09-06 16:53:53 -0400197 updateNavColors();
Selim Cinek707e2072017-06-30 18:32:40 +0200198 }
199 }
200
Evan Laird6b284732017-02-28 17:27:04 -0500201 private void queryTiles() {
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400202 mTileQueryHelper.queryTiles(mHost);
Evan Laird6b284732017-02-28 17:27:04 -0500203 }
204
Fabian Kozynski0db39af2018-08-31 15:56:45 -0400205 public void hide() {
Jason Monk8c7e47f2016-02-17 10:39:31 -0500206 if (isShown) {
Jason Monk96defbe2016-03-29 16:51:03 -0400207 MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
Jason Monk8c7e47f2016-02-17 10:39:31 -0500208 isShown = false;
Jason Monkdf8cbc32016-03-16 16:10:56 -0400209 mToolbar.dismissPopupMenus();
Jason Monk8b9d67f2016-03-02 08:59:08 -0500210 setCustomizing(false);
Jason Monk04fd2492016-02-29 14:29:26 -0500211 save();
Jason Monke5b770e2017-03-03 21:49:29 -0500212 mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
Jason Monk8b9d67f2016-03-02 08:59:08 -0500213 mNotifQsContainer.setCustomizerAnimating(true);
Jason Monk428914d2016-03-30 09:35:36 -0400214 mNotifQsContainer.setCustomizerShowing(false);
Jason Monk9c7844c2017-01-18 15:21:53 -0500215 Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
Jason Monk73112d32017-09-06 16:53:53 -0400216 updateNavColors();
Jason Monk8c7e47f2016-02-17 10:39:31 -0500217 }
Jason Monkbd6dbb02015-09-03 15:46:25 -0400218 }
219
Yoshinori Hirano1dd7d072016-09-05 16:11:59 +0900220 public boolean isShown() {
221 return isShown;
222 }
223
Jason Monk8b9d67f2016-03-02 08:59:08 -0500224 private void setCustomizing(boolean customizing) {
225 mCustomizing = customizing;
Jason Monk0ceef212016-11-02 14:05:23 -0400226 mQs.notifyCustomizeChanged();
Jason Monk8b9d67f2016-03-02 08:59:08 -0500227 }
228
Jason Monkbd6dbb02015-09-03 15:46:25 -0400229 public boolean isCustomizing() {
Amin Shaikh1420e7e2018-04-11 11:12:33 -0400230 return mCustomizing || mOpening;
Jason Monkbd6dbb02015-09-03 15:46:25 -0400231 }
232
Jason Monkb53b6c52016-02-24 17:25:49 -0500233 @Override
234 public boolean onMenuItemClick(MenuItem item) {
235 switch (item.getItemId()) {
236 case MENU_RESET:
Jason Monk96defbe2016-03-29 16:51:03 -0400237 MetricsLogger.action(getContext(), MetricsProto.MetricsEvent.ACTION_QS_EDIT_RESET);
Jason Monkb53b6c52016-02-24 17:25:49 -0500238 reset();
239 break;
240 }
241 return false;
242 }
243
Jason Monkbd6dbb02015-09-03 15:46:25 -0400244 private void reset() {
245 ArrayList<String> tiles = new ArrayList<>();
Jason Monk9d02a432016-01-20 16:33:46 -0500246 String defTiles = mContext.getString(R.string.quick_settings_tiles_default);
247 for (String tile : defTiles.split(",")) {
Jason Monkbd6dbb02015-09-03 15:46:25 -0400248 tiles.add(tile);
249 }
Jason Monk903556b2017-03-29 13:13:51 -0400250 mTileAdapter.resetTileSpecs(mHost, tiles);
Jason Monkbd6dbb02015-09-03 15:46:25 -0400251 }
252
Jason Monk62b63a02016-02-02 15:15:31 -0500253 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);
Jason Monk4efc9242016-03-31 14:10:37 -0400259 mRecyclerView.setAdapter(mTileAdapter);
Jason Monkf7fe83f2015-09-09 13:03:20 -0400260 }
261
Jason Monkbd6dbb02015-09-03 15:46:25 -0400262 private void save() {
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400263 if (mTileQueryHelper.isFinished()) {
Evan Laird6b284732017-02-28 17:27:04 -0500264 mTileAdapter.saveSpecs(mHost);
265 }
Jason Monk2681dcb2015-09-09 13:50:34 -0400266 }
Jason Monkb9c00192015-10-07 11:45:33 -0400267
Selim Cinek707e2072017-06-30 18:32:40 +0200268
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
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 }
Fabian Kozynski0db39af2018-08-31 15:56:45 -0400291 /** @param x,y Location on screen of animation center.
292 */
Selim Cinek707e2072017-06-30 18:32:40 +0200293 public void setEditLocation(int x, int y) {
Fabian Kozynski0db39af2018-08-31 15:56:45 -0400294 int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen();
295 mX = x - containerLocation[0];
296 mY = y - containerLocation[1];
Selim Cinek707e2072017-06-30 18:32:40 +0200297 }
298
Selim Cinek72cd9a72019-08-09 17:19:57 -0700299 private final Callback mKeyguardCallback = new Callback() {
300 @Override
301 public void onKeyguardShowingChanged() {
302 if (!isAttachedToWindow()) return;
303 if (Dependency.get(KeyguardMonitor.class).isShowing() && !mOpening) {
304 hide();
305 }
Jason Monk181d3fe2016-06-06 16:18:50 -0400306 }
307 };
308
Jason Monk8b9d67f2016-03-02 08:59:08 -0500309 private final AnimatorListener mExpandAnimationListener = new AnimatorListenerAdapter() {
310 @Override
311 public void onAnimationEnd(Animator animation) {
Yoshinori Hirano1dd7d072016-09-05 16:11:59 +0900312 if (isShown) {
313 setCustomizing(true);
314 }
Jason Monk78a3d602017-03-16 12:12:57 -0400315 mOpening = false;
Jason Monk8b9d67f2016-03-02 08:59:08 -0500316 mNotifQsContainer.setCustomizerAnimating(false);
317 }
318
319 @Override
320 public void onAnimationCancel(Animator animation) {
Jason Monk78a3d602017-03-16 12:12:57 -0400321 mOpening = false;
Jason Monk8b9d67f2016-03-02 08:59:08 -0500322 mNotifQsContainer.setCustomizerAnimating(false);
323 }
324 };
325
Jason Monk77a2c5f2016-02-22 15:18:36 -0500326 private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
327 @Override
328 public void onAnimationEnd(Animator animation) {
329 if (!isShown) {
Jason Monk8b9d67f2016-03-02 08:59:08 -0500330 setVisibility(View.GONE);
Jason Monk77a2c5f2016-02-22 15:18:36 -0500331 }
Jason Monk8b9d67f2016-03-02 08:59:08 -0500332 mNotifQsContainer.setCustomizerAnimating(false);
Jason Monkb213ac42016-06-20 10:27:27 -0400333 mRecyclerView.setAdapter(mTileAdapter);
Jason Monkb9c00192015-10-07 11:45:33 -0400334 }
Jason Monkb9c00192015-10-07 11:45:33 -0400335
Jason Monk77a2c5f2016-02-22 15:18:36 -0500336 @Override
337 public void onAnimationCancel(Animator animation) {
338 if (!isShown) {
Jason Monk8b9d67f2016-03-02 08:59:08 -0500339 setVisibility(View.GONE);
Jason Monk77a2c5f2016-02-22 15:18:36 -0500340 }
Jason Monk8b9d67f2016-03-02 08:59:08 -0500341 mNotifQsContainer.setCustomizerAnimating(false);
Jason Monkb9c00192015-10-07 11:45:33 -0400342 }
Jason Monk77a2c5f2016-02-22 15:18:36 -0500343 };
Jason Monk62b63a02016-02-02 15:15:31 -0500344}