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 */ 16package com.android.systemui.qs.customize; 17 18import android.animation.Animator; 19import android.animation.Animator.AnimatorListener; 20import android.animation.AnimatorListenerAdapter; 21import android.content.Context; 22import android.content.res.Configuration; 23import android.support.v7.widget.DefaultItemAnimator; 24import android.support.v7.widget.GridLayoutManager; 25import android.support.v7.widget.RecyclerView; 26import android.util.AttributeSet; 27import android.util.TypedValue; 28import android.view.ContextThemeWrapper; 29import android.view.LayoutInflater; 30import android.view.Menu; 31import android.view.MenuItem; 32import android.view.View; 33import android.widget.LinearLayout; 34import android.widget.Toolbar; 35import android.widget.Toolbar.OnMenuItemClickListener; 36import com.android.internal.logging.MetricsLogger; 37import com.android.internal.logging.MetricsProto; 38import com.android.systemui.R; 39import com.android.systemui.qs.QSContainer; 40import com.android.systemui.qs.QSDetailClipper; 41import com.android.systemui.qs.QSTile; 42import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; 43import com.android.systemui.statusbar.phone.PhoneStatusBar; 44import com.android.systemui.statusbar.phone.QSTileHost; 45import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback; 46 47import java.util.ArrayList; 48import java.util.List; 49 50/** 51 * Allows full-screen customization of QS, through show() and hide(). 52 * 53 * This adds itself to the status bar window, so it can appear on top of quick settings and 54 * *someday* do fancy animations to get into/out of it. 55 */ 56public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener { 57 58 private static final int MENU_RESET = Menu.FIRST; 59 60 private final QSDetailClipper mClipper; 61 62 private PhoneStatusBar mPhoneStatusBar; 63 64 private boolean isShown; 65 private QSTileHost mHost; 66 private RecyclerView mRecyclerView; 67 private TileAdapter mTileAdapter; 68 private Toolbar mToolbar; 69 private boolean mCustomizing; 70 private NotificationsQuickSettingsContainer mNotifQsContainer; 71 private QSContainer mQsContainer; 72 73 public QSCustomizer(Context context, AttributeSet attrs) { 74 super(new ContextThemeWrapper(context, R.style.edit_theme), attrs); 75 mClipper = new QSDetailClipper(this); 76 77 LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this); 78 79 mToolbar = (Toolbar) findViewById(com.android.internal.R.id.action_bar); 80 TypedValue value = new TypedValue(); 81 mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true); 82 mToolbar.setNavigationIcon( 83 getResources().getDrawable(value.resourceId, mContext.getTheme())); 84 mToolbar.setNavigationOnClickListener(new OnClickListener() { 85 @Override 86 public void onClick(View v) { 87 hide((int) v.getX() + v.getWidth() / 2, (int) v.getY() + v.getHeight() / 2); 88 } 89 }); 90 mToolbar.setOnMenuItemClickListener(this); 91 mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, 92 mContext.getString(com.android.internal.R.string.reset)); 93 mToolbar.setTitle(R.string.qs_edit); 94 95 mRecyclerView = (RecyclerView) findViewById(android.R.id.list); 96 mTileAdapter = new TileAdapter(getContext()); 97 mRecyclerView.setAdapter(mTileAdapter); 98 mTileAdapter.getItemTouchHelper().attachToRecyclerView(mRecyclerView); 99 GridLayoutManager layout = new GridLayoutManager(getContext(), 3); 100 layout.setSpanSizeLookup(mTileAdapter.getSizeLookup()); 101 mRecyclerView.setLayoutManager(layout); 102 mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration()); 103 DefaultItemAnimator animator = new DefaultItemAnimator(); 104 animator.setMoveDuration(TileAdapter.MOVE_DURATION); 105 mRecyclerView.setItemAnimator(animator); 106 } 107 108 @Override 109 protected void onConfigurationChanged(Configuration newConfig) { 110 super.onConfigurationChanged(newConfig); 111 View navBackdrop = findViewById(R.id.nav_bar_background); 112 if (navBackdrop != null) { 113 boolean shouldShow = newConfig.smallestScreenWidthDp >= 600 114 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE; 115 navBackdrop.setVisibility(shouldShow ? View.VISIBLE : View.GONE); 116 } 117 } 118 119 public void setHost(QSTileHost host) { 120 mHost = host; 121 mPhoneStatusBar = host.getPhoneStatusBar(); 122 mTileAdapter.setHost(host); 123 } 124 125 public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) { 126 mNotifQsContainer = notificationsQsContainer; 127 } 128 129 public void setQsContainer(QSContainer qsContainer) { 130 mQsContainer = qsContainer; 131 } 132 133 public void show(int x, int y) { 134 if (!isShown) { 135 MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT); 136 isShown = true; 137 setTileSpecs(); 138 setVisibility(View.VISIBLE); 139 mClipper.animateCircularClip(x, y, true, mExpandAnimationListener); 140 new TileQueryHelper(mContext, mHost).setListener(mTileAdapter); 141 mNotifQsContainer.setCustomizerAnimating(true); 142 mNotifQsContainer.setCustomizerShowing(true); 143 announceForAccessibility(mContext.getString( 144 R.string.accessibility_desc_quick_settings_edit)); 145 mHost.getKeyguardMonitor().addCallback(mKeyguardCallback); 146 } 147 } 148 149 public void hide(int x, int y) { 150 if (isShown) { 151 MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT); 152 isShown = false; 153 mToolbar.dismissPopupMenus(); 154 setCustomizing(false); 155 save(); 156 mClipper.animateCircularClip(x, y, false, mCollapseAnimationListener); 157 mNotifQsContainer.setCustomizerAnimating(true); 158 mNotifQsContainer.setCustomizerShowing(false); 159 announceForAccessibility(mContext.getString( 160 R.string.accessibility_desc_quick_settings)); 161 mHost.getKeyguardMonitor().removeCallback(mKeyguardCallback); 162 } 163 } 164 165 private void setCustomizing(boolean customizing) { 166 mCustomizing = customizing; 167 mQsContainer.notifyCustomizeChanged(); 168 } 169 170 public boolean isCustomizing() { 171 return mCustomizing; 172 } 173 174 @Override 175 public boolean onMenuItemClick(MenuItem item) { 176 switch (item.getItemId()) { 177 case MENU_RESET: 178 MetricsLogger.action(getContext(), MetricsProto.MetricsEvent.ACTION_QS_EDIT_RESET); 179 reset(); 180 break; 181 } 182 return false; 183 } 184 185 private void reset() { 186 ArrayList<String> tiles = new ArrayList<>(); 187 String defTiles = mContext.getString(R.string.quick_settings_tiles_default); 188 for (String tile : defTiles.split(",")) { 189 tiles.add(tile); 190 } 191 mTileAdapter.setTileSpecs(tiles); 192 } 193 194 private void setTileSpecs() { 195 List<String> specs = new ArrayList<>(); 196 for (QSTile tile : mHost.getTiles()) { 197 specs.add(tile.getTileSpec()); 198 } 199 mTileAdapter.setTileSpecs(specs); 200 mRecyclerView.setAdapter(mTileAdapter); 201 } 202 203 private void save() { 204 mTileAdapter.saveSpecs(mHost); 205 } 206 207 private final Callback mKeyguardCallback = new Callback() { 208 @Override 209 public void onKeyguardChanged() { 210 if (mHost.getKeyguardMonitor().isShowing()) { 211 hide(0, 0); 212 } 213 } 214 }; 215 216 private final AnimatorListener mExpandAnimationListener = new AnimatorListenerAdapter() { 217 @Override 218 public void onAnimationEnd(Animator animation) { 219 setCustomizing(true); 220 mNotifQsContainer.setCustomizerAnimating(false); 221 } 222 223 @Override 224 public void onAnimationCancel(Animator animation) { 225 mNotifQsContainer.setCustomizerAnimating(false); 226 } 227 }; 228 229 private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() { 230 @Override 231 public void onAnimationEnd(Animator animation) { 232 if (!isShown) { 233 setVisibility(View.GONE); 234 } 235 mNotifQsContainer.setCustomizerAnimating(false); 236 mRecyclerView.setAdapter(mTileAdapter); 237 } 238 239 @Override 240 public void onAnimationCancel(Animator animation) { 241 if (!isShown) { 242 setVisibility(View.GONE); 243 } 244 mNotifQsContainer.setCustomizerAnimating(false); 245 } 246 }; 247} 248