Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b6a0e70 authored by Ivan Tkachenko's avatar Ivan Tkachenko
Browse files

Bubble bar user education

The bubble bar user education consisits of 2 user education prompts:
* Stack education - implemented in this CL, shows user education when
  the user interacts with the conversation bubbles the first time.
* Manage education - implemented in ag/24420064, shows user education
  when the conversation bubble is expanded the first time.

The user education logic is split between WMShell and Launcher as bubble
bar is implemented in both. The user education state (seen/unseen) is
stored in WMShell shared preferences. The Launcher is provided with
`shouldShowEducation` flag in `BubbleBarUpdate` to enable user education
interactions:
* If the stack user education hasn't been seen and the selected bubble
  is a conversation bubble, `BubbleController` in WMShell dispatches an
  update with `shouldShowEducation` true.
* When `BubbleBarController` in Launcher applies the update, it shows
  bubble bar in a collapsed state.
* When the bubble bar is clicked, `BubbleBarController` notifies the
  WMShell to show the user education relative to the position provided.
* When the user education is presented:
  * If user clicks outside the user education view, the education gets
    hidden. The next time user clicks on the bubble bar the WMShell will
    show expanded bubble view.
  * If user clicks on the user education view, `BubbleController`
    expands the selected bubble and notifies Laucnher to expand the
    bubble bar.
  * If the user clicks on the bubble bar, it expands and notifes WMShell
    to expand the selected bubble (which hides the user education).

Bug: 275077944
Test: atest BubblesTest BubbleDataTest, TBD
Flag: WM_BUBBLE_BAR
Change-Id: I04143e20f59fa4ba7143c3b4ff9d7e5ce4b94721
parent 9d62188b
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2023 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
  -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
  <path
      android:pathData="M4,18H20V6H4V18ZM22,18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4H20C21.1,4 22,4.9 22,6V18ZM13,8H18V14H13V8Z"
      android:fillColor="#455A64"
      android:fillType="evenOdd"/>
</vector>
+56 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2023 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.wm.shell.common.bubbles.BubblePopupView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="@dimen/bubble_popup_margin_horizontal"
    android:layout_marginBottom="120dp"
    android:elevation="@dimen/bubble_manage_menu_elevation"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <ImageView
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:tint="?android:attr/colorAccent"
        android:contentDescription="@null"
        android:src="@drawable/ic_floating_landscape"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:maxWidth="@dimen/bubble_popup_content_max_width"
        android:maxLines="1"
        android:ellipsize="end"
        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
        android:textColor="?android:attr/textColorPrimary"
        android:text="@string/bubble_bar_education_stack_title"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:maxWidth="@dimen/bubble_popup_content_max_width"
        android:textAppearance="@android:style/TextAppearance.DeviceDefault"
        android:textColor="?android:attr/textColorSecondary"
        android:textAlignment="center"
        android:text="@string/bubble_bar_education_stack_text"/>

</com.android.wm.shell.common.bubbles.BubblePopupView>
 No newline at end of file
+4 −0
Original line number Diff line number Diff line
@@ -163,6 +163,10 @@
    <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
    <string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>

    <!-- Title text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
    <string name="bubble_bar_education_stack_title">Chat using bubbles</string>
    <!-- Descriptive text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
    <string name="bubble_bar_education_stack_text">New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them.</string>
    <!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
    <string name="bubble_bar_education_manage_title">Control bubbles anytime</string>
    <!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
+28 −1
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Binder;
@@ -1063,6 +1064,15 @@ public class BubbleController implements ConfigurationChangeListener,
        }
    }

    /**
     * Show bubble bar user education relative to the reference position.
     * @param position the reference position in Screen coordinates.
     */
    public void showUserEducation(Point position) {
        if (mLayerView == null) return;
        mLayerView.showUserEducation(position);
    }

    @VisibleForTesting
    public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
        boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1114,6 +1124,16 @@ public class BubbleController implements ConfigurationChangeListener,
        }
    }

    /**
     * Expands the stack if the selected bubble is present. This is currently used when user
     * education view is clicked to expand the selected bubble.
     */
    public void expandStackWithSelectedBubble() {
        if (mBubbleData.getSelectedBubble() != null) {
            mBubbleData.setExpanded(true);
        }
    }

    /**
     * Expands and selects the provided bubble as long as it already exists in the stack or the
     * overflow. This is currently used when opening a bubble via clicking on a conversation widget.
@@ -1730,7 +1750,8 @@ public class BubbleController implements ConfigurationChangeListener,
                        + " expandedChanged=" + update.expandedChanged
                        + " selectionChanged=" + update.selectionChanged
                        + " suppressed=" + (update.suppressedBubble != null)
                        + " unsuppressed=" + (update.unsuppressedBubble != null));
                        + " unsuppressed=" + (update.unsuppressedBubble != null)
                        + " shouldShowEducation=" + update.shouldShowEducation);
            }

            ensureBubbleViewsAndWindowCreated();
@@ -2148,6 +2169,12 @@ public class BubbleController implements ConfigurationChangeListener,
        public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
            mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
        }

        @Override
        public void showUserEducation(int positionX, int positionY) {
            mMainExecutor.execute(() ->
                    mController.showUserEducation(new Point(positionX, positionY)));
        }
    }

    private class BubblesImpl implements Bubbles {
+10 −1
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ public class BubbleData {
        boolean orderChanged;
        boolean suppressedSummaryChanged;
        boolean expanded;
        boolean shouldShowEducation;
        @Nullable BubbleViewProvider selectedBubble;
        @Nullable Bubble addedBubble;
        @Nullable Bubble updatedBubble;
@@ -126,6 +127,7 @@ public class BubbleData {

            bubbleBarUpdate.expandedChanged = expandedChanged;
            bubbleBarUpdate.expanded = expanded;
            bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
            if (selectionChanged) {
                bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
                        ? selectedBubble.getKey()
@@ -165,6 +167,7 @@ public class BubbleData {
         */
        BubbleBarUpdate getInitialState() {
            BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
            bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
            for (int i = 0; i < bubbles.size(); i++) {
                bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
            }
@@ -187,6 +190,7 @@ public class BubbleData {

    private final Context mContext;
    private final BubblePositioner mPositioner;
    private final BubbleEducationController mEducationController;
    private final Executor mMainExecutor;
    /** Bubbles that are actively in the stack. */
    private final List<Bubble> mBubbles;
@@ -233,10 +237,11 @@ public class BubbleData {
    private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();

    public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner,
            Executor mainExecutor) {
            BubbleEducationController educationController, Executor mainExecutor) {
        mContext = context;
        mLogger = bubbleLogger;
        mPositioner = positioner;
        mEducationController = educationController;
        mMainExecutor = mainExecutor;
        mOverflow = new BubbleOverflow(context, positioner);
        mBubbles = new ArrayList<>();
@@ -447,6 +452,7 @@ public class BubbleData {
        if (bubble.shouldAutoExpand()) {
            bubble.setShouldAutoExpand(false);
            setSelectedBubbleInternal(bubble);

            if (!mExpanded) {
                setExpandedInternal(true);
            }
@@ -877,6 +883,9 @@ public class BubbleData {

    private void dispatchPendingChanges() {
        if (mListener != null && mStateChange.anythingChanged()) {
            mStateChange.shouldShowEducation = mSelectedBubble != null
                    && mEducationController.shouldShowStackEducation(mSelectedBubble)
                    && !mExpanded;
            mListener.applyUpdate(mStateChange);
        }
        mStateChange = new Update(mBubbles, mOverflowBubbles);
Loading