| /* |
| * 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.settings.datausage |
| |
| import android.app.settings.SettingsEnums |
| import android.content.Context |
| import android.content.Intent |
| import android.os.Bundle |
| import androidx.core.net.toUri |
| import androidx.fragment.app.Fragment |
| import com.android.settings.R |
| import com.android.settings.applications.InstalledPackageName |
| import com.android.settings.applications.getApplicationInfo |
| import com.android.settings.contract.TAG_DEVICE_STATE_PREFERENCE |
| import com.android.settings.contract.TAG_DEVICE_STATE_SCREEN |
| import com.android.settings.core.PreferenceScreenMixin |
| import com.android.settings.flags.Flags |
| import com.android.settings.utils.makeLaunchIntent |
| import com.android.settingslib.datastore.HandlerExecutor |
| import com.android.settingslib.datastore.KeyedObserver |
| import com.android.settingslib.metadata.CatalystFlagProviderFactory |
| import com.android.settingslib.metadata.KeyParametersSchema |
| import com.android.settingslib.metadata.ParameterizedPreferenceScreenArgumentsFactory |
| import com.android.settingslib.metadata.PreferenceAvailabilityProvider |
| import com.android.settingslib.metadata.preferencesapi.preconditions.PreconditionStability |
| import com.android.settingslib.metadata.PreferenceLifecycleContext |
| import com.android.settingslib.metadata.PreferenceLifecycleProvider |
| import com.android.settingslib.metadata.PreferenceMetadata |
| import com.android.settingslib.metadata.PreferenceTitleProvider |
| import com.android.settingslib.metadata.ProvidePreferenceScreen |
| import com.android.settingslib.metadata.UI_ONLY_PREFERENCE |
| import com.android.settingslib.metadata.ValidatedKeyParameters |
| import com.android.settingslib.metadata.preferenceHierarchy |
| import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl |
| import com.android.settingslib.utils.applications.PackageObservable |
| import kotlinx.coroutines.CoroutineScope |
| import kotlinx.coroutines.flow.Flow |
| import kotlinx.coroutines.flow.flow |
| import kotlinx.coroutines.flow.map |
| import com.android.settingslib.metadata.preferencesapi.PreferencesApiScreen.Companion.APP_FUNCTION_MOBILE_DATA |
| |
| /** Preference screen for Apps -> Individual App Info -> Mobile data usage. */ |
| @ProvidePreferenceScreen(DataUsageAppDetailScreen.KEY, parameterized = true) |
| open class DataUsageAppDetailScreen |
| private constructor( |
| val context: Context, |
| @Deprecated( |
| "This property will be removed once the catalyst framework stops passing the arguments as a bundle. Use the keyParameters instead." |
| ) |
| final override val arguments: Bundle?, |
| final override val keyParameters: ValidatedKeyParameters?, |
| ) : |
| PreferenceScreenMixin, |
| PreferenceTitleProvider, |
| PreferenceLifecycleProvider, |
| PreferenceAvailabilityProvider { |
| |
| override fun tags(context: Context) = arrayOf( |
| APP_FUNCTION_MOBILE_DATA, |
| TAG_DEVICE_STATE_SCREEN, |
| TAG_DEVICE_STATE_PREFERENCE, |
| // exclude this screen from api result since we have the same data in api_app_data_usage_screen |
| UI_ONLY_PREFERENCE |
| ) |
| |
| private lateinit var keyedObserver: KeyedObserver<String> |
| |
| private val packageName: String = |
| if (CatalystFlagProviderFactory.catalystUseKeyParameters()) { |
| keyParameters!!.getRequired(KEY_APP_PACKAGE_NAME) |
| } else { |
| arguments!!.getString(KEY_APP_PACKAGE_NAME)!! |
| } |
| |
| private var appInfo = context.getApplicationInfo(packageName) |
| |
| @Deprecated( |
| "This constructor will be removed once the catalyst framework stops passing the arguments as a bundle. Use the other constructor instead." |
| ) |
| constructor(context: Context, args: Bundle) : this(context, args, null) |
| |
| constructor( |
| context: Context, |
| keyParameters: ValidatedKeyParameters, |
| ) : this(context, null, keyParameters) |
| |
| override val key: String |
| get() = KEY |
| |
| override val keyParametersSchema: KeyParametersSchema |
| get() = parametersSchema |
| |
| //TODO(b/462618020) Catalyst-purpose: replace default purpose with 2 line description |
| override val purpose: Int |
| get() = R.string.app_data_usage_screen_purpose |
| |
| override val bindingKey |
| get() = "$KEY-$packageName" |
| |
| override val screenTitle: Int |
| get() = R.string.data_usage_app_summary_title |
| |
| override val highlightMenuKey: Int |
| get() = R.string.menu_key_apps |
| |
| override fun getMetricsCategory() = SettingsEnums.APP_DATA_USAGE |
| |
| |
| override fun getTitle(context: Context): CharSequence? = |
| appInfo?.loadLabel(context.packageManager) |
| |
| override fun isFlagEnabled(context: Context) = Flags.deeplinkApps25q4() |
| |
| override val availabilityDescription = |
| "The app must be enabled." |
| |
| override fun getAvailabilityStability() = PreconditionStability.UNSTABLE |
| |
| override fun isAvailable(context: Context) = appInfo != null |
| |
| override fun hasCompleteHierarchy() = false |
| |
| override fun fragmentClass(): Class<out Fragment>? = AppDataUsage::class.java |
| |
| override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent { |
| val intent = |
| if (CatalystFlagProviderFactory.catalystUseKeyParameters()) { |
| makeLaunchIntent( |
| context, |
| AppDataUsageActivity::class.java, |
| keyParameters!!, |
| metadata?.bindingKey, |
| ) |
| } else { |
| makeLaunchIntent( |
| context, |
| AppDataUsageActivity::class.java, |
| arguments!!, |
| metadata?.bindingKey, |
| ) |
| } |
| intent.apply { data = "package:$packageName".toUri() } |
| |
| return intent |
| } |
| |
| override fun getPreferenceHierarchy(context: Context, coroutineScope: CoroutineScope) = |
| preferenceHierarchy(context) {} |
| |
| override fun onCreate(context: PreferenceLifecycleContext) { |
| // observer to detect package changes (disabled/enabled/uninstall) |
| val observer = |
| KeyedObserver<String> { _, _ -> |
| appInfo = context.getApplicationInfo(packageName) |
| context.notifyPreferenceChange(bindingKey) |
| } |
| keyedObserver = observer |
| val executor = HandlerExecutor.main |
| if (isContainer(context)) { |
| PackageObservable.get(context).addObserver(packageName, observer, executor) |
| } |
| } |
| |
| override fun onDestroy(context: PreferenceLifecycleContext) { |
| if (isContainer(context)) { |
| PackageObservable.get(context).removeObserver(packageName, keyedObserver) |
| } |
| } |
| |
| companion object : ParameterizedPreferenceScreenArgumentsFactory { |
| const val KEY = "app_data_usage_screen" |
| const val KEY_APP_PACKAGE_NAME = "app" |
| |
| @JvmStatic |
| override val parametersSchema = KeyParametersSchema { |
| parameter(KEY_APP_PACKAGE_NAME, "The package name of the app", required = true, type = InstalledPackageName) |
| } |
| |
| @JvmStatic |
| override fun keyParameters(context: Context): Flow<ValidatedKeyParameters> { |
| // TODO (b/457649430): when the catalyst framework stops passing the arguments as a |
| // bundle: replace the parameters(context) call to the actual implementation, |
| // or make this function the primary implementation and the legacy parameters() should |
| // call this one. |
| return parameters(context).map { bundle -> parametersSchema.prepare(bundle) } |
| } |
| |
| @Deprecated( |
| "This method will be removed once the catalyst framework stops passing the arguments as a bundle. Use keyParameters instead." |
| ) |
| @JvmStatic |
| fun parameters(context: Context): Flow<Bundle> = flow { |
| val repo = AppListRepositoryImpl(context) |
| // Make sure to exclude system apps |
| repo.loadAndMaybeExcludeSystemApps(context.userId, true).forEach { app -> |
| emit(Bundle(1).apply { putString(KEY_APP_PACKAGE_NAME, app.packageName) }) |
| } |
| } |
| } |
| } |