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.R; 37 import com.android.systemui.plugins.qs.QS; 38 import com.android.systemui.plugins.qs.QSContainerController; 39 import com.android.systemui.qs.QSDetailClipper; 40 import com.android.systemui.qs.QSUtils; 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 QS mQs; 62 private int mX; 63 private int mY; 64 private boolean mOpening; 65 private boolean mIsShowingNavBackdrop; 66 QSCustomizer(Context context, AttributeSet attrs)67 public QSCustomizer(Context context, AttributeSet attrs) { 68 super(context, attrs); 69 70 LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this); 71 mClipper = new QSDetailClipper(findViewById(R.id.customize_container)); 72 Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar); 73 TypedValue value = new TypedValue(); 74 mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); 75 toolbar.setNavigationIcon( 76 getResources().getDrawable(value.resourceId, mContext.getTheme())); 77 78 toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) 79 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 80 toolbar.setTitle(R.string.qs_edit); 81 mRecyclerView = findViewById(android.R.id.list); 82 mTransparentView = findViewById(R.id.customizer_transparent_view); 83 DefaultItemAnimator animator = new DefaultItemAnimator(); 84 animator.setMoveDuration(TileAdapter.MOVE_DURATION); 85 mRecyclerView.setItemAnimator(animator); 86 87 updateTransparentViewHeight(); 88 } 89 updateResources()90 void updateResources() { 91 updateTransparentViewHeight(); 92 mRecyclerView.getAdapter().notifyItemChanged(0); 93 } 94 updateNavBackDrop(Configuration newConfig, LightBarController lightBarController)95 void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) { 96 View navBackdrop = findViewById(R.id.nav_bar_background); 97 mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600 98 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE; 99 if (navBackdrop != null) { 100 navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE); 101 } 102 updateNavColors(lightBarController); 103 } 104 updateNavColors(LightBarController lightBarController)105 void updateNavColors(LightBarController lightBarController) { 106 lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown); 107 } 108 setContainerController(QSContainerController controller)109 public void setContainerController(QSContainerController controller) { 110 mQsContainerController = controller; 111 } 112 setQs(@ullable QS qs)113 public void setQs(@Nullable QS qs) { 114 mQs = qs; 115 } 116 reloadAdapterTileHeight(@ullable RecyclerView.Adapter adapter)117 private void reloadAdapterTileHeight(@Nullable RecyclerView.Adapter adapter) { 118 if (adapter instanceof TileAdapter) { 119 ((TileAdapter) adapter).reloadTileHeight(); 120 } 121 } 122 123 /** Animate and show QSCustomizer panel. 124 * @param x,y Location on screen of {@code edit} button to determine center of animation. 125 */ show(int x, int y, TileAdapter tileAdapter)126 void show(int x, int y, TileAdapter tileAdapter) { 127 if (!isShown) { 128 reloadAdapterTileHeight(tileAdapter); 129 mRecyclerView.getLayoutManager().scrollToPosition(0); 130 int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen(); 131 mX = x - containerLocation[0]; 132 mY = y - containerLocation[1]; 133 isShown = true; 134 mOpening = true; 135 setVisibility(View.VISIBLE); 136 long duration = mClipper.animateCircularClip( 137 mX, mY, true, new ExpandAnimatorListener(tileAdapter)); 138 mQsContainerController.setCustomizerAnimating(true); 139 mQsContainerController.setCustomizerShowing(true, duration); 140 } 141 } 142 143 showImmediately()144 void showImmediately() { 145 if (!isShown) { 146 reloadAdapterTileHeight(mRecyclerView.getAdapter()); 147 mRecyclerView.getLayoutManager().scrollToPosition(0); 148 setVisibility(VISIBLE); 149 mClipper.cancelAnimator(); 150 mClipper.showBackground(); 151 isShown = true; 152 setCustomizing(true); 153 mQsContainerController.setCustomizerAnimating(false); 154 mQsContainerController.setCustomizerShowing(true); 155 } 156 } 157 158 /** Hide the customizer. */ hide(boolean animate)159 public void hide(boolean animate) { 160 if (isShown) { 161 isShown = false; 162 mClipper.cancelAnimator(); 163 // Make sure we're not opening (because we're closing). Nobody can think we are 164 // customizing after the next two lines. 165 mOpening = false; 166 long duration = 0; 167 if (animate) { 168 duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); 169 } else { 170 setVisibility(View.GONE); 171 } 172 mQsContainerController.setCustomizerAnimating(animate); 173 mQsContainerController.setCustomizerShowing(false, duration); 174 } 175 } 176 isShown()177 public boolean isShown() { 178 return isShown; 179 } 180 setCustomizing(boolean customizing)181 void setCustomizing(boolean customizing) { 182 mCustomizing = customizing; 183 mQs.notifyCustomizeChanged(); 184 } 185 isCustomizing()186 public boolean isCustomizing() { 187 return mCustomizing || mOpening; 188 } 189 190 /** @param x,y Location on screen of animation center. 191 */ setEditLocation(int x, int y)192 public void setEditLocation(int x, int y) { 193 int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen(); 194 mX = x - containerLocation[0]; 195 mY = y - containerLocation[1]; 196 } 197 198 class ExpandAnimatorListener extends AnimatorListenerAdapter { 199 private final TileAdapter mTileAdapter; 200 ExpandAnimatorListener(TileAdapter tileAdapter)201 ExpandAnimatorListener(TileAdapter tileAdapter) { 202 mTileAdapter = tileAdapter; 203 } 204 205 @Override onAnimationEnd(Animator animation)206 public void onAnimationEnd(Animator animation) { 207 if (isShown) { 208 setCustomizing(true); 209 } 210 mOpening = false; 211 mQsContainerController.setCustomizerAnimating(false); 212 mRecyclerView.setAdapter(mTileAdapter); 213 } 214 215 @Override onAnimationCancel(Animator animation)216 public void onAnimationCancel(Animator animation) { 217 mOpening = false; 218 mQs.notifyCustomizeChanged(); 219 mQsContainerController.setCustomizerAnimating(false); 220 } 221 } 222 223 private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() { 224 @Override 225 public void onAnimationEnd(Animator animation) { 226 if (!isShown) { 227 setVisibility(View.GONE); 228 } 229 mQsContainerController.setCustomizerAnimating(false); 230 } 231 232 @Override 233 public void onAnimationCancel(Animator animation) { 234 if (!isShown) { 235 setVisibility(View.GONE); 236 } 237 mQsContainerController.setCustomizerAnimating(false); 238 } 239 }; 240 getRecyclerView()241 public RecyclerView getRecyclerView() { 242 return mRecyclerView; 243 } 244 isOpening()245 public boolean isOpening() { 246 return mOpening; 247 } 248 updateTransparentViewHeight()249 private void updateTransparentViewHeight() { 250 LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams(); 251 lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext); 252 mTransparentView.setLayoutParams(lp); 253 } 254 }