Merge "Bubbles user education for stack & manage" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
5688bc3e4c
@@ -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>
|
||||
@@ -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>
|
||||
48
packages/SystemUI/res/layout/bubble_stack_user_education.xml
Normal file
48
packages/SystemUI/res/layout/bubble_stack_user_education.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user