Merge "Bubbles user education for stack & manage" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-02-28 02:11:46 +00:00
committed by Android (Google) Code Review
13 changed files with 544 additions and 59 deletions

View File

@@ -0,0 +1,21 @@
<!--
~ 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?android:attr/colorAccent"/>
<corners
android:radius="?android:attr/dialogCornerRadius" />
</shape>

View File

@@ -0,0 +1,22 @@
<!--
~ 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?android:attr/colorAccent"/>
<corners
android:bottomRightRadius="360dp"
android:topRightRadius="360dp" />
</shape>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<LinearLayout
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"
android:paddingBottom="48dp"
android:paddingStart="@dimen/bubble_stack_user_education_side_inset"
android:paddingEnd="16dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg">
<TextView
android:id="@+id/user_education_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
android:maxLines="1"
android:text="@string/bubbles_user_education_title"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
<TextView
android:id="@+id/user_education_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bubbles_user_education_description"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
</LinearLayout>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<com.android.systemui.bubbles.BubbleManageEducationView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/manage_education_view"
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="@dimen/bubbles_manage_education_width">
<TextView
android:id="@+id/user_education_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/bubbles_user_education_manage"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
android:background="@drawable/bubble_manage_user_education_bg"
/>
<View
android:id="@+id/user_education_pointer"
android:layout_width="@dimen/bubble_pointer_width"
android:layout_height="@dimen/bubble_pointer_height"
/>
</LinearLayout>
</com.android.systemui.bubbles.BubbleManageEducationView>

View File

@@ -1159,7 +1159,7 @@
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
<!-- Height of button allowing users to adjust settings for bubbles. -->
<dimen name="bubble_settings_size">48dp</dimen>
<dimen name="bubble_manage_button_height">48dp</dimen>
<!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
<dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
<!-- How far, vertically, to animate the expanded view over when animating in/out. -->
@@ -1175,16 +1175,22 @@
<!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
<dimen name="bubble_stack_offscreen">9dp</dimen>
<!-- How far down the screen the stack starts. -->
<dimen name="bubble_stack_starting_offset_y">96dp</dimen>
<dimen name="bubble_stack_starting_offset_y">120dp</dimen>
<!-- Space between the pointer triangle and the bubble expanded view -->
<dimen name="bubble_pointer_margin">8dp</dimen>
<!-- Height of the permission prompt shown with bubbles -->
<dimen name="bubble_permission_height">120dp</dimen>
<!-- Padding applied to the bubble dismiss target. Touches in this padding cause the bubbles to
snap to the dismiss target. -->
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
<!-- Bubbles user education views -->
<dimen name="bubbles_manage_education_width">160dp</dimen>
<!-- The inset from the top bound of the manage button to place the user education. -->
<dimen name="bubbles_manage_education_top_inset">10dp</dimen>
<!-- Size of padding for the user education cling, this should at minimum be larger than
individual_bubble_size + some padding. -->
<dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
<!-- Size of the RAT type for CellularTile -->
<dimen name="celltile_rat_type_size">10sp</dimen>

View File

@@ -2586,6 +2586,12 @@
<string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
<!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=20] -->
<string name="bubble_dismiss_text">Dismiss</string>
<!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
<string name="bubbles_user_education_title">Keep chats up front</string>
<!-- Descriptive text for the bubble feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
<string name="bubbles_user_education_description">New chats from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> will appear as bubbles. Tap a bubble to open it. Drag to move it.\n\nTap the bubble</string>
<!-- Text for the bubble "manage" button feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=80]-->
<string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
<!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] -->
<string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string>

View File

@@ -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) {

View File

@@ -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<Bubble> bubbles, Bubble selected) {
StringBuilder sb = new StringBuilder();

View File

@@ -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.
*/

View File

@@ -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();
}
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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<Bubble> getBubblesOnScreen() {
List<Bubble> bubbles = new ArrayList<>();

View File

@@ -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) {

View File

@@ -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);
}