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

Commit a80ddb0f authored by Nick Chameyev's avatar Nick Chameyev Committed by Android (Google) Code Review
Browse files

Merge "[Partial Screensharing] Add enterprise policies resolver" into tm-qpr-dev

parents d2fe4b9e 47b3f309
Loading
Loading
Loading
Loading
+6 −21
Original line number Diff line number Diff line
@@ -34,8 +34,8 @@ import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnail
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Binds
@@ -54,13 +54,12 @@ import kotlinx.coroutines.SupervisorJob

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile

@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope

@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
@Module(
    subcomponents = [MediaProjectionAppSelectorComponent::class],
    includes = [MediaProjectionDevicePolicyModule::class]
)
interface MediaProjectionModule {
    @Binds
    @IntoMap
@@ -109,20 +108,6 @@ interface MediaProjectionAppSelectorModule {
            activity: MediaProjectionAppSelectorActivity
        ): ConfigurationController = ConfigurationControllerImpl(activity)

        @Provides
        @PersonalProfile
        @MediaProjectionAppSelectorScope
        fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
            // Current foreground user is the 'personal' profile
            return UserHandle.of(activityManagerWrapper.currentUserId)
        }

        @Provides
        @WorkProfile
        @MediaProjectionAppSelectorScope
        fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
            userTracker.userProfiles.find { it.isManagedProfile }?.userHandle

        @Provides
        @HostUserHandle
        @MediaProjectionAppSelectorScope
+70 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.mediaprojection.appselector

import android.content.Context
import android.os.UserHandle
import com.android.internal.R as AndroidR
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
import com.android.internal.app.ResolverListAdapter
import com.android.systemui.R
import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
import javax.inject.Inject

@MediaProjectionAppSelectorScope
class MediaProjectionBlockerEmptyStateProvider
@Inject
constructor(
    @HostUserHandle private val hostAppHandle: UserHandle,
    @PersonalProfile private val personalProfileHandle: UserHandle,
    private val policyResolver: ScreenCaptureDevicePolicyResolver,
    private val context: Context
) : EmptyStateProvider {

    override fun getEmptyState(resolverListAdapter: ResolverListAdapter): EmptyState? {
        val screenCaptureAllowed =
            policyResolver.isScreenCaptureAllowed(
                targetAppUserHandle = resolverListAdapter.userHandle,
                hostAppUserHandle = hostAppHandle
            )

        val isHostAppInPersonalProfile = hostAppHandle == personalProfileHandle

        val subtitle =
            if (isHostAppInPersonalProfile) {
                AndroidR.string.resolver_cant_share_with_personal_apps_explanation
            } else {
                AndroidR.string.resolver_cant_share_with_work_apps_explanation
            }

        if (!screenCaptureAllowed) {
            return object : EmptyState {
                override fun getSubtitle(): String = context.resources.getString(subtitle)
                override fun getTitle(): String =
                    context.resources.getString(
                        R.string.screen_capturing_disabled_by_policy_dialog_title
                    )
                override fun onEmptyStateShown() {
                    // TODO(b/237397740) report analytics
                }
                override fun shouldSkipDataRebuild(): Boolean = true
            }
        }
        return null
    }
}
+43 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.mediaprojection.devicepolicy

import android.os.UserHandle
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import dagger.Module
import dagger.Provides
import javax.inject.Qualifier

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile

/** Module for media projection device policy related dependencies */
@Module
class MediaProjectionDevicePolicyModule {
    @Provides
    @PersonalProfile
    fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
        // Current foreground user is the 'personal' profile
        return UserHandle.of(activityManagerWrapper.currentUserId)
    }

    @Provides
    @WorkProfile
    fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
        userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
}
+125 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.mediaprojection.devicepolicy

import android.app.admin.DevicePolicyManager
import android.os.UserHandle
import android.os.UserManager
import javax.inject.Inject

/**
 * Utility class to resolve if screen capture allowed for a particular target app/host app pair. It
 * caches the state of the policies, so you need to create a new instance of this class if you want
 * to react to updated policies state.
 */
class ScreenCaptureDevicePolicyResolver
@Inject
constructor(
    private val devicePolicyManager: DevicePolicyManager,
    private val userManager: UserManager,
    @PersonalProfile private val personalProfileUserHandle: UserHandle,
    @WorkProfile private val workProfileUserHandle: UserHandle?
) {

    /**
     * Returns true if [hostAppUserHandle] is allowed to perform screen capture of
     * [targetAppUserHandle]
     */
    fun isScreenCaptureAllowed(
        targetAppUserHandle: UserHandle,
        hostAppUserHandle: UserHandle,
    ): Boolean {
        if (hostAppUserHandle.isWorkProfile() && workProfileScreenCaptureDisabled) {
            // Disable screen capturing as host apps should not capture the screen
            return false
        }

        if (!hostAppUserHandle.isWorkProfile() && personalProfileScreenCaptureDisabled) {
            // Disable screen capturing as personal apps should not capture the screen
            return false
        }

        if (targetAppUserHandle.isWorkProfile()) {
            // Work profile target
            if (workProfileScreenCaptureDisabled) {
                // Do not allow sharing work profile apps as work profile capturing is disabled
                return false
            }
        } else {
            // Personal profile target
            if (hostAppUserHandle.isWorkProfile() && disallowSharingIntoManagedProfile) {
                // Do not allow sharing of personal apps into work profile apps
                return false
            }

            if (personalProfileScreenCaptureDisabled) {
                // Disable screen capturing as personal apps should not be captured
                return false
            }
        }

        return true
    }

    /**
     * Returns true if [hostAppUserHandle] is NOT allowed to capture an app from any profile,
     * could be useful to finish the screen capture flow as soon as possible when the screen
     * could not be captured at all.
     */
    fun isScreenCaptureCompletelyDisabled(hostAppUserHandle: UserHandle): Boolean {
        val isWorkAppsCaptureDisabled =
                if (workProfileUserHandle != null) {
                    !isScreenCaptureAllowed(
                            targetAppUserHandle = workProfileUserHandle,
                            hostAppUserHandle = hostAppUserHandle
                    )
                } else true

        val isPersonalAppsCaptureDisabled =
                !isScreenCaptureAllowed(
                        targetAppUserHandle = personalProfileUserHandle,
                        hostAppUserHandle = hostAppUserHandle
                )

        return isWorkAppsCaptureDisabled && isPersonalAppsCaptureDisabled
    }

    private val personalProfileScreenCaptureDisabled: Boolean by lazy {
        devicePolicyManager.getScreenCaptureDisabled(
            /* admin */ null,
            personalProfileUserHandle.identifier
        )
    }

    private val workProfileScreenCaptureDisabled: Boolean by lazy {
        workProfileUserHandle?.let {
            devicePolicyManager.getScreenCaptureDisabled(/* admin */ null, it.identifier)
        }
            ?: false
    }

    private val disallowSharingIntoManagedProfile: Boolean by lazy {
        workProfileUserHandle?.let {
            userManager.hasUserRestrictionForUser(
                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
                it
            )
        }
            ?: false
    }

    private fun UserHandle?.isWorkProfile(): Boolean = this == workProfileUserHandle
}
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.mediaprojection.devicepolicy

import android.content.Context
import com.android.systemui.R
import com.android.systemui.statusbar.phone.SystemUIDialog

/** Dialog that shows that screen capture is disabled on this device. */
class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {

    init {
        setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
        setMessage(
            context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
        )
        setIcon(R.drawable.ic_cast)
        setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
    }
}
Loading