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

Commit e5a5e586 authored by Mady Mellor's avatar Mady Mellor
Browse files

Change BubbleTaskViewHelper to implement the listener

The helper was really the listener plus some extra stuff
around state that's relevant to the listener.

This CL makes it so that the listener is the top level class
making it easier to test.

Adds a test for BubbleTaskViewHelper

Currently this is only used by bubble bar expanded view.
Will migrate bubble expanded view to this in a separate CL.

Will rename in a separate CL

Flag: com.android.wm.shell.enable_bubble_bar
Bug: 385361517 (proper tests for the fix for this bug)
Bug: 272102927
Test: atest BubbleTaskViewHelperTest
Change-Id: I92de8b1838e3ef2bc18d0b55723a216cc876e27a
parent 77e7b277
Loading
Loading
Loading
Loading
+491 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.wm.shell.bubbles

import android.app.Notification
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
import android.view.View
import android.widget.FrameLayout
import androidx.test.core.app.ApplicationProvider
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.R
import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.taskview.TaskView
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.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

/**
 * Tests for [BubbleTaskViewHelper].
 */
@SmallTest
@RunWith(AndroidJUnit4::class)
class BubbleTaskViewHelperTest {

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

    private var taskViewController = mock<TaskViewController>()
    private var listenerCallback = mock<BubbleTaskViewHelper.Callback>()
    private var expandedViewManager = mock<BubbleExpandedViewManager>()

    private lateinit var bubbleTaskViewListener: BubbleTaskViewHelper
    private lateinit var taskView: TaskView
    private lateinit var bubbleTaskView: BubbleTaskView
    private lateinit var parentView: ViewPoster
    private lateinit var mainExecutor: TestShellExecutor
    private lateinit var bgExecutor: TestShellExecutor

    @Before
    fun setUp() {
        ProtoLog.REQUIRE_PROTOLOGTOOL = false
        ProtoLog.init()

        parentView = ViewPoster(context)
        mainExecutor = TestShellExecutor()
        bgExecutor = TestShellExecutor()

        taskView = TaskView(context, taskViewController, mock<TaskViewTaskController>())
        bubbleTaskView = BubbleTaskView(taskView, mainExecutor)

        bubbleTaskViewListener =
            BubbleTaskViewHelper(
                context,
                bubbleTaskView,
                parentView,
                expandedViewManager,
                listenerCallback
            )
    }

    @Test
    fun createBubbleTaskViewListener_withCreatedTaskView() {
        // Make the bubbleTaskView look like it's been created
        val taskId = 123
        bubbleTaskView.listener.onTaskCreated(taskId, mock<ComponentName>())
        reset(listenerCallback)

        bubbleTaskViewListener =
            BubbleTaskViewHelper(
                context,
                bubbleTaskView,
                parentView,
                expandedViewManager,
                listenerCallback
            )

        assertThat(bubbleTaskView.delegateListener).isEqualTo(bubbleTaskViewListener)
        assertThat(bubbleTaskViewListener.taskView).isEqualTo(bubbleTaskView.taskView)

        verify(listenerCallback).onTaskCreated()
        assertThat(bubbleTaskViewListener.taskId).isEqualTo(taskId)
    }

    @Test
    fun createBubbleTaskViewListener() {
        bubbleTaskViewListener =
            BubbleTaskViewHelper(
                context,
                bubbleTaskView,
                parentView,
                expandedViewManager,
                listenerCallback
            )

        assertThat(bubbleTaskView.delegateListener).isEqualTo(bubbleTaskViewListener)
        assertThat(bubbleTaskViewListener.taskView).isEqualTo(bubbleTaskView.taskView)
        verify(listenerCallback, never()).onTaskCreated()
    }

