summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Austin Tankiang <austinct@google.com> 2025-02-07 06:36:40 +0000
committer Austin Tankiang <austinct@google.com> 2025-03-03 08:02:02 +0000
commit77f3e752b26b58e8ccbe2a40cb8ff0bb44b66628 (patch)
treef0964c2593a5b4aece083e6138f168aefc6a097b
parente5ed5939712aae6965459f3ac32d641a96136f73 (diff)
Add functions to Jobs to retrieve current progress
There is no change in behaviour as this patch just adds functions. Due to limitations in the current job implementations, there are a few shortcominings: * Failure reasons aren't exposed. * Transfers of zero bytes (e.g. folder only or empty file) show as indeterminate progress. * In cases where file size can't be determined, the job keeps track via files processed, but these will show as indeterminate progress. Bug: 385841721 Test: atest -c 'DocumentsUIGoogleTests:com.android.documentsui.services' Flag: com.android.documentsui.flags.visual_signals Change-Id: I7b0d863bf7b974090c0e1a17f06c0f7160c59699
-rw-r--r--res/values/strings.xml17
-rw-r--r--src/com/android/documentsui/services/CompressJob.java25
-rw-r--r--src/com/android/documentsui/services/CopyJob.java61
-rw-r--r--src/com/android/documentsui/services/DeleteJob.java33
-rw-r--r--src/com/android/documentsui/services/Job.java2
-rw-r--r--src/com/android/documentsui/services/JobProgress.kt69
-rw-r--r--src/com/android/documentsui/services/MoveJob.java27
-rw-r--r--tests/common/com/android/documentsui/services/TestJob.java8
8 files changed, 240 insertions, 2 deletions
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5f78c93e7..cd7ad917c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -409,6 +409,23 @@
during a copy. [CHAR LIMIT=48] -->
<string name="notification_copy_files_converted_title">Some files were converted</string>
+ <string name="copy_in_progress" translatable="false">{count, plural,
+ =1 {Copying <xliff:g id="filename" example="foobar.txt">{filename}</xliff:g> to <xliff:g id="directory" example="example folder">{directory}</xliff:g>}
+ other {Copying # files to <xliff:g id="directory" example="example folder">{directory}</xliff:g>}
+ }</string>
+ <string name="move_in_progress" translatable="false">{count, plural,
+ =1 {Moving <xliff:g id="filename" example="foobar.txt">{filename}</xliff:g> to <xliff:g id="directory" example="example folder">{directory}</xliff:g>}
+ other {Moving # files to <xliff:g id="directory" example="example folder">{directory}</xliff:g>}
+ }</string>
+ <string name="delete_in_progress" translatable="false">{count, plural,
+ =1 {Deleting <xliff:g id="filename" example="foobar.txt">{filename}</xliff:g>}
+ other {Deleting # files}
+ }</string>
+ <string name="compress_in_progress" translatable="false">{count, plural,
+ =1 {Zipping <xliff:g id="filename" example="foobar.txt">{filename}</xliff:g>}
+ other {Zipping # files}
+ }</string>
+
<!-- Text in an alert dialog asking user to grant app access to a given directory in an external storage volume -->
<string name="open_external_dialog_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
access to <xliff:g id="directory" example="Pictures"><i>^2</i></xliff:g> directory on
diff --git a/src/com/android/documentsui/services/CompressJob.java b/src/com/android/documentsui/services/CompressJob.java
index a7d2de9aa..ccb3ee835 100644
--- a/src/com/android/documentsui/services/CompressJob.java
+++ b/src/com/android/documentsui/services/CompressJob.java
@@ -24,11 +24,13 @@ import android.app.Notification;
import android.app.Notification.Builder;
import android.content.ContentResolver;
import android.content.Context;
+import android.icu.text.MessageFormat;
import android.net.Uri;
import android.os.Messenger;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.DocumentsContract;
+import android.text.BidiFormatter;
import android.util.Log;
import com.android.documentsui.R;
@@ -40,6 +42,9 @@ import com.android.documentsui.base.UserId;
import com.android.documentsui.clipping.UrisSupplier;
import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
// TODO: Stop extending CopyJob.
final class CompressJob extends CopyJob {
@@ -87,6 +92,26 @@ final class CompressJob extends CopyJob {
}
@Override
+ protected String getProgressMessage() {
+ switch (getState()) {
+ case Job.STATE_SET_UP:
+ case Job.STATE_COMPLETED:
+ case Job.STATE_CANCELED:
+ Map<String, Object> formatArgs = new HashMap<>();
+ formatArgs.put("count", mResolvedDocs.size());
+ if (mResolvedDocs.size() == 1) {
+ formatArgs.put("filename", BidiFormatter.getInstance().unicodeWrap(
+ mResolvedDocs.get(0).displayName));
+ }
+ return (new MessageFormat(
+ service.getString(R.string.compress_in_progress), Locale.getDefault()))
+ .format(formatArgs);
+ default:
+ return "";
+ }
+ }
+
+ @Override
public boolean setUp() {
if (!super.setUp()) {
return false;
diff --git a/src/com/android/documentsui/services/CopyJob.java b/src/com/android/documentsui/services/CopyJob.java
index c972c33ef..9fb3f5d09 100644
--- a/src/com/android/documentsui/services/CopyJob.java
+++ b/src/com/android/documentsui/services/CopyJob.java
@@ -46,6 +46,7 @@ import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
import android.database.Cursor;
+import android.icu.text.MessageFormat;
import android.net.Uri;
import android.os.DeadObjectException;
import android.os.FileUtils;
@@ -66,6 +67,7 @@ import android.system.Int64Ref;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
+import android.text.BidiFormatter;
import android.util.ArrayMap;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -93,6 +95,8 @@ import java.io.InputStream;
import java.io.SyncFailedException;
import java.text.NumberFormat;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
@@ -195,6 +199,49 @@ class CopyJob extends ResolvedResourcesJob {
return warningBuilder.build();
}
+ protected String getProgressMessage() {
+ switch (getState()) {
+ case Job.STATE_SET_UP:
+ case Job.STATE_COMPLETED:
+ case Job.STATE_CANCELED:
+ Map<String, Object> formatArgs = new HashMap<>();
+ formatArgs.put("count", mResolvedDocs.size());
+ formatArgs.put("directory",
+ BidiFormatter.getInstance().unicodeWrap(mDstInfo.displayName));
+ if (mResolvedDocs.size() == 1) {
+ formatArgs.put("filename",
+ BidiFormatter.getInstance().unicodeWrap(
+ mResolvedDocs.get(0).displayName));
+ }
+ return (new MessageFormat(
+ service.getString(R.string.copy_in_progress), Locale.getDefault()))
+ .format(formatArgs);
+
+ default:
+ return "";
+ }
+ }
+
+ @Override
+ JobProgress getJobProgress() {
+ if (mProgressTracker == null) {
+ return new JobProgress(
+ id,
+ getState(),
+ getProgressMessage(),
+ hasFailures());
+ }
+ mProgressTracker.updateEstimateRemainingTime();
+ return new JobProgress(
+ id,
+ getState(),
+ getProgressMessage(),
+ hasFailures(),
+ mProgressTracker.getCurrentBytes(),
+ mProgressTracker.getRequiredBytes(),
+ mProgressTracker.getRemainingTimeEstimate());
+ }
+
@Override
boolean setUp() {
if (!super.setUp()) {
@@ -986,6 +1033,10 @@ class CopyJob extends ResolvedResourcesJob {
return -1;
}
+ protected long getCurrentBytes() {
+ return -1;
+ }
+
protected void start() {
mStartTime = mElapsedRealTimeSupplier.getAsLong();
}
@@ -1058,6 +1109,16 @@ class CopyJob extends ResolvedResourcesJob {
}
@Override
+ protected long getRequiredBytes() {
+ return mBytesRequired;
+ }
+
+ @Override
+ protected long getCurrentBytes() {
+ return mBytesCopied.get();
+ }
+
+ @Override
public void onBytesCopied(long numBytes) {
mBytesCopied.getAndAdd(numBytes);
}
diff --git a/src/com/android/documentsui/services/DeleteJob.java b/src/com/android/documentsui/services/DeleteJob.java
index ede46a937..801cc6dd3 100644
--- a/src/com/android/documentsui/services/DeleteJob.java
+++ b/src/com/android/documentsui/services/DeleteJob.java
@@ -23,7 +23,9 @@ import android.app.Notification;
import android.app.Notification.Builder;
import android.content.ContentResolver;
import android.content.Context;
+import android.icu.text.MessageFormat;
import android.net.Uri;
+import android.text.BidiFormatter;
import android.util.Log;
import com.android.documentsui.MetricConsts;
@@ -36,6 +38,9 @@ import com.android.documentsui.base.UserId;
import com.android.documentsui.clipping.UrisSupplier;
import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
import javax.annotation.Nullable;
@@ -97,6 +102,34 @@ final class DeleteJob extends ResolvedResourcesJob {
throw new UnsupportedOperationException();
}
+ private String getProgressMessage() {
+ switch (getState()) {
+ case Job.STATE_SET_UP:
+ case Job.STATE_COMPLETED:
+ case Job.STATE_CANCELED:
+ Map<String, Object> formatArgs = new HashMap<>();
+ formatArgs.put("count", mResolvedDocs.size());
+ if (mResolvedDocs.size() == 1) {
+ formatArgs.put("filename", BidiFormatter.getInstance().unicodeWrap(
+ mResolvedDocs.get(0).displayName));
+ }
+ return (new MessageFormat(
+ service.getString(R.string.delete_in_progress), Locale.getDefault()))
+ .format(formatArgs);
+ default:
+ return "";
+ }
+ }
+
+ @Override
+ JobProgress getJobProgress() {
+ return new JobProgress(
+ id,
+ getState(),
+ getProgressMessage(),
+ hasFailures());
+ }
+
@Override
void start() {
ContentResolver resolver = appContext.getContentResolver();
diff --git a/src/com/android/documentsui/services/Job.java b/src/com/android/documentsui/services/Job.java
index 71f0ae861..0f432cc19 100644
--- a/src/com/android/documentsui/services/Job.java
+++ b/src/com/android/documentsui/services/Job.java
@@ -190,6 +190,8 @@ abstract public class Job implements Runnable {
abstract Notification getWarningNotification();
+ abstract JobProgress getJobProgress();
+
Uri getDataUriForIntent(String tag) {
return Uri.parse(String.format("data,%s-%s", tag, id));
}
diff --git a/src/com/android/documentsui/services/JobProgress.kt b/src/com/android/documentsui/services/JobProgress.kt
new file mode 100644
index 000000000..98be92f6a
--- /dev/null
+++ b/src/com/android/documentsui/services/JobProgress.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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.documentsui.services
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Represents the current progress on an individual job owned by the FileOperationService.
+ * JobProgress objects are broadcast from the service to activities in order to update the UI.
+ */
+data class JobProgress @JvmOverloads constructor(
+ @JvmField val id: String,
+ @JvmField @Job.State val state: Int,
+ @JvmField val msg: String?,
+ @JvmField val hasFailures: Boolean,
+ @JvmField val currentBytes: Long = -1,
+ @JvmField val requiredBytes: Long = -1,
+ @JvmField val msRemaining: Long = -1,
+) : Parcelable {
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.apply {
+ writeString(id)
+ writeInt(state)
+ writeString(msg)
+ writeBoolean(hasFailures)
+ writeLong(currentBytes)
+ writeLong(requiredBytes)
+ writeLong(msRemaining)
+ }
+ }
+
+ companion object CREATOR : Parcelable.Creator<JobProgress?> {
+ override fun createFromParcel(parcel: Parcel): JobProgress? {
+ return JobProgress(
+ parcel.readString()!!,
+ parcel.readInt(),
+ parcel.readString(),
+ parcel.readBoolean(),
+ parcel.readLong(),
+ parcel.readLong(),
+ parcel.readLong(),
+ )
+ }
+
+ override fun newArray(size: Int): Array<JobProgress?> {
+ return arrayOfNulls(size)
+ }
+ }
+}
diff --git a/src/com/android/documentsui/services/MoveJob.java b/src/com/android/documentsui/services/MoveJob.java
index ddbe727ac..b2974c5e7 100644
--- a/src/com/android/documentsui/services/MoveJob.java
+++ b/src/com/android/documentsui/services/MoveJob.java
@@ -24,12 +24,14 @@ import static com.android.documentsui.services.FileOperationService.OPERATION_MO
import android.app.Notification;
import android.app.Notification.Builder;
import android.content.Context;
+import android.icu.text.MessageFormat;
import android.net.Uri;
import android.os.DeadObjectException;
import android.os.Messenger;
import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.text.BidiFormatter;
import android.util.Log;
import com.android.documentsui.MetricConsts;
@@ -42,6 +44,9 @@ import com.android.documentsui.base.UserId;
import com.android.documentsui.clipping.UrisSupplier;
import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
import javax.annotation.Nullable;
@@ -94,6 +99,28 @@ final class MoveJob extends CopyJob {
}
@Override
+ protected String getProgressMessage() {
+ switch (getState()) {
+ case Job.STATE_SET_UP:
+ case Job.STATE_COMPLETED:
+ case Job.STATE_CANCELED:
+ Map<String, Object> formatArgs = new HashMap<>();
+ formatArgs.put("count", mResolvedDocs.size());
+ formatArgs.put("directory",
+ BidiFormatter.getInstance().unicodeWrap(mDstInfo.displayName));
+ if (mResolvedDocs.size() == 1) {
+ formatArgs.put("filename", BidiFormatter.getInstance().unicodeWrap(
+ mResolvedDocs.get(0).displayName));
+ }
+ return (new MessageFormat(
+ service.getString(R.string.move_in_progress), Locale.getDefault()))
+ .format(formatArgs);
+ default:
+ return "";
+ }
+ }
+
+ @Override
public boolean setUp() {
if (mSrcParentUri != null) {
try {
diff --git a/tests/common/com/android/documentsui/services/TestJob.java b/tests/common/com/android/documentsui/services/TestJob.java
index 10addebd9..426cb9575 100644
--- a/tests/common/com/android/documentsui/services/TestJob.java
+++ b/tests/common/com/android/documentsui/services/TestJob.java
@@ -23,11 +23,11 @@ import android.app.Notification;
import android.app.Notification.Builder;
import android.content.Context;
-import com.android.documentsui.base.Features;
-import com.android.documentsui.clipping.UrisSupplier;
import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.Features;
+import com.android.documentsui.clipping.UrisSupplier;
import com.android.documentsui.services.FileOperationService.OpType;
import java.text.NumberFormat;
@@ -97,6 +97,10 @@ public class TestJob extends Job {
throw new UnsupportedOperationException();
}
+ JobProgress getJobProgress() {
+ return new JobProgress(id, getState(), "test job", false);
+ }
+
@Override
Builder createProgressBuilder() {
++mNumOfNotifications;