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

Commit 8be94fe7 authored by Joe Antonetti's avatar Joe Antonetti
Browse files

Synchronize Remote Task Information on Connection

When first connected to a device, pull tasks from ActivityTaskManager and send them to the other device.

Flag: android.companion.enable_task_continuity
Test: Added unit tests
Bug: 400970610

Change-Id: I3aa81fd4c38a11973f3f1d98f7d104dfb7559cfa
parent 4f946451
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -29,4 +29,12 @@ message TaskContinuityMessage {

message ContinuityDeviceConnected {
    int32 currentForegroundTaskId = 1;
    repeated RemoteTaskInfo remoteTasks = 2;
}

message RemoteTaskInfo {
    int32 id = 1;
    string label = 2;
    int64 lastUsedTimeMillis = 3;
    bytes taskIcon = 4;
}
+9 −1
Original line number Diff line number Diff line
@@ -26,9 +26,11 @@ import android.content.Context;
import android.util.Slog;

import com.android.server.companion.datatransfer.continuity.messages.ContinuityDeviceConnected;
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;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -122,13 +124,19 @@ class TaskBroadcaster {
            currentForegroundTaskId = runningTasks.get(0).taskId;
        }

        List<RemoteTaskInfo> remoteTasks = new ArrayList<>();
        for (ActivityManager.RunningTaskInfo taskInfo : runningTasks) {
            remoteTasks.add(new RemoteTaskInfo(taskInfo));
        }

        ContinuityDeviceConnected deviceConnectedMessage =
            new ContinuityDeviceConnected(currentForegroundTaskId);
            new ContinuityDeviceConnected(currentForegroundTaskId, remoteTasks);

        sendMessage(associationId, deviceConnectedMessage);
    }

    private void sendMessage(int associationId, TaskContinuityMessageData data) {

        Slog.v(
            TAG,
            "Sending message to association id: "
+32 −1
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoParseException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Deserialized version of the {@link ContinuityDeviceConnected} proto.
@@ -28,15 +30,21 @@ import java.io.IOException;
public class ContinuityDeviceConnected implements TaskContinuityMessageData {

    private int mCurrentForegroundTaskId = 0;
    private List<RemoteTaskInfo> mRemoteTasks;

    public ContinuityDeviceConnected(
        int currentForegroundTaskId,
        List<RemoteTaskInfo> remoteTasks) {

    public ContinuityDeviceConnected(int currentForegroundTaskId) {
        mCurrentForegroundTaskId = currentForegroundTaskId;
        mRemoteTasks = remoteTasks;
    }

    ContinuityDeviceConnected(ProtoInputStream pis)
        throws IOException, ProtoParseException {

        boolean hasReadForegroundTaskId = false;
        List<RemoteTaskInfo> remoteTasks = new ArrayList<>();
        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
            switch (pis.getFieldNumber()) {
                case (int) android.companion.ContinuityDeviceConnected.CURRENT_FOREGROUND_TASK_ID:
@@ -46,6 +54,13 @@ public class ContinuityDeviceConnected implements TaskContinuityMessageData {

                    hasReadForegroundTaskId = true;
                    break;

                case (int) android.companion.ContinuityDeviceConnected.REMOTE_TASKS:
                    final long remoteTasksToken = pis.start(
                        android.companion.ContinuityDeviceConnected.REMOTE_TASKS);
                    remoteTasks.add(new RemoteTaskInfo(pis));
                    pis.end(remoteTasksToken);
                    break;
            }
        }

@@ -53,6 +68,8 @@ public class ContinuityDeviceConnected implements TaskContinuityMessageData {
            throw new ProtoParseException(
                "Missing required field: current_foreground_task_id");
        }

        mRemoteTasks = remoteTasks;
    }

    /**
@@ -62,6 +79,13 @@ public class ContinuityDeviceConnected implements TaskContinuityMessageData {
        return mCurrentForegroundTaskId;
    }

    /**
     * Gets which remote tasks are running on the device.
     */
    public List<RemoteTaskInfo> getRemoteTasks() {
        return mRemoteTasks;
    }

    /**
     * Writes this object to a proto output stream.
     */
@@ -70,5 +94,12 @@ public class ContinuityDeviceConnected implements TaskContinuityMessageData {
        pos.writeInt32(
            android.companion.ContinuityDeviceConnected.CURRENT_FOREGROUND_TASK_ID,
            mCurrentForegroundTaskId);

        for (RemoteTaskInfo remoteTaskInfo : mRemoteTasks) {
            long remoteTasksToken = pos.start(
                android.companion.ContinuityDeviceConnected.REMOTE_TASKS);
            remoteTaskInfo.writeToProto(pos);
            pos.end(remoteTasksToken);
        }
    }
}
 No newline at end of file
+94 −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.messages;

import android.app.TaskInfo;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoParseException;

import java.io.IOException;

public class RemoteTaskInfo {

    private int mId = 0;
    private String mLabel = "";
    private long mLastUsedTimeMillis = 0;
    private byte[] mTaskIcon = new byte[0];

    public RemoteTaskInfo(TaskInfo taskInfo) {
        mId = taskInfo.taskId;
        mLabel = taskInfo.taskDescription.getLabel().toString();
        mLastUsedTimeMillis = taskInfo.lastActiveTime;
    }

    public RemoteTaskInfo(ProtoInputStream protoInputStream)
        throws IOException, ProtoParseException {

        while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
            switch (protoInputStream.getFieldNumber()) {
              case (int) android.companion.RemoteTaskInfo.ID:
                    mId = protoInputStream.readInt(
                        android.companion.RemoteTaskInfo.ID);

                    break;
                case (int) android.companion.RemoteTaskInfo.LABEL:
                    mLabel = protoInputStream.readString(
                        android.companion.RemoteTaskInfo.LABEL);

                    break;
                case (int) android.companion.RemoteTaskInfo.LAST_USED_TIME_MILLIS:
                    mLastUsedTimeMillis = protoInputStream.readLong(
                        android.companion.RemoteTaskInfo.LAST_USED_TIME_MILLIS);
                    break;
                case (int) android.companion.RemoteTaskInfo.TASK_ICON:
                    mTaskIcon = protoInputStream
                        .readBytes(android.companion.RemoteTaskInfo.TASK_ICON);
                    break;
            }
       }
   }

    public int getId() {
        return mId;
    }

    public String getLabel() {
        return mLabel;
    }

    public long getLastUsedTimeMillis() {
        return mLastUsedTimeMillis;
    }

    public byte[] getTaskIcon() {
        return mTaskIcon;
    }

    public void writeToProto(ProtoOutputStream protoOutputStream) {
        protoOutputStream
            .writeInt32(android.companion.RemoteTaskInfo.ID, mId);
        protoOutputStream
            .writeString(android.companion.RemoteTaskInfo.LABEL, mLabel);
        protoOutputStream
            .writeInt64(
                android.companion.RemoteTaskInfo.LAST_USED_TIME_MILLIS,
                mLastUsedTimeMillis);
        protoOutputStream
            .writeBytes(android.companion.RemoteTaskInfo.TASK_ICON, mTaskIcon);
    }
}
 No newline at end of file
+9 −0
Original line number Diff line number Diff line
@@ -132,9 +132,13 @@ public class TaskBroadcasterTest {
        IOnTransportsChangedListener listener = listenerCaptor.getValue();

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

        when(mMockActivityTaskManager.getTasks(Integer.MAX_VALUE, true))
            .thenReturn(Arrays.asList(taskInfo));

@@ -161,5 +165,10 @@ public class TaskBroadcasterTest {
            = (ContinuityDeviceConnected) taskContinuityMessage.getData();
        assertThat(continuityDeviceConnected.getCurrentForegroundTaskId())
            .isEqualTo(taskInfo.taskId);
        assertThat(continuityDeviceConnected.getRemoteTasks()).hasSize(1);
        assertThat(continuityDeviceConnected.getRemoteTasks().get(0).getId())
            .isEqualTo(taskInfo.taskId);
        assertThat(continuityDeviceConnected.getRemoteTasks().get(0).getLabel())
            .isEqualTo(expectedLabel);
    }
}
 No newline at end of file
Loading