2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar;
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.os.SystemProperties;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.accessibility.AccessibilityNodeInfo;
27 import com.android.systemui.Interpolators;
28 import com.android.systemui.R;
29 import com.android.systemui.ViewInvertHelper;
30 import com.android.systemui.statusbar.notification.NotificationUtils;
31 import com.android.systemui.statusbar.phone.NotificationIconContainer;
32 import com.android.systemui.statusbar.phone.NotificationPanelView;
33 import com.android.systemui.statusbar.stack.AmbientState;
34 import com.android.systemui.statusbar.stack.AnimationProperties;
35 import com.android.systemui.statusbar.stack.ExpandableViewState;
36 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
37 import com.android.systemui.statusbar.stack.StackScrollState;
38 import com.android.systemui.statusbar.stack.ViewState;
41 * A notification shelf view that is placed inside the notification scroller. It manages the
42 * overflow icons that don't fit into the regular list anymore.
44 public class NotificationShelf extends ActivatableNotificationView implements
45 View.OnLayoutChangeListener {
47 public static final boolean SHOW_AMBIENT_ICONS = true;
48 private static final boolean USE_ANIMATIONS_WHEN_OPENING =
49 SystemProperties.getBoolean("debug.icon_opening_animations", true);
50 private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
51 = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
52 private ViewInvertHelper mViewInvertHelper;
53 private boolean mDark;
54 private NotificationIconContainer mShelfIcons;
55 private ShelfState mShelfState;
56 private int[] mTmp = new int[2];
57 private boolean mHideBackground;
58 private int mIconAppearTopPadding;
59 private int mStatusBarHeight;
60 private int mStatusBarPaddingStart;
61 private AmbientState mAmbientState;
62 private NotificationStackScrollLayout mHostLayout;
63 private int mMaxLayoutHeight;
64 private int mPaddingBetweenElements;
65 private int mNotGoneIndex;
66 private boolean mHasItemsInStableShelf;
67 private NotificationIconContainer mCollapsedIcons;
68 private int mScrollFastThreshold;
69 private int mStatusBarState;
70 private float mMaxShelfEnd;
71 private int mRelativeOffset;
72 private boolean mInteractive;
73 private boolean mAnimationsEnabled = true;
75 public NotificationShelf(Context context, AttributeSet attrs) {
76 super(context, attrs);
80 protected void onFinishInflate() {
81 super.onFinishInflate();
82 mShelfIcons = findViewById(R.id.content);
83 mShelfIcons.setClipChildren(false);
84 mShelfIcons.setClipToPadding(false);
86 setClipToActualHeight(false);
87 setClipChildren(false);
88 setClipToPadding(false);
89 mShelfIcons.setShowAllIcons(false);
90 mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
91 NotificationPanelView.DOZE_ANIMATION_DURATION);
92 mShelfState = new ShelfState();
96 public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
97 mAmbientState = ambientState;
98 mHostLayout = hostLayout;
101 private void initDimens() {
102 mIconAppearTopPadding = getResources().getDimensionPixelSize(
103 R.dimen.notification_icon_appear_padding);
104 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
105 mStatusBarPaddingStart = getResources().getDimensionPixelOffset(
106 R.dimen.status_bar_padding_start);
107 mPaddingBetweenElements = getResources().getDimensionPixelSize(
108 R.dimen.notification_divider_height);
109 ViewGroup.LayoutParams layoutParams = getLayoutParams();
110 layoutParams.height = getResources().getDimensionPixelOffset(
111 R.dimen.notification_shelf_height);
112 setLayoutParams(layoutParams);
113 int padding = getResources().getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
114 mShelfIcons.setPadding(padding, 0, padding, 0);
115 mScrollFastThreshold = getResources().getDimensionPixelOffset(
116 R.dimen.scroll_fast_threshold);
120 protected void onConfigurationChanged(Configuration newConfig) {
121 super.onConfigurationChanged(newConfig);
126 public void setDark(boolean dark, boolean fade, long delay) {
127 super.setDark(dark, fade, delay);
128 if (mDark == dark) return;
130 mShelfIcons.setDark(dark, fade, delay);
131 updateInteractiveness();
135 protected View getContentView() {
139 public NotificationIconContainer getShelfIcons() {
144 public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
148 public void updateState(StackScrollState resultState,
149 AmbientState ambientState) {
150 View lastView = ambientState.getLastVisibleBackgroundChild();
151 if (lastView != null) {
152 float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
153 + ambientState.getStackTranslation();
154 ExpandableViewState lastViewState = resultState.getViewStateForView(lastView);
155 float viewEnd = lastViewState.yTranslation + lastViewState.height;
156 mShelfState.copyFrom(lastViewState);
157 mShelfState.height = getIntrinsicHeight();
158 mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height,
159 getFullyClosedTranslation());
160 mShelfState.zTranslation = ambientState.getBaseZHeight();
161 float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation())
162 / (getIntrinsicHeight() * 2);
163 openedAmount = Math.min(1.0f, openedAmount);
164 mShelfState.openedAmount = openedAmount;
165 mShelfState.clipTopAmount = 0;
166 mShelfState.alpha = mAmbientState.isPulsing() ? 0 : 1;
167 mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0;
168 mShelfState.shadowAlpha = 1.0f;
169 mShelfState.hideSensitive = false;
170 mShelfState.xTranslation = getTranslationX();
171 if (mNotGoneIndex != -1) {
172 mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
174 mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
175 mShelfState.hidden = !mAmbientState.isShadeExpanded();
176 mShelfState.maxShelfEnd = maxShelfEnd;
178 mShelfState.hidden = true;
179 mShelfState.location = ExpandableViewState.LOCATION_GONE;
180 mShelfState.hasItemsInStableShelf = false;
185 * Update the shelf appearance based on the other notifications around it. This transforms
186 * the icons from the notification area into the shelf.
188 public void updateAppearance() {
189 mShelfIcons.resetViewStates();
190 float shelfStart = getTranslationY();
191 float numViewsInShelf = 0.0f;
192 View lastChild = mAmbientState.getLastVisibleBackgroundChild();
194 float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
195 float expandAmount = 0.0f;
196 if (shelfStart >= interpolationStart) {
197 expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
198 expandAmount = Math.min(1.0f, expandAmount);
200 // find the first view that doesn't overlap with the shelf
201 int notificationIndex = 0;
202 int notGoneIndex = 0;
203 int colorOfViewBeforeLast = 0;
204 boolean backgroundForceHidden = false;
205 if (mHideBackground && !mShelfState.hasItemsInStableShelf) {
206 backgroundForceHidden = true;
208 int colorTwoBefore = NO_COLOR;
209 int previousColor = NO_COLOR;
210 float transitionAmount = 0.0f;
211 float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
212 boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
213 || (mAmbientState.isExpansionChanging()
214 && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
215 boolean scrolling = currentScrollVelocity > 0;
216 boolean expandingAnimated = mAmbientState.isExpansionChanging()
217 && !mAmbientState.isPanelTracking();
218 int baseZHeight = mAmbientState.getBaseZHeight();
219 while (notificationIndex < mHostLayout.getChildCount()) {
220 ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
222 if (!(child instanceof ExpandableNotificationRow)
223 || child.getVisibility() == GONE) {
226 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
227 float notificationClipEnd;
228 boolean aboveShelf = row.getTranslationZ() > baseZHeight;
229 boolean isLastChild = child == lastChild;
230 float rowTranslationY = row.getTranslationY();
231 if (isLastChild || aboveShelf || backgroundForceHidden) {
232 notificationClipEnd = shelfStart + getIntrinsicHeight();
234 notificationClipEnd = shelfStart - mPaddingBetweenElements;
235 float height = notificationClipEnd - rowTranslationY;
236 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
237 // We want the gap to close when we reached the minimum size and only shrink
239 notificationClipEnd = Math.min(shelfStart,
240 rowTranslationY + getNotificationMergeSize());
243 updateNotificationClipHeight(row, notificationClipEnd);
244 float inShelfAmount = updateIconAppearance(row, expandAmount, scrolling, scrollingFast,
245 expandingAnimated, isLastChild);
246 numViewsInShelf += inShelfAmount;
247 int ownColorUntinted = row.getBackgroundColorWithoutTint();
248 if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
249 mNotGoneIndex = notGoneIndex;
250 setTintColor(previousColor);
251 setOverrideTintColor(colorTwoBefore, transitionAmount);
253 } else if (mNotGoneIndex == -1) {
254 colorTwoBefore = previousColor;
255 transitionAmount = inShelfAmount;
257 if (isLastChild && colorOfViewBeforeLast != NO_COLOR) {
258 row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount);
260 colorOfViewBeforeLast = ownColorUntinted;
261 row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */);
263 if (notGoneIndex != 0 || !aboveShelf) {
264 row.setAboveShelf(false);
267 previousColor = ownColorUntinted;
269 mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
270 mShelfIcons.calculateIconTranslations();
271 mShelfIcons.applyIconStates();
272 boolean hideBackground = numViewsInShelf < 1.0f;
273 setHideBackground(hideBackground || backgroundForceHidden);
274 if (mNotGoneIndex == -1) {
275 mNotGoneIndex = notGoneIndex;
279 private void updateNotificationClipHeight(ExpandableNotificationRow row,
280 float notificationClipEnd) {
281 float viewEnd = row.getTranslationY() + row.getActualHeight();
282 if (viewEnd > notificationClipEnd
283 && (mAmbientState.isShadeExpanded()
284 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
285 row.setClipBottomAmount((int) (viewEnd - notificationClipEnd));
287 row.setClipBottomAmount(0);
292 * @return the icon amount how much this notification is in the shelf;
294 private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
295 boolean scrolling, boolean scrollingFast, boolean expandingAnimated,
296 boolean isLastChild) {
297 // Let calculate how much the view is in the shelf
298 float viewStart = row.getTranslationY();
299 int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
300 float iconTransformDistance = getIntrinsicHeight() * 1.5f;
301 iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
303 fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
304 iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
305 - getIntrinsicHeight());
307 float viewEnd = viewStart + fullHeight;
308 float fullTransitionAmount;
309 float iconTransitionAmount;
310 float shelfStart = getTranslationY();
311 if (viewEnd >= shelfStart && (mAmbientState.isShadeExpanded()
312 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
313 if (viewStart < shelfStart) {
315 float fullAmount = (shelfStart - viewStart) / fullHeight;
316 float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation(
318 interpolatedAmount = NotificationUtils.interpolate(
319 interpolatedAmount, fullAmount, expandAmount);
320 fullTransitionAmount = 1.0f - interpolatedAmount;
322 iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
323 iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
324 iconTransitionAmount = 1.0f - iconTransitionAmount;
327 fullTransitionAmount = 1.0f;
328 iconTransitionAmount = 1.0f;
331 fullTransitionAmount = 0.0f;
332 iconTransitionAmount = 0.0f;
334 updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount,
335 iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
336 return fullTransitionAmount;
339 private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
340 float fullTransitionAmount, float iconTransformDistance, boolean scrolling,
341 boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
342 StatusBarIconView icon = row.getEntry().expandedIcon;
343 NotificationIconContainer.IconState iconState = getIconState(icon);
344 if (iconState == null) {
347 float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
348 if (clampedAmount == fullTransitionAmount) {
349 iconState.useFullTransitionAmount = scrollingFast || expandingAnimated
350 || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling);
351 iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
352 && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging();
353 iconState.translateContent = mMaxLayoutHeight - getTranslationY()
354 - getIntrinsicHeight() > 0;
356 if (scrollingFast || (expandingAnimated && iconState.useFullTransitionAmount
357 && !ViewState.isAnimatingY(icon))) {
358 iconState.cancelAnimations(icon);
359 iconState.useFullTransitionAmount = true;
361 float transitionAmount;
362 if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
363 || iconState.useLinearTransitionAmount) {
364 transitionAmount = iconTransitionAmount;
366 // We take the clamped position instead
367 transitionAmount = clampedAmount;
368 iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount;
370 iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
371 || iconState.useFullTransitionAmount
372 ? fullTransitionAmount
374 iconState.clampedAppearAmount = clampedAmount;
375 float contentTransformationAmount = isLastChild || iconState.translateContent
376 ? iconTransitionAmount
378 row.setContentTransformationAmount(contentTransformationAmount, isLastChild);
379 setIconTransformationAmount(row, transitionAmount, iconTransformDistance,
380 clampedAmount != transitionAmount, isLastChild);
383 private void setIconTransformationAmount(ExpandableNotificationRow row,
384 float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation,
385 boolean isLastChild) {
386 StatusBarIconView icon = row.getEntry().expandedIcon;
387 NotificationIconContainer.IconState iconState = getIconState(icon);
389 View rowIcon = row.getNotificationIcon();
390 float notificationIconPosition = row.getTranslationY() + row.getContentTranslation();
391 boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
392 if (usingLinearInterpolation && !stayingInShelf) {
393 // If we interpolate from the notification position, this might lead to a slightly
394 // odd interpolation, since the notification position changes as well. Let's interpolate
395 // from a fixed distance. We can only do this if we don't animate and the icon is
396 // always in the interpolated positon.
397 notificationIconPosition = getTranslationY() - iconTransformDistance;
399 float notificationIconSize = 0.0f;
401 if (rowIcon != null) {
402 iconTopPadding = row.getRelativeTopPadding(rowIcon);
403 notificationIconSize = rowIcon.getHeight();
405 iconTopPadding = mIconAppearTopPadding;
407 notificationIconPosition += iconTopPadding;
408 float shelfIconPosition = getTranslationY() + icon.getTop();
409 shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
410 float iconYTranslation = NotificationUtils.interpolate(
411 notificationIconPosition - shelfIconPosition,
414 float shelfIconSize = icon.getHeight() * icon.getIconScale();
416 boolean noIcon = !row.isShowingIcon();
418 // The view currently doesn't have an icon, lets transform it in!
419 alpha = transitionAmount;
420 notificationIconSize = shelfIconSize / 2.0f;
422 // The notification size is different from the size in the shelf / statusbar
423 float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
425 if (iconState != null) {
426 iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
427 iconState.scaleY = iconState.scaleX;
428 iconState.hidden = transitionAmount == 0.0f;
429 iconState.alpha = alpha;
430 iconState.yTranslation = iconYTranslation;
431 if (stayingInShelf) {
432 iconState.iconAppearAmount = 1.0f;
433 iconState.alpha = 1.0f;
434 iconState.scaleX = 1.0f;
435 iconState.scaleY = 1.0f;
436 iconState.hidden = false;
438 if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed()
439 || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
440 iconState.hidden = true;
442 int shelfColor = icon.getStaticDrawableColor();
443 if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
444 int notificationColor
445 = row.getVisibleNotificationHeader().getOriginalNotificationColor();
446 shelfColor = NotificationUtils.interpolateColors(notificationColor, shelfColor,
447 iconState.iconAppearAmount);
449 iconState.iconColor = shelfColor;
453 private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
454 return mShelfIcons.getIconState(icon);
457 private float getFullyClosedTranslation() {
458 return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
461 public int getNotificationMergeSize() {
462 return getIntrinsicHeight();
466 public boolean hasNoContentHeight() {
470 private void setHideBackground(boolean hideBackground) {
471 if (mHideBackground != hideBackground) {
472 mHideBackground = hideBackground;
478 public boolean hidesBackground() {
479 return mHideBackground;
483 protected boolean needsOutline() {
484 return !mHideBackground && super.needsOutline();
488 protected boolean shouldHideBackground() {
489 return super.shouldHideBackground() || mHideBackground;
493 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
494 super.onLayout(changed, left, top, right, bottom);
495 updateRelativeOffset();
498 private void updateRelativeOffset() {
499 mCollapsedIcons.getLocationOnScreen(mTmp);
500 mRelativeOffset = mTmp[0];
501 getLocationOnScreen(mTmp);
502 mRelativeOffset -= mTmp[0];
505 private void setOpenedAmount(float openedAmount) {
506 if (!mAmbientState.isPanelFullWidth()) {
507 // We don't do a transformation at all, lets just assume we are fully opened
510 int start = mRelativeOffset;
512 start = getWidth() - start - mCollapsedIcons.getWidth();
514 int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
515 mShelfIcons.getWidth(),
517 mShelfIcons.setActualLayoutWidth(width);
518 boolean hasOverflow = mCollapsedIcons.hasOverflow();
519 int collapsedPadding = mCollapsedIcons.getPaddingEnd();
521 // we have to ensure that adding the low priority notification won't lead to an
523 collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT)
524 * mCollapsedIcons.getIconSize();
526 float padding = NotificationUtils.interpolate(collapsedPadding,
527 mShelfIcons.getPaddingEnd(),
529 mShelfIcons.setActualPaddingEnd(padding);
530 float paddingStart = NotificationUtils.interpolate(start,
531 mShelfIcons.getPaddingStart(), openedAmount);
532 mShelfIcons.setActualPaddingStart(paddingStart);
533 mShelfIcons.setOpenedAmount(openedAmount);
534 mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
537 public void setMaxLayoutHeight(int maxLayoutHeight) {
538 mMaxLayoutHeight = maxLayoutHeight;
542 * @return the index of the notification at which the shelf visually resides
544 public int getNotGoneIndex() {
545 return mNotGoneIndex;
548 private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) {
549 if (mHasItemsInStableShelf != hasItemsInStableShelf) {
550 mHasItemsInStableShelf = hasItemsInStableShelf;
551 updateInteractiveness();
556 * @return whether the shelf has any icons in it when a potential animation has finished, i.e
557 * if the current state would be applied right now
559 public boolean hasItemsInStableShelf() {
560 return mHasItemsInStableShelf;
563 public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
564 mCollapsedIcons = collapsedIcons;
565 mCollapsedIcons.addOnLayoutChangeListener(this);
568 public void setStatusBarState(int statusBarState) {
569 if (mStatusBarState != statusBarState) {
570 mStatusBarState = statusBarState;
571 updateInteractiveness();
575 private void updateInteractiveness() {
576 mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf
578 setClickable(mInteractive);
579 setFocusable(mInteractive);
580 setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
581 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
585 protected boolean isInteractive() {
589 public void setMaxShelfEnd(float maxShelfEnd) {
590 mMaxShelfEnd = maxShelfEnd;
593 public void setAnimationsEnabled(boolean enabled) {
594 mAnimationsEnabled = enabled;
595 mCollapsedIcons.setAnimationsEnabled(enabled);
597 // we need to wait with enabling the animations until the first frame has passed
598 mShelfIcons.setAnimationsEnabled(false);
603 public boolean hasOverlappingRendering() {
604 return false; // Shelf only uses alpha for transitions where the difference can't be seen.
608 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
609 super.onInitializeAccessibilityNodeInfo(info);
611 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
612 AccessibilityNodeInfo.AccessibilityAction unlock
613 = new AccessibilityNodeInfo.AccessibilityAction(
614 AccessibilityNodeInfo.ACTION_CLICK,
615 getContext().getString(R.string.accessibility_overflow_action));
616 info.addAction(unlock);
621 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
622 int oldTop, int oldRight, int oldBottom) {
623 updateRelativeOffset();
626 private class ShelfState extends ExpandableViewState {
627 private float openedAmount;
628 private boolean hasItemsInStableShelf;
629 private float maxShelfEnd;
632 public void applyToView(View view) {
633 super.applyToView(view);
634 setMaxShelfEnd(maxShelfEnd);
635 setOpenedAmount(openedAmount);
637 setHasItemsInStableShelf(hasItemsInStableShelf);
638 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
642 public void animateTo(View child, AnimationProperties properties) {
643 super.animateTo(child, properties);
644 setMaxShelfEnd(maxShelfEnd);
645 setOpenedAmount(openedAmount);
647 setHasItemsInStableShelf(hasItemsInStableShelf);
648 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);