1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver.emptystate; 18 19 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; 20 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; 21 22 import static com.android.intentresolver.ChooserActivity.METRICS_CATEGORY_CHOOSER; 23 24 import static java.util.Objects.requireNonNull; 25 26 import android.content.Intent; 27 28 import androidx.annotation.Nullable; 29 30 import com.android.intentresolver.ProfileHelper; 31 import com.android.intentresolver.ResolverListAdapter; 32 import com.android.intentresolver.data.repository.DevicePolicyResources; 33 import com.android.intentresolver.shared.model.Profile; 34 35 import java.util.List; 36 37 /** 38 * Empty state provider that informs about a lack of cross profile sharing. It will return 39 * an empty state in case there are no intents which can be forwarded to another profile. 40 */ 41 public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider { 42 43 private final ProfileHelper mProfileHelper; 44 private final DevicePolicyResources mDevicePolicyResources; 45 private final boolean mIsShare; 46 private final CrossProfileIntentsChecker mCrossProfileIntentsChecker; 47 NoCrossProfileEmptyStateProvider( ProfileHelper profileHelper, DevicePolicyResources devicePolicyResources, CrossProfileIntentsChecker crossProfileIntentsChecker, boolean isShare)48 public NoCrossProfileEmptyStateProvider( 49 ProfileHelper profileHelper, 50 DevicePolicyResources devicePolicyResources, 51 CrossProfileIntentsChecker crossProfileIntentsChecker, 52 boolean isShare) { 53 mProfileHelper = profileHelper; 54 mDevicePolicyResources = devicePolicyResources; 55 mIsShare = isShare; 56 mCrossProfileIntentsChecker = crossProfileIntentsChecker; 57 } 58 hasCrossProfileIntents(List<Intent> intents, Profile source, Profile target)59 private boolean hasCrossProfileIntents(List<Intent> intents, Profile source, Profile target) { 60 if (source.getPrimary().getHandle().equals(target.getPrimary().getHandle())) { 61 return true; 62 } 63 // Note: Use of getPrimary() here also handles delegation of CLONE profile to parent. 64 return mCrossProfileIntentsChecker.hasCrossProfileIntents(intents, 65 source.getPrimary().getId(), target.getPrimary().getId()); 66 } 67 68 @Nullable 69 @Override getEmptyState(ResolverListAdapter adapter)70 public EmptyState getEmptyState(ResolverListAdapter adapter) { 71 Profile launchedBy = mProfileHelper.getLaunchedAsProfile(); 72 Profile tabOwner = requireNonNull(mProfileHelper.findProfile(adapter.getUserHandle())); 73 74 // When sharing into or out of Private profile, perform the check using the parent profile 75 // instead. (Hard-coded application of CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) 76 77 Profile effectiveSource = launchedBy; 78 Profile effectiveTarget = tabOwner; 79 80 // Assumption baked into design: "Personal" profile is the parent of all other profiles. 81 if (launchedBy.getType() == Profile.Type.PRIVATE) { 82 effectiveSource = mProfileHelper.getPersonalProfile(); 83 } 84 85 if (tabOwner.getType() == Profile.Type.PRIVATE) { 86 effectiveTarget = mProfileHelper.getPersonalProfile(); 87 } 88 89 // Allow access to the tab when there is at least one target permitted to cross profiles. 90 if (hasCrossProfileIntents(adapter.getIntents(), effectiveSource, effectiveTarget)) { 91 return null; 92 } 93 94 switch (tabOwner.getType()) { 95 case PERSONAL: 96 return new DevicePolicyBlockerEmptyState( 97 mDevicePolicyResources.getCrossProfileBlocked(), 98 mDevicePolicyResources.toPersonalBlockedByPolicyMessage(mIsShare), 99 RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL, 100 METRICS_CATEGORY_CHOOSER); 101 102 case WORK: 103 return new DevicePolicyBlockerEmptyState( 104 mDevicePolicyResources.getCrossProfileBlocked(), 105 mDevicePolicyResources.toWorkBlockedByPolicyMessage(mIsShare), 106 RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK, 107 METRICS_CATEGORY_CHOOSER); 108 109 case PRIVATE: 110 return new DevicePolicyBlockerEmptyState( 111 mDevicePolicyResources.getCrossProfileBlocked(), 112 mDevicePolicyResources.toPrivateBlockedByPolicyMessage(mIsShare), 113 /* Suppress log event. TODO: Define a new metrics event for this? */ -1, 114 METRICS_CATEGORY_CHOOSER); 115 } 116 return null; 117 } 118 } 119