Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 60ab4dfb authored by Austin Tankiang's avatar Austin Tankiang
Browse files

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
parent e1604abe
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<?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" />
+6 −0
Original line number Diff line number Diff line
@@ -33,6 +33,12 @@
        android:visible="false"
        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"
+1 −0
Original line number Diff line number Diff line
@@ -218,6 +218,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>
+5 −0
Original line number Diff line number Diff line
@@ -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>
+126 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}
Loading