bubbles, Bubble selected) {
StringBuilder sb = new StringBuilder();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index e3983c5b2d92d..a6f759f3e0b90 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -243,7 +243,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mPointerView.setVisibility(INVISIBLE);
mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
- R.dimen.bubble_settings_size);
+ R.dimen.bubble_manage_button_height);
mSettingsIcon = findViewById(R.id.settings_button);
mSettingsIcon.setOnClickListener(this);
@@ -530,6 +530,16 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mPointerView.setVisibility(VISIBLE);
}
+ /**
+ * Position of the manage button displayed in the expanded view. Used for placing user
+ * education about the manage button.
+ */
+ public Rect getManageButtonLocationOnScreen() {
+ mTempLoc = mSettingsIcon.getLocationOnScreen();
+ return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(),
+ mTempLoc[1] + mSettingsIcon.getHeight());
+ }
+
/**
* Removes and releases an ActivityView if one was previously created for this bubble.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
new file mode 100644
index 0000000000000..f4d64322c7ff6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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.bubbles;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.util.ContrastColorUtil;
+import com.android.systemui.R;
+import com.android.systemui.recents.TriangleShape;
+
+/**
+ * Educational view to highlight the manage button that allows a user to configure the settings
+ * for the bubble. Shown only the first time a user expands a bubble.
+ */
+public class BubbleManageEducationView extends LinearLayout {
+
+ private View mPointerView;
+ private View mManageView;
+
+ public BubbleManageEducationView(Context context) {
+ this(context, null);
+ }
+
+ public BubbleManageEducationView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mManageView = findViewById(R.id.manage_education_view);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.colorAccent,
+ android.R.attr.textColorPrimaryInverse});
+ final int bgColor = ta.getColor(0, Color.BLACK);
+ int textColor = ta.getColor(1, Color.WHITE);
+ ta.recycle();
+
+ textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
+ ((TextView) findViewById(R.id.user_education_description)).setTextColor(textColor);
+
+ final Resources res = getResources();
+ final int pointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
+ final int pointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
+
+ ShapeDrawable triangleShape =
+ new ShapeDrawable(TriangleShape.create(
+ pointerWidth, pointerHeight, false /* isPointingUp */));
+ triangleShape.setTint(bgColor);
+
+ mPointerView = findViewById(R.id.user_education_pointer);
+ mPointerView.setBackground(triangleShape);
+ }
+
+ /**
+ * Specifies the x value this pointer should point to.
+ */
+ public void setPointerPosition(int x) {
+ mPointerView.setTranslationX(x - (mPointerView.getWidth() / 2));
+ }
+
+ /**
+ * Specifies the position for the manage view.
+ */
+ public void setManageViewPosition(int x, int y) {
+ mManageView.setTranslationX(x);
+ mManageView.setTranslationY(y);
+ }
+
+ /**
+ * @return the height of the view that shows the educational text and pointer.
+ */
+ public int getManageViewHeight() {
+ return mManageView.getHeight();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index cff62c1ba87b4..6647069f20337 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,9 +19,13 @@ package com.android.systemui.bubbles;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION;
+import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION;
import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_DEFAULT;
import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_SUPPRESSED_FOR_FLYOUT;
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -33,6 +37,8 @@ import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
@@ -57,6 +63,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
+import android.widget.TextView;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -66,7 +73,9 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
@@ -88,6 +97,10 @@ import java.util.List;
public class BubbleStackView extends FrameLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
+ /** Animation durations for bubble stack user education views. **/
+ private static final int ANIMATE_STACK_USER_EDUCATION_DURATION = 200;
+ private static final int ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT = 40;
+
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
@@ -171,6 +184,12 @@ public class BubbleStackView extends FrameLayout {
* previous one animates out.
*/
private Runnable mAfterFlyoutHidden;
+ /**
+ * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
+ * once it collapses.
+ */
+ @Nullable
+ private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
private OnLayoutChangeListener mOrientationChangedListener;
@@ -319,6 +338,14 @@ public class BubbleStackView extends FrameLayout {
private BubbleOverflow mBubbleOverflow;
+ private boolean mShouldShowUserEducation;
+ private boolean mAnimatingEducationAway;
+ private View mUserEducationView;
+
+ private boolean mShouldShowManageEducation;
+ private BubbleManageEducationView mManageEducationView;
+ private boolean mAnimatingManageEducationAway;
+
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator) {
@@ -361,6 +388,8 @@ public class BubbleStackView extends FrameLayout {
mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
+ setUpUserEducation();
+
mBubbleContainer = new PhysicsAnimationLayout(context);
mBubbleContainer.setActiveController(mStackAnimationController);
mBubbleContainer.setElevation(elevation);
@@ -500,10 +529,50 @@ public class BubbleStackView extends FrameLayout {
});
}
- void showExpandedViewContents(int displayId) {
- if (mExpandedBubble != null
- && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
- mExpandedBubble.setContentVisibility(true);
+ private void setUpUserEducation() {
+ if (mUserEducationView != null) {
+ removeView(mUserEducationView);
+ }
+ mShouldShowUserEducation = shouldShowBubblesEducation();
+ if (DEBUG_USER_EDUCATION) {
+ Log.d(TAG, "shouldShowUserEducation: " + mShouldShowUserEducation);
+ }
+ if (mShouldShowUserEducation) {
+ mUserEducationView = mInflater.inflate(R.layout.bubble_stack_user_education, this,
+ false /* attachToRoot */);
+ mUserEducationView.setVisibility(GONE);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.colorAccent,
+ android.R.attr.textColorPrimaryInverse});
+ final int bgColor = ta.getColor(0, Color.BLACK);
+ int textColor = ta.getColor(1, Color.WHITE);
+ ta.recycle();
+ textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
+
+ TextView title = mUserEducationView.findViewById(R.id.user_education_title);
+ TextView description = mUserEducationView.findViewById(R.id.user_education_description);
+ title.setTextColor(textColor);
+ description.setTextColor(textColor);
+
+ addView(mUserEducationView);
+ }
+
+ if (mManageEducationView != null) {
+ removeView(mManageEducationView);
+ }
+ mShouldShowManageEducation = shouldShowManageEducation();
+ if (DEBUG_USER_EDUCATION) {
+ Log.d(TAG, "shouldShowManageEducation: " + mShouldShowManageEducation);
+ }
+ if (mShouldShowManageEducation) {
+ mManageEducationView = (BubbleManageEducationView)
+ mInflater.inflate(R.layout.bubbles_manage_button_education, this,
+ false /* attachToRoot */);
+ mManageEducationView.setVisibility(GONE);
+ mManageEducationView.setElevation(mBubbleElevation);
+
+ addView(mManageEducationView);
}
}
@@ -539,6 +608,7 @@ public class BubbleStackView extends FrameLayout {
public void onThemeChanged() {
setUpFlyout();
setUpOverflow();
+ setUpUserEducation();
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -731,7 +801,7 @@ public class BubbleStackView extends FrameLayout {
Bubble getExpandedBubble() {
if (mExpandedBubble == null
|| (mExpandedBubble.getIconView() == mBubbleOverflow.getBtn()
- && mExpandedBubble.getKey() == BubbleOverflow.KEY)) {
+ && BubbleOverflow.KEY.equals(mExpandedBubble.getKey()))) {
return null;
}
return (Bubble) mExpandedBubble;
@@ -743,6 +813,12 @@ public class BubbleStackView extends FrameLayout {
Log.d(TAG, "addBubble: " + bubble);
}
+ if (getBubbleCount() == 0 && mShouldShowUserEducation) {
+ // Override the default stack position if we're showing user education.
+ mStackAnimationController.setStackPosition(
+ mStackAnimationController.getDefaultStartPosition());
+ }
+
if (getBubbleCount() == 0) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
@@ -880,6 +956,109 @@ public class BubbleStackView extends FrameLayout {
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
+ /**
+ * If necessary, shows the user education view for the bubble stack. This appears the first
+ * time a user taps on a bubble.
+ *
+ * @return true if user education was shown, false otherwise.
+ */
+ private boolean maybeShowStackUserEducation() {
+ if (mShouldShowUserEducation && mUserEducationView.getVisibility() != VISIBLE) {
+ Bubble b = mBubbleData.getSelectedBubble();
+ TextView description = mUserEducationView.findViewById(R.id.user_education_description);
+ description.setText(mContext.getString(
+ R.string.bubbles_user_education_description, b.getAppName()));
+
+ mUserEducationView.setAlpha(0);
+ mUserEducationView.setVisibility(VISIBLE);
+ // Post so we have height of mUserEducationView
+ mUserEducationView.post(() -> {
+ final int viewHeight = mUserEducationView.getHeight();
+ PointF stackPosition = mStackAnimationController.getDefaultStartPosition();
+ final float translationY = stackPosition.y + (mBubbleSize / 2) - (viewHeight / 2);
+ mUserEducationView.setTranslationY(translationY);
+ mUserEducationView.animate()
+ .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .setInterpolator(FAST_OUT_SLOW_IN)
+ .alpha(1);
+ });
+ Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_EDUCATION, true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * If necessary, hides the user education view for the bubble stack.
+ *
+ * @param fromExpansion if true this indicates the hide is happening due to the bubble being
+ * expanded, false if due to a touch outside of the bubble stack.
+ */
+ void hideStackUserEducation(boolean fromExpansion) {
+ if (mShouldShowUserEducation
+ && mUserEducationView.getVisibility() == VISIBLE
+ && !mAnimatingEducationAway) {
+ mAnimatingEducationAway = true;
+ mUserEducationView.animate()
+ .alpha(0)
+ .setDuration(fromExpansion
+ ? ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT
+ : ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .withEndAction(() -> {
+ mAnimatingEducationAway = false;
+ mShouldShowUserEducation = shouldShowBubblesEducation();
+ mUserEducationView.setVisibility(GONE);
+ });
+ }
+ }
+
+ /**
+ * If necessary, toggles the user education view for the manage button. This is shown when the
+ * bubble stack is expanded for the first time.
+ *
+ * @param show whether the user education view should show or not.
+ */
+ void maybeShowManageEducation(boolean show) {
+ if (mManageEducationView == null) {
+ return;
+ }
+ if (show
+ && mShouldShowManageEducation
+ && mManageEducationView.getVisibility() != VISIBLE
+ && mIsExpanded) {
+ mManageEducationView.setAlpha(0);
+ mManageEducationView.setVisibility(VISIBLE);
+ mManageEducationView.post(() -> {
+ final Rect position =
+ mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen();
+ final int viewHeight = mManageEducationView.getManageViewHeight();
+ final int inset = getResources().getDimensionPixelSize(
+ R.dimen.bubbles_manage_education_top_inset);
+ mManageEducationView.bringToFront();
+ mManageEducationView.setManageViewPosition(position.left,
+ position.top - viewHeight + inset);
+ mManageEducationView.setPointerPosition(position.centerX() - position.left);
+ mManageEducationView.animate()
+ .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
+ });
+ Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, true);
+ } else if (!show
+ && mManageEducationView.getVisibility() == VISIBLE
+ && !mAnimatingManageEducationAway) {
+ mManageEducationView.animate()
+ .alpha(0)
+ .setDuration(mIsExpansionAnimating
+ ? ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT
+ : ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .withEndAction(() -> {
+ mAnimatingManageEducationAway = false;
+ mShouldShowManageEducation = shouldShowManageEducation();
+ mManageEducationView.setVisibility(GONE);
+ });
+ }
+ }
+
/**
* Dismiss the stack of bubbles.
*
@@ -923,7 +1102,17 @@ public class BubbleStackView extends FrameLayout {
return null;
} else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
return mFlyout;
+ } else if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
+ View bubbleChild = mBubbleContainer.getChildAt(0);
+ if (isIntersecting(bubbleChild, x, y)) {
+ return this;
+ } else if (isIntersecting(mUserEducationView, x, y)) {
+ return mUserEducationView;
+ } else {
+ return null;
+ }
}
+
// If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
return this;
}
@@ -932,22 +1121,6 @@ public class BubbleStackView extends FrameLayout {
return mFlyout;
}
- /**
- * Collapses the stack of bubbles.
- *
- * Must be called from the main thread.
- *
- * @deprecated use {@link #setExpanded(boolean)} and {@link #setSelectedBubble(Bubble)}
- */
- @Deprecated
- @MainThread
- void collapseStack() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "collapseStack()");
- }
- mBubbleData.setExpanded(false);
- }
-
/**
* @deprecated use {@link #setExpanded(boolean)} and {@link #setSelectedBubble(Bubble)}
*/
@@ -957,25 +1130,16 @@ public class BubbleStackView extends FrameLayout {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "collapseStack(endRunnable)");
}
- collapseStack();
+ mBubbleData.setExpanded(false);
// TODO - use the runnable at end of animation
endRunnable.run();
}
- /**
- * Expands the stack of bubbles.
- *
- * Must be called from the main thread.
- *
- * @deprecated use {@link #setExpanded(boolean)} and {@link #setSelectedBubble(Bubble)}
- */
- @Deprecated
- @MainThread
- void expandStack() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "expandStack()");
+ void showExpandedViewContents(int displayId) {
+ if (mExpandedBubble != null
+ && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
+ mExpandedBubble.setContentVisibility(true);
}
- mBubbleData.setExpanded(true);
}
private void beforeExpandedViewAnimation() {
@@ -995,11 +1159,12 @@ public class BubbleStackView extends FrameLayout {
mIsExpanded = false;
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
+ maybeShowManageEducation(false);
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(this.getBubblesOnScreen(),
- this.getExpandedBubble()));
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
+ getExpandedBubble()));
}
updateOverflowBtnVisibility(/* apply */ false);
mBubbleContainer.cancelAllAnimations();
@@ -1021,6 +1186,7 @@ public class BubbleStackView extends FrameLayout {
private void animateExpansion() {
mIsExpanded = true;
+ hideStackUserEducation(true /* fromExpansion */);
beforeExpandedViewAnimation();
mBubbleContainer.setActiveController(mExpandedAnimationController);
@@ -1028,6 +1194,7 @@ public class BubbleStackView extends FrameLayout {
mExpandedAnimationController.expandFromStack(() -> {
updatePointerPosition();
afterExpandedViewAnimation();
+ maybeShowManageEducation(true);
} /* after */);
mExpandedViewContainer.setTranslationX(getCollapsedX());
@@ -1074,11 +1241,19 @@ public class BubbleStackView extends FrameLayout {
}
}
+ /** Called when the collapsed stack is tapped on. */
+ void onStackTapped() {
+ if (!maybeShowStackUserEducation()) {
+ mBubbleData.setExpanded(true);
+ }
+ }
+
/** Called when a drag operation on an individual bubble has started. */
public void onBubbleDragStart(View bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
}
+ maybeShowManageEducation(false);
mExpandedAnimationController.prepareForBubbleDrag(bubble);
}
@@ -1129,6 +1304,7 @@ public class BubbleStackView extends FrameLayout {
return;
}
+ hideStackUserEducation(false /* fromExpansion */);
springInDismissTarget();
mStackAnimationController.moveStackFromTouch(x, y);
}
@@ -1191,14 +1367,13 @@ public class BubbleStackView extends FrameLayout {
mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
}
- /**
- * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
- * once it collapses.
- */
- @Nullable private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
-
void onFlyoutTapped() {
- mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+ if (maybeShowStackUserEducation()) {
+ // If we're showing user education, don't open the bubble show the education first
+ mBubbleToExpandAfterFlyoutCollapse = null;
+ } else {
+ mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+ }
mFlyout.removeCallbacks(mHideFlyout);
mHideFlyout.run();
@@ -1221,6 +1396,8 @@ public class BubbleStackView extends FrameLayout {
mFlyout.removeCallbacks(mHideFlyout);
animateFlyoutCollapsed(shouldDismiss, velX);
+
+ maybeShowStackUserEducation();
}
/**
@@ -1432,6 +1609,7 @@ public class BubbleStackView extends FrameLayout {
if (flyoutMessage == null
|| flyoutMessage.message == null
|| !bubble.showFlyout()
+ || (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE)
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
@@ -1517,7 +1695,12 @@ public class BubbleStackView extends FrameLayout {
@Override
public void getBoundsOnScreen(Rect outRect) {
- // If the bubble menu is open, the entire screen should capture touch events.
+ if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
+ // When user education shows then capture all touches
+ outRect.set(0, 0, getWidth(), getHeight());
+ return;
+ }
+
if (!mIsExpanded) {
if (getBubbleCount() > 0) {
mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
@@ -1704,6 +1887,18 @@ public class BubbleStackView extends FrameLayout {
return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
}
+ /** Whether the educational view should appear for bubbles. **/
+ private boolean shouldShowBubblesEducation() {
+ return BubbleDebugConfig.forceShowUserEducation(getContext())
+ || !Prefs.getBoolean(getContext(), HAS_SEEN_BUBBLES_EDUCATION, false);
+ }
+
+ /** Whether the educational view should appear for the expanded view "manage" button. **/
+ private boolean shouldShowManageEducation() {
+ return BubbleDebugConfig.forceShowUserEducation(getContext())
+ || !Prefs.getBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false);
+ }
+
/** For debugging only */
List getBubblesOnScreen() {
List bubbles = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 5e3e747ad2c08..46d1e0dfbab75 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -24,7 +24,6 @@ import android.view.View;
import android.view.ViewConfiguration;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
/**
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
@@ -92,6 +91,7 @@ class BubbleTouchHandler implements View.OnTouchListener {
// anything, collapse the stack.
if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
mBubbleData.setExpanded(false);
+ mStack.hideStackUserEducation(false /* fromExpansion */);
resetForNextGesture();
return false;
}
@@ -102,6 +102,7 @@ class BubbleTouchHandler implements View.OnTouchListener {
// Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
// of expanded view).
+ mStack.maybeShowManageEducation(false);
resetForNextGesture();
return false;
}
@@ -217,9 +218,8 @@ class BubbleTouchHandler implements View.OnTouchListener {
}
} else if (mTouchedView == mStack.getExpandedBubbleView()) {
mBubbleData.setExpanded(false);
- } else if (isStack || isFlyout) {
- // Toggle expansion
- mBubbleData.setExpanded(!mBubbleData.isExpanded());
+ } else if (isStack) {
+ mStack.onStackTapped();
} else {
final String key = ((BadgedImageView) mTouchedView).getKey();
if (key == BubbleOverflow.KEY) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 245d4afbf015a..f22c8faad95c1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -879,9 +879,10 @@ public class StackAnimationController extends
}
/** Moves the stack to a position instantly, with no animation. */
- private void setStackPosition(PointF pos) {
+ public void setStackPosition(PointF pos) {
Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
mStackPosition.set(pos.x, pos.y);
+ mRestingStackPosition = mStackPosition;
// If we're not the active controller, we don't want to physically move the bubble views.
if (isActiveController()) {
@@ -902,10 +903,10 @@ public class StackAnimationController extends
}
}
- /** Returns the default stack position, which is on the top right. */
- private PointF getDefaultStartPosition() {
+ /** Returns the default stack position, which is on the top left. */
+ public PointF getDefaultStartPosition() {
return new PointF(
- getAllowableStackPositionRegion().right,
+ getAllowableStackPositionRegion().left,
getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
}