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

Commit 77229605 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Clean up foreground service manager controllers"

parents 30d45531 9deea4a4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -2379,6 +2379,8 @@
    <string name="fgs_manager_dialog_title">Active apps</string>
    <!-- Label of the button to stop an app from running [CHAR LIMIT=12]-->
    <string name="fgs_manager_app_item_stop_button_label">Stop</string>
    <!-- Label of the button to stop an app from running but the app is already stopped and the button is disabled [CHAR LIMIT=12]-->
    <string name="fgs_manager_app_item_stop_button_stopped_label">Stopped</string>

    <!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] -->
    <string name="clipboard_edit_text_copy">Copy</string>
+0 −141
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.systemui.fgsmanager

import android.content.Context
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.GuardedBy
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.fgsmanager.FgsManagerDialogController.RunningApp
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.time.SystemClock
import java.util.concurrent.Executor

/**
 * Dialog which shows a list of running foreground services and offers controls to them
 */
class FgsManagerDialog(
    context: Context,
    private val executor: Executor,
    @Background private val backgroundExecutor: Executor,
    private val systemClock: SystemClock,
    private val fgsManagerDialogController: FgsManagerDialogController
) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog) {

    private val appListRecyclerView: RecyclerView = RecyclerView(this.context)
    private val adapter: AppListAdapter = AppListAdapter()

    init {
        setTitle(R.string.fgs_manager_dialog_title)
        setView(appListRecyclerView)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        appListRecyclerView.layoutManager = LinearLayoutManager(context)
        fgsManagerDialogController.registerDialogForChanges(
                object : FgsManagerDialogController.FgsManagerDialogCallback {
                    override fun onRunningAppsChanged(apps: List<RunningApp>) {
                        executor.execute {
                            adapter.setData(apps)
                        }
                    }
                }
        )
        appListRecyclerView.adapter = adapter
        backgroundExecutor.execute { adapter.setData(fgsManagerDialogController.runningAppList) }
    }

    private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() {
        private val lock = Any()

        @GuardedBy("lock")
        private val data: MutableList<RunningApp> = ArrayList()
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder {
            return AppItemViewHolder(LayoutInflater.from(context)
                            .inflate(R.layout.fgs_manager_app_item, parent, false))
        }

        override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) {
            var runningApp: RunningApp
            synchronized(lock) {
                runningApp = data[position]
            }
            with(holder) {
                iconView.setImageDrawable(runningApp.mIcon)
                appLabelView.text = runningApp.mAppLabel
                durationView.text = DateUtils.formatDuration(
                        Math.max(systemClock.elapsedRealtime() - runningApp.mTimeStarted, 60000),
                        DateUtils.LENGTH_MEDIUM)
                stopButton.setOnClickListener {
                    fgsManagerDialogController
                            .stopAllFgs(runningApp.mUserId, runningApp.mPackageName)
                }
            }
        }

        override fun getItemCount(): Int {
            synchronized(lock) { return data.size }
        }

        fun setData(newData: List<RunningApp>) {
            var oldData: List<RunningApp>
            synchronized(lock) {
                oldData = ArrayList(data)
                data.clear()
                data.addAll(newData)
            }

            DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                override fun getOldListSize(): Int {
                    return oldData.size
                }

                override fun getNewListSize(): Int {
                    return newData.size
                }

                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int):
                        Boolean {
                    return oldData[oldItemPosition] == newData[newItemPosition]
                }

                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int):
                        Boolean {
                    return true // TODO, look into updating the time subtext
                }
            }).dispatchUpdatesTo(this)
        }
    }

    private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
        val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label)
        val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration)
        val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon)
        val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button)
    }
}
 No newline at end of file
+0 −151
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.systemui.fgsmanager

import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.UserHandle
import android.util.ArrayMap
import android.util.Log
import androidx.annotation.GuardedBy
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.policy.RunningFgsController
import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime
import javax.inject.Inject

/**
 * Controls events relevant to FgsManagerDialog
 */
