diff options
-rw-r--r-- | res/values/strings.xml | 17 | ||||
-rw-r--r-- | src/com/android/documentsui/services/CompressJob.java | 25 | ||||
-rw-r--r-- | src/com/android/documentsui/services/CopyJob.java | 61 | ||||
-rw-r--r-- | src/com/android/documentsui/services/DeleteJob.java | 33 | ||||
-rw-r--r-- | src/com/android/documentsui/services/Job.java | 2 | ||||
-rw-r--r-- | src/com/android/documentsui/services/JobProgress.kt | 69 | ||||
-rw-r--r-- | src/com/android/documentsui/services/MoveJob.java | 27 | ||||
-rw-r--r-- | tests/common/com/android/documentsui/services/TestJob.java | 8 |
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; |