summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Austin Tankiang <austinct@google.com> 2025-03-17 22:01:15 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-17 22:01:15 -0700
commit91bc3ee29efaba6a55bb1eb8e5cc6c214cde7ca0 (patch)
treec86525d4857ac85ab19f052f2875a424a0c974d5
parent8f03bda5e1e45faa4c76a6f43da3fe7d91ab6283 (diff)
parent60ab4dfb4e8814d1da61ba450cd228b4771b84bf (diff)
Merge "Add job progress icon to the app bar" into main
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/layout/job_progress_toolbar_item.xml25
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml6
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml1
-rw-r--r--res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml5
-rw-r--r--src/com/android/documentsui/JobPanelController.kt126
-rw-r--r--src/com/android/documentsui/MenuManager.java9
-rw-r--r--src/com/android/documentsui/NavigationViewManager.java4
-rw-r--r--src/com/android/documentsui/files/MenuManager.java18
-rw-r--r--src/com/android/documentsui/services/FileOperationService.java10
-rw-r--r--src/com/android/documentsui/services/Job.java10
-rw-r--r--tests/unit/com/android/documentsui/JobPanelControllerTest.kt207
11 files changed, 413 insertions, 8 deletions
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/job_progress_toolbar_item.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/job_progress_toolbar_item.xml
new file mode 100644
index 000000000..222c8d43e
--- /dev/null
+++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/job_progress_toolbar_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<com.google.android.material.progressindicator.CircularProgressIndicator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ style="@style/JobProgressToolbarIndicatorStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:focusable="true" />
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml b/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml
index 0e636a18c..809a1386a 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/menu/activity.xml
@@ -34,6 +34,12 @@
app:showAsAction="always|collapseActionView"
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
<item
+ android:id="@+id/option_menu_job_progress"
+ android:enabled="false"
+ android:visible="false"
+ app:actionLayout="@layout/job_progress_toolbar_item"
+ app:showAsAction="always" />
+ <item
android:id="@+id/sub_menu_grid"
android:title="@string/menu_grid"
android:icon="@drawable/ic_menu_view_grid"
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml
index 872cf32ce..a76cb131b 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml
@@ -226,6 +226,7 @@
<!-- Main margin is set by main_container_padding_start for the menu button, here is for
the space between the button the text/title. -->
<dimen name="search_bar_text_margin_start">@dimen/space_extra_small_6</dimen>
+ <dimen name="job_progress_toolbar_indicator_size">24dp</dimen>
<!-- The main margin is controlled above on paddingStart, zeroing toolbar_content_insets to
avoid pushing the title or button further. -->
<dimen name="toolbar_content_inset_start">0dp</dimen>
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml
index 5d0687a37..6819c12a3 100644
--- a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml
+++ b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml
@@ -34,6 +34,11 @@
<item name="android:maxHeight">@dimen/icon_size_headline_large</item>
</style>
+ <style name="JobProgressToolbarIndicatorStyle" parent="@style/Widget.Material3.CircularProgressIndicator.ExtraSmall">
+ <item name="indicatorSize">@dimen/job_progress_toolbar_indicator_size</item>
+ <item name="indicatorInset">@dimen/space_extra_small_6</item>
+ </style>
+
<style name="ToolbarStyles" parent="@style/Widget.Material3.Toolbar">
<item name="android:paddingStart">@dimen/toolbar_padding_start</item>
<item name="android:paddingEnd">@dimen/toolbar_padding_end</item>
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<String, JobProgress>()
+
+ /** 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<JobProgress>(
+ EXTRA_PROGRESS,
+ JobProgress::class.java
+ )
+ updateProgress(progresses!!)
+ }
+
+ private fun updateProgress(progresses: List<JobProgress>) {
+ 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<KeyboardShortcutGroup> data, IntFunction<String> 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<St
mActivity.getResources().getBoolean(R.bool.full_bar_search_view);
boolean showSearchBar = mActivity.getResources().getBoolean(R.bool.show_search_bar);
mInjector.searchManager.install(mToolbar.getMenu(), fullBarSearch, showSearchBar);
+ if (isVisualSignalsFlagEnabled()) {
+ mInjector.menuManager.instantiateJobProgress(mToolbar.getMenu());
+ }
}
mInjector.menuManager.updateOptionMenu(mToolbar.getMenu());
mInjector.searchManager.showMenu(mState.stack);
diff --git a/src/com/android/documentsui/files/MenuManager.java b/src/com/android/documentsui/files/MenuManager.java
index 7dc6b57d6..2c85dcb01 100644
--- a/src/com/android/documentsui/files/MenuManager.java
+++ b/src/com/android/documentsui/files/MenuManager.java
@@ -17,6 +17,7 @@
package com.android.documentsui.files;
import static com.android.documentsui.util.FlagUtils.isDesktopFileHandlingFlagEnabled;
+import static com.android.documentsui.util.FlagUtils.isVisualSignalsFlagEnabled;
import android.content.Context;
import android.content.res.Resources;
@@ -30,9 +31,11 @@ import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.selection.SelectionTracker;
+import com.android.documentsui.JobPanelController;
import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Features;
@@ -56,6 +59,7 @@ public final class MenuManager extends com.android.documentsui.MenuManager {
private final SelectionTracker<String> mSelectionManager;
private final Lookup<String, Uri> 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
@@ -142,6 +152,14 @@ public final class MenuManager extends com.android.documentsui.MenuManager {
}
@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.
diff --git a/tests/unit/com/android/documentsui/JobPanelControllerTest.kt b/tests/unit/com/android/documentsui/JobPanelControllerTest.kt
new file mode 100644
index 000000000..be0c9adbd
--- /dev/null
+++ b/tests/unit/com/android/documentsui/JobPanelControllerTest.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.Intent
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.widget.ActionMenuView
+import android.widget.ProgressBar
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.documentsui.flags.Flags.FLAG_USE_MATERIAL3
+import com.android.documentsui.flags.Flags.FLAG_VISUAL_SIGNALS_RO
+import com.android.documentsui.services.FileOperationService.ACTION_PROGRESS
+import com.android.documentsui.services.FileOperationService.EXTRA_PROGRESS
+import com.android.documentsui.services.Job
+import com.android.documentsui.services.JobProgress
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private data class MutableJobProgress(
+ var id: String,
+ @Job.State var state: Int,
+ var msg: String?,
+ var hasFailures: Boolean,
+ var currentBytes: Long = -1,
+ var requiredBytes: Long = -1,
+ var msRemaining: Long = -1,
+) {
+ fun toJobProgress() =
+ JobProgress(id, state, msg, hasFailures, currentBytes, requiredBytes, msRemaining)
+}
+
+@SmallTest
+@RequiresFlagsEnabled(FLAG_USE_MATERIAL3, FLAG_VISUAL_SIGNALS_RO)
+@RunWith(AndroidJUnit4::class)
+class JobPanelControllerTest {
+ @get:Rule
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private val mContext = InstrumentationRegistry.getInstrumentation().targetContext
+
+ // The default progress bar only has an indeterminate state, so we need to style it to allow
+ // determinate progress.
+ private val mProgressBar = ProgressBar(
+ mContext,
+ null,
+ android.R.attr.progressBarStyleHorizontal
+ )
+ private val mMenuItem = ActionMenuView(mContext).menu.add("job_panel").apply {
+ actionView = mProgressBar
+ }
+ private lateinit var mController: JobPanelController
+ private var mLastId = 0L
+
+ private fun sendProgress(progress: ArrayList<JobProgress>, id: Long = mLastId++) {
+ var intent = Intent(ACTION_PROGRESS).apply {
+ `package` = mContext.packageName
+ putExtra("id", id)
+ putParcelableArrayListExtra(EXTRA_PROGRESS, progress)
+ }
+ mController.onReceive(mContext, intent)
+ }
+
+ @Before
+ fun setUp() {
+ mController = JobPanelController(mContext)
+ mController.setMenuItem(mMenuItem)
+ }
+
+ @Test
+ fun testSingleJob() {
+ assertFalse(mMenuItem.isVisible())
+ assertFalse(mMenuItem.isEnabled())
+
+ val progress = MutableJobProgress(
+ id = "jobId1",
+ state = Job.STATE_STARTED,
+ msg = "Job started",
+ hasFailures = false,
+ currentBytes = 0,
+ requiredBytes = 10,
+ msRemaining = -1
+ )
+ sendProgress(arrayListOf(progress.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(0, mProgressBar.progress)
+
+ progress.apply {
+ state = Job.STATE_SET_UP
+ msg = "Job in progress"
+ currentBytes = 4
+ }
+ sendProgress(arrayListOf(progress.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(40, mProgressBar.progress)
+
+ progress.apply {
+ state = Job.STATE_COMPLETED
+ msg = "Job completed"
+ currentBytes = 10
+ }
+ sendProgress(arrayListOf(progress.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(100, mProgressBar.progress)
+ }
+
+ @Test
+ fun testMultipleJobs() {
+ assertFalse(mMenuItem.isVisible())
+ assertFalse(mMenuItem.isEnabled())
+
+ val progress1 = MutableJobProgress(
+ id = "jobId1",
+ state = Job.STATE_STARTED,
+ msg = "Job started",
+ hasFailures = false,
+ currentBytes = 0,
+ requiredBytes = 10,
+ msRemaining = -1
+ )
+ val progress2 = MutableJobProgress(
+ id = "jobId2",
+ state = Job.STATE_STARTED,
+ msg = "Job started",
+ hasFailures = false,
+ currentBytes = 0,
+ requiredBytes = 40,
+ msRemaining = -1
+ )
+ sendProgress(arrayListOf(progress1.toJobProgress(), progress2.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(0, mProgressBar.progress)
+
+ progress1.apply {
+ state = Job.STATE_SET_UP
+ msg = "Job in progress"
+ currentBytes = 4
+ }
+ sendProgress(arrayListOf(progress1.toJobProgress(), progress2.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(8, mProgressBar.progress)
+
+ progress1.apply {
+ state = Job.STATE_COMPLETED
+ msg = "Job completed"
+ currentBytes = 10
+ }
+ sendProgress(arrayListOf(progress1.toJobProgress(), progress2.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(20, mProgressBar.progress)
+
+ progress2.apply {
+ state = Job.STATE_SET_UP
+ msg = "Job in progress"
+ currentBytes = 30
+ }
+ sendProgress(arrayListOf(progress1.toJobProgress(), progress2.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(80, mProgressBar.progress)
+
+ progress2.apply {
+ state = Job.STATE_COMPLETED
+ msg = "Job completed"
+ currentBytes = 40
+ }
+ sendProgress(arrayListOf(progress1.toJobProgress(), progress2.toJobProgress()))
+
+ assertTrue(mMenuItem.isVisible())
+ assertTrue(mMenuItem.isEnabled())
+ assertEquals(100, mProgressBar.progress)
+ }
+}