class FgsManagerDialogController @Inject constructor(
    private val packageManager: PackageManager,
    @Background private val backgroundHandler: Handler,
    private val runningFgsController: RunningFgsController
) : RunningFgsController.Callback {
    private val lock = Any()
    private val clearCacheToken = Any()

    @GuardedBy("lock")
    private var runningApps: Map<UserPackageTime, RunningApp>? = null
    @GuardedBy("lock")
    private var listener: FgsManagerDialogCallback? = null

    interface FgsManagerDialogCallback {
        fun onRunningAppsChanged(apps: List<RunningApp>)
    }

    data class RunningApp(
        val mUserId: Int,
        val mPackageName: String,
        val mAppLabel: CharSequence,
        val mIcon: Drawable,
        val mTimeStarted: Long
    )

    val runningAppList: List<RunningApp>
        get() {
            synchronized(lock) {
                if (runningApps == null) {
                    onFgsPackagesChangedLocked(runningFgsController.getPackagesWithFgs())
                }
                return convertToRunningAppList(runningApps!!)
            }
        }

    fun registerDialogForChanges(callback: FgsManagerDialogCallback) {
        synchronized(lock) {
            runningFgsController.addCallback(this)
            listener = callback
            backgroundHandler.removeCallbacksAndMessages(clearCacheToken)
        }
    }

    fun onFinishDialog() {
        synchronized(lock) {
            listener = null
            // Keep data such as icons cached for some time since loading can be slow
            backgroundHandler.postDelayed(
                    {
                        synchronized(lock) {
                            runningFgsController.removeCallback(this)
                            runningApps = null
                        }
                    }, clearCacheToken, RUNNING_APP_CACHE_TIMEOUT_MILLIS)
        }
    }

    private fun onRunningAppsChanged(apps: ArrayMap<UserPackageTime, RunningApp>) {
        listener?.let {
            backgroundHandler.post { it.onRunningAppsChanged(convertToRunningAppList(apps)) }
        }
    }

    override fun onFgsPackagesChanged(packages: List<UserPackageTime>) {
        backgroundHandler.post {
            synchronized(lock) { onFgsPackagesChangedLocked(packages) }
        }
    }

    /**
     * Run on background thread
     */
    private fun onFgsPackagesChangedLocked(packages: List<UserPackageTime>) {
        val newRunningApps = ArrayMap<UserPackageTime, RunningApp>()
        for (packageWithFgs in packages) {
            val ra = runningApps?.get(packageWithFgs)
            if (ra == null) {
                val userId = packageWithFgs.userId
                val packageName = packageWithFgs.packageName
                try {
                    val ai = packageManager.getApplicationInfo(packageName, 0)
                    var icon = packageManager.getApplicationIcon(ai)
                    icon = packageManager.getUserBadgedIcon(icon,
                            UserHandle.of(userId))
                    val label = packageManager.getApplicationLabel(ai)
                    newRunningApps[packageWithFgs] = RunningApp(userId, packageName,
                            label, icon, packageWithFgs.startTimeMillis)
                } catch (e: NameNotFoundException) {
                    Log.e(LOG_TAG,
                            "Application info not found: $packageName", e)
                }
            } else {
                newRunningApps[packageWithFgs] = ra
            }
        }
        runningApps = newRunningApps
        onRunningAppsChanged(newRunningApps)
    }

    fun stopAllFgs(userId: Int, packageName: String) {
        runningFgsController.stopFgs(userId, packageName)
    }

    companion object {
        private val LOG_TAG = FgsManagerDialogController::class.java.simpleName
        private const val RUNNING_APP_CACHE_TIMEOUT_MILLIS: Long = 20_000

        private fun convertToRunningAppList(apps: Map<UserPackageTime, RunningApp>):
                List<RunningApp> {
            val result = mutableListOf<RunningApp>()
            result.addAll(apps.values)
            result.sortWith { a: RunningApp, b: RunningApp ->
                b.mTimeStarted.compareTo(a.mTimeStarted)
            }
            return result
        }
    }
}
+0 −63
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.systemui.fgsmanager

import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.animation.DialogLaunchAnimator
import android.content.DialogInterface
import android.view.View
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.time.SystemClock
import java.util.concurrent.Executor
import javax.inject.Inject

/**
 * Factory to create [FgsManagerDialog] instances
 */
@SysUISingleton
class FgsManagerDialogFactory
@Inject constructor(
    private val context: Context,
    @Main private val executor: Executor,
    @Background private val backgroundExecutor: Executor,
    private val systemClock: SystemClock,
    private val dialogLaunchAnimator: DialogLaunchAnimator,
    private val fgsManagerDialogController: FgsManagerDialogController
) {

    val lock = Any()

    companion object {
        private var fgsManagerDialog: FgsManagerDialog? = null
    }

    /**
     * Creates the dialog if it doesn't exist
     */
    fun create(viewLaunchedFrom: View?) {
        if (fgsManagerDialog == null) {
            fgsManagerDialog = FgsManagerDialog(context, executor, backgroundExecutor,
                    systemClock, fgsManagerDialogController)
            fgsManagerDialog!!.setOnDismissListener { i: DialogInterface? ->
                fgsManagerDialogController.onFinishDialog()
                fgsManagerDialog = null
            }
            dialogLaunchAnimator.showFromView(fgsManagerDialog!!, viewLaunchedFrom!!)
        }
    }
}
 No newline at end of file
+430 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading