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

Commit d1e102fa authored by Chris Antol's avatar Chris Antol Committed by Android (Google) Code Review
Browse files

Merge "Support Storage App List in Catalyst" into main

parents 47cc00a5 39cf8de8
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
package: "com.android.settings.flags"
container: "system_ext"

flag {
  name: "catalyst_app_list"
  namespace: "android_settings"
  description: "Basic support for App List pages"
  bug: "404280548"
}
 No newline at end of file
+148 −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.settings.spa.app.catalyst

import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.Bundle
import com.android.settings.R
import com.android.settings.flags.Flags
import com.android.settings.spa.app.storage.StorageType
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.NoOpKeyedObservable
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceFragment
import com.android.settingslib.preference.PreferenceScreenCreator
import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl
import com.android.settingslib.spaprivileged.model.app.AppStorageRepositoryImpl
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map

@ProvidePreferenceScreen(AppInfoStorageScreen.KEY, parameterized = true)
class AppInfoStorageScreen(
    private val context: Context,
    override val arguments: Bundle
) : PreferenceScreenCreator,
    PreferenceSummaryProvider,
    PreferenceTitleProvider,
    PersistentPreference<Long> {


    private val appInfo by lazy {
        context.packageManager.getApplicationInfo(arguments.getString("app")!!, 0)
    }

    override val key: String
        get() = KEY

    override val valueType: Class<Long>
        get() = Long::class.javaObjectType

    override val sensitivityLevel: @SensitivityLevel Int
        get() = SensitivityLevel.NO_SENSITIVITY

    override val screenTitle: Int
        get() = R.string.storage_label

    override fun getTitle(context: Context): CharSequence? {
        return appInfo.loadLabel(context.packageManager).toString()
    }

    override fun getSummary(context: Context): CharSequence? {
        return AppStorageRepositoryImpl(context).formatSize(appInfo)
    }

    override fun isFlagEnabled(context: Context) = Flags.catalystAppList()

    override fun extras(context: Context): Bundle? {
        return Bundle(1).apply {
            putString(KEY_EXTRA_PACKAGE_NAME, arguments.getString("app"))
        }
    }

    override fun hasCompleteHierarchy() = false

    override fun fragmentClass() = PreferenceFragment::class.java

    override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(context, this) {
        // TODO(b/404280477): app info screen contents
    }

    override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
        ReadWritePermit.ALLOW

    override fun getWritePermit(context: Context, callingPid: Int, callingUid: Int) =
        ReadWritePermit.DISALLOW

    override fun storage(context: Context): KeyValueStore {
        return AppStorageStore(context, appInfo)
    }

    companion object {
        const val KEY = "app_info_storage_screen"

        const val KEY_EXTRA_PACKAGE_NAME = "package_name"

        @JvmStatic
        fun parameters(context: Context): Flow<Bundle> {
            val repo = AppListRepositoryImpl(context)
            val apps = flow {
                repo.loadAndFilterApps(context.userId, true)
                    .filter { app -> StorageType.Apps.filter(app) }.forEach { emit(it) }
            }
            return apps.map { app ->
                Bundle(1).apply {
                    putString("app", app.packageName)
                }
            }
        }
    }
}

private class AppStorageStore(
    private val context: Context,
    private val appInfo: ApplicationInfo
) :
    NoOpKeyedObservable<String>(),
    KeyValueStore {

    override fun contains(key: String): Boolean {
        return true
    }

    override fun <T : Any> getValue(
        key: String,
        valueType: Class<T>
    ): T? {
        return (AppStorageRepositoryImpl(context).calculateSizeBytes(appInfo) ?: 0L) as T
    }

    override fun <T : Any> setValue(
        key: String,
        valueType: Class<T>,
        value: T?
    ) {
    }

}
+82 −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.settings.spa.app.catalyst

import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceFragment
import com.android.settingslib.preference.PreferenceScreenCreator
import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl
import com.android.settings.flags.Flags
import com.android.settings.spa.app.storage.StorageType
import com.android.settingslib.metadata.PreferenceMetadata
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking

// future improvement = parameterize this to support apps (default) and games
@ProvidePreferenceScreen(AppStorageAppListScreen.KEY, parameterized = true)
class AppStorageAppListScreen(
    override val arguments: Bundle
) : PreferenceScreenCreator {
    override val key: String
        get() = KEY

    override val title: Int
        get() = StorageType.Apps.titleResource

    override fun isFlagEnabled(context: Context) = Flags.catalystAppList()

    override fun hasCompleteHierarchy() = false

    override fun fragmentClass() = PreferenceFragment::class.java

    override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent? {
        return Intent("com.android.settings.APP_STORAGE_SETTINGS")
            .setPackage(context.packageName)
    }

    override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(context, this) {
        val repo = AppListRepositoryImpl(context)
        val apps = runBlocking {
            repo.loadAndFilterApps(
                context.userId,
                arguments.getBoolean(KEY_INCLUDE_SYSTEM_APPS)
            )
        }.filter { app -> StorageType.Apps.filter(app) }
        for (app in apps) {
            addParameterizedScreen(
                AppInfoStorageScreen.KEY,
                Bundle(1).apply { putString("app", app.packageName) }
            )
        }
    }

    companion object {
        const val KEY = "app_storage_app_list"

        const val KEY_INCLUDE_SYSTEM_APPS = "include_system"

        @JvmStatic
        fun parameters(context: Context): Flow<Bundle> {
            return flowOf(Bundle(1).apply { putBoolean(KEY_INCLUDE_SYSTEM_APPS, true) })
        }
    }
}
+6 −6
Original line number Diff line number Diff line
@@ -60,20 +60,20 @@ sealed class StorageAppListPageProvider(private val type: StorageType) : Setting

sealed class StorageType(
    @StringRes val titleResource: Int,
    val filter: (AppRecordWithSize) -> Boolean
    val filter: (ApplicationInfo) -> Boolean
) {
    object Apps : StorageType(
        titleResource = R.string.apps_storage,
        filter = {
            (it.app.flags and ApplicationInfo.FLAG_IS_GAME) == 0 &&
            it.app.category != ApplicationInfo.CATEGORY_GAME
            (it.flags and ApplicationInfo.FLAG_IS_GAME) == 0 &&
            it.category != ApplicationInfo.CATEGORY_GAME
        }
    )
    object Games : StorageType(
        titleResource = R.string.game_storage_settings,
        filter = {
            (it.app.flags and ApplicationInfo.FLAG_IS_GAME) != 0 ||
                it.app.category == ApplicationInfo.CATEGORY_GAME
            (it.flags and ApplicationInfo.FLAG_IS_GAME) != 0 ||
                it.category == ApplicationInfo.CATEGORY_GAME
        }
    )
}
@@ -120,7 +120,7 @@ class StorageAppListModel(
        userIdFlow: Flow<Int>,
        option: Int,
        recordListFlow: Flow<List<AppRecordWithSize>>
    ): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem { type.filter(it) }
    ): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem { type.filter(it.app) }

    @Composable
    override fun getSummary(option: Int, record: AppRecordWithSize): () -> String {