<com.android.systemui.statusbar.NotificationOverflowContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="32dp"
android:focusable="true"
android:clickable="true"
- android:background="@*android:drawable/notification_quantum_bg_dim"
>
<TextView
android:id="@+id/more_text"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="true"
- android:background="@*android:drawable/notification_quantum_bg"
>
- <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expanded"
+ <com.android.systemui.statusbar.NotificationContentView android:id="@+id/expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expandedPublic"
+ <com.android.systemui.statusbar.NotificationContentView android:id="@+id/expandedPublic"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewConfiguration;
-import android.view.ViewGroup;
+import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.ScrollAdapter;
public class ExpandHelper implements Gefingerpoken, OnClickListener {
float focusY = detector.getFocusY();
final View underFocus = findView(focusX, focusY);
- if (underFocus != null) {
- startExpanding(underFocus, STRETCH);
- }
+ startExpanding(underFocus, STRETCH);
return mExpanding;
}
};
private class ViewScaler {
- View mView;
+ ExpandableView mView;
public ViewScaler() {}
- public void setView(View v) {
+ public void setView(ExpandableView v) {
mView = v;
}
public void setHeight(float h) {
if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
- ViewGroup.LayoutParams lp = mView.getLayoutParams();
- lp.height = (int)h;
- mView.setLayoutParams(lp);
- mView.requestLayout();
+ mView.setActualHeight((int) h);
}
public float getHeight() {
- int height = mView.getLayoutParams().height;
- if (height < 0) {
- height = mView.getMeasuredHeight();
- }
- return height;
+ return mView.getActualHeight();
}
public int getNaturalHeight(int maximum) {
- ViewGroup.LayoutParams lp = mView.getLayoutParams();
- if (DEBUG_SCALE) Log.v(TAG, "Inspecting a child of type: " +
- mView.getClass().getName());
- int oldHeight = lp.height;
- lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- mView.setLayoutParams(lp);
- mView.measure(
- View.MeasureSpec.makeMeasureSpec(mView.getMeasuredWidth(),
- View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(maximum,
- View.MeasureSpec.AT_MOST));
- lp.height = oldHeight;
- mView.setLayoutParams(lp);
- return mView.getMeasuredHeight();
+ return Math.min(maximum, mView.getMaxHeight());
}
}
mGravity = Gravity.TOP;
mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
mScaleAnimation.setDuration(EXPAND_DURATION);
- mScaleAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mCallback.setUserLockedChild(mCurrView, false);
- }
- });
mPopLimit = mContext.getResources().getDimension(R.dimen.blinds_pop_threshold);
mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms);
mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
if (DEBUG_SCALE) Log.v(TAG, "got pull gesture (xspan=" + xspan + "px)");
final View underFocus = findView(x, y);
- if (underFocus != null) {
- startExpanding(underFocus, PULL);
- }
+ startExpanding(underFocus, PULL);
return true;
}
if (mScrollAdapter != null && !mScrollAdapter.isScrolledToTop()) {
if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
mLastMotionY = y;
final View underFocus = findView(x, y);
- if (underFocus != null) {
- startExpanding(underFocus, BLINDS);
+ if (startExpanding(underFocus, BLINDS)) {
mInitialTouchY = mLastMotionY;
mHasPopped = false;
}
return true;
}
- private void startExpanding(View v, int expandType) {
+ /**
+ * @return True if the view is expandable, false otherwise.
+ */
+ private boolean startExpanding(View v, int expandType) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ return false;
+ }
mExpansionStyle = expandType;
- if (mExpanding && v == mCurrView) {
- return;
+ if (mExpanding && v == mCurrView) {
+ return true;
}
mExpanding = true;
if (DEBUG) Log.d(TAG, "scale type " + expandType + " beginning on view: " + v);
mCallback.setUserLockedChild(v, true);
setView(v);
- mScaler.setView(v);
+ mScaler.setView((ExpandableView) v);
mOldHeight = mScaler.getHeight();
if (mCallback.canChildBeExpanded(v)) {
if (DEBUG) Log.d(TAG, "working on an expandable child");
if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight +
" mNaturalHeight: " + mNaturalHeight);
v.getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
}
private void finishExpanding(boolean force) {
if (mScaleAnimation.isRunning()) {
mScaleAnimation.cancel();
}
- mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
+ mCallback.setUserExpandedChild(mCurrView, targetHeight == mNaturalHeight);
if (targetHeight != currentHeight) {
mScaleAnimation.setFloatValues(targetHeight);
mScaleAnimation.setupStartValues();
+ final View scaledView = mCurrView;
+ mScaleAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCallback.setUserLockedChild(scaledView, false);
+ mScaleAnimation.removeListener(this);
+ }
+ });
mScaleAnimation.start();
} else {
mCallback.setUserLockedChild(mCurrView, false);
* Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
* to implement dimming/activating on Keyguard for the double-tap gesture
*/
-public class ActivatableNotificationView extends FrameLayout {
+public abstract class ActivatableNotificationView extends ExpandableOutlineView {
private static final long DOUBLETAP_TIMEOUT_MS = 1000;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ updateBackgroundResource();
}
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
+ if (mDownY > getActualHeight()) {
+ return false;
+ }
// Call the listener tentatively directly, even if we don't know whether the user
// will stay within the touch slop, as the listener is implemented as a scale
}
private void makeActive(float x, float y) {
- getBackground().setHotspot(0, x, y);
+ mCustomBackground.setHotspot(0, x, y);
mActivated = true;
}
private void makeInactive() {
if (mActivated) {
// Make sure that we clear the hotspot from the center.
- getBackground().setHotspot(0, getWidth() / 2, getHeight() / 2);
- getBackground().removeHotspot(0);
+ mCustomBackground.setHotspot(0, getWidth() / 2, getActualHeight() / 2);
+ mCustomBackground.removeHotspot(0);
mActivated = false;
}
if (mOnActivatedListener != null) {
}
private void updateBackgroundResource() {
- setBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId);
+ setCustomBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ setPivotX(getWidth()/2);
+ }
+
+ @Override
+ public void setActualHeight(int actualHeight) {
+ super.setActualHeight(actualHeight);
+ setPivotY(actualHeight/2);
}
public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
// NB: the large icon is now handled entirely by the template
// bind the click event to the content area
- SizeAdaptiveLayout expanded = (SizeAdaptiveLayout)row.findViewById(R.id.expanded);
- SizeAdaptiveLayout expandedPublic
- = (SizeAdaptiveLayout)row.findViewById(R.id.expandedPublic);
+ NotificationContentView expanded =
+ (NotificationContentView) row.findViewById(R.id.expanded);
+ NotificationContentView expandedPublic =
+ (NotificationContentView) row.findViewById(R.id.expandedPublic);
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (contentViewLocal != null) {
contentViewLocal.setIsRootNamespace(true);
- SizeAdaptiveLayout.LayoutParams params =
- new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
- params.minHeight = minHeight;
- params.maxHeight = minHeight;
- expanded.addView(contentViewLocal, params);
+ expanded.setContractedChild(contentViewLocal);
}
if (bigContentViewLocal != null) {
bigContentViewLocal.setIsRootNamespace(true);
- SizeAdaptiveLayout.LayoutParams params =
- new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
- params.minHeight = minHeight+1;
- params.maxHeight = maxHeight;
- expanded.addView(bigContentViewLocal, params);
+ expanded.setExpandedChild(bigContentViewLocal);
}
PackageManager pm = mContext.getPackageManager();
if (publicViewLocal != null) {
publicViewLocal.setIsRootNamespace(true);
- SizeAdaptiveLayout.LayoutParams params =
- new SizeAdaptiveLayout.LayoutParams(publicViewLocal.getLayoutParams());
- params.minHeight = minHeight;
- params.maxHeight = minHeight;
- expandedPublic.addView(publicViewLocal, params);
+ expandedPublic.setContractedChild(publicViewLocal);
}
}
catch (RuntimeException e) {
} else {
entry.row.setOnClickListener(null);
}
+ entry.row.notifyContentUpdated();
}
protected void notifyHeadsUpScreenOn(boolean screenOn) {
package com.android.systemui.statusbar;
import android.content.Context;
+import android.graphics.Canvas;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import com.android.internal.widget.SizeAdaptiveLayout;
import com.android.systemui.R;
public class ExpandableNotificationRow extends ActivatableNotificationView {
* user expansion.
*/
private boolean mIsSystemExpanded;
- private SizeAdaptiveLayout mPublicLayout;
- private SizeAdaptiveLayout mPrivateLayout;
+ private NotificationContentView mPublicLayout;
+ private NotificationContentView mPrivateLayout;
private int mMaxExpandHeight;
- private boolean mMaxHeightNeedsUpdate;
private NotificationActivator mActivator;
- private boolean mSelfInitiatedLayout;
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic);
- mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded);
+ mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
+ mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
mActivator = new NotificationActivator(this);
}
public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
mRowMinHeight = rowMinHeight;
mRowMaxHeight = rowMaxHeight;
- mMaxHeightNeedsUpdate = true;
}
public boolean isExpandable() {
* @param expand should the layout be in the expanded state
*/
public void applyExpansionToLayout(boolean expand) {
- ViewGroup.LayoutParams lp = getLayoutParams();
if (expand && mExpandable) {
- lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ setActualHeight(mMaxExpandHeight);
} else {
- lp.height = mRowMinHeight;
+ setActualHeight(mRowMinHeight);
}
- setLayoutParams(lp);
}
/**
* @return the maximum allowed expansion height of this view.
*/
public int getMaximumAllowedExpandHeight() {
+ if (isUserLocked()) {
+ return getActualHeight();
+ }
boolean inExpansionState = isExpanded();
if (!inExpansionState) {
// not expanded, so we return the collapsed size
return mShowingPublic ? mRowMinHeight : getMaxExpandHeight();
}
- private void updateMaxExpandHeight() {
-
- // We don't want this method to trigger a layout of the whole view hierarchy,
- // as the layout parameters in the end are the same which they were in the beginning.
- // Otherwise a loop may occur if this method is called on the layout of a parent.
- mSelfInitiatedLayout = true;
- ViewGroup.LayoutParams lp = getLayoutParams();
- int oldHeight = lp.height;
- lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- setLayoutParams(lp);
- measure(View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(mRowMaxHeight, View.MeasureSpec.AT_MOST));
- lp.height = oldHeight;
- setLayoutParams(lp);
- mMaxExpandHeight = getMeasuredHeight();
- mSelfInitiatedLayout = false;
- }
-
- @Override
- public void requestLayout() {
- if (!mSelfInitiatedLayout) {
- super.requestLayout();
- }
- }
-
/**
* Check whether the view state is currently expanded. This is given by the system in {@link
* #setSystemExpanded(boolean)} and can be overridden by user expansion or
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- mMaxHeightNeedsUpdate = true;
+ boolean updateExpandHeight = mMaxExpandHeight == 0;
+ mMaxExpandHeight = mPrivateLayout.getMaxHeight();
+ if (updateExpandHeight) {
+ applyExpansionToLayout(isExpanded());
+ }
}
public void setShowingPublic(boolean show) {
}
public int getMaxExpandHeight() {
- if (mMaxHeightNeedsUpdate) {
- updateMaxExpandHeight();
- mMaxHeightNeedsUpdate = false;
- }
return mMaxExpandHeight;
}
* @return the potential height this view could expand in addition.
*/
public int getExpandPotential() {
- return getMaximumAllowedExpandHeight() - getHeight();
+ return getMaximumAllowedExpandHeight() - getActualHeight();
+ }
+
+ @Override
+ public void setActualHeight(int height) {
+ mPrivateLayout.setActualHeight(height);
+ invalidate();
+ super.setActualHeight(height);
+ }
+
+ @Override
+ public int getMaxHeight() {
+ return mPrivateLayout.getMaxHeight();
+ }
+
+ @Override
+ public void setClipTopAmount(int clipTopAmount) {
+ super.setClipTopAmount(clipTopAmount);
+ mPrivateLayout.setClipTopAmount(clipTopAmount);
+ }
+
+ public void notifyContentUpdated() {
+ mPrivateLayout.notifyContentUpdated();
}
}
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * Like {@link ExpandableView}, but setting an outline for the height and clipping.
+ */
+public abstract class ExpandableOutlineView extends ExpandableView {
+
+ private final Outline mOutline = new Outline();
+
+ public ExpandableOutlineView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setActualHeight(int actualHeight) {
+ super.setActualHeight(actualHeight);
+ updateOutline();
+ }
+
+ @Override
+ public void setClipTopAmount(int clipTopAmount) {
+ super.setClipTopAmount(clipTopAmount);
+ updateOutline();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ updateOutline();
+ }
+
+ private void updateOutline() {
+ mOutline.setRect(0,
+ mClipTopAmount,
+ getWidth(),
+ Math.max(mActualHeight, mClipTopAmount));
+ setOutline(mOutline);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * An abstract view for expandable views.
+ */
+public abstract class ExpandableView extends FrameLayout {
+
+ private OnHeightChangedListener mOnHeightChangedListener;
+ protected int mActualHeight;
+ protected int mClipTopAmount;
+ protected Drawable mCustomBackground;
+ private boolean mActualHeightInitialized;
+
+ public ExpandableView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mCustomBackground != null) {
+ mCustomBackground.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
+ mCustomBackground.draw(canvas);
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mCustomBackground;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ final Drawable d = mCustomBackground;
+ if (d != null && d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!mActualHeightInitialized && mActualHeight == 0) {
+ mActualHeight = getHeight();
+ }
+ mActualHeightInitialized = true;
+ }
+
+ /**
+ * Sets the actual height of this notification. This is different than the laid out
+ * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
+ */
+ public void setActualHeight(int actualHeight) {
+ mActualHeight = actualHeight;
+ invalidate();
+ if (mOnHeightChangedListener != null) {
+ mOnHeightChangedListener.onHeightChanged(this);
+ }
+ }
+
+ /**
+ * See {@link #setActualHeight}.
+ *
+ * @return The actual height of this notification.
+ */
+ public int getActualHeight() {
+ return mActualHeight;
+ }
+
+ /**
+ * @return The maximum height of this notification.
+ */
+ public abstract int getMaxHeight();
+
+ /**
+ * Sets the amount this view should be clipped from the top. This is used when an expanded
+ * notification is scrolling in the top or bottom stack.
+ *
+ * @param clipTopAmount The amount of pixels this view should be clipped from top.
+ */
+ public void setClipTopAmount(int clipTopAmount) {
+ mClipTopAmount = clipTopAmount;
+ invalidate();
+ }
+
+ public void setOnHeightChangedListener(OnHeightChangedListener listener) {
+ mOnHeightChangedListener = listener;
+ }
+
+ /**
+ * Sets a custom background drawable. As we need to change our bounds independently of layout,
+ * we need the notition of a custom background.
+ */
+ public void setCustomBackground(Drawable customBackground) {
+ if (mCustomBackground != null) {
+ mCustomBackground.setCallback(null);
+ unscheduleDrawable(mCustomBackground);
+ }
+ mCustomBackground = customBackground;
+ mCustomBackground.setCallback(this);
+ setWillNotDraw(customBackground == null);
+ invalidate();
+ }
+
+ public void setCustomBackgroundResource(int drawableResId) {
+ setCustomBackground(getResources().getDrawable(drawableResId));
+ }
+
+ /**
+ * A listener notifying when {@link #getActualHeight} changes.
+ */
+ public interface OnHeightChangedListener {
+ void onHeightChanged(ExpandableView view);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A frame layout containing the actual payload of the notification, including the contracted and
+ * expanded layout. This class is responsible for clipping the content and and switching between the
+ * expanded and contracted view depending on its clipped size.
+ */
+public class NotificationContentView extends ExpandableView {
+
+ private final Rect mClipBounds = new Rect();
+
+ private View mContractedChild;
+ private View mExpandedChild;
+
+ private int mSmallHeight;
+
+ public NotificationContentView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
+ mActualHeight = mSmallHeight;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ updateClipping();
+ }
+
+ public void setContractedChild(View child) {
+ if (mContractedChild != null) {
+ removeView(mContractedChild);
+ }
+ sanitizeContractedLayoutParams(child);
+ addView(child);
+ mContractedChild = child;
+ selectLayout();
+ }
+
+ public void setExpandedChild(View child) {
+ if (mExpandedChild != null) {
+ removeView(mExpandedChild);
+ }
+ addView(child);
+ mExpandedChild = child;
+ selectLayout();
+ }
+
+ @Override
+ public void setActualHeight(int actualHeight) {
+ super.setActualHeight(actualHeight);
+ selectLayout();
+ updateClipping();
+ }
+
+ @Override
+ public int getMaxHeight() {
+
+ // The maximum height is just the laid out height.
+ return getHeight();
+ }
+
+ @Override
+ public void setClipTopAmount(int clipTopAmount) {
+ super.setClipTopAmount(clipTopAmount);
+ updateClipping();
+ }
+
+ public int getClipTopAmount() {
+ return mClipTopAmount;
+ }
+
+ private void updateClipping() {
+ mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
+ setClipBounds(mClipBounds);
+ }
+
+ private void sanitizeContractedLayoutParams(View contractedChild) {
+ LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
+ lp.height = mSmallHeight;
+ contractedChild.setLayoutParams(lp);
+ }
+
+ private void selectLayout() {
+ if (mActualHeight <= mSmallHeight || mExpandedChild == null) {
+ if (mContractedChild.getVisibility() != View.VISIBLE) {
+ mContractedChild.setVisibility(View.VISIBLE);
+ }
+ if (mExpandedChild != null && mExpandedChild.getVisibility() != View.INVISIBLE) {
+ mExpandedChild.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ if (mExpandedChild.getVisibility() != View.VISIBLE) {
+ mExpandedChild.setVisibility(View.VISIBLE);
+ }
+ if (mContractedChild.getVisibility() != View.INVISIBLE) {
+ mContractedChild.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
+ public void notifyContentUpdated() {
+ selectLayout();
+ }
+}
}
@Override
+ public void setActualHeight(int currentHeight) {
+ // noop
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @Override
+ public int getMaxHeight() {
+ return getHeight();
+ }
+
+ @Override
+ public void setClipTopAmount(int clipTopAmount) {
+ // noop
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
mActivator = new NotificationActivator(this);
mActivator.setDimmed(true);
setLocked(true);
+ setDimmed(true);
}
public NotificationOverflowIconsView getIconsView() {
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-public class NotificationPanelView extends PanelView {
+public class NotificationPanelView extends PanelView implements
+ ExpandableView.OnHeightChangedListener {
public static final boolean DEBUG_GESTURES = true;
PhoneStatusBar mStatusBar;
mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
mNotificationStackScroller = (NotificationStackScrollLayout)
findViewById(R.id.notification_stack_scroller);
+ mNotificationStackScroller.setOnHeightChangedListener(this);
mNotificationParent = findViewById(R.id.notification_container_parent);
}
super.onExpandingFinished();
mNotificationStackScroller.onExpansionStopped();
}
+
+ @Override
+ public void onHeightChanged(ExpandableView view) {
+ requestPanelHeightUpdate();
+ }
}
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
import com.android.systemui.statusbar.policy.ScrollAdapter;
* A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
*/
public class NotificationStackScrollLayout extends ViewGroup
- implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter {
+ implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
+ ExpandableView.OnHeightChangedListener {
private static final String TAG = "NotificationStackScrollLayout";
private static final boolean DEBUG = false;
private int mBottomStackPeekSize;
private int mEmptyMarginBottom;
private int mPaddingBetweenElements;
+ private boolean mListenForHeightChanges = true;
/**
* The algorithm which calculates the properties for our children
private final StackScrollState mCurrentStackScrollState = new StackScrollState(this);
private OnChildLocationsChangedListener mListener;
+ private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
public NotificationStackScrollLayout(Context context) {
this(context, null);
if (!isCurrentlyAnimating()) {
mCurrentStackScrollState.setScrollY(mOwnScrollY);
mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState);
+ mListenForHeightChanges = false;
mCurrentStackScrollState.apply();
+ mListenForHeightChanges = true;
if (mListener != null) {
mListener.onChildLocationsChanged(this);
}
// find the view under the pointer, accounting for GONE views
final int count = getChildCount();
for (int childIdx = 0; childIdx < count; childIdx++) {
- View slidingChild = getChildAt(childIdx);
+ ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
if (slidingChild.getVisibility() == GONE) {
continue;
}
float top = slidingChild.getTranslationY();
- float bottom = top + slidingChild.getHeight();
+ float bottom = top + slidingChild.getActualHeight();
int left = slidingChild.getLeft();
int right = slidingChild.getRight();
private int getScrollRange() {
int scrollRange = 0;
- View firstChild = getFirstChildNotGone();
+ ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
if (firstChild != null) {
int contentHeight = getContentHeight();
int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
- int firstChildExpandPotential = firstChildMaxExpandHeight - firstChild.getHeight();
- // If we already scrolled in, the first child is layouted smaller than it actually
- // could be when expanded. We have to compensate for this loss of the contentHeight
- // by adding the expand potential again.
- contentHeight += firstChildExpandPotential;
scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize);
if (scrollRange > 0 && getChildCount() > 0) {
// We want to at least be able collapse the first item and not ending in a weird
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
- height += child.getHeight();
- if (i < getChildCount()-1) {
+ if (height != 0) {
+ // add the padding before this element
height += mPaddingBetweenElements;
}
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ height += row.getMaximumAllowedExpandHeight();
+ } else if (child instanceof ExpandableView) {
+ ExpandableView expandableView = (ExpandableView) child;
+ height += expandableView.getActualHeight();
+ }
}
}
mContentHeight = height;
@Override
protected void onViewRemoved(View child) {
super.onViewRemoved(child);
+ ((ExpandableView) child).setOnHeightChangedListener(null);
mCurrentStackScrollState.removeViewStateForView(child);
mStackScrollAlgorithm.notifyChildrenChanged(this);
}
protected void onViewAdded(View child) {
super.onViewAdded(child);
mStackScrollAlgorithm.notifyChildrenChanged(this);
+ ((ExpandableView) child).setOnHeightChangedListener(this);
}
private boolean onInterceptTouchEventScroll(MotionEvent ev) {
}
}
+ @Override
+ public void onHeightChanged(ExpandableView view) {
+ if (mListenForHeightChanges) {
+ updateContentHeight();
+ updateScrollPositionIfNecessary();
+ if (mOnHeightChangedListener != null) {
+ mOnHeightChangedListener.onHeightChanged(view);
+ }
+ updateChildren();
+ }
+ }
+
+ public void setOnHeightChangedListener(
+ ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
+ this.mOnHeightChangedListener = mOnHeightChangedListener;
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
import java.util.ArrayList;
private boolean mIsExpansionChanging;
private int mFirstChildMaxHeight;
private boolean mIsExpanded;
- private View mFirstChildWhileExpanding;
+ private ExpandableView mFirstChildWhileExpanding;
private boolean mExpandedOnStart;
private int mTopStackTotalSize;
mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
MAX_ITEMS_IN_BOTTOM_STACK,
mBottomStackPeekSize,
- mCollapsedSize + mPaddingBetweenElements + mBottomStackPeekSize,
+ mCollapsedSize + mBottomStackPeekSize + mPaddingBetweenElements,
0.5f);
}
algorithmState.scrolledPixelsTop = 0;
algorithmState.itemsInBottomStack = 0.0f;
algorithmState.partialInBottom = 0.0f;
+ algorithmState.scrollY = resultState.getScrollY() + mCollapsedSize;
updateVisibleChildren(resultState, algorithmState);
- algorithmState.scrollY = getAlgorithmScrollPosition(resultState, algorithmState);
-
// Phase 1:
findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState);
}
/**
- * Calculates the scroll offset of the algorithm, based on the resultState.
- *
- * @param resultState the state to base the calculation on
- * @param algorithmState The state in which the current pass of the algorithm is currently in
- * @return the scroll offset used for the algorithm
- */
- private int getAlgorithmScrollPosition(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
-
- int resultScroll = resultState.getScrollY() + mCollapsedSize;
-
- // If the first child was collapsed in an earlier pass, we have to decrease the scroll
- // position to get into the same state again.
- if (algorithmState.visibleChildren.size() > 0) {
- View firstView = algorithmState.visibleChildren.get(0);
- if (firstView instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow firstRow = (ExpandableNotificationRow) firstView;
- if (firstRow.isUserLocked()) {
- // User is currently modifying this height.
- return resultScroll;
- }
- int scrolledInAmount = 0;
- // If the child size was not decreased due to scrolling, we don't substract it,
- if (!mIsExpansionChanging) {
- scrolledInAmount = firstRow.getExpandPotential();
- } else if (mExpandedOnStart && mFirstChildWhileExpanding == firstView) {
- scrolledInAmount = firstRow.getMaximumAllowedExpandHeight() -
- mFirstChildMaxHeight;
- }
- resultScroll -= scrolledInAmount;
- }
- }
- return resultScroll;
- }
-
- /**
* Update the visible children on the state.
*/
private void updateVisibleChildren(StackScrollState resultState,
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
for (int i = 0; i < childCount; i++) {
- View v = hostView.getChildAt(i);
+ ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
state.visibleChildren.add(v);
}
int childCount = algorithmState.visibleChildren.size();
int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
for (int i = 0; i < childCount; i++) {
- View child = algorithmState.visibleChildren.get(i);
+ ExpandableView child = algorithmState.visibleChildren.get(i);
StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN;
- int childHeight = child.getHeight();
+ int childHeight = getMaxAllowedChildHeight(child);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
/**
* Clamp the yTranslation of the child up such that its end is at lest on the end of the top
- * stack.
+ * stack.get
*
* @param childViewState the view state of the child
* @param childHeight the height of this child
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
return row.getMaximumAllowedExpandHeight();
+ } else if (child instanceof ExpandableView) {
+ ExpandableView expandableView = (ExpandableView) child;
+ return expandableView.getActualHeight();
}
- return child.getHeight();
+ return child == null? mCollapsedSize : child.getHeight();
}
private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
// find the number of elements in the top stack.
for (int i = 0; i < childCount; i++) {
- View child = algorithmState.visibleChildren.get(i);
+ ExpandableView child = algorithmState.visibleChildren.get(i);
StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
- int childHeight = child.getHeight();
+ int childHeight = getMaxAllowedChildHeight(child);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
}
private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) {
- mFirstChildWhileExpanding = findFirstVisibleChild(hostView);
+ mFirstChildWhileExpanding = (ExpandableView) findFirstVisibleChild(hostView);
if (mFirstChildWhileExpanding != null) {
if (mExpandedOnStart) {
// We are collapsing the shade, so the first child can get as most as high as the
// current height.
- mFirstChildMaxHeight = mFirstChildWhileExpanding.getHeight();
+ mFirstChildMaxHeight = mFirstChildWhileExpanding.getActualHeight();
} else {
// We are expanding the shade, expand it to its full height.
/**
* The children from the host view which are not gone.
*/
- public final ArrayList<View> visibleChildren = new ArrayList<View>();
+ public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
}
}
import android.view.View;
import android.view.ViewGroup;
-import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableView;
import java.util.HashMap;
import java.util.Map;
private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
private final ViewGroup mHostView;
- private Map<View, ViewState> mStateMap;
+ private Map<ExpandableView, ViewState> mStateMap;
private int mScrollY;
private final Rect mClipRect = new Rect();
private int mBackgroundRoundedRectCornerRadius;
private final Outline mChildOutline = new Outline();
- private final int mChildDividerHeight;
public int getScrollY() {
return mScrollY;
public StackScrollState(ViewGroup hostView) {
mHostView = hostView;
- mStateMap = new HashMap<View, ViewState>();
+ mStateMap = new HashMap<ExpandableView, ViewState>();
mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_quantum_rounded_rect_radius);
- mChildDividerHeight = hostView.getResources().getDimensionPixelSize(R.dimen
- .notification_divider_height);
}
public ViewGroup getHostView() {
public void resetViewStates() {
int numChildren = mHostView.getChildCount();
for (int i = 0; i < numChildren; i++) {
- View child = mHostView.getChildAt(i);
+ ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
ViewState viewState = mStateMap.get(child);
if (viewState == null) {
viewState = new ViewState();
mStateMap.put(child, viewState);
}
// initialize with the default values of the view
- viewState.height = child.getHeight();
+ viewState.height = child.getActualHeight();
viewState.alpha = 1;
viewState.gone = child.getVisibility() == View.GONE;
}
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
for (int i = 0; i < numChildren; i++) {
- View child = mHostView.getChildAt(i);
+ ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
ViewState state = mStateMap.get(child);
if (state == null) {
Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
float alpha = child.getAlpha();
float yTranslation = child.getTranslationY();
float zTranslation = child.getTranslationZ();
- int height = child.getHeight();
+ int height = child.getActualHeight();
float newAlpha = state.alpha;
float newYTranslation = state.yTranslation;
float newZTranslation = state.zTranslation;
// apply height
if (height != newHeight) {
- applyNewHeight(child, newHeight);
+ child.setActualHeight(newHeight);
}
// apply clipping and shadow
float newNotificationEnd = newYTranslation + newHeight;
- updateChildClippingAndShadow(child, newHeight,
- newNotificationEnd - (previousNotificationEnd - mChildDividerHeight),
- newHeight - (previousNotificationStart - newYTranslation));
+ updateChildClippingAndBackground(child, newHeight,
+ newNotificationEnd - (previousNotificationEnd),
+ (int) (newHeight - (previousNotificationStart - newYTranslation)));
previousNotificationStart = newYTranslation;
previousNotificationEnd = newNotificationEnd;
* @param child the view to update
* @param realHeight the currently applied height of the view
* @param clipHeight the desired clip height, the rest of the view will be clipped from the top
- * @param shadowHeight the desired height of the shadow, the shadow ends on the bottom
+ * @param backgroundHeight the desired background height. The shadows of the view will be
+ * based on this height and the content will be clipped from the top
*/
- private void updateChildClippingAndShadow(View child, int realHeight, float clipHeight,
- float shadowHeight) {
- if (realHeight > shadowHeight) {
- updateChildOutline(child, realHeight, shadowHeight);
- } else {
- updateChildOutline(child, realHeight, realHeight);
- }
+ private void updateChildClippingAndBackground(ExpandableView child, int realHeight,
+ float clipHeight, int backgroundHeight) {
if (realHeight > clipHeight) {
updateChildClip(child, realHeight, clipHeight);
} else {
child.setClipBounds(null);
}
+ if (realHeight > backgroundHeight) {
+ child.setClipTopAmount(realHeight - backgroundHeight);
+ } else {
+ child.setClipTopAmount(0);
+ }
}
/**
child.setClipBounds(mClipRect);
}
- /**
- * Updates the outline of a view
- *
- * @param child the view to update
- * @param height the currently applied height of the view
- * @param outlineHeight the desired height of the outline, the outline ends on the bottom
- */
- private void updateChildOutline(View child, int height,
- float outlineHeight) {
- int shadowInset = (int) (height - outlineHeight);
- getOutlineForSize(child.getLeft(),
- child.getTop() + shadowInset,
- child.getWidth(),
- child.getHeight() - shadowInset,
- mChildOutline);
- child.setOutline(mChildOutline);
- }
-
- private void getOutlineForSize(int leftInset, int topInset, int width, int height,
- Outline result) {
- result.setRoundRect(leftInset, topInset, leftInset + width, topInset + height,
- mBackgroundRoundedRectCornerRadius);
- }
-
- private void applyNewHeight(View child, int newHeight) {
- ViewGroup.LayoutParams lp = child.getLayoutParams();
- lp.height = newHeight;
- child.setLayoutParams(lp);
- }
-
-
public static class ViewState {
// These are flags such that we can create masks for filtering.