diff options
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 |