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