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