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