Education view for collapsed stack; fix rtl text

- new view for stack education (similar to ManageEducationView)
- lazy init both stack and manage menu edu

fix rtl text
- set textAlignment to viewStart
- set TextView gravity to start in xml

Bug: 161939484

Test: force stack/manage edu to show, press both buttons, force rtl,
press both buttons again, change theme, change orientation, keep using
bubbles normally, no regressions

Test: don't force edu views to show; let them show once; reboot phone,
use bubbles normally (without edu views added to stack), no NPEs

Change-Id: Ia67001aef40f76d7161cc263f961375738f97939
This commit is contained in:
Lyn Han
2020-07-23 23:36:40 -05:00
parent ecfbd9b3aa
commit 9813fab61f
5 changed files with 354 additions and 302 deletions

View File

@@ -15,8 +15,8 @@
~ limitations under the License.
-->
<LinearLayout
android:id="@+id/stack_education_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/user_education_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingTop="48dp"
@@ -25,26 +25,29 @@
android:paddingEnd="16dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg">
android:background="@drawable/bubble_stack_user_education_bg"
>
<TextView
android:id="@+id/user_education_title"
android:id="@+id/stack_education_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
android:maxLines="2"
android:ellipsize="end"
android:gravity="start"
android:textAlignment="viewStart"
android:text="@string/bubbles_user_education_title"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
<TextView
android:id="@+id/user_education_description"
android:id="@+id/stack_education_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start"
android:textAlignment="viewStart"
android:text="@string/bubbles_user_education_description"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
</LinearLayout>

View File

@@ -14,77 +14,77 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<com.android.systemui.bubbles.ManageEducationView
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/manage_education_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:paddingTop="28dp"
android:paddingBottom="16dp"
android:paddingStart="@dimen/bubble_expanded_view_padding"
android:paddingEnd="48dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
<LinearLayout
<TextView
android:id="@+id/user_education_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/manage_education_view"
android:clickable="true"
android:paddingTop="28dp"
android:paddingStart="16dp"
android:paddingBottom="16dp"
android:paddingStart="@dimen/bubble_expanded_view_padding"
android:paddingEnd="48dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
android:maxLines="2"
android:ellipsize="end"
android:gravity="start"
android:textAlignment="viewStart"
android:text="@string/bubbles_user_education_manage_title"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
<TextView
android:id="@+id/user_education_title"
<TextView
android:id="@+id/user_education_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="24dp"
android:text="@string/bubbles_user_education_manage"
android:maxLines="2"
android:ellipsize="end"
android:gravity="start"
android:textAlignment="viewStart"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/button_layout"
android:orientation="horizontal" >
<com.android.systemui.statusbar.AlphaOptimizedButton
style="@android:style/Widget.Material.Button.Borderless"
android:id="@+id/manage"
android:layout_gravity="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="16dp"
android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
android:maxLines="2"
android:ellipsize="end"
android:text="@string/bubbles_user_education_manage_title"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
android:focusable="true"
android:clickable="false"
android:text="@string/manage_bubbles_text"
android:textColor="?attr/wallpaperTextColor"
/>
<TextView
android:id="@+id/user_education_description"
<com.android.systemui.statusbar.AlphaOptimizedButton
style="@android:style/Widget.Material.Button.Borderless"
android:id="@+id/got_it"
android:layout_gravity="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="24dp"
android:text="@string/bubbles_user_education_manage"
android:maxLines="2"
android:ellipsize="end"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/button_layout"
android:orientation="horizontal" >
<com.android.systemui.statusbar.AlphaOptimizedButton
style="@android:style/Widget.Material.Button.Borderless"
android:id="@+id/manage"
android:layout_gravity="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="false"
android:text="@string/manage_bubbles_text"
android:textColor="?attr/wallpaperTextColor"
/>
<com.android.systemui.statusbar.AlphaOptimizedButton
style="@android:style/Widget.Material.Button.Borderless"
android:id="@+id/got_it"
android:layout_gravity="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:text="@string/bubbles_user_education_got_it"
android:textColor="?attr/wallpaperTextColor"
/>
</LinearLayout>
android:focusable="true"
android:text="@string/bubbles_user_education_got_it"
android:textColor="?attr/wallpaperTextColor"
/>
</LinearLayout>
</com.android.systemui.bubbles.ManageEducationView>
</LinearLayout>

