summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Austin Tankiang <austinct@google.com> 2025-02-21 11:21:17 +0000
committer Austin Tankiang <austinct@google.com> 2025-03-06 09:45:19 +0000
commit9d5b960f0229d9a0e10bcca4851073bce2ead0eb (patch)
tree0abe76b36d0c22aceb43b3dfbc3a755950089a27
parent72e54720cf3306584e0d0e3358d4085b4eb3acdf (diff)
Broadcast the progress of Jobs
Instead of using the per process monitor, create a new global one. This to collate all job progress into one broadcast. Also add tests for job progress. Do it in this CL, as the CL introducing functions to get progress will get optimized out as the code wasn't used. Bug: 385841586 Test: atest -c 'DocumentsUIGoogleTests:com.android.documentsui.services' Flag: com.android.documentsui.flags.visual_signals_ro Change-Id: Ib0df69a303dbe8bf3ebd0c337491dc48fef0b87f
-rw-r--r--src/com/android/documentsui/services/FileOperationService.java76
-rw-r--r--src/com/android/documentsui/util/FlagUtils.kt5
-rw-r--r--tests/functional/com/android/documentsui/services/AbstractCopyJobTest.java60
-rw-r--r--tests/functional/com/android/documentsui/services/CopyJobTest.java10
-rw-r--r--tests/functional/com/android/documentsui/services/DeleteJobTest.java17
-rw-r--r--tests/functional/com/android/documentsui/services/FileOperationServiceTest.java2
6 files changed, 159 insertions, 11 deletions
diff --git a/src/com/android/documentsui/services/FileOperationService.java b/src/com/android/documentsui/services/FileOperationService.java
index c7be5f4fc..dcb2c1db4 100644
--- a/src/com/android/documentsui/services/FileOperationService.java
+++ b/src/com/android/documentsui/services/FileOperationService.java
@@ -17,6 +17,7 @@
package com.android.documentsui.services;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
+import static com.android.documentsui.util.FlagUtils.isVisualSignalsFlagEnabled;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -126,9 +127,15 @@ public class FileOperationService extends Service implements Job.Listener {
// Use a features to determine if notification channel is enabled.
@VisibleForTesting Features features;
+ // Used so tests can force the state of visual signals.
+ @VisibleForTesting Boolean mVisualSignalsEnabled = isVisualSignalsFlagEnabled();
+
@GuardedBy("mJobs")
private final Map<String, JobRecord> mJobs = new LinkedHashMap<>();
+ // Used to send periodic broadcasts for job progress.
+ private GlobalJobMonitor mJobMonitor;
+
// The job whose notification is used to keep the service in foreground mode.
@GuardedBy("mJobs")
private Job mForegroundJob;
@@ -162,6 +169,10 @@ public class FileOperationService extends Service implements Job.Listener {
notificationManager = getSystemService(NotificationManager.class);
}
+ if (mVisualSignalsEnabled && mJobMonitor == null) {
+ mJobMonitor = new GlobalJobMonitor();
+ }
+
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
features = new Features.RuntimeFeatures(getResources(), userManager);
setUpNotificationChannel();
@@ -188,6 +199,10 @@ public class FileOperationService extends Service implements Job.Listener {
Log.d(TAG, "Shutting down executor.");
}
+ if (mJobMonitor != null) {
+ mJobMonitor.stop();
+ }
+
List<Runnable> unfinishedCopies = executor.shutdownNow();
List<Runnable> unfinishedDeletions = deletionExecutor.shutdownNow();
List<Runnable> unfinished =
@@ -330,6 +345,10 @@ public class FileOperationService extends Service implements Job.Listener {
assert(record != null);
record.job.cleanup();
+ if (mVisualSignalsEnabled && mJobs.isEmpty()) {
+ mJobMonitor.stop();
+ }
+
// Delay the shutdown until we've cleaned up all notifications. shutdown() is now posted in
// onFinished(Job job) to main thread.
}
@@ -389,8 +408,12 @@ public class FileOperationService extends Service implements Job.Listener {
}
// Set up related monitor
- JobMonitor monitor = new JobMonitor(job);
- monitor.start();
+ if (mVisualSignalsEnabled) {
+ mJobMonitor.start();
+ } else {
+ JobMonitor monitor = new JobMonitor(job);
+ monitor.start();
+ }
}
@Override
@@ -399,6 +422,9 @@ public class FileOperationService extends Service implements Job.Listener {
if (DEBUG) {
Log.d(TAG, "onFinished: " + job.id);
}
+ if (mVisualSignalsEnabled) {
+ mJobMonitor.sendProgress();
+ }
synchronized (mJobs) {
// Delete the job from mJobs first to avoid this job being selected as the foreground
@@ -545,6 +571,52 @@ public class FileOperationService extends Service implements Job.Listener {
}
}
+ /**
+ * A class used to periodically poll the state of every running job.
+ *
+ * We need to be sending the progress of every job, so rather than having a single monitor per
+ * job, have one for the whole service.
+ */
+ private final class GlobalJobMonitor implements Runnable {
+ private static final long PROGRESS_INTERVAL_MILLIS = 500L;
+ private boolean mRunning = false;
+
+ private void start() {
+ if (!mRunning) {
+ handler.post(this);
+ }
+ mRunning = true;
+ }
+
+ private void stop() {
+ mRunning = false;
+ handler.removeCallbacks(this);
+ }
+
+ private void sendProgress() {
+ var progress = new ArrayList<JobProgress>();
+ synchronized (mJobs) {
+ for (JobRecord rec : mJobs.values()) {
+ progress.add(rec.job.getJobProgress());
+ }
+ }
+ Intent intent = new Intent();
+ intent.setPackage(getPackageName());
+ intent.setAction("com.android.documentsui.PROGRESS");
+ intent.putExtra("id", 0);
+ intent.putParcelableArrayListExtra("progress", progress);
+ sendBroadcast(intent);
+ }
+
+ @Override
+ public void run() {
+ sendProgress();
+ if (mRunning) {
+ handler.postDelayed(this, PROGRESS_INTERVAL_MILLIS);
+ }
+ }
+ }
+
@Override
public IBinder onBind(Intent intent) {
return null; // Boilerplate. See super#onBind
diff --git a/src/com/android/documentsui/util/FlagUtils.kt b/src/com/android/documentsui/util/FlagUtils.kt
index 606f071f2..eee51be89 100644
--- a/src/com/android/documentsui/util/FlagUtils.kt
+++ b/src/com/android/documentsui/util/FlagUtils.kt
@@ -45,6 +45,11 @@ class FlagUtils {
}
@JvmStatic
+ fun isVisualSignalsFlagEnabled(): Boolean {
+ return Flags.visualSignalsRo() && isUseMaterial3FlagEnabled()
+ }
+
+ @JvmStatic
fun isHideRootsOnDesktopFlagEnabled(): Boolean {
return Flags.hideRootsOnDesktopRo()
}
diff --git a/tests/functional/com/android/documentsui/services/AbstractCopyJobTest.java b/tests/functional/com/android/documentsui/services/AbstractCopyJobTest.java
index 525397bc0..b20942473 100644
--- a/tests/functional/com/android/documentsui/services/AbstractCopyJobTest.java
+++ b/tests/functional/com/android/documentsui/services/AbstractCopyJobTest.java
@@ -44,6 +44,23 @@ public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJob
mOpType = opType;
}
+ private String getVerb() {
+ switch(mOpType) {
+ case FileOperationService.OPERATION_COPY:
+ case FileOperationService.OPERATION_EXTRACT:
+ return "Copying";
+ case FileOperationService.OPERATION_COMPRESS:
+ return "Zipping";
+ case FileOperationService.OPERATION_MOVE:
+ return "Moving";
+ case FileOperationService.OPERATION_DELETE:
+ // DeleteJob does not inherit from CopyJob
+ case FileOperationService.OPERATION_UNKNOWN:
+ default:
+ return "";
+ }
+ }
+
public void runCopyFilesTest() throws Exception {
Uri testFile1 = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt");
mDocs.writeDocument(testFile1, HAM_BYTES);
@@ -51,7 +68,11 @@ public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJob
Uri testFile2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt");
mDocs.writeDocument(testFile2, FRUITY_BYTES);
- createJob(newArrayList(testFile1, testFile2)).run();
+ CopyJob job = createJob(newArrayList(testFile1, testFile2));
+ JobProgress progress = job.getJobProgress();
+ assertEquals(Job.STATE_CREATED, progress.state);
+
+ job.run();
mJobListener.waitForFinished();
mDocs.assertChildCount(mDestRoot, 2);
@@ -59,6 +80,13 @@ public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJob
mDocs.assertHasFile(mDestRoot, "test2.txt");
mDocs.assertFileContents(mDestRoot.documentId, "test1.txt", HAM_BYTES);
mDocs.assertFileContents(mDestRoot.documentId, "test2.txt", FRUITY_BYTES);
+
+ progress = job.getJobProgress();
+ assertEquals(Job.STATE_COMPLETED, progress.state);
+ assertFalse(progress.hasFailures);
+ assertEquals(getVerb() + " 2 files to " + mDestRoot.title, progress.msg);
+ assertEquals(HAM_BYTES.length + FRUITY_BYTES.length, progress.currentBytes);
+ assertEquals(HAM_BYTES.length + FRUITY_BYTES.length, progress.requiredBytes);
}
public void runCopyVirtualTypedFileTest() throws Exception {
@@ -66,13 +94,20 @@ public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJob
mSrcRoot, "/virtual.sth", "virtual/mime-type",
FRUITY_BYTES, "application/pdf", "text/html");
- createJob(newArrayList(testFile)).run();
-
+ CopyJob job = createJob(newArrayList(testFile));
+ job.run();
waitForJobFinished();
mDocs.assertChildCount(mDestRoot, 1);
mDocs.assertHasFile(mDestRoot, "virtual.sth.pdf"); // copy should convert file to PDF.
mDocs.assertFileContents(mDestRoot.documentId, "virtual.sth.pdf", FRUITY_BYTES);
+
+ JobProgress progress = job.getJobProgress();
+ assertEquals(Job.STATE_COMPLETED, progress.state);
+ assertFalse(progress.hasFailures);
+ assertEquals("Copying virtual.sth to " + mDestRoot.title, progress.msg);
+ assertEquals(FRUITY_BYTES.length, progress.currentBytes);
+ assertEquals(FRUITY_BYTES.length, progress.requiredBytes);
}
public void runCopyVirtualNonTypedFileTest() throws Exception {
@@ -80,13 +115,21 @@ public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJob
mSrcRoot, "/virtual.sth", "virtual/mime-type",
FRUITY_BYTES);
- createJob(newArrayList(testFile)).run();
-
+ CopyJob job = createJob(newArrayList(testFile));
+ job.run();
waitForJobFinished();
+
mJobListener.assertFailed();
mJobListener.assertFilesFailed(newArrayList("virtual.sth"));
mDocs.assertChildCount(mDestRoot, 0);
+
+ JobProgress progress = job.getJobProgress();
+ assertEquals(Job.STATE_COMPLETED, progress.state);
+ assertTrue(progress.hasFailures);
+ assertEquals(getVerb() + " virtual.sth to " + mDestRoot.title, progress.msg);
+ assertEquals(0, progress.currentBytes);
+ assertEquals(FRUITY_BYTES.length, progress.requiredBytes);
}
public void runCopyEmptyDirTest() throws Exception {
@@ -105,6 +148,13 @@ public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJob
mDocs.assertChildCount(mDestRoot, 1);
mDocs.assertHasDirectory(mDestRoot, "emptyDir");
+
+ JobProgress progress = job.getJobProgress();
+ assertEquals(Job.STATE_COMPLETED, progress.state);
+ assertFalse(progress.hasFailures);
+ assertEquals(getVerb() + " emptyDir to " + mDestRoot.title, progress.msg);
+ assertEquals(-1, progress.currentBytes);
+ assertEquals(-1, progress.requiredBytes);
}
public void runCopyDirRecursivelyTest() throws Exception {
diff --git a/tests/functional/com/android/documentsui/services/CopyJobTest.java b/tests/functional/com/android/documentsui/services/CopyJobTest.java
index fd552d1c0..9074ef4c3 100644
--- a/tests/functional/com/android/documentsui/services/CopyJobTest.java
+++ b/tests/functional/com/android/documentsui/services/CopyJobTest.java
@@ -52,11 +52,19 @@ public class CopyJobTest extends AbstractCopyJobTest<CopyJob> {
Document.FLAG_VIRTUAL_DOCUMENT | Document.FLAG_SUPPORTS_COPY
| Document.FLAG_SUPPORTS_MOVE, "application/pdf");
- createJob(newArrayList(testFile)).run();
+ CopyJob job = createJob(newArrayList(testFile));
+ job.run();
waitForJobFinished();
mDocs.assertChildCount(mDestRoot, 1);
mDocs.assertHasFile(mDestRoot, "tokyo.sth.pdf"); // Copy should convert file to PDF.
+
+ JobProgress progress = job.getJobProgress();
+ assertEquals(Job.STATE_COMPLETED, progress.state);
+ assertFalse(progress.hasFailures);
+ assertEquals("Copying tokyo.sth to " + mDestRoot.title, progress.msg);
+ assertEquals(-1, progress.currentBytes);
+ assertEquals(-1, progress.requiredBytes);
}
public void testCopyEmptyDir() throws Exception {
diff --git a/tests/functional/com/android/documentsui/services/DeleteJobTest.java b/tests/functional/com/android/documentsui/services/DeleteJobTest.java
index 55e804484..86d9930a2 100644
--- a/tests/functional/com/android/documentsui/services/DeleteJobTest.java
+++ b/tests/functional/com/android/documentsui/services/DeleteJobTest.java
@@ -37,11 +37,17 @@ public class DeleteJobTest extends AbstractJobTest<DeleteJob> {
Uri testFile2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt");
mDocs.writeDocument(testFile2, FRUITY_BYTES);
- createJob(newArrayList(testFile1, testFile2),
- DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId)).run();
+ DeleteJob job = createJob(newArrayList(testFile1, testFile2),
+ DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId));
+ job.run();
mJobListener.waitForFinished();
mDocs.assertChildCount(mSrcRoot, 0);
+
+ var progress = job.getJobProgress();
+ assertEquals(Job.STATE_COMPLETED, progress.state);
+ assertFalse(progress.hasFailures);
+ assertEquals("Deleting 2 files", progress.msg);
}
public void testDeleteFiles_NoSrcParent() throws Exception {
@@ -51,10 +57,15 @@ public class DeleteJobTest extends AbstractJobTest<DeleteJob> {
Uri testFile2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt");
mDocs.writeDocument(testFile2, FRUITY_BYTES);
- createJob(newArrayList(testFile1, testFile2), null).run();
+ DeleteJob job = createJob(newArrayList(testFile1, testFile2), null);
+ job.run();
mJobListener.waitForFinished();
mDocs.assertChildCount(mSrcRoot, 0);
+ var progress = job.getJobProgress();
+ assertEquals(Job.STATE_COMPLETED, progress.state);
+ assertFalse(progress.hasFailures);
+ assertEquals("Deleting 2 files", progress.msg);
}
/**
diff --git a/tests/functional/com/android/documentsui/services/FileOperationServiceTest.java b/tests/functional/com/android/documentsui/services/FileOperationServiceTest.java
index 1816ed540..5820f460f 100644
--- a/tests/functional/com/android/documentsui/services/FileOperationServiceTest.java
+++ b/tests/functional/com/android/documentsui/services/FileOperationServiceTest.java
@@ -109,6 +109,8 @@ public class FileOperationServiceTest extends ServiceTestCase<FileOperationServi
assertNull(mService.features);
mService.features = features;
+
+ mService.mVisualSignalsEnabled = false;
}
@Override