blob: a8ab8b0a4f931f6d4e4f736cbc590cd0eaa56680 [file] [log] [blame]
/*
* 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.LayoutInflater
import android.view.MenuItem
import android.view.ViewGroup
import android.widget.PopupWindow
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].
*/
@Suppress("ktlint:standard:comment-wrapping")
fun setMenuItem(menuItem: MenuItem) {
val progressIcon = menuItem.actionView as ProgressBar
progressIcon.max = MAX_PROGRESS
progressIcon.setOnClickListener { view ->
val panel = LayoutInflater.from(mContext).inflate(
R.layout.job_progress_panel,
/* root= */ null
)
val popupWidth = mContext.resources.getDimension(R.dimen.job_progress_panel_width) +
mContext.resources.getDimension(R.dimen.job_progress_panel_margin)
val popup = PopupWindow(
/* contentView= */ panel,
/* width= */ popupWidth.toInt(),
/* height= */ ViewGroup.LayoutParams.WRAP_CONTENT,
/* focusable= */ true
)
popup.showAsDropDown(
/* anchor= */ view,
/* xoff= */ view.width - popupWidth.toInt(),
/* yoff= */ 0
)
}
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)
}
}