View File

@@ -19,11 +19,7 @@ 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.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;
@@ -38,7 +34,6 @@ import android.content.Intent;
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.Outline;
@@ -54,7 +49,6 @@ import android.provider.Settings;
import android.util.Log;
import android.view.Choreographer;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -70,10 +64,8 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -82,7 +74,6 @@ 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.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -115,10 +106,6 @@ public class BubbleStackView extends FrameLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
/** Animation durations for bubble stack user education views. **/
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;
@@ -556,7 +543,7 @@ public class BubbleStackView extends FrameLayout
// Otherwise, we either tapped the stack (which means we're collapsed
// and should expand) or the currently selected bubble (we're expanded
// and should collapse).
if (!maybeShowStackUserEducation()) {
if (!maybeShowStackEdu()) {
mBubbleData.setExpanded(!mBubbleData.isExpanded());
}
}
@@ -582,7 +569,9 @@ public class BubbleStackView extends FrameLayout
}
if (mBubbleData.isExpanded()) {
maybeShowManageEducation(false /* show */);
if (mManageEduView != null) {
mManageEduView.hide(false /* show */);
}
// If we're expanded, tell the animation controller to prepare to drag this bubble,
// dispatching to the individual bubble magnet listener.
@@ -637,7 +626,9 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
hideStackUserEducation(false /* fromExpansion */);
if (mStackEduView != null) {
mStackEduView.hide(false /* fromExpansion */);
}
mStackAnimationController.moveStackFromTouch(
viewInitialX + dx, viewInitialY + dy);
}
@@ -684,7 +675,7 @@ public class BubbleStackView extends FrameLayout
private OnClickListener mFlyoutClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
if (maybeShowStackUserEducation()) {
if (maybeShowStackEdu()) {
// If we're showing user education, don't open the bubble show the education first
mBubbleToExpandAfterFlyoutCollapse = null;
} else {
@@ -728,7 +719,7 @@ public class BubbleStackView extends FrameLayout
mFlyout.removeCallbacks(mHideFlyout);
animateFlyoutCollapsed(shouldDismiss, velX);
maybeShowStackUserEducation();
maybeShowStackEdu();
}
};
@@ -737,14 +728,8 @@ public class BubbleStackView extends FrameLayout
@Nullable
private BubbleOverflow mBubbleOverflow;
private boolean mShouldShowUserEducation;
private boolean mAnimatingEducationAway;
private View mUserEducationView;
private boolean mShouldShowManageEducation;
private ManageEducationView mManageEducationView;
private boolean mAnimatingManageEducationAway;
private StackEducationView mStackEduView;
private ManageEducationView mManageEduView;
private ViewGroup mManageMenu;
private ImageView mManageSettingsIcon;
@@ -805,8 +790,6 @@ public class BubbleStackView extends FrameLayout
onBubbleAnimatedOut);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
setUpUserEducation();
// Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
// is centered. It greatly simplifies translation positioning/animations. Views that will
// actually lay out differently in RTL, such as the flyout and expanded view, will set their
@@ -819,6 +802,8 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.setClipChildren(false);
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
updateUserEdu();
mExpandedViewContainer = new FrameLayout(context);
mExpandedViewContainer.setElevation(elevation);
mExpandedViewContainer.setClipChildren(false);
@@ -1092,48 +1077,66 @@ public class BubbleStackView extends FrameLayout
addView(mManageMenu);
}
private void setUpUserEducation() {
if (mUserEducationView != null) {
removeView(mUserEducationView);
/**
* Whether the educational view should show for the expanded view "manage" menu.
*/
private boolean shouldShowManageEdu() {
final boolean seen = Prefs.getBoolean(mContext,
Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false /* default */);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null;
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show manage edu: " + shouldShow);
}
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);
return shouldShow;
}
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);
updateUserEducationForLayoutDirection();
addView(mUserEducationView);
private void maybeShowManageEdu() {
if (!shouldShowManageEdu()) {
return;
}
if (mManageEduView == null) {
mManageEduView = new ManageEducationView(mContext);
addView(mManageEduView);
}
mManageEduView.show(mExpandedBubble.getExpandedView(), mTempRect);
}
if (mManageEducationView != null) {
removeView(mManageEducationView);
/**
* Whether education view should show for the collapsed stack.
*/
private boolean shouldShowStackEdu() {
final boolean seen = Prefs.getBoolean(getContext(),
Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION, false /* default */);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show stack edu: " + shouldShow);
}
mShouldShowManageEducation = shouldShowManageEducation();
if (DEBUG_USER_EDUCATION) {
Log.d(TAG, "shouldShowManageEducation: " + mShouldShowManageEducation);
return shouldShow;
}
/**
* @return true if education view for collapsed stack should show and was not showing before.
*/
private boolean maybeShowStackEdu() {
if (!shouldShowStackEdu()) {
return false;
}
if (mShouldShowManageEducation) {
mManageEducationView = (ManageEducationView)
mInflater.inflate(R.layout.bubbles_manage_button_education, this /* root */,
false /* attachToRoot */);
addView(mManageEducationView);
if (mStackEduView == null) {
mStackEduView = new StackEducationView(mContext);
addView(mStackEduView);
}
return mStackEduView.show(mStackAnimationController.getStartPosition());
}
private void updateUserEdu() {
maybeShowStackEdu();
if (mManageEduView != null) {
mManageEduView.invalidate();
}
maybeShowManageEdu();
if (mStackEduView != null) {
mStackEduView.invalidate();
}
}
@@ -1164,9 +1167,9 @@ public class BubbleStackView extends FrameLayout
*/
public void onThemeChanged() {
setUpFlyout();
setUpUserEducation();
setUpManageMenu();
updateOverflow();
updateUserEdu();
updateExpandedViewTheme();
}
@@ -1197,12 +1200,11 @@ public class BubbleStackView extends FrameLayout
public void onLayoutDirectionChanged(int direction) {
mManageMenu.setLayoutDirection(direction);
mFlyout.setLayoutDirection(direction);
if (mUserEducationView != null) {
mUserEducationView.setLayoutDirection(direction);
updateUserEducationForLayoutDirection();
if (mStackEduView != null) {
mStackEduView.setLayoutDirection(direction);
}
if (mManageEducationView != null) {
mManageEducationView.setLayoutDirection(direction);
if (mManageEduView != null) {
mManageEduView.setLayoutDirection(direction);
}
updateExpandedViewDirection(direction);
}
@@ -1446,7 +1448,7 @@ public class BubbleStackView extends FrameLayout
Log.d(TAG, "addBubble: " + bubble);
}
if (getBubbleCount() == 0 && mShouldShowUserEducation) {
if (getBubbleCount() == 0 && shouldShowStackEdu()) {
// Override the default stack position if we're showing user education.
mStackAnimationController.setStackPosition(
mStackAnimationController.getStartPosition());
@@ -1649,115 +1651,6 @@ 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) {
mUserEducationView.setAlpha(0);
mUserEducationView.setVisibility(VISIBLE);
updateUserEducationForLayoutDirection();
// Post so we have height of mUserEducationView
mUserEducationView.post(() -> {
final int viewHeight = mUserEducationView.getHeight();
PointF stackPosition = mStackAnimationController.getStartPosition();
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;
}
private void updateUserEducationForLayoutDirection() {
if (mUserEducationView == null) {
return;
}
LinearLayout textLayout = mUserEducationView.findViewById(R.id.user_education_view);
TextView title = mUserEducationView.findViewById(R.id.user_education_title);
TextView description = mUserEducationView.findViewById(R.id.user_education_description);
boolean isLtr =
getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR;
if (isLtr) {
mUserEducationView.setLayoutDirection(LAYOUT_DIRECTION_LTR);
textLayout.setBackgroundResource(R.drawable.bubble_stack_user_education_bg);
title.setGravity(Gravity.LEFT);
description.setGravity(Gravity.LEFT);
} else {
mUserEducationView.setLayoutDirection(LAYOUT_DIRECTION_RTL);
textLayout.setBackgroundResource(R.drawable.bubble_stack_user_education_bg_rtl);
title.setGravity(Gravity.RIGHT);
description.setGravity(Gravity.RIGHT);
}
}
/**
* 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
&& mExpandedBubble.getExpandedView() != null) {
mManageEducationView.show(mExpandedBubble.getExpandedView(), mTempRect,
() -> maybeShowManageEducation(false) /* run on click */);
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);
});
}
}
void showExpandedViewContents(int displayId) {
if (mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null
@@ -1791,7 +1684,9 @@ public class BubbleStackView extends FrameLayout
cancelDelayedExpandCollapseSwitchAnimations();
mIsExpanded = true;
hideStackUserEducation(true /* fromExpansion */);
if (mStackEduView != null) {
mStackEduView.hide(true /* fromExpansion */);
}
beforeExpandedViewAnimation();
mBubbleContainer.setActiveController(mExpandedAnimationController);
@@ -1799,7 +1694,9 @@ public class BubbleStackView extends FrameLayout
updatePointerPosition();
mExpandedAnimationController.expandFromStack(() -> {
afterExpandedViewAnimation();
maybeShowManageEducation(true);
if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
} /* after */);
mExpandedViewContainer.setTranslationX(0);
@@ -1936,7 +1833,9 @@ public class BubbleStackView extends FrameLayout
.withEndActions(() -> {
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
maybeShowManageEducation(false);
if (mManageEduView != null) {
mManageEduView.hide(false /* fromExpansion */);
}
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "animateCollapse");
@@ -2104,8 +2003,8 @@ public class BubbleStackView extends FrameLayout
// from any location.
if (!mIsExpanded
|| mShowingManage
|| (mManageEducationView != null
&& mManageEducationView.getVisibility() == VISIBLE)) {
|| (mManageEduView != null
&& mManageEduView.getVisibility() == VISIBLE)) {
touchableRegion.setEmpty();
}
}
@@ -2289,7 +2188,7 @@ public class BubbleStackView extends FrameLayout
if (flyoutMessage == null
|| flyoutMessage.message == null
|| !bubble.showFlyout()
|| (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE)
|| (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE)
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
@@ -2398,7 +2297,7 @@ public class BubbleStackView extends FrameLayout
* them.
*/
public void getTouchableRegion(Rect outRect) {
if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
// When user education shows then capture all touches
outRect.set(0, 0, getWidth(), getHeight());
return;
@@ -2770,18 +2669,6 @@ 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<Bubble> getBubblesOnScreen() {
List<Bubble> bubbles = new ArrayList<>();

View File

@@ -18,52 +18,56 @@ package com.android.systemui.bubbles
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.Interpolators
import com.android.systemui.Prefs
import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION
import com.android.systemui.R
/**
* Educational view to highlight the manage button that allows a user to configure the settings
* User education 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.
*/
class ManageEducationView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
class ManageEducationView constructor(context: Context) : LinearLayout(context) {
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleManageEducationView"
else BubbleDebugConfig.TAG_BUBBLES
private val ANIMATE_DURATION : Long = 200
private val ANIMATE_DURATION_SHORT : Long = 40
private val manageView by lazy { findViewById<View>(R.id.manage_education_view) }
private val manageButton by lazy { findViewById<Button>(R.id.manage) }
private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
private val titleTextView by lazy { findViewById<TextView>(R.id.user_education_title) }
private val descTextView by lazy { findViewById<TextView>(R.id.user_education_description) }
private var isInflated = false
private var isHiding = false
init {
this.visibility = View.GONE
this.elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
this.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this);
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
// BubbleStackView forces LTR by default
// since most of Bubble UI direction depends on positioning by the user.
// This view actually lays out differently in RTL, so we set layout LOCALE here.
layoutDirection = View.LAYOUT_DIRECTION_LOCALE
}
override fun setLayoutDirection(direction: Int) {
super.setLayoutDirection(direction)
// setLayoutDirection runs before onFinishInflate
// so skip if views haven't inflated; otherwise we'll get NPEs
if (!isInflated) return
setDirection()
override fun setLayoutDirection(layoutDirection: Int) {
super.setLayoutDirection(layoutDirection)
setDrawableDirection()
}
override fun onFinishInflate() {
super.onFinishInflate()
isInflated = true
setDirection()
layoutDirection = resources.configuration.layoutDirection
setTextColor()
}
@@ -78,29 +82,35 @@ class ManageEducationView @JvmOverloads constructor(
descTextView.setTextColor(textColor)
}
fun setDirection() {
private fun setDrawableDirection() {
manageView.setBackgroundResource(
if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
R.drawable.bubble_stack_user_education_bg_rtl
else R.drawable.bubble_stack_user_education_bg)
titleTextView.gravity = Gravity.START
descTextView.gravity = Gravity.START
}
fun show(expandedView: BubbleExpandedView, rect : Rect, hideMenu: Runnable) {
/**
* 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.
*/
fun show(expandedView: BubbleExpandedView, rect : Rect) {
if (visibility == VISIBLE) return
alpha = 0f
visibility = View.VISIBLE
post {
expandedView.getManageButtonBoundsOnScreen(rect)
with(hideMenu) {
manageButton
.setOnClickListener {
expandedView.findViewById<View>(R.id.settings_button).performClick()
this.run()
}
gotItButton.setOnClickListener { this.run() }
setOnClickListener { this.run() }
}
manageButton
.setOnClickListener {
expandedView.findViewById<View>(R.id.settings_button).performClick()
hide(true /* isStackExpanding */)
}
gotItButton.setOnClickListener { hide(true /* isStackExpanding */) }
setOnClickListener { hide(true /* isStackExpanding */) }
with(manageView) {
translationX = 0f
val inset = resources.getDimensionPixelSize(
@@ -109,9 +119,27 @@ class ManageEducationView @JvmOverloads constructor(
}
bringToFront()
animate()
.setDuration(BubbleStackView.ANIMATE_STACK_USER_EDUCATION_DURATION.toLong())
.setDuration(ANIMATE_DURATION)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
setShouldShow(false)
}
fun hide(isStackExpanding: Boolean) {
if (visibility != VISIBLE || isHiding) return
animate()
.withStartAction { isHiding = true }
.alpha(0f)
.setDuration(if (isStackExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
.withEndAction {
isHiding = false
visibility = GONE
};
}
private fun setShouldShow(shouldShow: Boolean) {
Prefs.putBoolean(context, HAS_SEEN_BUBBLES_MANAGE_EDUCATION, !shouldShow)
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.graphics.Color
import android.graphics.PointF
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.Interpolators
import com.android.systemui.Prefs
import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION
import com.android.systemui.R
/**
* User education view to highlight the collapsed stack of bubbles.
* Shown only the first time a user taps the stack.
*/
class StackEducationView constructor(context: Context) : LinearLayout(context){
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
else BubbleDebugConfig.TAG_BUBBLES
private val ANIMATE_DURATION : Long = 200
private val ANIMATE_DURATION_SHORT : Long = 40
private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
private var isHiding = false
init {
LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this);
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
// BubbleStackView forces LTR by default
// since most of Bubble UI direction depends on positioning by the user.
// This view actually lays out differently in RTL, so we set layout LOCALE here.
layoutDirection = View.LAYOUT_DIRECTION_LOCALE
}
override fun setLayoutDirection(layoutDirection: Int) {
super.setLayoutDirection(layoutDirection)
setDrawableDirection()
}
override fun onFinishInflate() {
super.onFinishInflate()
layoutDirection = resources.configuration.layoutDirection
setTextColor()
}
private fun setTextColor() {
val ta = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent,
android.R.attr.textColorPrimaryInverse))
val bgColor = ta.getColor(0 /* index */, Color.BLACK)
var textColor = ta.getColor(1 /* index */, Color.WHITE)
ta.recycle()
textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true)
titleTextView.setTextColor(textColor)
descTextView.setTextColor(textColor)
}
private fun setDrawableDirection() {
view.setBackgroundResource(
if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR)
R.drawable.bubble_stack_user_education_bg
else R.drawable.bubble_stack_user_education_bg_rtl)
}
/**
* 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.
*/
fun show(stackPosition: PointF) : Boolean{
if (visibility == VISIBLE) return false
setAlpha(0f)
setVisibility(View.VISIBLE)
post {
with(view) {
val bubbleSize = context.resources.getDimensionPixelSize(
R.dimen.individual_bubble_size)
translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2
}
animate()
.setDuration(ANIMATE_DURATION)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
setShouldShow(false)
return true
}
/**
* If necessary, hides the stack education view.
*
* @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.
*/
fun hide(fromExpansion: Boolean) {
if (visibility != VISIBLE || isHiding) return
animate()
.alpha(0f)
.setDuration(if (fromExpansion) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
.withEndAction { visibility = GONE }
}
private fun setShouldShow(shouldShow: Boolean) {
Prefs.putBoolean(context, HAS_SEEN_BUBBLES_EDUCATION, !shouldShow)
}
}