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

Commit 04b85b60 authored by Joe Antonetti's avatar Joe Antonetti
Browse files

Send RemoteTaskAddedMessage when tasks are added

Flag: android.companion.enable_task_continuity
Test: Added unit tests
Bug: 400970610
Change-Id: Id5ae1f3502cb0991083218674060c6c3384abf48
parent 7844d421
Loading
Loading
Loading
Loading
+67 −4
Original line number Diff line number Diff line
@@ -20,12 +20,16 @@ import static android.companion.CompanionDeviceManager.MESSAGE_TASK_CONTINUITY;

import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.TaskStackListener;
import android.companion.CompanionDeviceManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
import android.util.Slog;

import com.android.server.companion.datatransfer.continuity.connectivity.ConnectedAssociationStore;
import com.android.server.companion.datatransfer.continuity.messages.ContinuityDeviceConnected;
import com.android.server.companion.datatransfer.continuity.messages.RemoteTaskAddedMessage;
import com.android.server.companion.datatransfer.continuity.messages.RemoteTaskInfo;
import com.android.server.companion.datatransfer.continuity.messages.TaskContinuityMessage;
import com.android.server.companion.datatransfer.continuity.messages.TaskContinuityMessageData;
@@ -37,9 +41,12 @@ import java.util.Set;

/**
 * Responsible for broadcasting recent tasks on the current device to the user's
 *
 * other devices via {@link CompanionDeviceManager}.
 */
class TaskBroadcaster implements ConnectedAssociationStore.Observer {
class TaskBroadcaster
    extends TaskStackListener
    implements ConnectedAssociationStore.Observer {

    private static final String TAG = "TaskBroadcaster";

@@ -72,6 +79,8 @@ class TaskBroadcaster implements ConnectedAssociationStore.Observer {

        Slog.v(TAG, "Starting broadcasting");
        mConnectedAssociationStore.addObserver(this);
        mActivityTaskManager.registerTaskStackListener(this);

        mIsBroadcasting = true;
    }

@@ -84,6 +93,7 @@ class TaskBroadcaster implements ConnectedAssociationStore.Observer {
        Slog.v(TAG, "Stopping broadcasting");
        mIsBroadcasting = false;
        mConnectedAssociationStore.removeObserver(this);
        mActivityTaskManager.unregisterTaskStackListener(this);
    }

    @Override
@@ -102,14 +112,33 @@ class TaskBroadcaster implements ConnectedAssociationStore.Observer {
            "Transport disconnected for association id: " + associationId);
    }

    @Override
    public void onTaskCreated(
        int taskId,
        ComponentName componentName) throws RemoteException {

        Slog.v(TAG, "onTaskCreated: taskId=" + taskId);

        ActivityManager.RunningTaskInfo taskInfo = getRunningTask(taskId);

        if (taskInfo != null) {
            RemoteTaskInfo remoteTaskInfo = new RemoteTaskInfo(taskInfo);
            RemoteTaskAddedMessage taskAddedMessage
                = new RemoteTaskAddedMessage(remoteTaskInfo);

            sendMessageToAllConnectedAssociations(taskAddedMessage);
        } else {
            Slog.w(TAG, "Could not find RunningTaskInfo for taskId: " + taskId);
        }
    }

