From b772a24d9e639b482bec668e41ee5e69baa28d9a Mon Sep 17 00:00:00 2001 From: Mark Renouf Date: Tue, 9 Apr 2024 09:39:18 -0400 Subject: Private profile share policy delegates to parent profile This change enables the Private tab sharing to respect cross profile share policy and present an appropriate message when sharing is not available. When considering private profile as a source or target, the parent profile is substituded when checking whether any intents can be forwarded. Specialized subclasses of DevicePolicyBlockerEmptyState are eliminated in favor of creating instances as needed and returned by NoCrossProfileEmptyStateProvider. They are now initialized directly with the required content instead of resource Ids. Resource are moved to DevicePolicyResources where they are shared with ResolverActivity. When sharing content to Private from Work when blocked by policy the message will now correctly refer to 'Private apps'. Test: atest NoAppsAvailableEmptyStateProviderTest Test: manual, install TestDPC, setup private space; test verious permutations Flag: EXEMPT bugfix Bug: 324428064 Bug: 300157408 Change-Id: Ieb8e725191691ea92f2994a693086c2029452365 --- java/res/values/strings.xml | 6 ++ .../android/intentresolver/ChooserActivity.java | 45 +---------- .../com/android/intentresolver/ProfileHelper.kt | 8 +- .../android/intentresolver/ResolverActivity.java | 41 +--------- .../data/repository/DevicePolicyResources.kt | 22 ++++-- .../emptystate/CompositeEmptyStateProvider.java | 46 ----------- .../emptystate/CompositeEmptyStateProvider.kt | 32 ++++++++ .../intentresolver/emptystate/DefaultEmptyState.kt | 20 +++++ .../emptystate/DevicePolicyBlockerEmptyState.java | 48 ++++-------- .../NoAppsAvailableEmptyStateProvider.java | 9 --- .../NoCrossProfileEmptyStateProvider.java | 91 ++++++++++++++-------- 11 files changed, 162 insertions(+), 206 deletions(-) delete mode 100644 java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.java create mode 100644 java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.kt create mode 100644 java/src/com/android/intentresolver/emptystate/DefaultEmptyState.kt (limited to 'java') diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index 17a514d7..32c61327 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -284,6 +284,12 @@ This content can\u2019t be opened with personal apps + + This content can\u2019t be shared with private apps + + + This content can\u2019t be opened with private apps + Work apps are paused diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index ccf2a3dc..cce614f4 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -17,14 +17,7 @@ package com.android.intentresolver; import static android.app.VoiceInteractor.PickOptionRequest.Option; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; -import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static androidx.lifecycle.LifecycleKt.getCoroutineScope; @@ -111,8 +104,6 @@ import com.android.intentresolver.data.repository.DevicePolicyResources; import com.android.intentresolver.domain.interactor.UserInteractor; import com.android.intentresolver.emptystate.CompositeEmptyStateProvider; import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.emptystate.DevicePolicyBlockerEmptyState; -import com.android.intentresolver.emptystate.EmptyState; import com.android.intentresolver.emptystate.EmptyStateProvider; import com.android.intentresolver.emptystate.NoAppsAvailableEmptyStateProvider; import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider; @@ -213,7 +204,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private static final String TAB_TAG_WORK = "work"; private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key"; - protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser"; + public static final String METRICS_CATEGORY_CHOOSER = "intent_chooser"; private int mLayoutId; private UserHandle mHeaderCreatorUser; @@ -1463,39 +1454,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } protected EmptyStateProvider createBlockerEmptyStateProvider() { - final boolean isSendAction = mRequest.isSendActionTarget(); - - final EmptyState noWorkToPersonalEmptyState = - new DevicePolicyBlockerEmptyState( - /* context= */ this, - /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, - /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked, - /* devicePolicyStringSubtitleId= */ - isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL, - /* defaultSubtitleResource= */ - isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation - : R.string.resolver_cant_access_personal_apps_explanation, - /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL, - /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER); - - final EmptyState noPersonalToWorkEmptyState = - new DevicePolicyBlockerEmptyState( - /* context= */ this, - /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, - /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked, - /* devicePolicyStringSubtitleId= */ - isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK, - /* defaultSubtitleResource= */ - isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation - : R.string.resolver_cant_access_work_apps_explanation, - /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK, - /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER); - return new NoCrossProfileEmptyStateProvider( mProfiles, - noWorkToPersonalEmptyState, - noPersonalToWorkEmptyState, - createCrossProfileIntentsChecker()); + mDevicePolicyResources, + createCrossProfileIntentsChecker(), + mRequest.isSendActionTarget()); } private int findSelectedProfile() { diff --git a/java/src/com/android/intentresolver/ProfileHelper.kt b/java/src/com/android/intentresolver/ProfileHelper.kt index e1d912c3..53a873a3 100644 --- a/java/src/com/android/intentresolver/ProfileHelper.kt +++ b/java/src/com/android/intentresolver/ProfileHelper.kt @@ -80,12 +80,12 @@ constructor( launchedByUser.handle } - fun findProfileType(handle: UserHandle): Profile.Type? { - val matched = - profiles.firstOrNull { it.primary.handle == handle || it.clone?.handle == handle } - return matched?.type + fun findProfile(handle: UserHandle): Profile? { + return profiles.firstOrNull { it.primary.handle == handle || it.clone?.handle == handle } } + fun findProfileType(handle: UserHandle): Profile.Type? = findProfile(handle)?.type + // Name retained for ease of review, to be renamed later fun getQueryIntentsHandle(handle: UserHandle): UserHandle? { return if (isLaunchedAsCloneProfile && handle == personalHandle) { diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java index 1b08d957..e79cb2d1 100644 --- a/java/src/com/android/intentresolver/ResolverActivity.java +++ b/java/src/com/android/intentresolver/ResolverActivity.java @@ -16,12 +16,7 @@ package com.android.intentresolver; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; -import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static androidx.lifecycle.LifecycleKt.getCoroutineScope; @@ -94,8 +89,6 @@ import com.android.intentresolver.data.repository.DevicePolicyResources; import com.android.intentresolver.domain.interactor.UserInteractor; import com.android.intentresolver.emptystate.CompositeEmptyStateProvider; import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.emptystate.DevicePolicyBlockerEmptyState; -import com.android.intentresolver.emptystate.EmptyState; import com.android.intentresolver.emptystate.EmptyStateProvider; import com.android.intentresolver.emptystate.NoAppsAvailableEmptyStateProvider; import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider; @@ -184,7 +177,6 @@ public class ResolverActivity extends Hilt_ResolverActivity implements private Space mFooterSpacer = null; protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver"; - protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser"; /** Tracks if we should ignore future broadcasts telling us the work profile is enabled */ private final boolean mWorkProfileHasBeenEnabled = false; @@ -449,42 +441,17 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } protected EmptyStateProvider createBlockerEmptyStateProvider() { - final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser()); + boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser()); if (!shouldShowNoCrossProfileIntentsEmptyState) { // Implementation that doesn't show any blockers return new EmptyStateProvider() {}; } - - final EmptyState noWorkToPersonalEmptyState = - new DevicePolicyBlockerEmptyState( - /* context= */ this, - /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, - /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked, - /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL, - /* defaultSubtitleResource= */ - R.string.resolver_cant_access_personal_apps_explanation, - /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL, - /* devicePolicyEventCategory= */ - ResolverActivity.METRICS_CATEGORY_RESOLVER); - - final EmptyState noPersonalToWorkEmptyState = - new DevicePolicyBlockerEmptyState( - /* context= */ this, - /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, - /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked, - /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK, - /* defaultSubtitleResource= */ - R.string.resolver_cant_access_work_apps_explanation, - /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK, - /* devicePolicyEventCategory= */ - ResolverActivity.METRICS_CATEGORY_RESOLVER); - return new NoCrossProfileEmptyStateProvider( mProfiles, - noWorkToPersonalEmptyState, - noPersonalToWorkEmptyState, - createCrossProfileIntentsChecker()); + mDevicePolicyResources, + createCrossProfileIntentsChecker(), + /* isShare= */ false); } /** diff --git a/java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt b/java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt index 75faa068..eb35a358 100644 --- a/java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt +++ b/java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt @@ -27,13 +27,15 @@ import android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFIL import android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB import android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY import android.content.res.Resources +import androidx.annotation.OpenForTesting import com.android.intentresolver.R import com.android.intentresolver.inject.ApplicationOwned import javax.inject.Inject import javax.inject.Singleton +@OpenForTesting @Singleton -class DevicePolicyResources +open class DevicePolicyResources @Inject constructor( @ApplicationOwned private val resources: Resources, @@ -102,7 +104,7 @@ constructor( ) } - val crossProfileBlocked by lazy { + open val crossProfileBlocked by lazy { requireNotNull( policyResources.getString(RESOLVER_CROSS_PROFILE_BLOCKED_TITLE) { resources.getString(R.string.resolver_cross_profile_blocked) @@ -110,22 +112,30 @@ constructor( ) } - fun toPersonalBlockedByPolicyMessage(sendAction: Boolean): String { - return if (sendAction) { + open fun toPersonalBlockedByPolicyMessage(share: Boolean): String { + return if (share) { resources.getString(R.string.resolver_cant_share_with_personal_apps_explanation) } else { resources.getString(R.string.resolver_cant_access_personal_apps_explanation) } } - fun toWorkBlockedByPolicyMessage(sendAction: Boolean): String { - return if (sendAction) { + open fun toWorkBlockedByPolicyMessage(share: Boolean): String { + return if (share) { resources.getString(R.string.resolver_cant_share_with_work_apps_explanation) } else { resources.getString(R.string.resolver_cant_access_work_apps_explanation) } } + open fun toPrivateBlockedByPolicyMessage(share: Boolean): String { + return if (share) { + resources.getString(R.string.resolver_cant_share_with_private_apps_explanation) + } else { + resources.getString(R.string.resolver_cant_access_private_apps_explanation) + } + } + fun getWorkProfileNotSupportedMessage(launcherName: String): String { return requireNotNull( policyResources.getString( diff --git a/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.java b/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.java deleted file mode 100644 index 41422b66..00000000 --- a/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.intentresolver.emptystate; - -import android.annotation.Nullable; - -import com.android.intentresolver.ResolverListAdapter; - -/** - * Empty state provider that combines multiple providers. Providers earlier in the list have - * priority, that is if there is a provider that returns non-null empty state then all further - * providers will be ignored. - */ -public class CompositeEmptyStateProvider implements EmptyStateProvider { - - private final EmptyStateProvider[] mProviders; - - public CompositeEmptyStateProvider(EmptyStateProvider... providers) { - mProviders = providers; - } - - @Nullable - @Override - public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) { - for (EmptyStateProvider provider : mProviders) { - EmptyState emptyState = provider.getEmptyState(resolverListAdapter); - if (emptyState != null) { - return emptyState; - } - } - return null; - } -} diff --git a/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.kt b/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.kt new file mode 100644 index 00000000..05062a4b --- /dev/null +++ b/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.kt @@ -0,0 +1,32 @@ +/* + * 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.intentresolver.emptystate + +import com.android.intentresolver.ResolverListAdapter + +/** + * Empty state provider that combines multiple providers. Providers earlier in the list have + * priority, that is if there is a provider that returns non-null empty state then all further + * providers will be ignored. + */ +class CompositeEmptyStateProvider( + private vararg val providers: EmptyStateProvider, +) : EmptyStateProvider { + + override fun getEmptyState(resolverListAdapter: ResolverListAdapter): EmptyState? { + return providers.firstNotNullOfOrNull { it.getEmptyState(resolverListAdapter) } + } +} diff --git a/java/src/com/android/intentresolver/emptystate/DefaultEmptyState.kt b/java/src/com/android/intentresolver/emptystate/DefaultEmptyState.kt new file mode 100644 index 00000000..ea1a03cc --- /dev/null +++ b/java/src/com/android/intentresolver/emptystate/DefaultEmptyState.kt @@ -0,0 +1,20 @@ +/* + * 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.intentresolver.emptystate + +class DefaultEmptyState : EmptyState { + override fun useDefaultEmptyView() = true +} diff --git a/java/src/com/android/intentresolver/emptystate/DevicePolicyBlockerEmptyState.java b/java/src/com/android/intentresolver/emptystate/DevicePolicyBlockerEmptyState.java index b627636e..1cbc6175 100644 --- a/java/src/com/android/intentresolver/emptystate/DevicePolicyBlockerEmptyState.java +++ b/java/src/com/android/intentresolver/emptystate/DevicePolicyBlockerEmptyState.java @@ -17,40 +17,26 @@ package com.android.intentresolver.emptystate; import android.app.admin.DevicePolicyEventLogger; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.StringRes; /** * Empty state that gets strings from the device policy manager and tracks events into * event logger of the device policy events. */ public class DevicePolicyBlockerEmptyState implements EmptyState { - - @NonNull - private final Context mContext; - private final String mDevicePolicyStringTitleId; - @StringRes - private final int mDefaultTitleResource; - private final String mDevicePolicyStringSubtitleId; - @StringRes - private final int mDefaultSubtitleResource; + private final String mTitle; + private final String mSubtitle; private final int mEventId; - @NonNull private final String mEventCategory; - public DevicePolicyBlockerEmptyState(@NonNull Context context, - String devicePolicyStringTitleId, @StringRes int defaultTitleResource, - String devicePolicyStringSubtitleId, @StringRes int defaultSubtitleResource, - int devicePolicyEventId, @NonNull String devicePolicyEventCategory) { - mContext = context; - mDevicePolicyStringTitleId = devicePolicyStringTitleId; - mDefaultTitleResource = defaultTitleResource; - mDevicePolicyStringSubtitleId = devicePolicyStringSubtitleId; - mDefaultSubtitleResource = defaultSubtitleResource; + public DevicePolicyBlockerEmptyState( + String title, + String subtitle, + int devicePolicyEventId, + String devicePolicyEventCategory) { + mTitle = title; + mSubtitle = subtitle; mEventId = devicePolicyEventId; mEventCategory = devicePolicyEventCategory; } @@ -58,24 +44,22 @@ public class DevicePolicyBlockerEmptyState implements EmptyState { @Nullable @Override public String getTitle() { - return mContext.getSystemService(DevicePolicyManager.class).getResources().getString( - mDevicePolicyStringTitleId, - () -> mContext.getString(mDefaultTitleResource)); + return mTitle; } @Nullable @Override public String getSubtitle() { - return mContext.getSystemService(DevicePolicyManager.class).getResources().getString( - mDevicePolicyStringSubtitleId, - () -> mContext.getString(mDefaultSubtitleResource)); + return mSubtitle; } @Override public void onEmptyStateShown() { - DevicePolicyEventLogger.createEvent(mEventId) - .setStrings(mEventCategory) - .write(); + if (mEventId != -1) { + DevicePolicyEventLogger.createEvent(mEventId) + .setStrings(mEventCategory) + .write(); + } } @Override diff --git a/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java b/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java index cd1448e4..b3d3e343 100644 --- a/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java @@ -70,13 +70,4 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider { ); } } - - - public static class DefaultEmptyState implements EmptyState { - @Override - public boolean useDefaultEmptyView() { - return true; - } - } - } diff --git a/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java index fa33928b..0cf2ea45 100644 --- a/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java @@ -16,15 +16,21 @@ package com.android.intentresolver.emptystate; +import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; +import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; + +import static com.android.intentresolver.ChooserActivity.METRICS_CATEGORY_CHOOSER; + +import static java.util.Objects.requireNonNull; + import android.content.Intent; -import android.os.UserHandle; import androidx.annotation.Nullable; import com.android.intentresolver.ProfileHelper; import com.android.intentresolver.ResolverListAdapter; +import com.android.intentresolver.data.repository.DevicePolicyResources; import com.android.intentresolver.shared.model.Profile; -import com.android.intentresolver.shared.model.User; import java.util.List; @@ -35,55 +41,78 @@ import java.util.List; public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider { private final ProfileHelper mProfileHelper; - private final EmptyState mNoWorkToPersonalEmptyState; - private final EmptyState mNoPersonalToWorkEmptyState; + private final DevicePolicyResources mDevicePolicyResources; + private final boolean mIsShare; private final CrossProfileIntentsChecker mCrossProfileIntentsChecker; public NoCrossProfileEmptyStateProvider( ProfileHelper profileHelper, - EmptyState noWorkToPersonalEmptyState, - EmptyState noPersonalToWorkEmptyState, - CrossProfileIntentsChecker crossProfileIntentsChecker) { + DevicePolicyResources devicePolicyResources, + CrossProfileIntentsChecker crossProfileIntentsChecker, + boolean isShare) { mProfileHelper = profileHelper; - mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState; - mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState; + mDevicePolicyResources = devicePolicyResources; + mIsShare = isShare; mCrossProfileIntentsChecker = crossProfileIntentsChecker; } - private boolean anyCrossProfileAllowedIntents(ResolverListAdapter selected, UserHandle source) { - List intents = selected.getIntents(); - UserHandle target = selected.getUserHandle(); + private boolean hasCrossProfileIntents(List intents, Profile source, Profile target) { + if (source.getPrimary().getHandle().equals(target.getPrimary().getHandle())) { + return true; + } + // Note: Use of getPrimary() here also handles delegation of CLONE profile to parent. return mCrossProfileIntentsChecker.hasCrossProfileIntents(intents, - source.getIdentifier(), target.getIdentifier()); + source.getPrimary().getId(), target.getPrimary().getId()); } @Nullable @Override public EmptyState getEmptyState(ResolverListAdapter adapter) { - Profile launchedAsProfile = mProfileHelper.getLaunchedAsProfile(); - User launchedAs = mProfileHelper.getLaunchedAsProfile().getPrimary(); - UserHandle tabOwnerHandle = adapter.getUserHandle(); - boolean launchedAsSameUser = launchedAs.getHandle().equals(tabOwnerHandle); - Profile.Type tabOwnerType = mProfileHelper.findProfileType(tabOwnerHandle); - - // Not applicable for private profile. - if (launchedAsProfile.getType() == Profile.Type.PRIVATE - || tabOwnerType == Profile.Type.PRIVATE) { - return null; + Profile launchedBy = mProfileHelper.getLaunchedAsProfile(); + Profile tabOwner = requireNonNull(mProfileHelper.findProfile(adapter.getUserHandle())); + + // When sharing into or out of Private profile, perform the check using the parent profile + // instead. (Hard-coded application of CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) + + Profile effectiveSource = launchedBy; + Profile effectiveTarget = tabOwner; + + // Assumption baked into design: "Personal" profile is the parent of all other profiles. + if (launchedBy.getType() == Profile.Type.PRIVATE) { + effectiveSource = mProfileHelper.getPersonalProfile(); + } + + if (tabOwner.getType() == Profile.Type.PRIVATE) { + effectiveTarget = mProfileHelper.getPersonalProfile(); } - // Allow access to the tab when launched by the same user as the tab owner - // or when there is at least one target which is permitted for cross-profile. - if (launchedAsSameUser || anyCrossProfileAllowedIntents(adapter, - /* source = */ launchedAs.getHandle())) { + // Allow access to the tab when there is at least one target permitted to cross profiles. + if (hasCrossProfileIntents(adapter.getIntents(), effectiveSource, effectiveTarget)) { return null; } - switch (launchedAsProfile.getType()) { - case WORK: return mNoWorkToPersonalEmptyState; - case PERSONAL: return mNoPersonalToWorkEmptyState; + switch (tabOwner.getType()) { + case PERSONAL: + return new DevicePolicyBlockerEmptyState( + mDevicePolicyResources.getCrossProfileBlocked(), + mDevicePolicyResources.toPersonalBlockedByPolicyMessage(mIsShare), + RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL, + METRICS_CATEGORY_CHOOSER); + + case WORK: + return new DevicePolicyBlockerEmptyState( + mDevicePolicyResources.getCrossProfileBlocked(), + mDevicePolicyResources.toWorkBlockedByPolicyMessage(mIsShare), + RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK, + METRICS_CATEGORY_CHOOSER); + + case PRIVATE: + return new DevicePolicyBlockerEmptyState( + mDevicePolicyResources.getCrossProfileBlocked(), + mDevicePolicyResources.toPrivateBlockedByPolicyMessage(mIsShare), + /* Suppress log event. TODO: Define a new metrics event for this? */ -1, + METRICS_CATEGORY_CHOOSER); } return null; } - } -- cgit v1.2.3-59-g8ed1b