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

Commit a7f12990 authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge changes Ia01f740a,Ide2298ce,Iacb6150c into main

* changes:
  Don't set bubble launch flags on non-chat shortcut bubbles
  Use BubbleTaskViewListener for floating bubbles (behind a flag)
  Add a flag for unifying taskview listener
parents 537efd66 a6c15388
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -184,3 +184,13 @@ flag {
    description: "Try out bubble bar on phones"
    bug: "394869612"
}

flag {
    name: "enable_bubble_task_view_listener"
    namespace: "multitasking"
    description: "Use the same taskview listener for bubble bar and floating"
    bug: "272102927"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
    package="com.android.wm.shell.multivalenttests">

    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>

    <application android:debuggable="true" android:supportsRtl="true" >
        <uses-library android:name="android.test.runner" />
+153 −8
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.wm.shell.bubbles

import android.app.ActivityOptions
import android.app.Notification
import android.app.PendingIntent
import android.content.ComponentName
@@ -24,6 +25,8 @@ import android.content.Intent
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
import android.view.View
@@ -33,6 +36,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
import com.android.wm.shell.common.TestShellExecutor
@@ -41,6 +45,7 @@ import com.android.wm.shell.taskview.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@@ -48,6 +53,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -61,6 +67,9 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class BubbleTaskViewListenerTest {

    @get:Rule
    val setFlagsRule = SetFlagsRule()

    private val context = ApplicationProvider.getApplicationContext<Context>()

    private var taskViewController = mock<TaskViewController>()
@@ -155,9 +164,22 @@ class BubbleTaskViewListenerTest {
        }
        getInstrumentation().waitForIdleSync()

        // ..so it's pending intent-based, and launches that
        // ..so it's pending intent-based, so the pending intent should be active
        assertThat(b.isPendingIntentActive).isTrue()
        verify(taskViewController).startActivity(any(), eq(pendingIntent), any(), any(), any())

        val intentCaptor = argumentCaptor<Intent>()
        val optionsCaptor = argumentCaptor<ActivityOptions>()

        verify(taskViewController).startActivity(any(),
            eq(pendingIntent),
            intentCaptor.capture(),
            optionsCaptor.capture(),
            any())
        val intentFlags = intentCaptor.lastValue.flags
        assertThat((intentFlags and Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0).isTrue()
        assertThat((intentFlags and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
        assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue()
        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
    }

    @Test
@@ -178,12 +200,52 @@ class BubbleTaskViewListenerTest {
        }
        getInstrumentation().waitForIdleSync()

        assertThat(b.isPendingIntentActive).isFalse()
        verify(taskViewController).startShortcutActivity(any(), eq(shortcutInfo), any(), any())
        val optionsCaptor = argumentCaptor<ActivityOptions>()

        assertThat(b.isPendingIntentActive).isFalse() // not triggered for shortcut chats
        verify(taskViewController).startShortcutActivity(any(),
            eq(shortcutInfo),
            optionsCaptor.capture(),
            any())
        assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue()
        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isTrue()
        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
    }

    @EnableFlags(FLAG_ENABLE_BUBBLE_ANYTHING)
    @Test
    fun onInitialized_appBubble() {
    fun onInitialized_shortcutBubble() {
        val shortcutInfo = ShortcutInfo.Builder(context)
            .setId("mockShortcutId")
            .build()

        val b = createShortcutBubble(shortcutInfo)
        bubbleTaskViewListener.setBubble(b)

        assertThat(b.isChat).isFalse()
        assertThat(b.isShortcut).isTrue()
        assertThat(b.shortcutInfo).isNotNull()

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        getInstrumentation().waitForIdleSync()

        val optionsCaptor = argumentCaptor<ActivityOptions>()

        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
        verify(taskViewController).startShortcutActivity(any(),
            eq(shortcutInfo),
            optionsCaptor.capture(),
            any())
        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.isApplyMultipleTaskFlagForShortcut).isTrue()
        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
    }

    @Test
    fun onInitialized_appBubble_intent() {
        val b = createAppBubble()
        bubbleTaskViewListener.setBubble(b)

@@ -194,10 +256,82 @@ class BubbleTaskViewListenerTest {
        }
        getInstrumentation().waitForIdleSync()

        assertThat(b.isPendingIntentActive).isFalse()
        verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())
        val intentCaptor = argumentCaptor<Intent>()
        val optionsCaptor = argumentCaptor<ActivityOptions>()

        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
        verify(taskViewController).startActivity(any(),
            any(),
            intentCaptor.capture(),
            optionsCaptor.capture(),
            any())

        assertThat((intentCaptor.lastValue.flags
                and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
    }

    @Test
    fun onInitialized_appBubble_pendingIntent() {
        val b = createAppBubble(usePendingIntent = true)
        bubbleTaskViewListener.setBubble(b)

        assertThat(b.isApp).isTrue()

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        getInstrumentation().waitForIdleSync()

        val intentCaptor = argumentCaptor<Intent>()
        val optionsCaptor = argumentCaptor<ActivityOptions>()

        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
        verify(taskViewController).startActivity(any(),
            any(),
            intentCaptor.capture(),
            optionsCaptor.capture(),
            any())

        assertThat((intentCaptor.lastValue.flags
                and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
    }

    @Test
    fun onInitialized_noteBubble() {
        val b = createNoteBubble()
        bubbleTaskViewListener.setBubble(b)

        assertThat(b.isNote).isTrue()

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        getInstrumentation().waitForIdleSync()

        val intentCaptor = argumentCaptor<Intent>()
        val optionsCaptor = argumentCaptor<ActivityOptions>()

        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
        verify(taskViewController).startActivity(any(),
            any(),
            intentCaptor.capture(),
            optionsCaptor.capture(),
            any())

        assertThat((intentCaptor.lastValue.flags
                and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
    }


    @Test
    fun onInitialized_preparingTransition() {
        val b = createAppBubble()
@@ -416,13 +550,24 @@ class BubbleTaskViewListenerTest {
        assertThat(isNew).isTrue()
    }

    private fun createAppBubble(): Bubble {
    private fun createAppBubble(usePendingIntent: Boolean = false): Bubble {
        val target = Intent(context, TestActivity::class.java)
        target.setPackage(context.packageName)
        if (usePendingIntent) {
            // Robolectric doesn't seem to play nice with PendingIntents, have to mock it.
            val pendingIntent = mock<PendingIntent>()
            whenever(pendingIntent.intent).thenReturn(target)
            return Bubble.createAppBubble(pendingIntent, mock<UserHandle>(),
                mainExecutor, bgExecutor)
        }
        return Bubble.createAppBubble(target, mock<UserHandle>(), mock<Icon>(),
            mainExecutor, bgExecutor)
    }

    private fun createShortcutBubble(shortcutInfo: ShortcutInfo): Bubble {
        return Bubble.createShortcutBubble(shortcutInfo, mainExecutor, bgExecutor)
    }

    private fun createNoteBubble(): Bubble {
        val target = Intent(context, TestActivity::class.java)
        target.setPackage(context.packageName)
+1 −1
Original line number Diff line number Diff line
@@ -364,7 +364,7 @@ public class Bubble implements BubbleViewProvider {
            @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
        return new Bubble(intent,
                user,
                /* key= */ getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user),
                /* key= */ getAppBubbleKeyForApp(intent.getIntent().getPackage(), user),
                mainExecutor, bgExecutor);
    }

+54 −11
Original line number Diff line number Diff line
@@ -197,6 +197,8 @@ public class BubbleExpandedView extends LinearLayout {
     */
    private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext());

    private TaskView.Listener mCurrentTaskViewListener;

    private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
        private boolean mInitialized = false;
        private boolean mDestroyed = false;
@@ -235,18 +237,24 @@ public class BubbleExpandedView extends LinearLayout {
                        Context context =
                                mContext.createContextAsUser(
                                        mBubble.getUser(), Context.CONTEXT_RESTRICTED);
                        Intent fillInIntent = new Intent();
                        fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                        PendingIntent pi = PendingIntent.getActivity(
                                context,
                                /* requestCode= */ 0,
                                mBubble.getIntent().addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
                                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
                                mBubble.getIntent(),
                                // Needs to be mutable for the fillInIntent
                                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
                                /* options= */ null);
                        mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
                                launchBounds);
                        mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
                    } else if (!mIsOverflow && isShortcutBubble) {
                        ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey());
                        if (mBubble.isChat()) {
                            options.setLaunchedFromBubble(true);
                            options.setApplyActivityFlagsForBubbles(true);
                        } else {
                            options.setApplyMultipleTaskFlagForShortcut(true);
                        }
                        mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                                options, launchBounds);
                    } else {
@@ -453,7 +461,34 @@ public class BubbleExpandedView extends LinearLayout {
            mTaskView = bubbleTaskView.getTaskView();
            // reset the insets that might left after TaskView is shown in BubbleBarExpandedView
            mTaskView.setCaptionInsets(null);
            bubbleTaskView.setDelegateListener(mTaskViewListener);
            if (Flags.enableBubbleTaskViewListener()) {
                mCurrentTaskViewListener = new BubbleTaskViewListener(mContext, bubbleTaskView,
                        /* viewParent= */ this, expandedViewManager,
                        new BubbleTaskViewListener.Callback() {
                            @Override
                            public void onTaskCreated() {
                                setContentVisibility(true);
                            }

                            @Override
                            public void onContentVisibilityChanged(boolean visible) {
                                setContentVisibility(visible);
                            }

                            @Override
                            public void onBackPressed() {
                                mStackView.onBackPressed();
                            }

                            @Override
                            public void onTaskRemovalStarted() {
                                // nothing to do / handled in listener.
                            }
                        });
            } else {
                mCurrentTaskViewListener = mTaskViewListener;
                bubbleTaskView.setDelegateListener(mCurrentTaskViewListener);
            }

            // set a fixed width so it is not recalculated as part of a rotation. the width will be
            // updated manually after the rotation.
@@ -464,12 +499,15 @@ public class BubbleExpandedView extends LinearLayout {
            }
            mExpandedViewContainer.addView(mTaskView, lp);
            bringChildToFront(mTaskView);

            if (!Flags.enableBubbleTaskViewListener()) {
                if (bubbleTaskView.isCreated()) {
                mTaskViewListener.onTaskCreated(
                    mCurrentTaskViewListener.onTaskCreated(
                            bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
                }
            }
        }
    }

    void updateDimensions() {
        Resources res = getResources();
@@ -897,7 +935,12 @@ public class BubbleExpandedView extends LinearLayout {
            Log.w(TAG, "Stack is null for bubble: " + bubble);
            return;
        }
        boolean isNew = mBubble == null || didBackingContentChange(bubble);
        boolean isNew;
        if (mCurrentTaskViewListener instanceof BubbleTaskViewListener) {
            isNew = ((BubbleTaskViewListener) mCurrentTaskViewListener).setBubble(bubble);
        } else {
            isNew = mBubble == null || didBackingContentChange(bubble);
        }
        boolean isUpdate = bubble != null && mBubble != null
                && bubble.getKey().equals(mBubble.getKey());
        ProtoLog.d(WM_SHELL_BUBBLES, "BubbleExpandedView - update bubble=%s; isNew=%b; isUpdate=%b",
Loading