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

Commit 235f62a5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[device_state] add support for Apps > Special Access > Connected Work &...

Merge "[device_state] add support for Apps > Special Access > Connected Work & Personal apps" into main
parents b6c07756 31da98bb
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -53,13 +53,15 @@ import com.android.settings.spa.app.catalyst.AllAppsScreen
import com.android.settings.spa.app.catalyst.AppInfoAllFilesAccessScreen
import com.android.settings.spa.app.catalyst.AppInfoDisplayOverOtherAppsScreen
import com.android.settings.spa.app.catalyst.AppInfoFullScreenIntentScreen
import com.android.settings.spa.app.catalyst.AppInfoInteractAcrossProfilesScreen
import com.android.settings.spa.app.catalyst.AppInfoPictureInPictureScreen
import com.android.settings.spa.app.catalyst.AppInfoStorageScreen
import com.android.settings.spa.app.catalyst.AppInteractAcrossProfilesAppListScreen
import com.android.settings.spa.app.catalyst.AppPictureInPictureAppListScreen
import com.android.settings.spa.app.catalyst.AppStorageAppListScreen
import com.android.settings.spa.app.catalyst.AppsAllFilesAccessAppListScreen
import com.android.settings.spa.app.catalyst.AppsDisplayOverOtherAppsAppListScreen
import com.android.settings.spa.app.catalyst.AppsFullScreenIntentAppListScreen
import com.android.settings.spa.app.catalyst.AppPictureInPictureAppListScreen
import com.android.settings.spa.app.catalyst.AppStorageAppListScreen
import com.android.settings.supervision.SupervisionDashboardScreen
import com.android.settings.supervision.SupervisionPinManagementScreen
import com.android.settingslib.metadata.PreferenceMetadata
@@ -219,6 +221,11 @@ fun getScreenConfigs() =
            screenKey = AppPictureInPictureAppListScreen.KEY,
            category = setOf(DeviceStateCategory.PERMISSION),
        ),
        PerScreenConfig(
            enabled = true,
            screenKey = AppInteractAcrossProfilesAppListScreen.KEY,
            category = setOf(DeviceStateCategory.PERMISSION),
        ),
    )

fun getDeviceStateItemList() =
@@ -670,4 +677,14 @@ fun getDeviceStateItemList() =
                    ?.getString(AppInfoPictureInPictureScreen.KEY_EXTRA_PACKAGE_NAME)
            },
        ),
        DeviceStateItemConfig(
            enabled = true,
            settingKey = AppInfoInteractAcrossProfilesScreen.KEY,
            settingScreenKey = AppInteractAcrossProfilesAppListScreen.KEY,
            hintText = { context, metadata ->
                metadata
                    .extras(context)
                    ?.getString(AppInfoInteractAcrossProfilesScreen.KEY_EXTRA_PACKAGE_NAME)
            },
        ),
    )
