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

Commit c165fabf authored by Josh's avatar Josh
Browse files

Fixed crashes due to HSUM issues

+ moved model conversion logic from UI layer to Domain layer
+ replaced default context (system) with current user context

Fix: 369425977
Test: Manual - open shortcut helper from Chrome on HSUM enabled device,
ensure device doesn't crash
Flag: com.android.systemui.keyboard_shortcut_helper_rewrite

Change-Id: Ifb9f0b6a193953190fa77a0b7865f6131f74a38d
parent e6634fe6
Loading
Loading
Loading
Loading
+15 −71
Original line number Diff line number Diff line
@@ -16,10 +16,7 @@

package com.android.systemui.keyboard.shortcut.ui.composable

import android.content.Context
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Icon
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
@@ -55,12 +52,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Apps
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Tv
import androidx.compose.material.icons.filled.VerticalSplit
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
@@ -111,16 +104,15 @@ import androidx.compose.ui.util.fastForEachIndexed
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.IconSource
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.CentralSurfaces

@Composable
fun ShortcutHelper(
@@ -187,7 +179,7 @@ private fun ActiveShortcutHelper(
private fun ShortcutHelperSinglePane(
    searchQuery: String,
    onSearchQueryChanged: (String) -> Unit,
    categories: List<ShortcutCategory>,
    categories: List<ShortcutCategoryUi>,
    selectedCategoryType: ShortcutCategoryType?,
    onCategorySelected: (ShortcutCategoryType?) -> Unit,
    onKeyboardSettingsClicked: () -> Unit,
@@ -228,7 +220,7 @@ private fun ShortcutHelperSinglePane(
@Composable
private fun CategoriesPanelSinglePane(
    searchQuery: String,
    categories: List<ShortcutCategory>,
    categories: List<ShortcutCategoryUi>,
    selectedCategoryType: ShortcutCategoryType?,
    onCategorySelected: (ShortcutCategoryType?) -> Unit,
) {
@@ -267,7 +259,7 @@ private fun CategoriesPanelSinglePane(
@Composable
private fun CategoryItemSinglePane(
    searchQuery: String,
    category: ShortcutCategory,
    category: ShortcutCategoryUi,
    isExpanded: Boolean,
    onClick: () -> Unit,
    shape: Shape,
@@ -278,9 +270,9 @@ private fun CategoryItemSinglePane(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp),
            ) {
                ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.icon)
                ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.iconSource)
                Spacer(modifier = Modifier.width(16.dp))
                Text(category.label(LocalContext.current))
                Text(category.label)
                Spacer(modifier = Modifier.weight(1f))
                RotatingExpandCollapseIcon(isExpanded)
            }
@@ -291,23 +283,6 @@ private fun CategoryItemSinglePane(
    }
}

private val ShortcutCategory.icon: IconSource
    @Composable
    get() =
        when (type) {
            ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
            ShortcutCategoryType.MultiTasking ->
                IconSource(imageVector = Icons.Default.VerticalSplit)
            ShortcutCategoryType.InputMethodEditor ->
                IconSource(imageVector = Icons.Default.Keyboard)
            ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
            is ShortcutCategoryType.CurrentApp -> {
                val context = LocalContext.current
                val iconDrawable = context.packageManager.getApplicationIcon(type.packageName)
                IconSource(painter = rememberDrawablePainter(drawable = iconDrawable))
            }
        }

@Composable
fun ShortcutCategoryIcon(
    source: IconSource,
@@ -322,37 +297,6 @@ fun ShortcutCategoryIcon(
    }
}

private fun ShortcutCategory.label(context: Context): String =
    when (type) {
        ShortcutCategoryType.System -> context.getString(R.string.shortcut_helper_category_system)
        ShortcutCategoryType.MultiTasking ->
            context.getString(R.string.shortcut_helper_category_multitasking)
        ShortcutCategoryType.InputMethodEditor ->
            context.getString(R.string.shortcut_helper_category_input)
        ShortcutCategoryType.AppCategories ->
            context.getString(R.string.shortcut_helper_category_app_shortcuts)
        is ShortcutCategoryType.CurrentApp -> getApplicationLabelForCurrentApp(type, context)
    }

private fun getApplicationLabelForCurrentApp(
    type: ShortcutCategoryType.CurrentApp,
    context: Context,
): String {
    val packageManagerForUser = CentralSurfaces.getPackageManagerForUser(context, context.userId)
    return try {
        val currentAppInfo =
            packageManagerForUser.getApplicationInfoAsUser(
                type.packageName,
                /* flags = */ 0,
                context.userId,
            )
        packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
    } catch (e: NameNotFoundException) {
        Log.wtf(ShortcutHelper.TAG, "Couldn't find app info by package name ${type.packageName}")
        context.getString(R.string.shortcut_helper_category_current_app_shortcuts)
    }
}

@Composable
private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
    val expandIconRotationDegrees by
@@ -384,7 +328,7 @@ private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
}

@Composable
private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategory) {
private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategoryUi) {
    Column(Modifier.padding(horizontal = 16.dp)) {
        category.subCategories.fastForEach { subCategory ->
            ShortcutSubCategorySinglePane(searchQuery, subCategory)
@@ -409,7 +353,7 @@ private fun ShortcutHelperTwoPane(
    searchQuery: String,
    onSearchQueryChanged: (String) -> Unit,
    modifier: Modifier = Modifier,
    categories: List<ShortcutCategory>,
    categories: List<ShortcutCategoryUi>,
    selectedCategoryType: ShortcutCategoryType?,
    onCategorySelected: (ShortcutCategoryType?) -> Unit,
    onKeyboardSettingsClicked: () -> Unit,
@@ -434,7 +378,7 @@ private fun ShortcutHelperTwoPane(
}

@Composable
private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategory?) {
private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategoryUi?) {
    val listState = rememberLazyListState()
    LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
    if (category == null) {
@@ -670,10 +614,10 @@ private fun textWithHighlightedSearchQuery(text: String, searchValue: String) =
private fun StartSidePanel(
    onSearchQueryChanged: (String) -> Unit,
    modifier: Modifier,
    categories: List<ShortcutCategory>,
    categories: List<ShortcutCategoryUi>,
    onKeyboardSettingsClicked: () -> Unit,
    selectedCategory: ShortcutCategoryType?,
    onCategoryClicked: (ShortcutCategory) -> Unit,
    onCategoryClicked: (ShortcutCategoryUi) -> Unit,
) {
    Column(modifier) {
        ShortcutsSearchBar(onSearchQueryChanged)
@@ -690,15 +634,15 @@ private fun StartSidePanel(

@Composable
private fun CategoriesPanelTwoPane(
    categories: List<ShortcutCategory>,
    categories: List<ShortcutCategoryUi>,
    selectedCategory: ShortcutCategoryType?,
    onCategoryClicked: (ShortcutCategory) -> Unit,
    onCategoryClicked: (ShortcutCategoryUi) -> Unit,
) {
    Column {
        categories.fastForEach {
            CategoryItemTwoPane(
                label = it.label(LocalContext.current),
                iconSource = it.icon,
                label = it.label,
                iconSource = it.iconSource,
                selected = selectedCategory == it.type,
                onClick = { onCategoryClicked(it) },
            )
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.keyboard.shortcut.ui.model

import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory

data class ShortcutCategoryUi(
    val label: String,
    val iconSource: IconSource,
    val type: ShortcutCategoryType,
    val subCategories: List<ShortcutSubCategory>,
) {
    constructor(
        label: String,
        iconSource: IconSource,
        shortcutCategory: ShortcutCategory,
    ) : this(label, iconSource, shortcutCategory.type, shortcutCategory.subCategories)
}
+1 −2
Original line number Diff line number Diff line
@@ -16,14 +16,13 @@

package com.android.systemui.keyboard.shortcut.ui.model

import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType

sealed interface ShortcutsUiState {

    data class Active(
        val searchQuery: String,
        val shortcutCategories: List<ShortcutCategory>,
        val shortcutCategories: List<ShortcutCategoryUi>,
        val defaultSelectedCategory: ShortcutCategoryType?,
    ) : ShortcutsUiState

+82 −4
Original line number Diff line number Diff line
@@ -17,6 +17,15 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel

import android.app.role.RoleManager
import android.content.pm.PackageManager.NameNotFoundException
import android.util.Log
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Android
import androidx.compose.material.icons.filled.Apps
import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Tv
import androidx.compose.material.icons.filled.VerticalSplit
import com.android.compose.ui.graphics.painter.DrawablePainter
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
@@ -25,7 +34,10 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.IconSource
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -51,6 +63,7 @@ constructor(
) {

    private val searchQuery = MutableStateFlow("")
    private val userContext = userTracker.createCurrentUserContext(userTracker.userContext)

    val shouldShow =
        categoriesInteractor.shortcutCategories
@@ -68,9 +81,10 @@ constructor(
                    val categoriesWithLauncherExcluded = excludeLauncherApp(categories)
                    val filteredCategories =
                        filterCategoriesBySearchQuery(query, categoriesWithLauncherExcluded)
                    val shortcutCategoriesUi = convertCategoriesModelToUiModel(filteredCategories)
                    ShortcutsUiState.Active(
                        searchQuery = query,
                        shortcutCategories = filteredCategories,
                        shortcutCategories = shortcutCategoriesUi,
                        defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
                    )
                }
@@ -78,9 +92,73 @@ constructor(
            .stateIn(
                scope = backgroundScope,
                started = SharingStarted.Lazily,
                initialValue = ShortcutsUiState.Inactive
                initialValue = ShortcutsUiState.Inactive,
            )

    private fun convertCategoriesModelToUiModel(
        categories: List<ShortcutCategory>
    ): List<ShortcutCategoryUi> {
        return categories.map { category ->
            ShortcutCategoryUi(
                label = getShortcutCategoryLabel(category.type),
                iconSource = getShortcutCategoryIcon(category.type),
                shortcutCategory = category,
            )
        }
    }

    private fun getShortcutCategoryIcon(type: ShortcutCategoryType): IconSource {
        return when (type) {
            ShortcutCategoryType.System -> IconSource(imageVector = Icons.Default.Tv)
            ShortcutCategoryType.MultiTasking ->
                IconSource(imageVector = Icons.Default.VerticalSplit)
            ShortcutCategoryType.InputMethodEditor ->
                IconSource(imageVector = Icons.Default.Keyboard)
            ShortcutCategoryType.AppCategories -> IconSource(imageVector = Icons.Default.Apps)
            is CurrentApp -> {
                try {
                    val iconDrawable =
                        userContext.packageManager.getApplicationIcon(type.packageName)
                    IconSource(painter = DrawablePainter(drawable = iconDrawable))
                } catch (e: NameNotFoundException) {
                    Log.wtf(
                        "ShortcutHelperViewModel",
                        "Package not found when retrieving icon for ${type.packageName}",
                    )
                    IconSource(imageVector = Icons.Default.Android)
                }
            }
        }
    }

    private fun getShortcutCategoryLabel(type: ShortcutCategoryType): String =
        when (type) {
            ShortcutCategoryType.System ->
                userContext.getString(R.string.shortcut_helper_category_system)
            ShortcutCategoryType.MultiTasking ->
                userContext.getString(R.string.shortcut_helper_category_multitasking)
            ShortcutCategoryType.InputMethodEditor ->
                userContext.getString(R.string.shortcut_helper_category_input)
            ShortcutCategoryType.AppCategories ->
                userContext.getString(R.string.shortcut_helper_category_app_shortcuts)
            is CurrentApp -> getApplicationLabelForCurrentApp(type)
        }

    private fun getApplicationLabelForCurrentApp(type: CurrentApp): String {
        try {
            val packageManagerForUser = userContext.packageManager
            val currentAppInfo =
                packageManagerForUser.getApplicationInfo(type.packageName, /* flags= */ 0)
            return packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
        } catch (e: NameNotFoundException) {
            Log.wtf(
                "ShortcutHelperViewModel",
                "Package Not found when retrieving Label for ${type.packageName}",
            )
            return "Current App"
        }
    }

    private suspend fun excludeLauncherApp(
        categories: List<ShortcutCategory>
    ): List<ShortcutCategory> {
@@ -111,7 +189,7 @@ constructor(

    private fun filterCategoriesBySearchQuery(
        query: String,
        categories: List<ShortcutCategory>
        categories: List<ShortcutCategory>,
    ): List<ShortcutCategory> {
        val lowerCaseTrimmedQuery = query.trim().lowercase()
        if (lowerCaseTrimmedQuery.isEmpty()) {
@@ -132,7 +210,7 @@ constructor(

    private fun filterSubCategoriesBySearchQuery(
        subCategories: List<ShortcutSubCategory>,
        query: String
        query: String,
    ) =
        subCategories
            .map { subCategory ->
+8 −4
Original line number Diff line number Diff line
@@ -18,14 +18,18 @@ package com.android.systemui.settings

import android.content.Context

/** Implemented by [UserTrackerImpl]. */
interface UserContextProvider {
    /**
 * Implemented by [UserTrackerImpl].
     * provides system context, not current user context.
     *
     * To get current user context use [createCurrentUserContext] passing [userContext] as context
     */
interface UserContextProvider {
    val userContext: Context

    /**
     * Creates the {@code context} with the current user.
     *
     * @see Context#createContextAsUser(UserHandle, int)
     */
    fun createCurrentUserContext(context: Context): Context
Loading