    @Test
    fun onInitialized_pendingIntentChatBubble() {
        val target = Intent(context, TestActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(context, 0, target,
            PendingIntent.FLAG_MUTABLE)

        val b = createChatBubble("key", pendingIntent)
        bubbleTaskViewListener.setBubble(b)

        assertThat(b.isChat).isTrue()
        // Has shortcut info
        assertThat(b.shortcutInfo).isNotNull()
        // But it didn't use that on bubble metadata
        assertThat(b.metadataShortcutId).isNull()

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

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

    @Test
    fun onInitialized_shortcutChatBubble() {
        val shortcutInfo = ShortcutInfo.Builder(context)
            .setId("mockShortcutId")
            .build()
        val b = createChatBubble("key", shortcutInfo)
        bubbleTaskViewListener.setBubble(b)

        assertThat(b.isChat).isTrue()
        assertThat(b.shortcutInfo).isNotNull()
        // Chat bubble using a shortcut
        assertThat(b.metadataShortcutId).isNotNull()

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

        assertThat(b.isPendingIntentActive).isFalse()
        verify(taskViewController).startShortcutActivity(any(), eq(shortcutInfo), any(), any())
    }

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

        assertThat(b.isApp).isTrue()

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

        assertThat(b.isPendingIntentActive).isFalse()
        verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())
    }

    @Test
    fun onInitialized_preparingTransition() {
        val b = createAppBubble()
        bubbleTaskViewListener.setBubble(b)
        taskView = Mockito.spy(taskView)
        val preparingTransition = mock<BubbleTransitions.BubbleTransition>()
        b.preparingTransition = preparingTransition

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

        verify(preparingTransition).surfaceCreated()
    }

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

        assertThat(b.isApp).isTrue()

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

