NotificationMenuRow.java revision de850bbcaa61c1874b803f2086443febbafd81a4
1/* 2 * Copyright (C) 2016 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 17package com.android.systemui.statusbar; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.content.Context; 23import android.content.res.Resources; 24import android.graphics.drawable.Drawable; 25import android.util.AttributeSet; 26import android.util.Log; 27import android.view.LayoutInflater; 28import android.view.View; 29import android.widget.FrameLayout; 30 31import java.util.ArrayList; 32 33import com.android.systemui.Dependency; 34import com.android.systemui.Interpolators; 35import com.android.systemui.R; 36import com.android.systemui.plugins.PluginListener; 37import com.android.systemui.plugins.PluginManager; 38import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider; 39import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem; 40import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener; 41 42public class NotificationMenuRow extends FrameLayout 43 implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener { 44 45 private static final int ICON_ALPHA_ANIM_DURATION = 200; 46 47 private ExpandableNotificationRow mParent; 48 private OnMenuClickListener mListener; 49 private NotificationMenuRowProvider mMenuProvider; 50 private ArrayList<MenuItem> mMenuItems = new ArrayList<>(); 51 52 private ValueAnimator mFadeAnimator; 53 private boolean mMenuFadedIn = false; 54 private boolean mAnimating = false; 55 private boolean mOnLeft = true; 56 private boolean mDismissing = false; 57 private boolean mSnapping = false; 58 private boolean mIconsPlaced = false; 59 60 private int[] mIconLocation = new int[2]; 61 private int[] mParentLocation = new int[2]; 62 63 private float mHorizSpaceForIcon; 64 private int mVertSpaceForIcons; 65 66 private int mIconPadding; 67 private int mIconTint; 68 69 private float mAlpha = 0f; 70 71 public NotificationMenuRow(Context context) { 72 this(context, null); 73 } 74 75 public NotificationMenuRow(Context context, AttributeSet attrs) { 76 this(context, attrs, 0); 77 } 78 79 public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) { 80 this(context, attrs, defStyleAttr, 0); 81 } 82 83 public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr, 84 int defStyleRes) { 85 super(context, attrs); 86 mMenuItems.addAll(getDefaultNotificationMenuItems()); 87 } 88 89 @Override 90 protected void onAttachedToWindow() { 91 super.onAttachedToWindow(); 92 Dependency.get(PluginManager.class).addPluginListener( 93 NotificationMenuRowProvider.ACTION, this, 94 NotificationMenuRowProvider.VERSION, false /* Allow multiple */); 95 } 96 97 @Override 98 protected void onDetachedFromWindow() { 99 super.onDetachedFromWindow(); 100 Dependency.get(PluginManager.class).removePluginListener(this); 101 } 102 103 @Override 104 protected void onFinishInflate() { 105 super.onFinishInflate(); 106 final Resources res = getResources(); 107 mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); 108 mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); 109 mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding); 110 mIconTint = res.getColor(R.color.notification_gear_color); 111 updateMenu(false /* notify */); 112 } 113 114 public static MenuItem getLongpressMenuItem(Context context) { 115 Resources res = context.getResources(); 116 Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings); 117 String settingsDescription = res.getString(R.string.notification_menu_gear_description); 118 NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate( 119 R.layout.notification_info, null, false); 120 MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent); 121 return settings; 122 } 123 124 public ArrayList<MenuItem> getDefaultNotificationMenuItems() { 125 ArrayList<MenuItem> items = new ArrayList<MenuItem>(); 126 Resources res = getResources(); 127 128 Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze); 129 NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext) 130 .inflate(R.layout.notification_snooze, null, false); 131 String snoozeDescription = res.getString(R.string.notification_menu_snooze_description); 132 MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content); 133 items.add(snooze); 134 135 Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings); 136 String settingsDescription = res.getString(R.string.notification_menu_gear_description); 137 NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate( 138 R.layout.notification_info, null, false); 139 MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent); 140 items.add(settings); 141 return items; 142 } 143 144 private void updateMenu(boolean notify) { 145 removeAllViews(); 146 mMenuItems.clear(); 147 if (mMenuProvider != null) { 148 mMenuItems.addAll(mMenuProvider.getMenuItems(getContext())); 149 } 150 mMenuItems.addAll(getDefaultNotificationMenuItems()); 151 for (int i = 0; i < mMenuItems.size(); i++) { 152 final View v = createMenuView(mMenuItems.get(i)); 153 mMenuItems.get(i).menuView = v; 154 } 155 resetState(notify); 156 } 157 158 private View createMenuView(MenuItem item) { 159 AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext()); 160 addView(iv); 161 iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding); 162 iv.setImageDrawable(item.icon); 163 iv.setOnClickListener(this); 164 iv.setColorFilter(mIconTint); 165 iv.setAlpha(mAlpha); 166 FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams(); 167 lp.width = (int) mHorizSpaceForIcon; 168 lp.height = (int) mHorizSpaceForIcon; 169 return iv; 170 } 171 172 public void resetState(boolean notify) { 173 setMenuAlpha(0f); 174 mIconsPlaced = false; 175 mMenuFadedIn = false; 176 mAnimating = false; 177 mSnapping = false; 178 mDismissing = false; 179 setMenuLocation(mOnLeft ? 1 : -1 /* on left */); 180 if (mListener != null && notify) { 181 mListener.onMenuReset(mParent); 182 } 183 } 184 185 public void setMenuClickListener(OnMenuClickListener listener) { 186 mListener = listener; 187 } 188 189 public void setNotificationRowParent(ExpandableNotificationRow parent) { 190 mParent = parent; 191 setMenuLocation(mOnLeft ? 1 : -1); 192 } 193 194 public void setAppName(String appName) { 195 Resources res = getResources(); 196 final int count = mMenuItems.size(); 197 for (int i = 0; i < count; i++) { 198 MenuItem item = mMenuItems.get(i); 199 String description = String.format( 200 res.getString(R.string.notification_menu_accessibility), 201 appName, item.menuDescription); 202 item.menuView.setContentDescription(description); 203 } 204 } 205 206 public ExpandableNotificationRow getNotificationParent() { 207 return mParent; 208 } 209 210 public void setMenuAlpha(float alpha) { 211 mAlpha = alpha; 212 if (alpha == 0) { 213 mMenuFadedIn = false; // Can fade in again once it's gone. 214 setVisibility(View.INVISIBLE); 215 } else { 216 setVisibility(View.VISIBLE); 217 } 218 final int count = getChildCount(); 219 for (int i = 0; i < count; i++) { 220 getChildAt(i).setAlpha(mAlpha); 221 } 222 } 223 224 /** 225 * Returns whether the menu is displayed on the left side of the view or not. 226 */ 227 public boolean isMenuOnLeft() { 228 return mOnLeft; 229 } 230 231 /** 232 * Returns the horizontal space in pixels required to display the menu. 233 */ 234 public float getSpaceForMenu() { 235 return mHorizSpaceForIcon * getChildCount(); 236 } 237 238 /** 239 * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is 240 * visible. 241 */ 242 public boolean isVisible() { 243 return mAlpha > 0; 244 } 245 246 public void cancelFadeAnimator() { 247 if (mFadeAnimator != null) { 248 mFadeAnimator.cancel(); 249 } 250 } 251 252 public void updateMenuAlpha(final float transX, final float size) { 253 if (mAnimating || !mMenuFadedIn) { 254 // Don't adjust when animating, or if the menu hasn't been shown yet. 255 return; 256 } 257 258 final float fadeThreshold = size * 0.3f; 259 final float absTrans = Math.abs(transX); 260 float desiredAlpha = 0; 261 262 if (absTrans == 0) { 263 desiredAlpha = 0; 264 } else if (absTrans <= fadeThreshold) { 265 desiredAlpha = 1; 266 } else { 267 desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold)); 268 } 269 setMenuAlpha(desiredAlpha); 270 } 271 272 public void fadeInMenu(final boolean fromLeft, final float transX, 273 final float notiThreshold) { 274 if (mDismissing || mAnimating) { 275 return; 276 } 277 if (isMenuLocationChange(transX)) { 278 setMenuAlpha(0f); 279 } 280 setMenuLocation((int) transX); 281 mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1); 282 mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 283 @Override 284 public void onAnimationUpdate(ValueAnimator animation) { 285 final float absTrans = Math.abs(transX); 286 287 boolean pastGear = (fromLeft && transX <= notiThreshold) 288 || (!fromLeft && absTrans <= notiThreshold); 289 if (pastGear && !mMenuFadedIn) { 290 setMenuAlpha((float) animation.getAnimatedValue()); 291 } 292 } 293 }); 294 mFadeAnimator.addListener(new AnimatorListenerAdapter() { 295 @Override 296 public void onAnimationStart(Animator animation) { 297 mAnimating = true; 298 } 299 300 @Override 301 public void onAnimationCancel(Animator animation) { 302 // TODO should animate back to 0f from current alpha 303 setMenuAlpha(0f); 304 } 305 306 @Override 307 public void onAnimationEnd(Animator animation) { 308 mAnimating = false; 309 mMenuFadedIn = mAlpha == 1; 310 } 311 }); 312 mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN); 313 mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION); 314 mFadeAnimator.start(); 315 } 316 317 public void updateVerticalLocation() { 318 if (mParent == null || mMenuItems.size() == 0) { 319 return; 320 } 321 int parentHeight = mParent.getCollapsedHeight(); 322 float translationY; 323 if (parentHeight < mVertSpaceForIcons) { 324 translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2); 325 } else { 326 translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2; 327 } 328 setTranslationY(translationY); 329 } 330 331 @Override 332 public void onRtlPropertiesChanged(int layoutDirection) { 333 mIconsPlaced = false; 334 setMenuLocation(mOnLeft ? 1 : -1); 335 } 336 337 public void setMenuLocation(int translation) { 338 boolean onLeft = translation > 0; 339 if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) { 340 // Do nothing 341 return; 342 } 343 final boolean isRtl = mParent.isLayoutRtl(); 344 final int count = getChildCount(); 345 final int width = getWidth(); 346 for (int i = 0; i < count; i++) { 347 final View v = getChildAt(i); 348 final float left = isRtl 349 ? -(width - mHorizSpaceForIcon * (i + 1)) 350 : i * mHorizSpaceForIcon; 351 final float right = isRtl 352 ? -i * mHorizSpaceForIcon 353 : width - (mHorizSpaceForIcon * (i + 1)); 354 v.setTranslationX(onLeft ? left : right); 355 } 356 mOnLeft = onLeft; 357 mIconsPlaced = true; 358 } 359 360 public boolean isMenuLocationChange(float translation) { 361 boolean onLeft = translation > mIconPadding; 362 boolean onRight = translation < -mIconPadding; 363 if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) { 364 return true; 365 } 366 return false; 367 } 368 369 public void setDismissing() { 370 mDismissing = true; 371 } 372 373 public void setSnapping(boolean snapping) { 374 mSnapping = snapping; 375 } 376 377 @Override 378 public void onClick(View v) { 379 if (mListener == null) { 380 // Nothing to do 381 return; 382 } 383 v.getLocationOnScreen(mIconLocation); 384 mParent.getLocationOnScreen(mParentLocation); 385 final int centerX = (int) (mHorizSpaceForIcon / 2); 386 final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2; 387 final int x = mIconLocation[0] - mParentLocation[0] + centerX; 388 final int y = mIconLocation[1] - mParentLocation[1] + centerY; 389 final int index = indexOfChild(v); 390 mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index)); 391 } 392 393 @Override 394 public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) { 395 mMenuProvider = plugin; 396 updateMenu(false /* notify */); 397 } 398 399 @Override 400 public void onPluginDisconnected(NotificationMenuRowProvider plugin) { 401 mMenuProvider = null; 402 updateMenu(false /* notify */); 403 } 404}