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 85 mRecyclerView = findViewById(android.R.id.list); 86 mTransparentView = findViewById(R.id.customizer_transparent_view); 87 DefaultItemAnimator animator = new DefaultItemAnimator(); 88 animator.setMoveDuration(TileAdapter.MOVE_DURATION); 89 mRecyclerView.setItemAnimator(animator); 90 91 updateTransparentViewHeight(); 92 } 93 applyBottomNavBarToPadding(int padding)94 void applyBottomNavBarToPadding(int padding) { 95 mRecyclerView.setPadding( 96 /* left= */ mRecyclerView.getPaddingLeft(), 97 /* top= */ mRecyclerView.getPaddingTop(), 98 /* right= */ mRecyclerView.getPaddingRight(), 99 /* bottom= */ padding 100 ); 101 } 102 setSceneContainerEnabled(boolean enabled)103 void setSceneContainerEnabled(boolean enabled) { 104 if (enabled != mSceneContainerEnabled) { 105 mSceneContainerEnabled = enabled; 106 updateTransparentViewHeight(); 107 if (mSceneContainerEnabled) { 108 findViewById(R.id.nav_bar_background).setVisibility(View.GONE); 109 } else { 110 findViewById(R.id.nav_bar_background) 111 .setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE); 112 } 113 } 114 } 115 updateResources()116 void updateResources() { 117 updateTransparentViewHeight(); 118 mRecyclerView.getAdapter().notifyItemChanged(0); 119 } 120 updateNavBackDrop(Configuration newConfig, LightBarController lightBarController)121 void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) { 122 View navBackdrop = findViewById(R.id.nav_bar_background); 123 mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600 124 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE; 125 if (navBackdrop != null) { 126 navBackdrop.setVisibility( 127 mIsShowingNavBackdrop && !mSceneContainerEnabled ? View.VISIBLE : View.GONE); 128 } 129 updateNavColors(lightBarController); 130 } 131 updateNavColors(LightBarController lightBarController)132 void updateNavColors(LightBarController lightBarController) { 133 lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown); 134 } 135 setContainerController(QSContainerController controller)136 public void setContainerController(QSContainerController controller) { 137 mQsContainerController = controller; 138 } 139 setQs(@ullable QS qs)140 public void setQs(@Nullable QS qs) { 141 mQs = qs; 142 } 143 reloadAdapterTileHeight(@ullable RecyclerView.Adapter adapter)144 private void reloadAdapterTileHeight(@Nullable RecyclerView.Adapter adapter) { 145 if (adapter instanceof TileAdapter) { 146 ((TileAdapter) adapter).reloadTileHeight(); 147 } 148 } 149 150 /** Animate and show QSCustomizer panel. 151 * @param x,y Location on screen of {@code edit} button to determine center of animation. 152 */ show(int x, int y, TileAdapter tileAdapter)153 void show(int x, int y, TileAdapter tileAdapter) { 154 if (!isShown) { 155 reloadAdapterTileHeight(tileAdapter); 156 mRecyclerView.getLayoutManager().scrollToPosition(0); 157 int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen(); 158 mX = x - containerLocation[0]; 159 mY = y - containerLocation[1]; 160 isShown = true; 161 mOpening = true; 162 setVisibility(View.VISIBLE); 163 long duration = mClipper.animateCircularClip( 164 mX, mY, true, new ExpandAnimatorListener(tileAdapter)); 165 if (mQsContainerController != null) { 166 mQsContainerController.setCustomizerAnimating(true); 167 mQsContainerController.setCustomizerShowing(true, duration); 168 } 169 } 170 } 171 172 showImmediately()173 void showImmediately() { 174 if (!isShown) { 175 reloadAdapterTileHeight(mRecyclerView.getAdapter()); 176 mRecyclerView.getLayoutManager().scrollToPosition(0); 177 setVisibility(VISIBLE); 178 mClipper.cancelAnimator(); 179 mClipper.showBackground(); 180 isShown = true; 181 setCustomizing(true); 182 if (mQsContainerController != null) { 183 mQsContainerController.setCustomizerAnimating(false); 184 mQsContainerController.setCustomizerShowing(true); 185 } 186 } 187 } 188 189 /** Hide the customizer. */ hide(boolean animate)190 public void hide(boolean animate) { 191 if (isShown) { 192 isShown = false; 193 mClipper.cancelAnimator(); 194 // Make sure we're not opening (because we're closing). Nobody can think we are 195 // customizing after the next two lines. 196 mOpening = false; 197 long duration = 0; 198 if (animate) { 199 duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); 200 } else { 201 setVisibility(View.GONE); 202 } 203 if (mQsContainerController != null) { 204 mQsContainerController.setCustomizerAnimating(animate); 205 mQsContainerController.setCustomizerShowing(false, duration); 206 } 207 } 208 } 209 isShown()210 public boolean isShown() { 211 return isShown; 212 } 213 214 @Override onConfigurationChanged(Configuration newConfig)215 protected void onConfigurationChanged(Configuration newConfig) { 216 super.onConfigurationChanged(newConfig); 217 mToolbar.setTitleTextAppearance(mContext, 218 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title); 219 updateToolbarMenuFontSize(); 220 } 221 setCustomizing(boolean customizing)222 void setCustomizing(boolean customizing) { 223 mCustomizing = customizing; 224 if (mQs != null) { 225 mQs.notifyCustomizeChanged(); 226 } 227 } 228 isCustomizing()229 public boolean isCustomizing() { 230 return mCustomizing || mOpening; 231 } 232 233 /** @param x,y Location on screen of animation center. 234 */ setEditLocation(int x, int y)235 public void setEditLocation(int x, int y) { 236 int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen(); 237 mX = x - containerLocation[0]; 238 mY = y - containerLocation[1]; 239 } 240 241 class ExpandAnimatorListener extends AnimatorListenerAdapter { 242 private final TileAdapter mTileAdapter; 243 ExpandAnimatorListener(TileAdapter tileAdapter)244 ExpandAnimatorListener(TileAdapter tileAdapter) { 245 mTileAdapter = tileAdapter; 246 } 247 248 @Override onAnimationEnd(Animator animation)249 public void onAnimationEnd(Animator animation) { 250 if (isShown) { 251 setCustomizing(true); 252 } 253 mOpening = false; 254 if (mQsContainerController != null) { 255 mQsContainerController.setCustomizerAnimating(false); 256 } 257 mRecyclerView.setAdapter(mTileAdapter); 258 } 259 260 @Override onAnimationCancel(Animator animation)261 public void onAnimationCancel(Animator animation) { 262 mOpening = false; 263 if (mQs != null) { 264 mQs.notifyCustomizeChanged(); 265 } 266 if (mQsContainerController != null) { 267 mQsContainerController.setCustomizerAnimating(false); 268 } 269 } 270 } 271 272 private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() { 273 @Override 274 public void onAnimationEnd(Animator animation) { 275 if (!isShown) { 276 setVisibility(View.GONE); 277 } 278 if (mQsContainerController != null) { 279 mQsContainerController.setCustomizerAnimating(false); 280 } 281 } 282 283 @Override 284 public void onAnimationCancel(Animator animation) { 285 if (!isShown) { 286 setVisibility(View.GONE); 287 } 288 if (mQsContainerController != null) { 289 mQsContainerController.setCustomizerAnimating(false); 290 } 291 } 292 }; 293 getRecyclerView()294 public RecyclerView getRecyclerView() { 295 return mRecyclerView; 296 } 297 isOpening()298 public boolean isOpening() { 299 return mOpening; 300 } 301 updateTransparentViewHeight()302 private void updateTransparentViewHeight() { 303 LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams(); 304 lp.height = mSceneContainerEnabled ? 0 : QSUtils.getQsHeaderSystemIconsAreaHeight(mContext); 305 mTransparentView.setLayoutParams(lp); 306 } 307 updateToolbarMenuFontSize()308 private void updateToolbarMenuFontSize() { 309 // Clearing and re-adding the toolbar action force updates the font size 310 mToolbar.getMenu().clear(); 311 mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) 312 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 313 } 314 }