+1 −1
Original line number Diff line number Diff line
@@ -368,7 +368,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
        return isInteractAcrossProfilesEnabled(mContext, mPackageName);
    }

    static boolean isInteractAcrossProfilesEnabled(
    public static boolean isInteractAcrossProfilesEnabled(
            Context context, String packageName) {
        UserManager userManager = context.getSystemService(UserManager.class);
        UserHandle workProfile = InteractAcrossProfilesSettings.getWorkProfile(userManager);
+4 −3
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.util.IconDrawableFactory;
import android.util.Pair;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
@@ -75,7 +76,7 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings {
        replaceEnterprisePreferenceScreenTitle(CONNECTED_WORK_AND_PERSONAL_APPS_TITLE,
                R.string.interact_across_profiles_title);

        final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps =
        final List<Pair<ApplicationInfo, UserHandle>> crossProfileApps =
                collectConfigurableApps(mPackageManager, mUserManager, mCrossProfileApps);

        final Context prefContext = getPrefContext();
@@ -129,7 +130,7 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings {
     * @return the list of applications for the personal profile in the calling user's profile group
     * that can configure interact across profiles.
     */
    static ArrayList<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps(
    public static @NonNull List<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps(
            PackageManager packageManager, UserManager userManager,
            CrossProfileApps crossProfileApps) {
        final UserHandle workProfile = getWorkProfile(userManager);
@@ -182,7 +183,7 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings {
        if (personalProfile == null) {
            return 0;
        }
        final ArrayList<Pair<ApplicationInfo, UserHandle>> apps =
        final List<Pair<ApplicationInfo, UserHandle>> apps =
                collectConfigurableApps(packageManager, userManager, crossProfileApps);
        apps.removeIf(
                app -> !InteractAcrossProfilesDetails.isInteractAcrossProfilesEnabled(
+153 −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.content.pm.ApplicationInfo
import android.content.pm.CrossProfileApps
import android.os.Bundle
import android.os.UserManager
import android.provider.Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS
import androidx.core.net.toUri
import com.android.settings.R
import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetails
import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesSettings
import com.android.settings.contract.TAG_DEVICE_STATE_PREFERENCE
import com.android.settings.contract.TAG_DEVICE_STATE_SCREEN
import com.android.settings.flags.Flags
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.NoOpKeyedObservable
import com.android.settingslib.metadata.BooleanValuePreference
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
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.widget.MainSwitchPreferenceBinding
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

// Note: This page is for DeviceState usages.
@ProvidePreferenceScreen(AppInfoInteractAcrossProfilesScreen.KEY, parameterized = true)
class AppInfoInteractAcrossProfilesScreen(context: Context, override val arguments: Bundle) :
    PreferenceScreenCreator, PreferenceSummaryProvider, PreferenceTitleProvider {

    private val packageName = arguments.getString("app")!!

    private val appInfo = context.packageManager.getApplicationInfo(packageName, 0)

    private val storage: KeyValueStore =
        InteractAcrossProfilesStorage(context, appInfo, packageName)

    override val key: String
        get() = KEY

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

    override fun tags(context: Context) =
        arrayOf(TAG_DEVICE_STATE_SCREEN, TAG_DEVICE_STATE_PREFERENCE)

    override fun getTitle(context: Context): CharSequence =
        appInfo.loadLabel(context.packageManager)

    override fun getSummary(context: Context): CharSequence =
        context.getString(
            when (storage.getBoolean(InteractAcrossProfilesMainSwitch.KEY)) {
                true -> R.string.app_permission_summary_allowed
                else -> R.string.app_permission_summary_not_allowed
            }
        )

    override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?) =
        Intent(ACTION_MANAGE_CROSS_PROFILE_ACCESS).apply {
            data = "package:${appInfo.packageName}".toUri()
            // Only one switch so no need to highlight it with [IntentUtils.highlightPreference].
        }

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

    override fun extras(context: Context): Bundle? =
        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) { +InteractAcrossProfilesMainSwitch(storage) }

    companion object {
        const val KEY = "device_state_app_info_interact_across_profiles"

        const val KEY_EXTRA_PACKAGE_NAME = "package_name"

        @JvmStatic
        fun parameters(context: Context): Flow<Bundle> = flow {
            val packageManager = context.packageManager
            val userManager = context.getSystemService(UserManager::class.java)
            val crossProfileApps = context.getSystemService(CrossProfileApps::class.java)

            InteractAcrossProfilesSettings.collectConfigurableApps(
                    packageManager,
                    userManager,
                    crossProfileApps,
                )
                .forEach { appUser ->
                    emit(Bundle(1).apply { putString("app", appUser.first.packageName) })
                }
        }
    }
}

private class InteractAcrossProfilesMainSwitch(private val storage: KeyValueStore) :
    BooleanValuePreference, MainSwitchPreferenceBinding {

    override val key
        get() = KEY

    override val title
        get() = R.string.interact_across_profiles_title

    override fun storage(context: Context) = storage

    companion object {
        const val KEY = "device_state_interact_across_profiles_settings_switch"
    }
}

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

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

    @Suppress("UNCHECKED_CAST")
    override fun <T : Any> getValue(key: String, valueType: Class<T>): T {
        return InteractAcrossProfilesDetails.isInteractAcrossProfilesEnabled(context, packageName)
            as T
    }

    override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {}
}
+77 −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.content.pm.CrossProfileApps
import android.os.Bundle
import android.os.UserManager
import android.provider.Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS
import com.android.settings.R
import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesSettings
import com.android.settings.contract.TAG_DEVICE_STATE_SCREEN
import com.android.settings.flags.Flags
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceFragment
import com.android.settingslib.preference.PreferenceScreenCreator

@ProvidePreferenceScreen(AppInteractAcrossProfilesAppListScreen.KEY)
class AppInteractAcrossProfilesAppListScreen : PreferenceScreenCreator {

    override val key: String
        get() = KEY

    override val title: Int
        get() = R.string.interact_across_profiles_title

    override fun tags(context: Context) = arrayOf(TAG_DEVICE_STATE_SCREEN)

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

    override fun hasCompleteHierarchy() = false

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

    override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent? =
        // TODO: highlight the app from the metadata when highlighting parameterized screens is
        // supported.
        Intent(ACTION_MANAGE_CROSS_PROFILE_ACCESS)

    override fun getPreferenceHierarchy(context: Context) =
        preferenceHierarchy(context, this) {
            val packageManager = context.packageManager
            val userManager = context.getSystemService(UserManager::class.java)
            val crossProfileApps = context.getSystemService(CrossProfileApps::class.java)

            InteractAcrossProfilesSettings.collectConfigurableApps(
                    packageManager,
                    userManager,
                    crossProfileApps,
                )
                .forEach { app_user ->
                    val arguments = Bundle(1).apply { putString("app", app_user.first.packageName) }
                    +(AppInfoInteractAcrossProfilesScreen.KEY args arguments)
                }
        }

    companion object {
        const val KEY = "device_state_apps_interact_across_profiles"
    }
}