/* * 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.permissioncontroller.appfunctions.ui import android.os.Bundle import android.util.Log import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle.State import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.preference.Preference import androidx.preference.Preference.OnPreferenceClickListener import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceGroup import com.android.permissioncontroller.R import com.android.permissioncontroller.appfunctions.domain.model.AppFunctionPackageInfo import com.android.permissioncontroller.appfunctions.ui.viewmodel.AgentListUiState import com.android.permissioncontroller.appfunctions.ui.viewmodel.AgentListViewModel import com.android.permissioncontroller.appfunctions.ui.viewmodel.AgentListViewModelFactory import com.android.permissioncontroller.common.model.Stateful import kotlinx.coroutines.launch /** * Child fragment for the list of app function agents. * *

* Must be added as a child fragment and its parent fragment must be a {@link * PreferenceFragmentCompat} that implements {@link Parent}. * * @param type of the parent fragment */ class AgentListChildFragment : Fragment(), OnPreferenceClickListener where PF : PreferenceFragmentCompat, PF : AgentListChildFragment.Parent { private lateinit var viewModel: AgentListViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val factory = AgentListViewModelFactory(requireActivity().application) viewModel = ViewModelProvider(this, factory).get(AgentListViewModel::class.java) val preferenceFragment = requirePreferenceFragment() preferenceFragment.lifecycleScope.launch { preferenceFragment.lifecycle.repeatOnLifecycle(State.STARTED) { viewModel.uiStateFlow.collect(::onUiStateChanged) } } } private fun onUiStateChanged(uiState: Stateful) { val preferenceFragment = requirePreferenceFragment() val preferenceManager = preferenceFragment.preferenceManager var preferenceScreen = preferenceFragment.preferenceScreen val context = preferenceManager.context val oldPreferences = mutableMapOf() if (preferenceScreen == null) { preferenceScreen = preferenceManager.createPreferenceScreen(context) preferenceFragment.preferenceScreen = preferenceScreen } else { clearPreferences(preferenceScreen, oldPreferences) } addHeaderPreference(preferenceScreen, oldPreferences) when (uiState) { is Stateful.Loading -> {} // do nothing is Stateful.Failure -> { Log.e(LOG_TAG, "Failed to load agent list", uiState.throwable) addEmptyStatePreference(preferenceScreen, oldPreferences) } is Stateful.Success -> { val agents = uiState.value.agents if (agents.isEmpty()) { addEmptyStatePreference(preferenceScreen, oldPreferences) } else { addAgentPreferences(preferenceScreen, agents, oldPreferences) } } } preferenceFragment.onPreferenceScreenChanged() } private fun clearPreferences( preferenceGroup: PreferenceGroup, oldPreferences: MutableMap, ) { for (i in preferenceGroup.preferenceCount - 1 downTo 0) { val preference = preferenceGroup.getPreference(i) preferenceGroup.removePreference(preference) preference.order = Preference.DEFAULT_ORDER oldPreferences[preference.key] = preference } } private fun addHeaderPreference( preferenceGroup: PreferenceGroup, oldPreferences: Map, ) { val preferenceFragment = requirePreferenceFragment() val preference = oldPreferences[PREFERENCE_KEY_INTRO] ?: preferenceFragment.createHeaderPreference().apply { key = PREFERENCE_KEY_INTRO setTitle(R.string.app_function_agent_list_summary) } preferenceGroup.addPreference(preference) } private fun addEmptyStatePreference( preferenceGroup: PreferenceGroup, oldPreferences: Map, ) { val preferenceFragment = requirePreferenceFragment() val preference = oldPreferences[PREFERENCE_KEY_ZERO_STATE] ?: preferenceFragment.createEmptyStatePreference().apply { key = PREFERENCE_KEY_ZERO_STATE setTitle(R.string.app_function_agent_list_empty_title) setSummary(R.string.app_function_agent_list_empty_summary) } preferenceGroup.addPreference(preference) } private fun addAgentPreferences( preferenceGroup: PreferenceGroup, agents: List, oldPreferences: Map, ) { val preferenceFragment = requirePreferenceFragment() for (agent in agents) { val preference = oldPreferences[agent.packageName] ?: preferenceFragment.createPreference().apply { key = agent.packageName title = agent.label icon = agent.icon onPreferenceClickListener = this@AgentListChildFragment } preferenceGroup.addPreference(preference) } } override fun onPreferenceClick(preference: Preference): Boolean { val agentPackageName = preference.key val context = requireContext() val intent = AgentAccessActivity.createIntent(context, agentPackageName) startActivity(intent) return true } private fun requirePreferenceFragment(): PF { @Suppress("UNCHECKED_CAST") return requireParentFragment() as PF } /** Interface that the parent fragment must implement. */ interface Parent { /** Creates a new header preference for the screen */ fun createHeaderPreference(): Preference /** Creates a new empty state preference for the screen */ fun createEmptyStatePreference(): Preference /** Creates a new preference for an agent. */ fun createPreference(): Preference /** * Callback when changes have been made to the {@link PreferenceScreen} of the parent {@link * PreferenceFragmentCompat}. */ fun onPreferenceScreenChanged() } companion object { private val LOG_TAG = AgentListChildFragment::class.java.simpleName private val PREFERENCE_KEY_INTRO = AgentListChildFragment::class.java.name + ".preference.INTRO" private val PREFERENCE_KEY_ZERO_STATE = AgentListChildFragment::class.java.name + ".preference.ZERO_STATE" /** * Create a new instance of AgentListChildFragment * * @return a new instance of AgentListChildFragment */ fun newInstance(): AgentListChildFragment<*> = AgentListChildFragment() } }