diff --git a/packages/SystemUI/res-keyguard/drawable/bubble_manage_user_education_bg.xml b/packages/SystemUI/res-keyguard/drawable/bubble_manage_user_education_bg.xml new file mode 100644 index 0000000000000..64db25b65a0b1 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/bubble_manage_user_education_bg.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml b/packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml new file mode 100644 index 0000000000000..4b9219cd6194b --- /dev/null +++ b/packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_stack_user_education.xml b/packages/SystemUI/res/layout/bubble_stack_user_education.xml new file mode 100644 index 0000000000000..81b28e612993c --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_stack_user_education.xml @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml new file mode 100644 index 0000000000000..0cabc3245152c --- /dev/null +++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 12b925406864e..aefe4a20a4962 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1159,7 +1159,7 @@ 16dp - 48dp + 48dp 100dp @@ -1175,16 +1175,22 @@ 9dp - 96dp + 120dp 8dp - - 120dp 40dp 20dp + + 160dp + + 10dp + + 72dp + 10sp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ff28b4d289e86..496ab439bdc02 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2586,6 +2586,12 @@ Move bottom right Dismiss + + Keep chats up front + + New chats from %1$s will appear as bubbles. Tap a bubble to open it. Drag to move it.\n\nTap the bubble + + Tap Manage to turn off bubbles from this app System navigation updated. To make changes, go to Settings. diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index f0a82c519d48d..5e6589f76c132 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -57,7 +57,9 @@ public final class Prefs { Key.SEEN_RINGER_GUIDANCE_COUNT, Key.QS_HAS_TURNED_OFF_MOBILE_DATA, Key.TOUCHED_RINGER_TOGGLE, - Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP + Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, + Key.HAS_SEEN_BUBBLES_EDUCATION, + Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION }) public @interface Key { @Deprecated @@ -103,6 +105,8 @@ public final class Prefs { String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData"; String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle"; String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip"; + String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding"; + String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java index 3190662216009..e800011981a96 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java @@ -16,6 +16,9 @@ package com.android.systemui.bubbles; +import android.content.Context; +import android.provider.Settings; + import java.util.List; /** @@ -41,6 +44,20 @@ public class BubbleDebugConfig { static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false; static final boolean DEBUG_EXPERIMENTS = true; static final boolean DEBUG_OVERFLOW = false; + static final boolean DEBUG_USER_EDUCATION = false; + + private static final boolean FORCE_SHOW_USER_EDUCATION = false; + private static final String FORCE_SHOW_USER_EDUCATION_SETTING = + "force_show_bubbles_user_education"; + + /** + * @return whether we should force show user education for bubbles. Used for debugging & demos. + */ + static boolean forceShowUserEducation(Context context) { + boolean forceShow = Settings.Secure.getInt(context.getContentResolver(), + FORCE_SHOW_USER_EDUCATION_SETTING, 0) != 0; + return FORCE_SHOW_USER_EDUCATION || forceShow; + } static String formatBubblesString(List 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 df8e3941178ef..1ce04df02381b 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); }