        verify(taskViewController, never()).startActivity(any(), any(), anyOrNull(), any(), any())
    }

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

        assertThat(b.isApp).isTrue()

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

        reset(taskViewController)

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        // Already initialized, so no activity should be started.
        verify(taskViewController, never()).startActivity(any(), any(), anyOrNull(), any(), any())
    }

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

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        getInstrumentation().waitForIdleSync()
        verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())

        val taskId = 123
        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
        }
        getInstrumentation().waitForIdleSync()

        verify(listenerCallback).onTaskCreated()
        verify(expandedViewManager, never()).setNoteBubbleTaskId(any(), any())
        assertThat(bubbleTaskViewListener.taskId).isEqualTo(taskId)
    }

    @Test
    fun onTaskCreated_noteBubble() {
        val b = createNoteBubble()
        bubbleTaskViewListener.setBubble(b)
        assertThat(b.isNote).isTrue()

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        getInstrumentation().waitForIdleSync()
        verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())

        val taskId = 123
        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
        }
        getInstrumentation().waitForIdleSync()

        verify(listenerCallback).onTaskCreated()
        verify(expandedViewManager).setNoteBubbleTaskId(eq(b.key), eq(taskId))
        assertThat(bubbleTaskViewListener.taskId).isEqualTo(taskId)
    }

    @Test
    fun onTaskVisibilityChanged_true() {
        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskVisibilityChanged(1, true)
        }
        verify(listenerCallback).onContentVisibilityChanged(eq(true))
    }

    @Test
    fun onTaskVisibilityChanged_false() {
        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskVisibilityChanged(1, false)
        }
        verify(listenerCallback).onContentVisibilityChanged(eq(false))
    }

    @Test
    fun onTaskRemovalStarted() {
        val mockTaskView = mock<TaskView>()
        bubbleTaskView = BubbleTaskView(mockTaskView, mainExecutor)

        bubbleTaskViewListener =
            BubbleTaskViewHelper(
                context,
                bubbleTaskView,
                parentView,
                expandedViewManager,
                listenerCallback
            )

        val b = createAppBubble()
        bubbleTaskViewListener.setBubble(b)

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onInitialized()
        }
        getInstrumentation().waitForIdleSync()
        verify(mockTaskView).startActivity(any(), anyOrNull(), any(), any())

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskRemovalStarted(1)
        }

        verify(expandedViewManager).removeBubble(eq(b.key), eq(Bubbles.DISMISS_TASK_FINISHED))
        verify(mockTaskView).release()
        assertThat(parentView.lastRemovedView).isEqualTo(mockTaskView)
        assertThat(bubbleTaskViewListener.taskView).isNull()
        verify(listenerCallback).onTaskRemovalStarted()
    }

    @Test
    fun onBackPressedOnTaskRoot_expanded() {
        val taskId = 123
        whenever(expandedViewManager.isStackExpanded()).doReturn(true)

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
            bubbleTaskViewListener.onBackPressedOnTaskRoot(taskId)
        }
        verify(listenerCallback).onBackPressed()
    }

    @Test
    fun onBackPressedOnTaskRoot_notExpanded() {
        val taskId = 123
        whenever(expandedViewManager.isStackExpanded()).doReturn(false)

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
            bubbleTaskViewListener.onBackPressedOnTaskRoot(taskId)
        }
        verify(listenerCallback, never()).onBackPressed()
    }

    @Test
    fun onBackPressedOnTaskRoot_taskIdMissMatch() {
        val taskId = 123
        whenever(expandedViewManager.isStackExpanded()).doReturn(true)

        getInstrumentation().runOnMainSync {
            bubbleTaskViewListener.onTaskCreated(taskId, mock<ComponentName>())
            bubbleTaskViewListener.onBackPressedOnTaskRoot(42)
        }
        verify(listenerCallback, never()).onBackPressed()
    }

    @Test
    fun setBubble_isNew() {
        val b = createAppBubble()
        val isNew = bubbleTaskViewListener.setBubble(b)
        assertThat(isNew).isTrue()
    }

    @Test
    fun setBubble_launchContentChanged() {
        val target = Intent(context, TestActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            context, 0, target,
            PendingIntent.FLAG_MUTABLE
        )

        val b = createChatBubble("key", pendingIntent)
        var isNew = bubbleTaskViewListener.setBubble(b)
        // First time bubble is set, so it is "new"
        assertThat(isNew).isTrue()

        val b2 = createChatBubble("key", pendingIntent)
        isNew = bubbleTaskViewListener.setBubble(b2)
        // Second time bubble is set & it uses same type of launch content, not "new"
        assertThat(isNew).isFalse()

        val shortcutInfo = ShortcutInfo.Builder(context)
            .setId("mockShortcutId")
            .build()
        val b3 = createChatBubble("key", shortcutInfo)
        // bubble is using different content, so it is "new"
        isNew = bubbleTaskViewListener.setBubble(b3)
        assertThat(isNew).isTrue()
    }

    private fun createAppBubble(): Bubble {
        val target = Intent(context, TestActivity::class.java)
        target.setPackage(context.packageName)
        return Bubble.createAppBubble(target, mock<UserHandle>(), mock<Icon>(),
            mainExecutor, bgExecutor)
    }

    private fun createNoteBubble(): Bubble {
        val target = Intent(context, TestActivity::class.java)
        target.setPackage(context.packageName)
        return Bubble.createNotesBubble(target, mock<UserHandle>(), mock<Icon>(),
            mainExecutor, bgExecutor)
    }

    private fun createChatBubble(key: String, shortcutInfo: ShortcutInfo): Bubble {
        return Bubble(
            key,
            shortcutInfo,
            0 /* desiredHeight */,
            0 /* desiredHeightResId */,
            "title",
            -1 /*taskId */,
            null /* locusId */, true /* isdismissabel */,
            mainExecutor, bgExecutor, mock<BubbleMetadataFlagListener>()
        )
    }

    private fun createChatBubble(key: String, pendingIntent: PendingIntent): Bubble {
        val metadata = Notification.BubbleMetadata.Builder(
            pendingIntent,
            Icon.createWithResource(context, R.drawable.bubble_ic_create_bubble)
        ).build()
        val shortcutInfo = ShortcutInfo.Builder(context)
            .setId("shortcutId")
            .build()
        val notification: Notification =
            Notification.Builder(context, key)
                .setSmallIcon(mock<Icon>())
                .setWhen(System.currentTimeMillis())
                .setContentTitle("title")
                .setContentText("content")
                .setBubbleMetadata(metadata)
                .build()
        val sbn = mock<StatusBarNotification>()
        val ranking = mock<Ranking>()
        whenever(sbn.getNotification()).thenReturn(notification)
        whenever(sbn.getKey()).thenReturn(key)
        whenever(ranking.getConversationShortcutInfo()).thenReturn(shortcutInfo)
        val entry = BubbleEntry(sbn, ranking, true, false, false, false)
        return Bubble(
            entry, mock<BubbleMetadataFlagListener>(), null, mainExecutor,
            bgExecutor
        )
    }

    /**
     * FrameLayout that immediately runs any runnables posted to it and tracks view removals.
     */
    class ViewPoster(context: Context) : FrameLayout(context) {

        lateinit var lastRemovedView: View

        override fun post(r: Runnable): Boolean {
            r.run()
            return true
        }

        override fun removeView(v: View) {
            super.removeView(v)
            lastRemovedView = v
        }
    }
}
 No newline at end of file
