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.graphics.Point; 24 import android.os.Bundle; 25 import android.support.v7.widget.DefaultItemAnimator; 26 import android.support.v7.widget.GridLayoutManager; 27 import android.support.v7.widget.RecyclerView; 28 import android.util.AttributeSet; 29 import android.util.TypedValue; 30 import android.view.ContextThemeWrapper; 31 import android.view.LayoutInflater; 32 import android.view.Menu; 33 import android.view.MenuItem; 34 import android.view.View; 35 import android.widget.LinearLayout; 36 import android.widget.Toolbar; 37 import android.widget.Toolbar.OnMenuItemClickListener; 38 39 import com.android.internal.logging.MetricsLogger; 40 import com.android.internal.logging.nano.MetricsProto; 41 import com.android.settingslib.Utils; 42 import com.android.systemui.Dependency; 43 import com.android.systemui.R; 44 import com.android.systemui.plugins.qs.QS; 45 import com.android.systemui.plugins.qs.QSTile; 46 import com.android.systemui.qs.QSDetailClipper; 47 import com.android.systemui.qs.QSTileHost; 48 import com.android.systemui.statusbar.phone.LightBarController; 49 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; 50 import com.android.systemui.statusbar.policy.KeyguardMonitor; 51 import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback; 52 53 import java.util.ArrayList; 54 import java.util.List; 55 56 /** 57 * Allows full-screen customization of QS, through show() and hide(). 58 * 59 * This adds itself to the status bar window, so it can appear on top of quick settings and 60 * *someday* do fancy animations to get into/out of it. 61 */ 62 public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener { 63 64 private static final int MENU_RESET = Menu.FIRST; 65 private static final String EXTRA_QS_CUSTOMIZING = "qs_customizing"; 66 67 private final QSDetailClipper mClipper; 68 private final LightBarController mLightBarController; 69 private final TileQueryHelper mTileQueryHelper; 70 71 private boolean isShown; 72 private QSTileHost mHost; 73 private RecyclerView mRecyclerView; 74 private TileAdapter mTileAdapter; 75 private Toolbar mToolbar; 76 private boolean mCustomizing; 77 private NotificationsQuickSettingsContainer mNotifQsContainer; 78 private QS mQs; 79 private int mX; 80 private int mY; 81 private boolean mOpening; 82 private boolean mIsShowingNavBackdrop; 83 QSCustomizer(Context context, AttributeSet attrs)84 public QSCustomizer(Context context, AttributeSet attrs) { 85 super(new ContextThemeWrapper(context, R.style.edit_theme), attrs); 86 87 LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this); 88 mClipper = new QSDetailClipper(findViewById(R.id.customize_container)); 89 mToolbar = findViewById(com.android.internal.R.id.action_bar); 90 TypedValue value = new TypedValue(); 91 mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); 92 mToolbar.setNavigationIcon( 93 getResources().getDrawable(value.resourceId, mContext.getTheme())); 94 mToolbar.setNavigationOnClickListener(new OnClickListener() { 95 @Override 96 public void onClick(View v) { 97 hide((int) v.getX() + v.getWidth() / 2, (int) v.getY() + v.getHeight() / 2); 98 } 99 }); 100 mToolbar.setOnMenuItemClickListener(this); 101 mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, 102 mContext.getString(com.android.internal.R.string.reset)); 103 mToolbar.setTitle(R.string.qs_edit); 104 int accentColor = Utils.getColorAttr(context, android.R.attr.colorAccent); 105 mToolbar.setTitleTextColor(accentColor); 106 mToolbar.getNavigationIcon().setTint(accentColor); 107 mToolbar.getOverflowIcon().setTint(accentColor); 108 mRecyclerView = findViewById(android.R.id.list); 109 mTileAdapter = new TileAdapter(getContext()); 110 mTileQueryHelper = new TileQueryHelper(context, mTileAdapter); 111 mRecyclerView.setAdapter(mTileAdapter); 112 mTileAdapter.getItemTouchHelper().attachToRecyclerView(mRecyclerView); 113 GridLayoutManager layout = new GridLayoutManager(getContext(), 3); 114 layout.setSpanSizeLookup(mTileAdapter.getSizeLookup()); 115 mRecyclerView.setLayoutManager(layout); 116 mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration()); 117 DefaultItemAnimator animator = new DefaultItemAnimator(); 118 animator.setMoveDuration(TileAdapter.MOVE_DURATION); 119 mRecyclerView.setItemAnimator(animator); 120 mLightBarController = Dependency.get(LightBarController.class); 121 updateNavBackDrop(getResources().getConfiguration()); 122 } 123 124 @Override onConfigurationChanged(Configuration newConfig)125 protected void onConfigurationChanged(Configuration newConfig) { 126 super.onConfigurationChanged(newConfig); 127 updateNavBackDrop(newConfig); 128 } 129 updateNavBackDrop(Configuration newConfig)130 private void updateNavBackDrop(Configuration newConfig) { 131 View navBackdrop = findViewById(R.id.nav_bar_background); 132 mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600 133 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE; 134 if (navBackdrop != null) { 135 navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE); 136 } 137 updateNavColors(); 138 } 139 updateNavColors()140 private void updateNavColors() { 141 mLightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown); 142 } 143 setHost(QSTileHost host)144 public void setHost(QSTileHost host) { 145 mHost = host; 146 mTileAdapter.setHost(host); 147 } 148 setContainer(NotificationsQuickSettingsContainer notificationsQsContainer)149 public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) { 150 mNotifQsContainer = notificationsQsContainer; 151 } 152 setQs(QS qs)153 public void setQs(QS qs) { 154 mQs = qs; 155 } 156 show(int x, int y)157 public void show(int x, int y) { 158 if (!isShown) { 159 mX = x; 160 mY = y; 161 MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT); 162 isShown = true; 163 mOpening = true; 164 setTileSpecs(); 165 setVisibility(View.VISIBLE); 166 mClipper.animateCircularClip(x, y, true, mExpandAnimationListener); 167 queryTiles(); 168 mNotifQsContainer.setCustomizerAnimating(true); 169 mNotifQsContainer.setCustomizerShowing(true); 170 Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback); 171 updateNavColors(); 172 } 173 } 174 175 showImmediately()176 public void showImmediately() { 177 if (!isShown) { 178 setVisibility(VISIBLE); 179 mClipper.showBackground(); 180 isShown = true; 181 setTileSpecs(); 182 setCustomizing(true); 183 queryTiles(); 184 mNotifQsContainer.setCustomizerAnimating(false); 185 mNotifQsContainer.setCustomizerShowing(true); 186 Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback); 187 updateNavColors(); 188 } 189 } 190 queryTiles()191 private void queryTiles() { 192 mTileQueryHelper.queryTiles(mHost); 193 } 194 hide(int x, int y)195 public void hide(int x, int y) { 196 if (isShown) { 197 MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT); 198 isShown = false; 199 mToolbar.dismissPopupMenus(); 200 setCustomizing(false); 201 save(); 202 mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); 203 mNotifQsContainer.setCustomizerAnimating(true); 204 mNotifQsContainer.setCustomizerShowing(false); 205 Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback); 206 updateNavColors(); 207 } 208 } 209 isShown()210 public boolean isShown() { 211 return isShown; 212 } 213 setCustomizing(boolean customizing)214 private void setCustomizing(boolean customizing) { 215 mCustomizing = customizing; 216 mQs.notifyCustomizeChanged(); 217 } 218 isCustomizing()219 public boolean isCustomizing() { 220 return mCustomizing || mOpening; 221 } 222 223 @Override onMenuItemClick(MenuItem item)224 public boolean onMenuItemClick(MenuItem item) { 225 switch (item.getItemId()) { 226 case MENU_RESET: 227 MetricsLogger.action(getContext(), MetricsProto.MetricsEvent.ACTION_QS_EDIT_RESET); 228 reset(); 229 break; 230 } 231 return false; 232 } 233 reset()234 private void reset() { 235 ArrayList<String> tiles = new ArrayList<>(); 236 String defTiles = mContext.getString(R.string.quick_settings_tiles_default); 237 for (String tile : defTiles.split(",")) { 238 tiles.add(tile); 239 } 240 mTileAdapter.resetTileSpecs(mHost, tiles); 241 } 242 setTileSpecs()243 private void setTileSpecs() { 244 List<String> specs = new ArrayList<>(); 245 for (QSTile tile : mHost.getTiles()) { 246 specs.add(tile.getTileSpec()); 247 } 248 mTileAdapter.setTileSpecs(specs); 249 mRecyclerView.setAdapter(mTileAdapter); 250 } 251 save()252 private void save() { 253 if (mTileQueryHelper.isFinished()) { 254 mTileAdapter.saveSpecs(mHost); 255 } 256 } 257 258 saveInstanceState(Bundle outState)259 public void saveInstanceState(Bundle outState) { 260 if (isShown) { 261 Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback); 262 } 263 outState.putBoolean(EXTRA_QS_CUSTOMIZING, mCustomizing); 264 } 265 restoreInstanceState(Bundle savedInstanceState)266 public void restoreInstanceState(Bundle savedInstanceState) { 267 boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING); 268 if (customizing) { 269 setVisibility(VISIBLE); 270 addOnLayoutChangeListener(new OnLayoutChangeListener() { 271 @Override 272 public void onLayoutChange(View v, int left, int top, int right, int bottom, 273 int oldLeft, 274 int oldTop, int oldRight, int oldBottom) { 275 removeOnLayoutChangeListener(this); 276 showImmediately(); 277 } 278 }); 279 } 280 } 281 setEditLocation(int x, int y)282 public void setEditLocation(int x, int y) { 283 mX = x; 284 mY = y; 285 } 286 287 private final Callback mKeyguardCallback = () -> { 288 if (!isAttachedToWindow()) return; 289 if (Dependency.get(KeyguardMonitor.class).isShowing() && !mOpening) { 290 hide(0, 0); 291 } 292 }; 293 294 private final AnimatorListener mExpandAnimationListener = new AnimatorListenerAdapter() { 295 @Override 296 public void onAnimationEnd(Animator animation) { 297 if (isShown) { 298 setCustomizing(true); 299 } 300 mOpening = false; 301 mNotifQsContainer.setCustomizerAnimating(false); 302 } 303 304 @Override 305 public void onAnimationCancel(Animator animation) { 306 mOpening = false; 307 mNotifQsContainer.setCustomizerAnimating(false); 308 } 309 }; 310 311 private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() { 312 @Override 313 public void onAnimationEnd(Animator animation) { 314 if (!isShown) { 315 setVisibility(View.GONE); 316 } 317 mNotifQsContainer.setCustomizerAnimating(false); 318 mRecyclerView.setAdapter(mTileAdapter); 319 } 320 321 @Override 322 public void onAnimationCancel(Animator animation) { 323 if (!isShown) { 324 setVisibility(View.GONE); 325 } 326 mNotifQsContainer.setCustomizerAnimating(false); 327 } 328 }; 329 } 330