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.util.AttributeSet; 24 import android.util.TypedValue; 25 import android.view.LayoutInflater; 26 import android.view.Menu; 27 import android.view.MenuItem; 28 import android.view.View; 29 import android.widget.LinearLayout; 30 import android.widget.Toolbar; 31 32 import androidx.annotation.Nullable; 33 import androidx.recyclerview.widget.DefaultItemAnimator; 34 import androidx.recyclerview.widget.RecyclerView; 35 36 import com.android.systemui.plugins.qs.QS; 37 import com.android.systemui.plugins.qs.QSContainerController; 38 import com.android.systemui.qs.QSDetailClipper; 39 import com.android.systemui.qs.QSUtils; 40 import com.android.systemui.res.R; 41 import com.android.systemui.statusbar.phone.LightBarController; 42 43 /** 44 * Allows full-screen customization of QS, through show() and hide(). 45 * 46 * This adds itself to the status bar window, so it can appear on top of quick settings and 47 * *someday* do fancy animations to get into/out of it. 48 */ 49 public class QSCustomizer extends LinearLayout { 50 51 static final int MENU_RESET = Menu.FIRST; 52 static final String EXTRA_QS_CUSTOMIZING = "qs_customizing"; 53 54 private final QSDetailClipper mClipper; 55 private final View mTransparentView; 56 57 private boolean isShown; 58 private final RecyclerView mRecyclerView; 59 private boolean mCustomizing; 60 private QSContainerController mQsContainerController; 61 private final Toolbar mToolbar; 62 private QS mQs; 63 private int mX; 64 private int mY; 65 private boolean mOpening; 66 private boolean mIsShowingNavBackdrop; 67 68 private boolean mSceneContainerEnabled; 69 QSCustomizer(Context context, AttributeSet attrs)70 public QSCustomizer(Context context, AttributeSet attrs) { 71 super(context, attrs); 72 73 LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this); 74 mClipper = new QSDetailClipper(findViewById(R.id.customize_container)); 75 mToolbar = findViewById(com.android.internal.R.id.action_bar); 76 TypedValue value = new TypedValue(); 77 mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); 78 mToolbar.setNavigationIcon( 79 getResources().getDrawable(value.resourceId, mContext.getTheme())); 80 81 mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) 82 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 83 mToolbar.setTitle(R.string.qs_edit); 84 mRecyclerView = findViewById(android.R.id.list); 85 mTransparentView = findViewById(R.id.customizer_transparent_view); 86 DefaultItemAnimator animator = new DefaultItemAnimator(); 87 animator.setMoveDuration(TileAdapter.MOVE_DURATION); 88 mRecyclerView.setItemAnimator(animator); 89 90 updateTransparentViewHeight(); 91 } 92 applyBottomNavBarToPadding(int padding)93 void applyBottomNavBarToPadding(int padding) { 94 mRecyclerView.setPadding( 95 /* left= */ mRecyclerView.getPaddingLeft(), 96 /* top= */ mRecyclerView.getPaddingTop(), 97 /* right= */ mRecyclerView.getPaddingRight(), 98 /* bottom= */ padding 99 ); 100 } 101 setSceneContainerEnabled(boolean enabled)102 void setSceneContainerEnabled(boolean enabled) { 103 if (enabled != mSceneContainerEnabled) { 104 mSceneContainerEnabled = enabled; 105 updateTransparentViewHeight(); 106 if (mSceneContainerEnabled) { 107 findViewById(R.id.nav_bar_background).setVisibility(View.GONE); 108 } else { 109 findViewById(R.id.nav_bar_background) 110 .setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE); 111 } 112 } 113 } 114 updateResources()115 void updateResources() { 116 updateTransparentViewHeight(); 117 mRecyclerView.getAdapter().notifyItemChanged(0); 118 } 119 updateNavBackDrop(Configuration newConfig, LightBarController lightBarController)120 void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) { 121 View navBackdrop = findViewById(R.id.nav_bar_background); 122 mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600 123 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE; 124 if (navBackdrop != null) { 125 navBackdrop.setVisibility( 126 mIsShowingNavBackdrop && !mSceneContainerEnabled ? View.VISIBLE : View.GONE); 127 } 128 updateNavColors(lightBarController); 129 } 130 updateNavColors(LightBarController lightBarController)131 void updateNavColors(LightBarController lightBarController) { 132 lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown); 133 } 134 setContainerController(QSContainerController controller)135 public void setContainerController(QSContainerController controller) { 136 mQsContainerController = controller; 137 } 138 setQs(@ullable QS qs)139 public void setQs(@Nullable QS qs) { 140 mQs = qs; 141 } 142 reloadAdapterTileHeight(@ullable RecyclerView.Adapter adapter)143 private void reloadAdapterTileHeight(@Nullable RecyclerView.Adapter adapter) { 144 if (adapter instanceof TileAdapter) { 145 ((TileAdapter) adapter).reloadTileHeight(); 146 } 147 } 148 149 /** Animate and show QSCustomizer panel. 150 * @param x,y Location on screen of {@code edit} button to determine center of animation. 151 */ show(int x, int y, TileAdapter tileAdapter)152 void show(int x, int y, TileAdapter tileAdapter) { 153 if (!isShown) { 154 reloadAdapterTileHeight(tileAdapter); 155 mRecyclerView.getLayoutManager().scrollToPosition(0); 156 int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen(); 157 mX = x - containerLocation[0]; 158 mY = y - containerLocation[1]; 159 isShown = true; 160 mOpening = true; 161 setVisibility(View.VISIBLE); 162 long duration = mClipper.animateCircularClip( 163 mX, mY, true, new ExpandAnimatorListener(tileAdapter)); 164 if (mQsContainerController != null) { 165 mQsContainerController.setCustomizerAnimating(true); 166 mQsContainerController.setCustomizerShowing(true, duration); 167 } 168 } 169 } 170 171 showImmediately()172 void showImmediately() { 173 if (!isShown) { 174 reloadAdapterTileHeight(mRecyclerView.getAdapter()); 175 mRecyclerView.getLayoutManager().scrollToPosition(0); 176 setVisibility(VISIBLE); 177 mClipper.cancelAnimator(); 178 mClipper.showBackground(); 179 isShown = true; 180 setCustomizing(true); 181 if (mQsContainerController != null) { 182 mQsContainerController.setCustomizerAnimating(false); 183 mQsContainerController.setCustomizerShowing(true); 184 } 185 } 186 } 187 188 /** Hide the customizer. */ hide(boolean animate)189 public void hide(boolean animate) { 190 if (isShown) { 191 isShown = false; 192 mClipper.cancelAnimator(); 193 // Make sure we're not opening (because we're closing). Nobody can think we are 194 // customizing after the next two lines. 195 mOpening = false; 196 long duration = 0; 197 if (animate) { 198 duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); 199 } else { 200 setVisibility(View.GONE); 201 } 202 if (mQsContainerController != null) { 203 mQsContainerController.setCustomizerAnimating(animate); 204 mQsContainerController.setCustomizerShowing(false, duration); 205 } 206 } 207 } 208 isShown()209 public boolean isShown() { 210 return isShown; 211 } 212 213 @Override onConfigurationChanged(Configuration newConfig)214 protected void onConfigurationChanged(Configuration newConfig) { 215 super.onConfigurationChanged(newConfig); 216 mToolbar.setTitleTextAppearance(mContext, 217 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title); 218 updateToolbarMenuFontSize(); 219 } 220 setCustomizing(boolean customizing)221 void setCustomizing(boolean customizing) { 222 mCustomizing = customizing; 223 if (mQs != null) { 224 mQs.notifyCustomizeChanged(); 225 } 226 } 227 isCustomizing()228 public boolean isCustomizing() { 229 return mCustomizing || mOpening; 230 } 231 232 /** @param x,y Location on screen of animation center. 233 */ setEditLocation(int x, int y)234 public void setEditLocation(int x, int y) { 235 int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen(); 236 mX = x - containerLocation[0]; 237 mY = y - containerLocation[1]; 238 } 239 240 class ExpandAnimatorListener extends AnimatorListenerAdapter { 241 private final TileAdapter mTileAdapter; 242 ExpandAnimatorListener(TileAdapter tileAdapter)243 ExpandAnimatorListener(TileAdapter tileAdapter) { 244 mTileAdapter = tileAdapter; 245 } 246 247 @Override onAnimationEnd(Animator animation)248 public void onAnimationEnd(Animator animation) { 249 if (isShown) { 250 setCustomizing(true); 251 } 252 mOpening = false; 253 if (mQsContainerController != null) { 254 mQsContainerController.setCustomizerAnimating(false); 255 } 256 mRecyclerView.setAdapter(mTileAdapter); 257 } 258 259 @Override onAnimationCancel(Animator animation)260 public void onAnimationCancel(Animator animation) { 261 mOpening = false; 262 if (mQs != null) { 263 mQs.notifyCustomizeChanged(); 264 } 265 if (mQsContainerController != null) { 266 mQsContainerController.setCustomizerAnimating(false); 267 } 268 } 269 } 270 271 private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() { 272 @Override 273 public void onAnimationEnd(Animator animation) { 274 if (!isShown) { 275 setVisibility(View.GONE); 276 } 277 if (mQsContainerController != null) { 278 mQsContainerController.setCustomizerAnimating(false); 279 } 280 } 281 282 @Override 283 public void onAnimationCancel(Animator animation) { 284 if (!isShown) { 285 setVisibility(View.GONE); 286 } 287 if (mQsContainerController != null) { 288 mQsContainerController.setCustomizerAnimating(false); 289 } 290 } 291 }; 292 getRecyclerView()293 public RecyclerView getRecyclerView() { 294 return mRecyclerView; 295 } 296 isOpening()297 public boolean isOpening() { 298 return mOpening; 299 } 300 updateTransparentViewHeight()301 private void updateTransparentViewHeight() { 302 LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams(); 303 lp.height = mSceneContainerEnabled ? 0 : QSUtils.getQsHeaderSystemIconsAreaHeight(mContext); 304 mTransparentView.setLayoutParams(lp); 305 } 306 updateToolbarMenuFontSize()307 private void updateToolbarMenuFontSize() { 308 // Clearing and re-adding the toolbar action force updates the font size 309 mToolbar.getMenu().clear(); 310 mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) 311 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 312 } 313 }