summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;