+138 −156
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * Copyright (C) 2025 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.
@@ -13,6 +13,7 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.bubbles;

import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
@@ -39,18 +40,13 @@ import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.taskview.TaskView;

/**
 * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
 */
public class BubbleTaskViewHelper {

public class BubbleTaskViewHelper implements TaskView.Listener {
    private static final String TAG = BubbleTaskViewHelper.class.getSimpleName();

    /**
     * Listener for users of {@link BubbleTaskViewHelper} to use to be notified of events
     * on the task.
     * Callback to let the view parent of TaskView to be notified of different events.
     */
    public interface Listener {
    public interface Callback {

        /** Called when the task is first created. */
        void onTaskCreated();
@@ -67,18 +63,15 @@ public class BubbleTaskViewHelper {

    private final Context mContext;
    private final BubbleExpandedViewManager mExpandedViewManager;
    private final BubbleTaskViewHelper.Listener mListener;
    private final BubbleTaskViewHelper.Callback mCallback;
    private final View mParentView;

    @Nullable
    private Bubble mBubble;
    @Nullable
    private PendingIntent mPendingIntent;
    @Nullable
    private TaskView mTaskView;
    private int mTaskId = INVALID_TASK_ID;
    private TaskView mTaskView;

    private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
    private boolean mInitialized = false;
    private boolean mDestroyed = false;

@@ -118,10 +111,10 @@ public class BubbleTaskViewHelper {
                            mContext.createContextAsUser(
                                    mBubble.getUser(), Context.CONTEXT_RESTRICTED);
                    Intent fillInIntent = null;
                        //first try get pending intent from the bubble
                    // First try get pending intent from the bubble
                    PendingIntent pi = mBubble.getPendingIntent();
                    if (pi == null) {
                            // if null - create new one
                        // If null - create new one
                        pi = PendingIntent.getActivity(
                                context,
                                /* requestCode= */ 0,
@@ -184,12 +177,12 @@ public class BubbleTaskViewHelper {

        // With the task org, the taskAppeared callback will only happen once the task has
        // already drawn
            mListener.onTaskCreated();
        mCallback.onTaskCreated();
    }

    @Override
    public void onTaskVisibilityChanged(int taskId, boolean visible) {
            mListener.onContentVisibilityChanged(visible);
        mCallback.onContentVisibilityChanged(visible);
    }

    @Override
@@ -204,53 +197,43 @@ public class BubbleTaskViewHelper {
            ((ViewGroup) mParentView).removeView(mTaskView);
            mTaskView = null;
        }
            mListener.onTaskRemovalStarted();
        mCallback.onTaskRemovalStarted();
    }

    @Override
    public void onBackPressedOnTaskRoot(int taskId) {
        if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) {
                mListener.onBackPressed();
            mCallback.onBackPressed();
        }
    }
    };

    public BubbleTaskViewHelper(Context context,
            BubbleExpandedViewManager expandedViewManager,
            BubbleTaskViewHelper.Listener listener,
            BubbleTaskView bubbleTaskView,
            View parent) {
    public BubbleTaskViewHelper(Context context, BubbleTaskView bubbleTaskView, View parentView,
            BubbleExpandedViewManager manager, BubbleTaskViewHelper.Callback callback) {
        mContext = context;
        mExpandedViewManager = expandedViewManager;
        mListener = listener;
        mParentView = parent;
        mTaskView = bubbleTaskView.getTaskView();
        bubbleTaskView.setDelegateListener(mTaskViewListener);
        mParentView = parentView;
        mExpandedViewManager = manager;
        mCallback = callback;
        bubbleTaskView.setDelegateListener(this);
        if (bubbleTaskView.isCreated()) {
            mTaskId = bubbleTaskView.getTaskId();
            mListener.onTaskCreated();
            callback.onTaskCreated();
        }
    }

    /**
     * Sets the bubble or updates the bubble used to populate the view.
     *
     * @return true if the bubble is new, false if it was an update to the same bubble.
     * @return true if the bubble is new or if the launch content of the bubble changed from the
     * previous bubble.
     */
    public boolean update(Bubble bubble) {
    public boolean setBubble(Bubble bubble) {
        boolean isNew = mBubble == null || didBackingContentChange(bubble);
        mBubble = bubble;
        if (isNew) {
            mPendingIntent = mBubble.getPendingIntent();
            return true;
        }
        return false;
        }

    /** Returns the bubble key associated with this view. */
    @Nullable
    public String getBubbleKey() {
        return mBubble != null ? mBubble.getKey() : null;
        return isNew;
    }

    /** Returns the TaskView associated with this view. */
@@ -267,9 +250,8 @@ public class BubbleTaskViewHelper {
        return mTaskId;
    }

    /** Returns whether the bubble set on the helper is valid to populate the task view. */
    public boolean isValidBubble() {
        return mBubble != null && (mPendingIntent != null || mBubble.hasMetadataShortcutId());
    private String getBubbleKey() {
        return mBubble != null ? mBubble.getKey() : "";
    }

    // TODO (b/274980695): Is this still relevant?
+10 −7
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ import java.util.function.Supplier;
import javax.inject.Inject;

/** Expanded view of a bubble when it's part of the bubble bar. */
public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Callback {
    /**
     * The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
     * actions and events
@@ -110,7 +110,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
    private BubbleExpandedViewManager mManager;
    private BubblePositioner mPositioner;
    private boolean mIsOverflow;
    private BubbleTaskViewHelper mBubbleTaskViewHelper;
    private BubbleTaskViewHelper mBubbleTaskViewListener;
    private BubbleBarMenuViewController mMenuViewController;
    @Nullable
    private Supplier<Rect> mLayerBoundsSupplier;
@@ -246,9 +246,10 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
            mHandleView.setVisibility(View.GONE);
        } else {
            mTaskView = bubbleTaskView.getTaskView();
            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
                    /* listener= */ this, bubbleTaskView,
                    /* viewParent= */ this);
            mBubbleTaskViewListener = new BubbleTaskViewHelper(mContext, bubbleTaskView,
                    /* viewParent= */ this,
                    expandedViewManager,
                    /* callback= */ this);

            // if the task view is already attached to a parent we need to remove it
            if (mTaskView.getParent() != null) {
@@ -535,13 +536,15 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
    /** Updates the bubble shown in the expanded view. */
    public void update(Bubble bubble) {
        mBubble = bubble;
        mBubbleTaskViewHelper.update(bubble);
        mBubbleTaskViewListener.setBubble(bubble);
        mMenuViewController.updateMenu(bubble);
    }

    /** The task id of the activity shown in the task view, if it exists. */
    public int getTaskId() {
        return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
        return mBubbleTaskViewListener != null
                ? mBubbleTaskViewListener.getTaskId()
                : INVALID_TASK_ID;
    }

    /** Sets layer bounds supplier used for obscured touchable region of task view */