    private void sendDeviceConnectedMessage(int associationId) {
        Slog.v(
            TAG,
            "Sending device connected message for association id: "
                + associationId);

        List<ActivityManager.RunningTaskInfo> runningTasks
            = mActivityTaskManager.getTasks(Integer.MAX_VALUE, true);
        List<ActivityManager.RunningTaskInfo> runningTasks = getRunningTasks();

        int currentForegroundTaskId = -1;
        if (runningTasks.size() > 0) {
@@ -127,7 +156,9 @@ class TaskBroadcaster implements ConnectedAssociationStore.Observer {
        sendMessage(associationId, deviceConnectedMessage);
    }

    private void sendMessage(int associationId, TaskContinuityMessageData data) {
    private void sendMessage(
        int associationId,
        TaskContinuityMessageData data) {

        Slog.v(
            TAG,
@@ -143,4 +174,36 @@ class TaskBroadcaster implements ConnectedAssociationStore.Observer {
            message.toBytes(),
            new int[] {associationId});
    }

    private void sendMessageToAllConnectedAssociations(
        TaskContinuityMessageData data) {

        Set<Integer> connectedAssociations
            = mConnectedAssociationStore.getConnectedAssociations();

        Slog.v(
            TAG,
            "Sending message to " + connectedAssociations.size() + " associations.");

        for (Integer associationId : connectedAssociations) {
            sendMessage(associationId, data);
        }
    }

    private ActivityManager.RunningTaskInfo getRunningTask(int taskId) {
        List<ActivityManager.RunningTaskInfo> runningTasks = getRunningTasks();
        if (runningTasks != null) {
            for (ActivityManager.RunningTaskInfo info : runningTasks) {
                if (info.taskId == taskId) {
                    return info;
                }
            }
        }

        return null;
    }

    private List<ActivityManager.RunningTaskInfo> getRunningTasks() {
        return mActivityTaskManager.getTasks(Integer.MAX_VALUE, true);
    }
}
 No newline at end of file
+53 −7
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.
@@ -24,8 +24,11 @@ import static org.mockito.Mockito.never;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;

import static com.android.server.companion.datatransfer.continuity.TaskContinuityTestUtils.createRunningTaskInfo;

import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.companion.IOnTransportsChangedListener;
@@ -41,16 +44,21 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.companion.datatransfer.continuity.connectivity.ConnectedAssociationStore;
import com.android.server.companion.datatransfer.continuity.messages.ContinuityDeviceConnected;
import com.android.server.companion.datatransfer.continuity.messages.TaskContinuityMessage;
import com.android.server.companion.datatransfer.continuity.messages.RemoteTaskAddedMessage;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Presubmit
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -113,11 +121,19 @@ public class TaskBroadcasterTest {
        mTaskBroadcaster.startBroadcasting();
        verify(mMockConnectedAssociationStore, times(1))
            .addObserver(mTaskBroadcaster);
        verify(mMockConnectedAssociationStore, times(1))
            .addObserver(mTaskBroadcaster);
        verify(mMockActivityTaskManager, times(1))
            .registerTaskStackListener(mTaskBroadcaster);

        // Stop broadcasting, verifying the association listener is removed.
        mTaskBroadcaster.stopBroadcasting();
        verify(mMockConnectedAssociationStore, times(1))
            .removeObserver(mTaskBroadcaster);
        verify(mMockActivityTaskManager, times(1))
            .unregisterTaskStackListener(mTaskBroadcaster);
        verify(mMockConnectedAssociationStore, times(1))
            .removeObserver(mTaskBroadcaster);
    }

    @Test
@@ -129,11 +145,7 @@ public class TaskBroadcasterTest {

        // Setup a fake foreground task.
        String expectedLabel = "test";
        ActivityManager.RunningTaskInfo taskInfo
            = new ActivityManager.RunningTaskInfo();
        taskInfo.taskId = 1;
        taskInfo.taskDescription
            = new ActivityManager.TaskDescription(expectedLabel);
        ActivityManager.RunningTaskInfo taskInfo = createRunningTaskInfo(1, expectedLabel, 0);

        when(mMockActivityTaskManager.getTasks(Integer.MAX_VALUE, true))
            .thenReturn(Arrays.asList(taskInfo));
@@ -162,4 +174,38 @@ public class TaskBroadcasterTest {
        assertThat(continuityDeviceConnected.getRemoteTasks().get(0).getLabel())
            .isEqualTo(expectedLabel);
    }

    @Test
    public void testOnTaskCreated_sendsMessageToAllAssociations() throws Exception {
        // Start broadcasting.
        mTaskBroadcaster.startBroadcasting();
        verify(mMockConnectedAssociationStore, times(1)).addObserver(mTaskBroadcaster);
        when(mMockConnectedAssociationStore.getConnectedAssociations())
            .thenReturn(new HashSet<>(Arrays.asList(1)));

        // Define a new task.
        String taskLabel = "newTask";
        int taskId = 123;
        ActivityManager.RunningTaskInfo taskInfo = createRunningTaskInfo(taskId, taskLabel, 0);

        // Mock ActivityTaskManager to return the new task.
        when(mMockActivityTaskManager.getTasks(Integer.MAX_VALUE, true))
                .thenReturn(List.of(taskInfo));

        // Notify TaskBroadcaster of the new task.
        mTaskBroadcaster.onTaskCreated(taskId, null);

        // Verify sendMessage is called
        ArgumentCaptor<byte[]> messageCaptor = ArgumentCaptor.forClass(byte[].class);
        verify(mMockCompanionDeviceManagerService, times(1)).sendMessage(
                eq(CompanionDeviceManager.MESSAGE_TASK_CONTINUITY),
                messageCaptor.capture(),
                any(int[].class));
        byte[] capturedMessage = messageCaptor.getValue();
        TaskContinuityMessage taskContinuityMessage = new TaskContinuityMessage(capturedMessage);
        assertThat(taskContinuityMessage.getData()).isInstanceOf(RemoteTaskAddedMessage.class);
        RemoteTaskAddedMessage remoteTaskAddedMessage =
                (RemoteTaskAddedMessage) taskContinuityMessage.getData();
        assertThat(remoteTaskAddedMessage.getTask().getId()).isEqualTo(taskId);
    }
}
 No newline at end of file
+34 −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.server.companion.datatransfer.continuity;

import android.app.ActivityManager;

public final class TaskContinuityTestUtils {

    public static ActivityManager.RunningTaskInfo createRunningTaskInfo(
        int taskId,
        String label,
        long lastActiveTime) {

        ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
        taskInfo.taskId = taskId;
        taskInfo.taskDescription = new ActivityManager.TaskDescription(label);
        taskInfo.lastActiveTime = lastActiveTime;
        return taskInfo;
    }
}
 No newline at end of file
+9 −11
Original line number Diff line number Diff line
@@ -17,8 +17,8 @@
package com.android.server.companion.datatransfer.continuity.messages;

import static com.google.common.truth.Truth.assertThat;
import static com.android.server.companion.datatransfer.continuity.TaskContinuityTestUtils.createRunningTaskInfo;

import android.app.ActivityManager;
import android.app.TaskInfo;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
@@ -40,11 +40,10 @@ public class RemoteTaskInfoTest {
        String expectedLabel = "test";
        long expectedLastActiveTime = 0;

        TaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
        taskInfo.taskId = expectedId;
        taskInfo.taskDescription
            = new ActivityManager.TaskDescription(expectedLabel);
        taskInfo.lastActiveTime = expectedLastActiveTime;
        TaskInfo taskInfo = createRunningTaskInfo(
            expectedId,
            expectedLabel,
            expectedLastActiveTime);

        RemoteTaskInfo remoteTaskInfo = new RemoteTaskInfo(taskInfo);

@@ -102,12 +101,11 @@ public class RemoteTaskInfoTest {
        int expectedId = 1;
        String expectedLabel = "test";
        long expectedLastActiveTime = 1;
        TaskInfo taskInfo = createRunningTaskInfo(
            expectedId,
            expectedLabel,
            expectedLastActiveTime);

       TaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
        taskInfo.taskId = expectedId;
        taskInfo.taskDescription
            = new ActivityManager.TaskDescription(expectedLabel);
        taskInfo.lastActiveTime = expectedLastActiveTime;
        RemoteTaskInfo remoteTaskInfo = new RemoteTaskInfo(taskInfo);

        ProtoOutputStream pos = new ProtoOutputStream();
+5 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.companion.datatransfer.continuity.tasks;

import static com.google.common.truth.Truth.assertThat;
import static com.android.server.companion.datatransfer.continuity.TaskContinuityTestUtils.createRunningTaskInfo;

import android.app.ActivityManager;
import android.platform.test.annotations.Presubmit;
@@ -108,13 +109,11 @@ public class RemoteDeviceTaskListTest {
        String label,
        long lastUsedTimeMillis) {

        ActivityManager.RunningTaskInfo runningTaskInfo
            = new ActivityManager.RunningTaskInfo();
        ActivityManager.RunningTaskInfo runningTaskInfo = createRunningTaskInfo(
            1,
            label,
            lastUsedTimeMillis);

        runningTaskInfo.taskId = 1;
        runningTaskInfo.taskDescription
            = new ActivityManager.TaskDescription(label);
        runningTaskInfo.lastActiveTime = lastUsedTimeMillis;
        return new RemoteTaskInfo(runningTaskInfo);
    }
}
 No newline at end of file
Loading