From 60ab4dfb4e8814d1da61ba450cd228b4771b84bf Mon Sep 17 00:00:00 2001 From: Austin Tankiang Date: Thu, 6 Mar 2025 06:47:53 +0000 Subject: Add job progress icon to the app bar This adds a progress indicator to the app bar showing total progress of running jobs. It is not yet interactable. Bug: 385840940 Test: atest -c 'DocumentsUIGoogleTests:com.android.documentsui.JobPanelControllerTest' Flag: com.android.documentsui.flags.visual_signals_ro Change-Id: I9d81a0f36500c2239b7c7007efb3b1aae98467eb --- src/com/android/documentsui/JobPanelController.kt | 126 +++++++++++++++++++++ src/com/android/documentsui/MenuManager.java | 9 ++ .../android/documentsui/NavigationViewManager.java | 4 + src/com/android/documentsui/files/MenuManager.java | 18 +++ .../documentsui/services/FileOperationService.java | 10 +- src/com/android/documentsui/services/Job.java | 10 +- 6 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 src/com/android/documentsui/JobPanelController.kt (limited to 'src/com') diff --git a/src/com/android/documentsui/JobPanelController.kt b/src/com/android/documentsui/JobPanelController.kt new file mode 100644 index 000000000..b3b5f1cfd --- /dev/null +++ b/src/com/android/documentsui/JobPanelController.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 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 + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import android.view.MenuItem +import android.widget.ProgressBar +import com.android.documentsui.base.Menus +import com.android.documentsui.services.FileOperationService +import com.android.documentsui.services.FileOperationService.EXTRA_PROGRESS +import com.android.documentsui.services.Job +import com.android.documentsui.services.JobProgress + +/** + * JobPanelController is responsible for receiving broadcast updates from the [FileOperationService] + * and updating a given menu item to reflect the current progress. + */ +class JobPanelController(private val mContext: Context) : BroadcastReceiver() { + companion object { + private const val TAG = "JobPanelController" + private const val MAX_PROGRESS = 100 + } + + private enum class State { + INVISIBLE, INDETERMINATE, VISIBLE + } + + /** The current state of the menu progress item. */ + private var mState = State.INVISIBLE + + /** The total progress from 0 to MAX_PROGRESS. */ + private var mTotalProgress = 0 + + /** List of jobs currently tracked by this class. */ + private val mCurrentJobs = LinkedHashMap() + + /** Current menu item being controlled by this class. */ + private var mMenuItem: MenuItem? = null + + init { + val filter = IntentFilter(FileOperationService.ACTION_PROGRESS) + mContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED) + } + + private fun updateMenuItem(animate: Boolean) { + mMenuItem?.let { + Menus.setEnabledAndVisible(it, mState != State.INVISIBLE) + val icon = it.actionView as ProgressBar + when (mState) { + State.INDETERMINATE -> icon.isIndeterminate = true + State.VISIBLE -> icon.apply { + isIndeterminate = false + setProgress(mTotalProgress, animate) + } + State.INVISIBLE -> {} + } + } + } + + /** + * Sets the menu item controlled by this class. The item's actionView must be a [ProgressBar]. + */ + fun setMenuItem(menuItem: MenuItem) { + (menuItem.actionView as ProgressBar).max = MAX_PROGRESS + mMenuItem = menuItem + updateMenuItem(animate = false) + } + + override fun onReceive(context: Context?, intent: Intent) { + val progresses = intent.getParcelableArrayListExtra( + EXTRA_PROGRESS, + JobProgress::class.java + ) + updateProgress(progresses!!) + } + + private fun updateProgress(progresses: List) { + var requiredBytes = 0L + var currentBytes = 0L + var allFinished = true + + for (jobProgress in progresses) { + Log.d(TAG, "Received $jobProgress") + mCurrentJobs.put(jobProgress.id, jobProgress) + } + for (jobProgress in mCurrentJobs.values) { + if (jobProgress.state != Job.STATE_COMPLETED) { + allFinished = false + } + if (jobProgress.requiredBytes != -1L && jobProgress.currentBytes != -1L) { + requiredBytes += jobProgress.requiredBytes + currentBytes += jobProgress.currentBytes + } + } + + if (mCurrentJobs.isEmpty()) { + mState = State.INVISIBLE + } else if (requiredBytes != 0L) { + mState = State.VISIBLE + mTotalProgress = (MAX_PROGRESS * currentBytes / requiredBytes).toInt() + } else if (allFinished) { + mState = State.VISIBLE + mTotalProgress = MAX_PROGRESS + } else { + mState = State.INDETERMINATE + } + updateMenuItem(animate = true) + } +} diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java index 888a45996..a46659c77 100644 --- a/src/com/android/documentsui/MenuManager.java +++ b/src/com/android/documentsui/MenuManager.java @@ -282,6 +282,15 @@ public abstract class MenuManager { public abstract void updateKeyboardShortcutsMenu( List data, IntFunction stringSupplier); + /** + * Called on option menu creation to instantiate the job progress item if applicable. + * + * @param menu The option menu created. + */ + public void instantiateJobProgress(Menu menu) { + // This icon is not shown in the picker. + } + protected void updateModePicker(MenuItem grid, MenuItem list) { // The order of enabling disabling menu item in wrong order removed accessibility focus. if (mState.derivedMode != State.MODE_LIST) { diff --git a/src/com/android/documentsui/NavigationViewManager.java b/src/com/android/documentsui/NavigationViewManager.java index 12afbd69b..bbae22d42 100644 --- a/src/com/android/documentsui/NavigationViewManager.java +++ b/src/com/android/documentsui/NavigationViewManager.java @@ -18,6 +18,7 @@ package com.android.documentsui; import static com.android.documentsui.base.SharedMinimal.VERBOSE; import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled; +import static com.android.documentsui.util.FlagUtils.isVisualSignalsFlagEnabled; import android.content.res.Resources; import android.content.res.TypedArray; @@ -409,6 +410,9 @@ public class NavigationViewManager extends SelectionTracker.SelectionObserver mSelectionManager; private final Lookup mUriLookup; private final LookupApplicationName mAppNameLookup; + @Nullable private final JobPanelController mJobPanelController; public MenuManager( Features features, @@ -75,6 +79,12 @@ public final class MenuManager extends com.android.documentsui.MenuManager { mSelectionManager = selectionManager; mAppNameLookup = appNameLookup; mUriLookup = uriLookup; + + if (isVisualSignalsFlagEnabled()) { + mJobPanelController = new JobPanelController(context); + } else { + mJobPanelController = null; + } } @Override @@ -141,6 +151,14 @@ public final class MenuManager extends com.android.documentsui.MenuManager { updateContextMenu(menu, selectionDetails); } + @Override + public void instantiateJobProgress(Menu menu) { + if (mJobPanelController == null) { + return; + } + mJobPanelController.setMenuItem(menu.findItem(R.id.option_menu_job_progress)); + } + @Override protected void updateSettings(MenuItem settings, RootInfo root) { Menus.setEnabledAndVisible(settings, root.hasSettings()); diff --git a/src/com/android/documentsui/services/FileOperationService.java b/src/com/android/documentsui/services/FileOperationService.java index dcb2c1db4..14b87ef5b 100644 --- a/src/com/android/documentsui/services/FileOperationService.java +++ b/src/com/android/documentsui/services/FileOperationService.java @@ -68,6 +68,9 @@ public class FileOperationService extends Service implements Job.Listener { public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION"; public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL"; + public static final String ACTION_PROGRESS = "com.android.documentsui.action.PROGRESS"; + public static final String EXTRA_PROGRESS = "com.android.documentsui.PROGRESS"; + @IntDef({ OPERATION_UNKNOWN, OPERATION_COPY, @@ -580,6 +583,7 @@ public class FileOperationService extends Service implements Job.Listener { private final class GlobalJobMonitor implements Runnable { private static final long PROGRESS_INTERVAL_MILLIS = 500L; private boolean mRunning = false; + private long mLastId = 0; private void start() { if (!mRunning) { @@ -602,9 +606,9 @@ public class FileOperationService extends Service implements Job.Listener { } Intent intent = new Intent(); intent.setPackage(getPackageName()); - intent.setAction("com.android.documentsui.PROGRESS"); - intent.putExtra("id", 0); - intent.putParcelableArrayListExtra("progress", progress); + intent.setAction(ACTION_PROGRESS); + intent.putExtra("id", mLastId++); + intent.putParcelableArrayListExtra(EXTRA_PROGRESS, progress); sendBroadcast(intent); } diff --git a/src/com/android/documentsui/services/Job.java b/src/com/android/documentsui/services/Job.java index 0f432cc19..95ad8b335 100644 --- a/src/com/android/documentsui/services/Job.java +++ b/src/com/android/documentsui/services/Job.java @@ -76,11 +76,11 @@ abstract public class Job implements Runnable { @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_CREATED, STATE_STARTED, STATE_SET_UP, STATE_COMPLETED, STATE_CANCELED}) - @interface State {} - static final int STATE_CREATED = 0; - static final int STATE_STARTED = 1; - static final int STATE_SET_UP = 2; - static final int STATE_COMPLETED = 3; + public @interface State {} + public static final int STATE_CREATED = 0; + public static final int STATE_STARTED = 1; + public static final int STATE_SET_UP = 2; + public static final int STATE_COMPLETED = 3; /** * A job is in canceled state as long as {@link #cancel()} is called on it, even after it is * completed. -- cgit v1.2.3-59-g8ed1b