Loading services/companion/java/com/android/server/companion/datatransfer/continuity/TaskContinuityManagerService.java +2 −0 Original line number Diff line number Diff line Loading @@ -85,10 +85,12 @@ public final class TaskContinuityManagerService extends SystemService { @Override public void registerRemoteTaskListener(@NonNull IRemoteTaskListener listener) { mRemoteTaskStore.addListener(listener); } @Override public void unregisterRemoteTaskListener(@NonNull IRemoteTaskListener listener) { mRemoteTaskStore.removeListener(listener); } @Override Loading services/companion/java/com/android/server/companion/datatransfer/continuity/tasks/RemoteDeviceTaskList.java +65 −19 Original line number Diff line number Diff line Loading @@ -17,26 +17,52 @@ package com.android.server.companion.datatransfer.continuity.tasks; import android.companion.datatransfer.continuity.RemoteTask; import android.util.Slog; import com.android.server.companion.datatransfer.continuity.messages.RemoteTaskInfo; import java.util.function.Consumer; import java.util.ArrayList; import java.util.List; import java.util.Comparator; import java.util.HashSet; import java.util.PriorityQueue; import java.util.Set; /** * Tracks remote tasks currently available on a specific remote device. */ class RemoteDeviceTaskList { private static final String TAG = "RemoteDeviceTaskList"; private final int mAssociationId; private final String mDeviceName; private final List<RemoteTaskInfo> mTasks = new ArrayList<>(); private final Consumer<RemoteTask> mOnMostRecentTaskChangedListener; private PriorityQueue<RemoteTaskInfo> mTasks; RemoteDeviceTaskList( int associationId, String deviceName) { String deviceName, Consumer<RemoteTask> onMostRecentTaskChangedListener) { mAssociationId = associationId; mDeviceName = deviceName; mOnMostRecentTaskChangedListener = onMostRecentTaskChangedListener; mTasks = new PriorityQueue<>(new Comparator<RemoteTaskInfo>() { @Override public int compare(RemoteTaskInfo task1, RemoteTaskInfo task2) { long lastUsedTime1 = task1.getLastUsedTimeMillis(); long lastUsedTime2 = task2.getLastUsedTimeMillis(); if (lastUsedTime1 < lastUsedTime2) { return 1; } else if (lastUsedTime1 > lastUsedTime2) { return -1; } else { return 0; } } }); } /** Loading @@ -58,22 +84,49 @@ class RemoteDeviceTaskList { * device. */ void addTask(RemoteTaskInfo taskInfo) { synchronized (mTasks) { Slog.v(TAG, "Adding task: " + taskInfo.getId() + " to association: " + mAssociationId); int previousTopTaskId = mTasks.peek() == null ? -1 : mTasks.peek().getId(); mTasks.add(taskInfo); if (taskInfo.getId() != previousTopTaskId) { Slog.v( TAG, "Notifying most recent task changed for association: " + mAssociationId); mOnMostRecentTaskChangedListener.accept(getMostRecentTask()); } } } /** * Sets the list of tasks currently available on the remote device. */ void setTasks(List<RemoteTaskInfo> tasks) { synchronized (mTasks) { Slog.v(TAG, "Setting remote tasks for association: " + mAssociationId); mTasks.clear(); mTasks.addAll(tasks); mOnMostRecentTaskChangedListener.accept(getMostRecentTask()); } } /** * Removes a task from the list of tasks currently available on the remote device. */ void removeTask(int taskId) { synchronized (mTasks) { Slog.v(TAG, "Removing task: " + taskId + " for association: " + mAssociationId); boolean shouldNotifyListeners = (mTasks.peek() != null && mTasks.peek().getId() == taskId); mTasks.removeIf(task -> task.getId() == taskId); if (shouldNotifyListeners) { Slog.v( TAG, "Notifying most recent task changed for association: " + mAssociationId); mOnMostRecentTaskChangedListener.accept(getMostRecentTask()); } } } /** Loading @@ -81,20 +134,13 @@ class RemoteDeviceTaskList { * tasks. */ RemoteTask getMostRecentTask() { synchronized (mTasks) { Slog.v(TAG, "Getting most recent task for association: " + mAssociationId); if (mTasks.isEmpty()) { return null; } RemoteTaskInfo mostRecentTask = mTasks.get(0); for (RemoteTaskInfo task : mTasks) { if ( task.getLastUsedTimeMillis() > mostRecentTask.getLastUsedTimeMillis()) { mostRecentTask = task; } return mTasks.peek().toRemoteTask(mAssociationId, mDeviceName); } return mostRecentTask.toRemoteTask(mAssociationId, mDeviceName); } } No newline at end of file services/companion/java/com/android/server/companion/datatransfer/continuity/tasks/RemoteTaskStore.java +46 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.server.companion.datatransfer.continuity.tasks; import android.companion.AssociationInfo; import android.companion.datatransfer.continuity.IRemoteTaskListener; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Slog; import com.android.server.companion.datatransfer.continuity.connectivity.ConnectedAssociationStore; Loading @@ -35,6 +38,8 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { private final ConnectedAssociationStore mConnectedAssociationStore; private final Map<Integer, RemoteDeviceTaskList> mRemoteDeviceTaskLists = new HashMap<>(); private final RemoteCallbackList<IRemoteTaskListener> mRemoteTaskListeners = new RemoteCallbackList<>(); public RemoteTaskStore( ConnectedAssociationStore connectedAssociationStore) { Loading @@ -57,7 +62,8 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { if (!mRemoteDeviceTaskLists.containsKey(associationId)) { Slog.e( TAG, "Attempted to set tasks for association: " + associationId + " which is not connected."); "Attempted to set tasks for association: " + associationId + " which is not connected."); return; } Loading Loading @@ -110,18 +116,32 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { } } public void addListener(IRemoteTaskListener listener) { synchronized (mRemoteTaskListeners) { mRemoteTaskListeners.register(listener); } } public void removeListener(IRemoteTaskListener listener) { synchronized (mRemoteTaskListeners) { mRemoteTaskListeners.unregister(listener); } } @Override public void onTransportConnected(AssociationInfo associationInfo) { synchronized (mRemoteDeviceTaskLists) { if (!mRemoteDeviceTaskLists.containsKey(associationInfo.getId())) { Slog.v( TAG, "Creating new RemoteDeviceTaskList for association: " + associationInfo.getId()); "Creating new RemoteDeviceTaskList for association: " + associationInfo.getId()); RemoteDeviceTaskList taskList = new RemoteDeviceTaskList( associationInfo.getId(), associationInfo.getDisplayName().toString()); associationInfo.getDisplayName().toString(), this::onMostRecentTaskChanged); mRemoteDeviceTaskLists.put(associationInfo.getId(), taskList); } else { Loading @@ -140,6 +160,29 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { "Deleting RemoteDeviceTaskList for association: " + associationId); mRemoteDeviceTaskLists.remove(associationId); notifyListeners(); } } private void onMostRecentTaskChanged(RemoteTask task) { notifyListeners(); } private void notifyListeners() { synchronized (mRemoteTaskListeners) { List<RemoteTask> remoteTasks = getMostRecentTasks(); int i = mRemoteTaskListeners.beginBroadcast(); while (i > 0) { i--; try { mRemoteTaskListeners .getBroadcastItem(i) .onRemoteTasksChanged(remoteTasks); } catch (RemoteException e) { Slog.e(TAG, "Failed to notify listener: " + e.getMessage()); } } mRemoteTaskListeners.finishBroadcast(); } } } No newline at end of file services/tests/servicestests/src/com/android/server/companion/datatransfer/continuity/tasks/RemoteDeviceTaskListTest.java +81 −39 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.testing.AndroidTestingRunner; import com.android.server.companion.datatransfer.continuity.messages.RemoteTaskInfo; import org.junit.Before; import org.junit.Test; import org.junit.Before; import org.junit.runner.RunWith; Loading @@ -38,6 +39,9 @@ import java.util.List; @RunWith(AndroidTestingRunner.class) public class RemoteDeviceTaskListTest { private RemoteTask mMostRecentTask; private int mObserverCallCount; private static final int ASSOCIATION_ID = 123; private static final String DEVICE_NAME = "device1"; Loading @@ -45,7 +49,13 @@ public class RemoteDeviceTaskListTest { @Before public void setUp() { taskList = new RemoteDeviceTaskList(ASSOCIATION_ID, DEVICE_NAME); mMostRecentTask = null; mObserverCallCount = 0; taskList = new RemoteDeviceTaskList( ASSOCIATION_ID, DEVICE_NAME, this::onMostRecentTaskChanged); } @Test Loading @@ -56,27 +66,37 @@ public class RemoteDeviceTaskListTest { } @Test public void testAddTask_updatesMostRecentTask() { RemoteTaskInfo firstAddedTask = createNewRemoteTaskInfo(2, "task2", 200); taskList.addTask(firstAddedTask); public void testAddTask_updatesMostRecentTaskAndNotifiesListeners() { // Before adding any tasks, the most recent task should be null. assertThat(taskList.getMostRecentTask()).isNull(); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstAddedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); // Add a task, verify it automatically becomes the most recent task. RemoteTaskInfo firstAddedTaskInfo = createNewRemoteTaskInfo(2, "task2", 200); RemoteTask firstAddedTask = firstAddedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.addTask(firstAddedTaskInfo); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask).isEqualTo(firstAddedTask); assertThat(taskList.getMostRecentTask()).isEqualTo(firstAddedTask); // Add another task with an older timestamp, verify it doesn't update // the most recent task. RemoteTaskInfo secondAddedTask = createNewRemoteTaskInfo(1, "task1", 100); taskList.addTask(secondAddedTask); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstAddedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); RemoteTaskInfo secondAddedTaskInfo = createNewRemoteTaskInfo(1, "task1", 100); taskList.addTask(secondAddedTaskInfo); assertThat(taskList.getMostRecentTask()).isEqualTo(firstAddedTask); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(firstAddedTask); // Add another task with a newer timestamp, verifying it changes the // most recently used task. RemoteTaskInfo thirdAddedTask = createNewRemoteTaskInfo(3, "task3", 300); taskList.addTask(thirdAddedTask); assertThat(taskList.getMostRecentTask()) .isEqualTo(thirdAddedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); RemoteTaskInfo thirdAddedTaskInfo = createNewRemoteTaskInfo(3, "task3", 300); RemoteTask thirdAddedTask = thirdAddedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.addTask(thirdAddedTaskInfo); assertThat(taskList.getMostRecentTask()).isEqualTo(thirdAddedTask); assertThat(mObserverCallCount).isEqualTo(3); assertThat(mMostRecentTask).isEqualTo(thirdAddedTask); } @Test Loading @@ -94,12 +114,11 @@ public class RemoteDeviceTaskListTest { taskList.setTasks(initialTasks); assertThat(taskList.getMostRecentTask()) .isEqualTo(expectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask().getId()).isEqualTo(expectedTask.getId()); } @Test public void testRemoveTask_removesTask() { public void testRemoveTask_removesTaskAndNotifiesListeners() { RemoteTaskInfo mostRecentTaskInfo = createNewRemoteTaskInfo(1, "task2", 200); RemoteTask mostRecentTask = mostRecentTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); RemoteTaskInfo secondMostRecentTaskInfo = createNewRemoteTaskInfo(2, "task1", 100); Loading @@ -107,57 +126,75 @@ public class RemoteDeviceTaskListTest { = secondMostRecentTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(Arrays.asList(mostRecentTaskInfo, secondMostRecentTaskInfo)); assertThat(taskList.getMostRecentTask()) .isEqualTo(mostRecentTask); assertThat(taskList.getMostRecentTask()).isEqualTo(mostRecentTask); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask).isEqualTo(mostRecentTask); taskList.removeTask(mostRecentTask.getId()); assertThat(taskList.getMostRecentTask()) .isEqualTo(secondMostRecentTask); assertThat(taskList.getMostRecentTask()).isEqualTo(secondMostRecentTask); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(secondMostRecentTask); } @Test public void testSetTasks_updatesMostRecentTask() { public void testSetTasks_updatesMostRecentTaskAndNotifiesListeners() { // Set tasks initially, verify the most recent task is the first one. RemoteTaskInfo firstExpectedTask = createNewRemoteTaskInfo(1, "task2", 200); RemoteTaskInfo firstExpectedTaskInfo = createNewRemoteTaskInfo(1, "task2", 200); RemoteTask firstExpectedTask = firstExpectedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); List<RemoteTaskInfo> initialTasks = Arrays.asList( createNewRemoteTaskInfo(2, "task1", 100), firstExpectedTask, firstExpectedTaskInfo, createNewRemoteTaskInfo(3, "task3", 150)); taskList.setTasks(initialTasks); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask().getId()).isEqualTo(firstExpectedTask.getId()); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask.getId()).isEqualTo(firstExpectedTask.getId()); // Set the tasks to a different list, verify the most recent task is the // first one. RemoteTaskInfo secondExpectedTask = createNewRemoteTaskInfo(4, "task4", 300); RemoteTaskInfo secondExpectedTaskInfo = createNewRemoteTaskInfo(4, "task4", 300); List<RemoteTaskInfo> secondExpectedTasks = Arrays.asList( secondExpectedTask, secondExpectedTaskInfo, createNewRemoteTaskInfo(7, "task7", 200), createNewRemoteTaskInfo(5, "task5", 200), createNewRemoteTaskInfo(6, "task6", 100)); RemoteTask secondExpectedTask = secondExpectedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(secondExpectedTasks); assertThat(taskList.getMostRecentTask()) .isEqualTo(secondExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(secondExpectedTask); assertThat(taskList.getMostRecentTask()).isEqualTo(secondExpectedTask); } @Test public void testSetTasks_overwritesExistingTasks() { public void testSetTasks_overwritesExistingTasksAndNotifiesListeners() { // Set the initial state of the list. RemoteTaskInfo firstExpectedTask = createNewRemoteTaskInfo(1, "task1", 100); RemoteTask firstExpectedRemoteTask = firstExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(Arrays.asList(firstExpectedTask)); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask()).isEqualTo(firstExpectedRemoteTask); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask).isEqualTo(firstExpectedRemoteTask); // Replace the tasks with a different list. The only task in this was used before the // previous task. RemoteTaskInfo secondExpectedTask = createNewRemoteTaskInfo(2, "task2", 10); RemoteTaskInfo secondExpectedTask = createNewRemoteTaskInfo(2, "task2", 200); RemoteTask secondExpectedRemoteTask = secondExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(Arrays.asList(secondExpectedTask)); // Because the task list is overwritten, the most recent task should be the second task. assertThat(taskList.getMostRecentTask()) .isEqualTo(secondExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask()).isEqualTo(secondExpectedRemoteTask); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(secondExpectedRemoteTask); } private RemoteTaskInfo createNewRemoteTaskInfo( Loading @@ -172,4 +209,9 @@ public class RemoteDeviceTaskListTest { return new RemoteTaskInfo(runningTaskInfo); } public void onMostRecentTaskChanged(RemoteTask task) { mMostRecentTask = task; mObserverCallCount++; } } No newline at end of file services/tests/servicestests/src/com/android/server/companion/datatransfer/continuity/tasks/RemoteTaskStoreTest.java +40 −7 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static com.android.server.companion.datatransfer.continuity.TaskContinuit import android.app.ActivityManager; import android.companion.AssociationInfo; import android.companion.datatransfer.continuity.RemoteTask; import android.companion.datatransfer.continuity.IRemoteTaskListener; import android.platform.test.annotations.Presubmit; import android.testing.AndroidTestingRunner; Loading @@ -42,6 +43,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.List; Loading @@ -52,13 +54,23 @@ public class RemoteTaskStoreTest { @Mock private ConnectedAssociationStore mMockConnectedAssociationStore; private final IRemoteTaskListener mRemoteTaskListener = new IRemoteTaskListener.Stub() { @Override public void onRemoteTasksChanged(List<RemoteTask> remoteTasks) { remoteTasksReportedToListener.add(remoteTasks); } }; private final List<List<RemoteTask>> remoteTasksReportedToListener = new ArrayList<>(); private RemoteTaskStore taskStore; @Before public void setUp() { MockitoAnnotations.initMocks(this); remoteTasksReportedToListener.clear(); taskStore = new RemoteTaskStore(mMockConnectedAssociationStore); taskStore.addListener(mRemoteTaskListener); } @Test Loading @@ -68,20 +80,25 @@ public class RemoteTaskStoreTest { } @Test public void onTransportConnected_addsNewAssociation() { public void onTransportConnected_addsNewAssociationAndNotifiesListeners() { // Simulate a new association being connected. AssociationInfo associationInfo = createAssociationInfo(1, "name"); taskStore.onTransportConnected(associationInfo); assertThat(remoteTasksReportedToListener).hasSize(0); // Add tasks to the new association. RemoteTaskInfo remoteTaskInfo = createNewRemoteTaskInfo(1, "task1", 100L); RemoteTask remoteTask = remoteTaskInfo.toRemoteTask(associationInfo.getId(), "name"); taskStore.setTasks( associationInfo.getId(), Collections.singletonList(remoteTaskInfo)); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)).containsExactly(remoteTask); // Verify the most recent task is added to the task store. assertThat(taskStore.getMostRecentTasks()) .containsExactly(remoteTaskInfo.toRemoteTask(associationInfo.getId(), "name")); assertThat(taskStore.getMostRecentTasks()).containsExactly(remoteTask); } @Test Loading @@ -96,6 +113,7 @@ public class RemoteTaskStoreTest { taskStore.setTasks(0, Collections.singletonList(remoteTaskInfo)); assertThat(taskStore.getMostRecentTasks()).isEmpty(); assertThat(remoteTasksReportedToListener).isEmpty(); } @Test Loading @@ -117,32 +135,43 @@ public class RemoteTaskStoreTest { assertThat(taskStore.getMostRecentTasks()) .containsExactly(mostRecentTask); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)).containsExactly(mostRecentTask); taskStore.removeTask(associationInfo.getId(), mostRecentTaskInfo.getId()); assertThat(taskStore.getMostRecentTasks()).containsExactly(secondMostRecentTask); assertThat(remoteTasksReportedToListener).hasSize(2); assertThat(remoteTasksReportedToListener.get(1)) .containsExactly(secondMostRecentTask); } @Test public void onTransportDisconnected_removesAssociation() { public void onTransportDisconnected_removesAssociationAndNotifiesListeners() { // Create a fake association info, and have connected association store // return it. AssociationInfo associationInfo = createAssociationInfo(1, "name"); when(mMockConnectedAssociationStore.getConnectedAssociationById(1)) .thenReturn(associationInfo); taskStore.onTransportConnected(associationInfo); // Set tasks for the association. RemoteTaskInfo remoteTaskInfo = createNewRemoteTaskInfo(1, "task1", 100L); taskStore.setTasks(0, Collections.singletonList(remoteTaskInfo)); taskStore.setTasks(associationInfo.getId(), Collections.singletonList(remoteTaskInfo)); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)) .containsExactly(remoteTaskInfo.toRemoteTask(1, "name")); // Simulate the association being disconnected. taskStore.onTransportDisconnected(0); taskStore.onTransportDisconnected(associationInfo.getId()); // Verify the most recent task is added to the task store. assertThat(taskStore.getMostRecentTasks()).isEmpty(); assertThat(remoteTasksReportedToListener).hasSize(2); assertThat(remoteTasksReportedToListener.get(1)).isEmpty(); } @Test public void addTask_addsTaskToAssociation() { public void addTask_addsTaskToAssociationAndNotifiesListeners() { // Create a fake association info, and have connected association store return it. AssociationInfo associationInfo = createAssociationInfo(1, "name"); when(mMockConnectedAssociationStore.getConnectedAssociationById(1)) Loading @@ -153,6 +182,8 @@ public class RemoteTaskStoreTest { RemoteTask remoteTask = remoteTaskInfo.toRemoteTask(associationInfo.getId(), "name"); taskStore.setTasks(1, Collections.singletonList(remoteTaskInfo)); assertThat(taskStore.getMostRecentTasks()).containsExactly(remoteTask); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)).containsExactly(remoteTask); // Add a new task to the association. RemoteTaskInfo newRemoteTaskInfo = createNewRemoteTaskInfo(2, "task2", 200L); Loading @@ -161,6 +192,8 @@ public class RemoteTaskStoreTest { // Verify the most recent tasks are added to the task store. assertThat(taskStore.getMostRecentTasks()).containsExactly(newRemoteTask); assertThat(remoteTasksReportedToListener).hasSize(2); assertThat(remoteTasksReportedToListener.get(1)).containsExactly(newRemoteTask); } @Test Loading Loading
services/companion/java/com/android/server/companion/datatransfer/continuity/TaskContinuityManagerService.java +2 −0 Original line number Diff line number Diff line Loading @@ -85,10 +85,12 @@ public final class TaskContinuityManagerService extends SystemService { @Override public void registerRemoteTaskListener(@NonNull IRemoteTaskListener listener) { mRemoteTaskStore.addListener(listener); } @Override public void unregisterRemoteTaskListener(@NonNull IRemoteTaskListener listener) { mRemoteTaskStore.removeListener(listener); } @Override Loading
services/companion/java/com/android/server/companion/datatransfer/continuity/tasks/RemoteDeviceTaskList.java +65 −19 Original line number Diff line number Diff line Loading @@ -17,26 +17,52 @@ package com.android.server.companion.datatransfer.continuity.tasks; import android.companion.datatransfer.continuity.RemoteTask; import android.util.Slog; import com.android.server.companion.datatransfer.continuity.messages.RemoteTaskInfo; import java.util.function.Consumer; import java.util.ArrayList; import java.util.List; import java.util.Comparator; import java.util.HashSet; import java.util.PriorityQueue; import java.util.Set; /** * Tracks remote tasks currently available on a specific remote device. */ class RemoteDeviceTaskList { private static final String TAG = "RemoteDeviceTaskList"; private final int mAssociationId; private final String mDeviceName; private final List<RemoteTaskInfo> mTasks = new ArrayList<>(); private final Consumer<RemoteTask> mOnMostRecentTaskChangedListener; private PriorityQueue<RemoteTaskInfo> mTasks; RemoteDeviceTaskList( int associationId, String deviceName) { String deviceName, Consumer<RemoteTask> onMostRecentTaskChangedListener) { mAssociationId = associationId; mDeviceName = deviceName; mOnMostRecentTaskChangedListener = onMostRecentTaskChangedListener; mTasks = new PriorityQueue<>(new Comparator<RemoteTaskInfo>() { @Override public int compare(RemoteTaskInfo task1, RemoteTaskInfo task2) { long lastUsedTime1 = task1.getLastUsedTimeMillis(); long lastUsedTime2 = task2.getLastUsedTimeMillis(); if (lastUsedTime1 < lastUsedTime2) { return 1; } else if (lastUsedTime1 > lastUsedTime2) { return -1; } else { return 0; } } }); } /** Loading @@ -58,22 +84,49 @@ class RemoteDeviceTaskList { * device. */ void addTask(RemoteTaskInfo taskInfo) { synchronized (mTasks) { Slog.v(TAG, "Adding task: " + taskInfo.getId() + " to association: " + mAssociationId); int previousTopTaskId = mTasks.peek() == null ? -1 : mTasks.peek().getId(); mTasks.add(taskInfo); if (taskInfo.getId() != previousTopTaskId) { Slog.v( TAG, "Notifying most recent task changed for association: " + mAssociationId); mOnMostRecentTaskChangedListener.accept(getMostRecentTask()); } } } /** * Sets the list of tasks currently available on the remote device. */ void setTasks(List<RemoteTaskInfo> tasks) { synchronized (mTasks) { Slog.v(TAG, "Setting remote tasks for association: " + mAssociationId); mTasks.clear(); mTasks.addAll(tasks); mOnMostRecentTaskChangedListener.accept(getMostRecentTask()); } } /** * Removes a task from the list of tasks currently available on the remote device. */ void removeTask(int taskId) { synchronized (mTasks) { Slog.v(TAG, "Removing task: " + taskId + " for association: " + mAssociationId); boolean shouldNotifyListeners = (mTasks.peek() != null && mTasks.peek().getId() == taskId); mTasks.removeIf(task -> task.getId() == taskId); if (shouldNotifyListeners) { Slog.v( TAG, "Notifying most recent task changed for association: " + mAssociationId); mOnMostRecentTaskChangedListener.accept(getMostRecentTask()); } } } /** Loading @@ -81,20 +134,13 @@ class RemoteDeviceTaskList { * tasks. */ RemoteTask getMostRecentTask() { synchronized (mTasks) { Slog.v(TAG, "Getting most recent task for association: " + mAssociationId); if (mTasks.isEmpty()) { return null; } RemoteTaskInfo mostRecentTask = mTasks.get(0); for (RemoteTaskInfo task : mTasks) { if ( task.getLastUsedTimeMillis() > mostRecentTask.getLastUsedTimeMillis()) { mostRecentTask = task; } return mTasks.peek().toRemoteTask(mAssociationId, mDeviceName); } return mostRecentTask.toRemoteTask(mAssociationId, mDeviceName); } } No newline at end of file
services/companion/java/com/android/server/companion/datatransfer/continuity/tasks/RemoteTaskStore.java +46 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.server.companion.datatransfer.continuity.tasks; import android.companion.AssociationInfo; import android.companion.datatransfer.continuity.IRemoteTaskListener; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Slog; import com.android.server.companion.datatransfer.continuity.connectivity.ConnectedAssociationStore; Loading @@ -35,6 +38,8 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { private final ConnectedAssociationStore mConnectedAssociationStore; private final Map<Integer, RemoteDeviceTaskList> mRemoteDeviceTaskLists = new HashMap<>(); private final RemoteCallbackList<IRemoteTaskListener> mRemoteTaskListeners = new RemoteCallbackList<>(); public RemoteTaskStore( ConnectedAssociationStore connectedAssociationStore) { Loading @@ -57,7 +62,8 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { if (!mRemoteDeviceTaskLists.containsKey(associationId)) { Slog.e( TAG, "Attempted to set tasks for association: " + associationId + " which is not connected."); "Attempted to set tasks for association: " + associationId + " which is not connected."); return; } Loading Loading @@ -110,18 +116,32 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { } } public void addListener(IRemoteTaskListener listener) { synchronized (mRemoteTaskListeners) { mRemoteTaskListeners.register(listener); } } public void removeListener(IRemoteTaskListener listener) { synchronized (mRemoteTaskListeners) { mRemoteTaskListeners.unregister(listener); } } @Override public void onTransportConnected(AssociationInfo associationInfo) { synchronized (mRemoteDeviceTaskLists) { if (!mRemoteDeviceTaskLists.containsKey(associationInfo.getId())) { Slog.v( TAG, "Creating new RemoteDeviceTaskList for association: " + associationInfo.getId()); "Creating new RemoteDeviceTaskList for association: " + associationInfo.getId()); RemoteDeviceTaskList taskList = new RemoteDeviceTaskList( associationInfo.getId(), associationInfo.getDisplayName().toString()); associationInfo.getDisplayName().toString(), this::onMostRecentTaskChanged); mRemoteDeviceTaskLists.put(associationInfo.getId(), taskList); } else { Loading @@ -140,6 +160,29 @@ public class RemoteTaskStore implements ConnectedAssociationStore.Observer { "Deleting RemoteDeviceTaskList for association: " + associationId); mRemoteDeviceTaskLists.remove(associationId); notifyListeners(); } } private void onMostRecentTaskChanged(RemoteTask task) { notifyListeners(); } private void notifyListeners() { synchronized (mRemoteTaskListeners) { List<RemoteTask> remoteTasks = getMostRecentTasks(); int i = mRemoteTaskListeners.beginBroadcast(); while (i > 0) { i--; try { mRemoteTaskListeners .getBroadcastItem(i) .onRemoteTasksChanged(remoteTasks); } catch (RemoteException e) { Slog.e(TAG, "Failed to notify listener: " + e.getMessage()); } } mRemoteTaskListeners.finishBroadcast(); } } } No newline at end of file
services/tests/servicestests/src/com/android/server/companion/datatransfer/continuity/tasks/RemoteDeviceTaskListTest.java +81 −39 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.testing.AndroidTestingRunner; import com.android.server.companion.datatransfer.continuity.messages.RemoteTaskInfo; import org.junit.Before; import org.junit.Test; import org.junit.Before; import org.junit.runner.RunWith; Loading @@ -38,6 +39,9 @@ import java.util.List; @RunWith(AndroidTestingRunner.class) public class RemoteDeviceTaskListTest { private RemoteTask mMostRecentTask; private int mObserverCallCount; private static final int ASSOCIATION_ID = 123; private static final String DEVICE_NAME = "device1"; Loading @@ -45,7 +49,13 @@ public class RemoteDeviceTaskListTest { @Before public void setUp() { taskList = new RemoteDeviceTaskList(ASSOCIATION_ID, DEVICE_NAME); mMostRecentTask = null; mObserverCallCount = 0; taskList = new RemoteDeviceTaskList( ASSOCIATION_ID, DEVICE_NAME, this::onMostRecentTaskChanged); } @Test Loading @@ -56,27 +66,37 @@ public class RemoteDeviceTaskListTest { } @Test public void testAddTask_updatesMostRecentTask() { RemoteTaskInfo firstAddedTask = createNewRemoteTaskInfo(2, "task2", 200); taskList.addTask(firstAddedTask); public void testAddTask_updatesMostRecentTaskAndNotifiesListeners() { // Before adding any tasks, the most recent task should be null. assertThat(taskList.getMostRecentTask()).isNull(); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstAddedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); // Add a task, verify it automatically becomes the most recent task. RemoteTaskInfo firstAddedTaskInfo = createNewRemoteTaskInfo(2, "task2", 200); RemoteTask firstAddedTask = firstAddedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.addTask(firstAddedTaskInfo); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask).isEqualTo(firstAddedTask); assertThat(taskList.getMostRecentTask()).isEqualTo(firstAddedTask); // Add another task with an older timestamp, verify it doesn't update // the most recent task. RemoteTaskInfo secondAddedTask = createNewRemoteTaskInfo(1, "task1", 100); taskList.addTask(secondAddedTask); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstAddedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); RemoteTaskInfo secondAddedTaskInfo = createNewRemoteTaskInfo(1, "task1", 100); taskList.addTask(secondAddedTaskInfo); assertThat(taskList.getMostRecentTask()).isEqualTo(firstAddedTask); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(firstAddedTask); // Add another task with a newer timestamp, verifying it changes the // most recently used task. RemoteTaskInfo thirdAddedTask = createNewRemoteTaskInfo(3, "task3", 300); taskList.addTask(thirdAddedTask); assertThat(taskList.getMostRecentTask()) .isEqualTo(thirdAddedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); RemoteTaskInfo thirdAddedTaskInfo = createNewRemoteTaskInfo(3, "task3", 300); RemoteTask thirdAddedTask = thirdAddedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.addTask(thirdAddedTaskInfo); assertThat(taskList.getMostRecentTask()).isEqualTo(thirdAddedTask); assertThat(mObserverCallCount).isEqualTo(3); assertThat(mMostRecentTask).isEqualTo(thirdAddedTask); } @Test Loading @@ -94,12 +114,11 @@ public class RemoteDeviceTaskListTest { taskList.setTasks(initialTasks); assertThat(taskList.getMostRecentTask()) .isEqualTo(expectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask().getId()).isEqualTo(expectedTask.getId()); } @Test public void testRemoveTask_removesTask() { public void testRemoveTask_removesTaskAndNotifiesListeners() { RemoteTaskInfo mostRecentTaskInfo = createNewRemoteTaskInfo(1, "task2", 200); RemoteTask mostRecentTask = mostRecentTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); RemoteTaskInfo secondMostRecentTaskInfo = createNewRemoteTaskInfo(2, "task1", 100); Loading @@ -107,57 +126,75 @@ public class RemoteDeviceTaskListTest { = secondMostRecentTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(Arrays.asList(mostRecentTaskInfo, secondMostRecentTaskInfo)); assertThat(taskList.getMostRecentTask()) .isEqualTo(mostRecentTask); assertThat(taskList.getMostRecentTask()).isEqualTo(mostRecentTask); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask).isEqualTo(mostRecentTask); taskList.removeTask(mostRecentTask.getId()); assertThat(taskList.getMostRecentTask()) .isEqualTo(secondMostRecentTask); assertThat(taskList.getMostRecentTask()).isEqualTo(secondMostRecentTask); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(secondMostRecentTask); } @Test public void testSetTasks_updatesMostRecentTask() { public void testSetTasks_updatesMostRecentTaskAndNotifiesListeners() { // Set tasks initially, verify the most recent task is the first one. RemoteTaskInfo firstExpectedTask = createNewRemoteTaskInfo(1, "task2", 200); RemoteTaskInfo firstExpectedTaskInfo = createNewRemoteTaskInfo(1, "task2", 200); RemoteTask firstExpectedTask = firstExpectedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); List<RemoteTaskInfo> initialTasks = Arrays.asList( createNewRemoteTaskInfo(2, "task1", 100), firstExpectedTask, firstExpectedTaskInfo, createNewRemoteTaskInfo(3, "task3", 150)); taskList.setTasks(initialTasks); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask().getId()).isEqualTo(firstExpectedTask.getId()); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask.getId()).isEqualTo(firstExpectedTask.getId()); // Set the tasks to a different list, verify the most recent task is the // first one. RemoteTaskInfo secondExpectedTask = createNewRemoteTaskInfo(4, "task4", 300); RemoteTaskInfo secondExpectedTaskInfo = createNewRemoteTaskInfo(4, "task4", 300); List<RemoteTaskInfo> secondExpectedTasks = Arrays.asList( secondExpectedTask, secondExpectedTaskInfo, createNewRemoteTaskInfo(7, "task7", 200), createNewRemoteTaskInfo(5, "task5", 200), createNewRemoteTaskInfo(6, "task6", 100)); RemoteTask secondExpectedTask = secondExpectedTaskInfo.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(secondExpectedTasks); assertThat(taskList.getMostRecentTask()) .isEqualTo(secondExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(secondExpectedTask); assertThat(taskList.getMostRecentTask()).isEqualTo(secondExpectedTask); } @Test public void testSetTasks_overwritesExistingTasks() { public void testSetTasks_overwritesExistingTasksAndNotifiesListeners() { // Set the initial state of the list. RemoteTaskInfo firstExpectedTask = createNewRemoteTaskInfo(1, "task1", 100); RemoteTask firstExpectedRemoteTask = firstExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(Arrays.asList(firstExpectedTask)); assertThat(taskList.getMostRecentTask()) .isEqualTo(firstExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask()).isEqualTo(firstExpectedRemoteTask); assertThat(mObserverCallCount).isEqualTo(1); assertThat(mMostRecentTask).isEqualTo(firstExpectedRemoteTask); // Replace the tasks with a different list. The only task in this was used before the // previous task. RemoteTaskInfo secondExpectedTask = createNewRemoteTaskInfo(2, "task2", 10); RemoteTaskInfo secondExpectedTask = createNewRemoteTaskInfo(2, "task2", 200); RemoteTask secondExpectedRemoteTask = secondExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME); taskList.setTasks(Arrays.asList(secondExpectedTask)); // Because the task list is overwritten, the most recent task should be the second task. assertThat(taskList.getMostRecentTask()) .isEqualTo(secondExpectedTask.toRemoteTask(ASSOCIATION_ID, DEVICE_NAME)); assertThat(taskList.getMostRecentTask()).isEqualTo(secondExpectedRemoteTask); assertThat(mObserverCallCount).isEqualTo(2); assertThat(mMostRecentTask).isEqualTo(secondExpectedRemoteTask); } private RemoteTaskInfo createNewRemoteTaskInfo( Loading @@ -172,4 +209,9 @@ public class RemoteDeviceTaskListTest { return new RemoteTaskInfo(runningTaskInfo); } public void onMostRecentTaskChanged(RemoteTask task) { mMostRecentTask = task; mObserverCallCount++; } } No newline at end of file
services/tests/servicestests/src/com/android/server/companion/datatransfer/continuity/tasks/RemoteTaskStoreTest.java +40 −7 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static com.android.server.companion.datatransfer.continuity.TaskContinuit import android.app.ActivityManager; import android.companion.AssociationInfo; import android.companion.datatransfer.continuity.RemoteTask; import android.companion.datatransfer.continuity.IRemoteTaskListener; import android.platform.test.annotations.Presubmit; import android.testing.AndroidTestingRunner; Loading @@ -42,6 +43,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.List; Loading @@ -52,13 +54,23 @@ public class RemoteTaskStoreTest { @Mock private ConnectedAssociationStore mMockConnectedAssociationStore; private final IRemoteTaskListener mRemoteTaskListener = new IRemoteTaskListener.Stub() { @Override public void onRemoteTasksChanged(List<RemoteTask> remoteTasks) { remoteTasksReportedToListener.add(remoteTasks); } }; private final List<List<RemoteTask>> remoteTasksReportedToListener = new ArrayList<>(); private RemoteTaskStore taskStore; @Before public void setUp() { MockitoAnnotations.initMocks(this); remoteTasksReportedToListener.clear(); taskStore = new RemoteTaskStore(mMockConnectedAssociationStore); taskStore.addListener(mRemoteTaskListener); } @Test Loading @@ -68,20 +80,25 @@ public class RemoteTaskStoreTest { } @Test public void onTransportConnected_addsNewAssociation() { public void onTransportConnected_addsNewAssociationAndNotifiesListeners() { // Simulate a new association being connected. AssociationInfo associationInfo = createAssociationInfo(1, "name"); taskStore.onTransportConnected(associationInfo); assertThat(remoteTasksReportedToListener).hasSize(0); // Add tasks to the new association. RemoteTaskInfo remoteTaskInfo = createNewRemoteTaskInfo(1, "task1", 100L); RemoteTask remoteTask = remoteTaskInfo.toRemoteTask(associationInfo.getId(), "name"); taskStore.setTasks( associationInfo.getId(), Collections.singletonList(remoteTaskInfo)); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)).containsExactly(remoteTask); // Verify the most recent task is added to the task store. assertThat(taskStore.getMostRecentTasks()) .containsExactly(remoteTaskInfo.toRemoteTask(associationInfo.getId(), "name")); assertThat(taskStore.getMostRecentTasks()).containsExactly(remoteTask); } @Test Loading @@ -96,6 +113,7 @@ public class RemoteTaskStoreTest { taskStore.setTasks(0, Collections.singletonList(remoteTaskInfo)); assertThat(taskStore.getMostRecentTasks()).isEmpty(); assertThat(remoteTasksReportedToListener).isEmpty(); } @Test Loading @@ -117,32 +135,43 @@ public class RemoteTaskStoreTest { assertThat(taskStore.getMostRecentTasks()) .containsExactly(mostRecentTask); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)).containsExactly(mostRecentTask); taskStore.removeTask(associationInfo.getId(), mostRecentTaskInfo.getId()); assertThat(taskStore.getMostRecentTasks()).containsExactly(secondMostRecentTask); assertThat(remoteTasksReportedToListener).hasSize(2); assertThat(remoteTasksReportedToListener.get(1)) .containsExactly(secondMostRecentTask); } @Test public void onTransportDisconnected_removesAssociation() { public void onTransportDisconnected_removesAssociationAndNotifiesListeners() { // Create a fake association info, and have connected association store // return it. AssociationInfo associationInfo = createAssociationInfo(1, "name"); when(mMockConnectedAssociationStore.getConnectedAssociationById(1)) .thenReturn(associationInfo); taskStore.onTransportConnected(associationInfo); // Set tasks for the association. RemoteTaskInfo remoteTaskInfo = createNewRemoteTaskInfo(1, "task1", 100L); taskStore.setTasks(0, Collections.singletonList(remoteTaskInfo)); taskStore.setTasks(associationInfo.getId(), Collections.singletonList(remoteTaskInfo)); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)) .containsExactly(remoteTaskInfo.toRemoteTask(1, "name")); // Simulate the association being disconnected. taskStore.onTransportDisconnected(0); taskStore.onTransportDisconnected(associationInfo.getId()); // Verify the most recent task is added to the task store. assertThat(taskStore.getMostRecentTasks()).isEmpty(); assertThat(remoteTasksReportedToListener).hasSize(2); assertThat(remoteTasksReportedToListener.get(1)).isEmpty(); } @Test public void addTask_addsTaskToAssociation() { public void addTask_addsTaskToAssociationAndNotifiesListeners() { // Create a fake association info, and have connected association store return it. AssociationInfo associationInfo = createAssociationInfo(1, "name"); when(mMockConnectedAssociationStore.getConnectedAssociationById(1)) Loading @@ -153,6 +182,8 @@ public class RemoteTaskStoreTest { RemoteTask remoteTask = remoteTaskInfo.toRemoteTask(associationInfo.getId(), "name"); taskStore.setTasks(1, Collections.singletonList(remoteTaskInfo)); assertThat(taskStore.getMostRecentTasks()).containsExactly(remoteTask); assertThat(remoteTasksReportedToListener).hasSize(1); assertThat(remoteTasksReportedToListener.get(0)).containsExactly(remoteTask); // Add a new task to the association. RemoteTaskInfo newRemoteTaskInfo = createNewRemoteTaskInfo(2, "task2", 200L); Loading @@ -161,6 +192,8 @@ public class RemoteTaskStoreTest { // Verify the most recent tasks are added to the task store. assertThat(taskStore.getMostRecentTasks()).containsExactly(newRemoteTask); assertThat(remoteTasksReportedToListener).hasSize(2); assertThat(remoteTasksReportedToListener.get(1)).containsExactly(newRemoteTask); } @Test Loading