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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user