diff options
| author | 2024-04-09 09:39:18 -0400 | |
|---|---|---|
| committer | 2024-05-03 15:45:16 +0000 | |
| commit | b772a24d9e639b482bec668e41ee5e69baa28d9a (patch) | |
| tree | 82b897cdc36b4d4541f0e7145138e503cc6579ba /java/src | |
| parent | 8bcb1b4b6811ad123f6c37068577fd85228d6674 (diff) | |
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
Diffstat (limited to 'java/src')
| -rw-r--r-- | java/src/com/android/intentresolver/ChooserActivity.java | 45 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/ProfileHelper.kt | 8 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/ResolverActivity.java | 41 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt | 22 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.kt (renamed from java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.java) | 28 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/emptystate/DefaultEmptyState.kt | 20 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/emptystate/DevicePolicyBlockerEmptyState.java | 48 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java | 9 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java | 91 |
9 files changed, 131 insertions, 181 deletions
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.kt index 41422b66..05062a4b 100644 --- a/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/emptystate/CompositeEmptyStateProvider.kt @@ -13,34 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.intentresolver.emptystate; +package com.android.intentresolver.emptystate -import android.annotation.Nullable; - -import com.android.intentresolver.ResolverListAdapter; +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; - } +class CompositeEmptyStateProvider( + private vararg val providers: EmptyStateProvider, +) : EmptyStateProvider { - @Nullable - @Override - public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) { - for (EmptyStateProvider provider : mProviders) { - EmptyState emptyState = provider.getEmptyState(resolverListAdapter); - if (emptyState != null) { - return emptyState; - } - } - return null; + 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<Intent> intents = selected.getIntents(); - UserHandle target = selected.getUserHandle(); + private boolean hasCrossProfileIntents(List<Intent> 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; } - } |