diff options
| author | 2024-01-09 16:02:08 -0500 | |
|---|---|---|
| committer | 2024-01-09 16:06:05 -0500 | |
| commit | 6d0e03bb3eba2bfea7ac63755f390ca88a4b7faa (patch) | |
| tree | f5f6f030b08863c9599bf2971c71fbdcaf1b032e /java/src | |
| parent | c50f7196ef9ae1abac546f49d45128e42678fec3 (diff) | |
Move common intent forwarding code into shared module
This change combines and deduplicates intent forwarding code
from both Chooser and Resolver, as well the temporary scaffolding
from ActivityLogic.
Bug: 300157408
Test: atest IntentResolver-tests-activity:com.android.intentresolver.v2
Change-Id: If9dde430f24608e4a0c5bb9ec69a374386c044ed
Diffstat (limited to 'java/src')
5 files changed, 186 insertions, 173 deletions
diff --git a/java/src/com/android/intentresolver/v2/ActivityLogic.kt b/java/src/com/android/intentresolver/v2/ActivityLogic.kt index 5c7594f5..495c39e6 100644 --- a/java/src/com/android/intentresolver/v2/ActivityLogic.kt +++ b/java/src/com/android/intentresolver/v2/ActivityLogic.kt @@ -1,8 +1,5 @@ package com.android.intentresolver.v2 -import android.app.admin.DevicePolicyManager -import android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL -import android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK import android.content.Intent import android.os.UserHandle import android.os.UserManager @@ -10,7 +7,6 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.core.content.getSystemService import com.android.intentresolver.AnnotatedUserHandles -import com.android.intentresolver.R import com.android.intentresolver.WorkProfileAvailabilityManager import com.android.intentresolver.icons.TargetDataLoader @@ -50,15 +46,10 @@ interface CommonActivityLogic { val referrerPackageName: String? /** User manager system service. */ val userManager: UserManager - /** Device policy manager system service. */ - val devicePolicyManager: DevicePolicyManager /** Current [UserHandle]s retrievable by type. */ val annotatedUserHandles: AnnotatedUserHandles? /** Monitors for changes to work profile availability. */ val workProfileAvailabilityManager: WorkProfileAvailabilityManager - - /** Returns display message indicating intent forwarding or null if not intent forwarding. */ - fun forwardMessageFor(intent: Intent): String? } /** @@ -72,58 +63,32 @@ class CommonActivityLogicImpl( onWorkProfileStatusUpdated: () -> Unit, ) : CommonActivityLogic { - override val referrerPackageName: String? = activity.referrer.let { - if (ANDROID_APP_URI_SCHEME == it?.scheme) { - it.host - } else { - null + override val referrerPackageName: String? = + activity.referrer.let { + if (ANDROID_APP_URI_SCHEME == it?.scheme) { + it.host + } else { + null + } } - } override val userManager: UserManager = activity.getSystemService()!! - override val devicePolicyManager: DevicePolicyManager = activity.getSystemService()!! - - override val annotatedUserHandles: AnnotatedUserHandles? = try { - AnnotatedUserHandles.forShareActivity(activity) - } catch (e: SecurityException) { - Log.e(tag, "Request from UID without necessary permissions", e) - null - } + override val annotatedUserHandles: AnnotatedUserHandles? = + try { + AnnotatedUserHandles.forShareActivity(activity) + } catch (e: SecurityException) { + Log.e(tag, "Request from UID without necessary permissions", e) + null + } - override val workProfileAvailabilityManager = WorkProfileAvailabilityManager( + override val workProfileAvailabilityManager = + WorkProfileAvailabilityManager( userManager, annotatedUserHandles?.workProfileUserHandle, onWorkProfileStatusUpdated, ) - private val forwardToPersonalMessage: String? = - devicePolicyManager.resources.getString(FORWARD_INTENT_TO_PERSONAL) { - activity.getString(R.string.forward_intent_to_owner) - } - - private val forwardToWorkMessage: String? = - devicePolicyManager.resources.getString(FORWARD_INTENT_TO_WORK) { - activity.getString(R.string.forward_intent_to_work) - } - - override fun forwardMessageFor(intent: Intent): String? { - val contentUserHint = intent.contentUserHint - if ( - contentUserHint != UserHandle.USER_CURRENT && contentUserHint != UserHandle.myUserId() - ) { - val originUserInfo = userManager.getUserInfo(contentUserHint) - val originIsManaged = originUserInfo?.isManagedProfile ?: false - val targetIsManaged = userManager.isManagedProfile - return when { - originIsManaged && !targetIsManaged -> forwardToPersonalMessage - !originIsManaged && targetIsManaged -> forwardToWorkMessage - else -> null - } - } - return null - } - companion object { private const val ANDROID_APP_URI_SCHEME = "android-app" } diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java index 78246213..fe55a936 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActivity.java +++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java @@ -16,7 +16,6 @@ package com.android.intentresolver.v2; -import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; 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; @@ -24,7 +23,6 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT 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.content.PermissionChecker.PID_UNKNOWN; 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; @@ -54,7 +52,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.PermissionChecker; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -269,6 +266,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Inject public TargetDataLoader mTargetDataLoader; @Inject public DevicePolicyResources mDevicePolicyResources; @Inject public PackageManager mPackageManager; + @Inject public IntentForwarding mIntentForwarding; private ChooserRefinementManager mRefinementManager; @@ -553,51 +551,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return false; } - private int isPermissionGranted(String permission, int uid) { - return ActivityManager.checkComponentPermission(permission, uid, - /* owningUid= */-1, /* exported= */ true); - } - - /** - * Returns whether the package has the necessary permissions to interact across profiles on - * behalf of a given user. - * - * <p>This means meeting the following condition: - * <ul> - * <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least - * one of the following conditions must be fulfilled</li> - * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li> - * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li> - * <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding - * AppOps {@code android:interact_across_profiles} is set to "allow".</li> - * </ul> - * - */ - private boolean canAppInteractCrossProfiles(String packageName) { - ApplicationInfo applicationInfo; - try { - applicationInfo = mPackageManager.getApplicationInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Package " + packageName + " does not exist on current user."); - return false; - } - if (!applicationInfo.crossProfile) { - return false; - } - - int packageUid = applicationInfo.uid; - - if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - packageUid) == PackageManager.PERMISSION_GRANTED) { - return true; - } - if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - return PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES, - PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED; - } private boolean isTwoPagePersonalAndWorkConfiguration() { return (mChooserMultiProfilePagerAdapter.getCount() == 2) @@ -649,7 +602,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } String packageName = activeProfileTarget.getResolvedComponentName().getPackageName(); - if (!canAppInteractCrossProfiles(packageName)) { + if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) { return false; } @@ -849,7 +802,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } // If needed, show that intent is forwarded // from managed profile to owner or other way around. - String profileSwitchMessage = mLogic.forwardMessageFor(mLogic.getTargetIntent()); + String profileSwitchMessage = mIntentForwarding.forwardMessageFor(mLogic.getTargetIntent()); if (profileSwitchMessage != null) { Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show(); } diff --git a/java/src/com/android/intentresolver/v2/IntentForwarding.kt b/java/src/com/android/intentresolver/v2/IntentForwarding.kt new file mode 100644 index 00000000..3d366d10 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/IntentForwarding.kt @@ -0,0 +1,111 @@ +/* + * 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.v2 + +import android.Manifest +import android.Manifest.permission.INTERACT_ACROSS_USERS +import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.content.PermissionChecker +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.os.UserHandle +import android.os.UserManager +import android.util.Log +import com.android.intentresolver.v2.data.repository.DevicePolicyResources +import javax.inject.Inject +import javax.inject.Singleton + +private const val TAG: String = "IntentForwarding" + +@Singleton +class IntentForwarding +@Inject +constructor( + private val resources: DevicePolicyResources, + private val userManager: UserManager, + private val packageManager: PackageManager +) { + + fun forwardMessageFor(intent: Intent): String? { + val contentUserHint = intent.contentUserHint + if ( + contentUserHint != UserHandle.USER_CURRENT && contentUserHint != UserHandle.myUserId() + ) { + val originUserInfo = userManager.getUserInfo(contentUserHint) + val originIsManaged = originUserInfo?.isManagedProfile ?: false + val targetIsManaged = userManager.isManagedProfile + return when { + originIsManaged && !targetIsManaged -> resources.forwardToPersonalMessage + !originIsManaged && targetIsManaged -> resources.forwardToWorkMessage + else -> null + } + } + return null + } + + private fun isPermissionGranted(permission: String, uid: Int) = + ActivityManager.checkComponentPermission( + /* permission = */ permission, + /* uid = */ uid, + /* owningUid= */ -1, + /* exported= */ true + ) + + /** + * Returns whether the package has the necessary permissions to interact across profiles on + * behalf of a given user. + * + * This means meeting the following condition: + * * The app's [ApplicationInfo.crossProfile] flag must be true, and at least one of the + * following conditions must be fulfilled + * * `Manifest.permission.INTERACT_ACROSS_USERS_FULL` granted. + * * `Manifest.permission.INTERACT_ACROSS_USERS` granted. + * * `Manifest.permission.INTERACT_ACROSS_PROFILES` granted, or the corresponding AppOps + * `android:interact_across_profiles` is set to "allow". + */ + fun canAppInteractAcrossProfiles(context: Context, packageName: String): Boolean { + val applicationInfo: ApplicationInfo + try { + applicationInfo = packageManager.getApplicationInfo(packageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Package $packageName does not exist on current user.") + return false + } + if (!applicationInfo.crossProfile) { + return false + } + + val packageUid = applicationInfo.uid + + if (isPermissionGranted(INTERACT_ACROSS_USERS_FULL, packageUid) == PERMISSION_GRANTED) { + return true + } + if (isPermissionGranted(INTERACT_ACROSS_USERS, packageUid) == PERMISSION_GRANTED) { + return true + } + return PermissionChecker.checkPermissionForPreflight( + context, + Manifest.permission.INTERACT_ACROSS_PROFILES, + PermissionChecker.PID_UNKNOWN, + packageUid, + packageName + ) == PERMISSION_GRANTED + } +} diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java index 44224c10..c3654f3f 100644 --- a/java/src/com/android/intentresolver/v2/ResolverActivity.java +++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java @@ -16,12 +16,10 @@ package com.android.intentresolver.v2; -import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; 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.content.PermissionChecker.PID_UNKNOWN; 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; @@ -32,7 +30,6 @@ import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; -import android.app.ActivityManager; import android.app.ActivityThread; import android.app.VoiceInteractor.PickOptionRequest; import android.app.VoiceInteractor.PickOptionRequest.Option; @@ -42,7 +39,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.PermissionChecker; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -138,7 +134,9 @@ import javax.inject.Inject; @AndroidEntryPoint(FragmentActivity.class) public class ResolverActivity extends Hilt_ResolverActivity implements ResolverListAdapter.ResolverListCommunicator { + @Inject public DevicePolicyResources mDevicePolicyResources; + @Inject public IntentForwarding mIntentForwarding; protected ActivityLogic mLogic; @@ -1466,7 +1464,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } // If needed, show that intent is forwarded // from managed profile to owner or other way around. - String profileSwitchMessage = mLogic.forwardMessageFor(mLogic.getTargetIntent()); + String profileSwitchMessage = mIntentForwarding.forwardMessageFor(mLogic.getTargetIntent()); if (profileSwitchMessage != null) { Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show(); } @@ -1505,11 +1503,6 @@ public class ResolverActivity extends Hilt_ResolverActivity implements return false; } - private int isPermissionGranted(String permission, int uid) { - return ActivityManager.checkComponentPermission(permission, uid, - /* owningUid= */-1, /* exported= */ true); - } - /** * Mini resolver should be used when all of the following are true: * 1. This is the intent picker (ResolverActivity). @@ -1619,7 +1612,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } String packageName = activeProfileTarget.getResolvedComponentName().getPackageName(); - if (!canAppInteractCrossProfiles(packageName)) { + if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) { return false; } @@ -1634,47 +1627,6 @@ public class ResolverActivity extends Hilt_ResolverActivity implements return true; } - /** - * Returns whether the package has the necessary permissions to interact across profiles on - * behalf of a given user. - * - * <p>This means meeting the following condition: - * <ul> - * <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least - * one of the following conditions must be fulfilled</li> - * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li> - * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li> - * <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding - * AppOps {@code android:interact_across_profiles} is set to "allow".</li> - * </ul> - * - */ - private boolean canAppInteractCrossProfiles(String packageName) { - ApplicationInfo applicationInfo; - try { - applicationInfo = getPackageManager().getApplicationInfo(packageName, 0); - } catch (NameNotFoundException e) { - Log.e(TAG, "Package " + packageName + " does not exist on current user."); - return false; - } - if (!applicationInfo.crossProfile) { - return false; - } - - int packageUid = applicationInfo.uid; - - if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - packageUid) == PackageManager.PERMISSION_GRANTED) { - return true; - } - if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - return PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES, - PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED; - } - private boolean isAutolaunching() { return !mRegistered && isFinishing(); } @@ -1719,7 +1671,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } String packageName = activeProfileTarget.getResolvedComponentName().getPackageName(); - if (!canAppInteractCrossProfiles(packageName)) { + if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) { return false; } diff --git a/java/src/com/android/intentresolver/v2/data/repository/DevicePolicyResources.kt b/java/src/com/android/intentresolver/v2/data/repository/DevicePolicyResources.kt index 7debdf07..5719ff08 100644 --- a/java/src/com/android/intentresolver/v2/data/repository/DevicePolicyResources.kt +++ b/java/src/com/android/intentresolver/v2/data/repository/DevicePolicyResources.kt @@ -16,6 +16,8 @@ package com.android.intentresolver.v2.data.repository import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL +import android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK import android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB import android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY import android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED @@ -28,41 +30,71 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class DevicePolicyResources @Inject constructor( +class DevicePolicyResources +@Inject +constructor( @ApplicationOwned private val resources: Resources, devicePolicyManager: DevicePolicyManager ) { private val policyResources = devicePolicyManager.resources val personalTabLabel by lazy { - requireNotNull(policyResources.getString(RESOLVER_PERSONAL_TAB) { - resources.getString(R.string.resolver_personal_tab) - }) + requireNotNull( + policyResources.getString(RESOLVER_PERSONAL_TAB) { + resources.getString(R.string.resolver_personal_tab) + } + ) } val workTabLabel by lazy { - requireNotNull(policyResources.getString(RESOLVER_WORK_TAB) { - resources.getString(R.string.resolver_work_tab) - }) + requireNotNull( + policyResources.getString(RESOLVER_WORK_TAB) { + resources.getString(R.string.resolver_work_tab) + } + ) } val personalTabAccessibilityLabel by lazy { - requireNotNull(policyResources.getString(RESOLVER_PERSONAL_TAB_ACCESSIBILITY) { - resources.getString(R.string.resolver_personal_tab_accessibility) - }) + requireNotNull( + policyResources.getString(RESOLVER_PERSONAL_TAB_ACCESSIBILITY) { + resources.getString(R.string.resolver_personal_tab_accessibility) + } + ) } val workTabAccessibilityLabel by lazy { - requireNotNull(policyResources.getString(RESOLVER_WORK_TAB_ACCESSIBILITY) { - resources.getString(R.string.resolver_work_tab_accessibility) - }) + requireNotNull( + policyResources.getString(RESOLVER_WORK_TAB_ACCESSIBILITY) { + resources.getString(R.string.resolver_work_tab_accessibility) + } + ) + } + + val forwardToPersonalMessage: String? = + devicePolicyManager.resources.getString(FORWARD_INTENT_TO_PERSONAL) { + resources.getString(R.string.forward_intent_to_owner) + } + + val forwardToWorkMessage by lazy { + requireNotNull( + policyResources.getString(FORWARD_INTENT_TO_WORK) { + resources.getString(R.string.forward_intent_to_work) + } + ) } fun getWorkProfileNotSupportedMessage(launcherName: String): String { - return requireNotNull(policyResources.getString(RESOLVER_WORK_PROFILE_NOT_SUPPORTED, { - resources.getString( - R.string.activity_resolver_work_profiles_support, - launcherName) - }, launcherName)) + return requireNotNull( + policyResources.getString( + RESOLVER_WORK_PROFILE_NOT_SUPPORTED, + { + resources.getString( + R.string.activity_resolver_work_profiles_support, + launcherName + ) + }, + launcherName + ) + ) } -}
\ No newline at end of file +} |