1 /* 2 * Copyright (C) 2014 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.internal.app; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS; 20 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; 21 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; 22 import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_CALL_FROM_WORK; 23 import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_WORK; 24 import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_SWITCH_TO_WORK; 25 import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_WORK_TELEPHONY_CALL_BLOCKED_INFORMATION; 26 import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_WORK_TELEPHONY_TEXT_BLOCKED_INFORMATION; 27 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; 28 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 29 30 import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER; 31 import static com.android.internal.app.ResolverActivity.EXTRA_RESTRICT_TO_SINGLE_USER; 32 import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE; 33 34 import android.annotation.Nullable; 35 import android.annotation.TestApi; 36 import android.app.Activity; 37 import android.app.ActivityOptions; 38 import android.app.ActivityThread; 39 import android.app.AppGlobals; 40 import android.app.admin.DevicePolicyManager; 41 import android.app.admin.ManagedSubscriptionsPolicy; 42 import android.compat.annotation.UnsupportedAppUsage; 43 import android.content.ComponentName; 44 import android.content.ContentResolver; 45 import android.content.Intent; 46 import android.content.pm.ActivityInfo; 47 import android.content.pm.IPackageManager; 48 import android.content.pm.PackageManager; 49 import android.content.pm.ResolveInfo; 50 import android.content.pm.UserInfo; 51 import android.graphics.drawable.Drawable; 52 import android.metrics.LogMaker; 53 import android.os.Build; 54 import android.os.Bundle; 55 import android.os.RemoteException; 56 import android.os.UserHandle; 57 import android.os.UserManager; 58 import android.provider.Settings; 59 import android.telecom.TelecomManager; 60 import android.util.Log; 61 import android.util.Slog; 62 import android.view.View; 63 import android.widget.Button; 64 import android.widget.ImageView; 65 import android.widget.TextView; 66 import android.widget.Toast; 67 68 import com.android.internal.R; 69 import com.android.internal.annotations.VisibleForTesting; 70 import com.android.internal.logging.MetricsLogger; 71 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 72 73 import java.util.Arrays; 74 import java.util.HashSet; 75 import java.util.List; 76 import java.util.Set; 77 import java.util.concurrent.CompletableFuture; 78 import java.util.concurrent.ExecutorService; 79 import java.util.concurrent.Executors; 80 81 /** 82 * This is used in conjunction with 83 * {@link DevicePolicyManager#addCrossProfileIntentFilter} to enable intents to 84 * be passed in and out of a managed profile. 85 */ 86 public class IntentForwarderActivity extends Activity { 87 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 88 public static String TAG = "IntentForwarderActivity"; 89 90 public static String FORWARD_INTENT_TO_PARENT 91 = "com.android.internal.app.ForwardIntentToParent"; 92 93 public static String FORWARD_INTENT_TO_MANAGED_PROFILE 94 = "com.android.internal.app.ForwardIntentToManagedProfile"; 95 96 @TestApi 97 public static final String EXTRA_SKIP_USER_CONFIRMATION = 98 "com.android.internal.app.EXTRA_SKIP_USER_CONFIRMATION"; 99 100 private static final Set<String> ALLOWED_TEXT_MESSAGE_SCHEMES 101 = new HashSet<>(Arrays.asList("sms", "smsto", "mms", "mmsto")); 102 103 private static final String TEL_SCHEME = "tel"; 104 105 private static final ComponentName RESOLVER_COMPONENT_NAME = 106 new ComponentName("android", ResolverActivity.class.getName()); 107 108 private Injector mInjector; 109 110 private MetricsLogger mMetricsLogger; 111 protected ExecutorService mExecutorService; 112 113 @Override onDestroy()114 protected void onDestroy() { 115 super.onDestroy(); 116 mExecutorService.shutdown(); 117 } 118 119 @Override onCreate(Bundle savedInstanceState)120 protected void onCreate(Bundle savedInstanceState) { 121 super.onCreate(savedInstanceState); 122 mInjector = createInjector(); 123 mExecutorService = Executors.newSingleThreadExecutor(); 124 125 Intent intentReceived = getIntent(); 126 String className = intentReceived.getComponent().getClassName(); 127 final int targetUserId; 128 final String userMessage; 129 final UserInfo managedProfile; 130 if (className.equals(FORWARD_INTENT_TO_PARENT)) { 131 userMessage = getForwardToPersonalMessage(); 132 targetUserId = getProfileParent(); 133 managedProfile = null; 134 135 getMetricsLogger().write( 136 new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) 137 .setSubtype(MetricsEvent.PARENT_PROFILE)); 138 } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { 139 userMessage = getForwardToWorkMessage(); 140 managedProfile = getManagedProfile(); 141 targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; 142 143 getMetricsLogger().write( 144 new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) 145 .setSubtype(MetricsEvent.MANAGED_PROFILE)); 146 } else { 147 Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); 148 userMessage = null; 149 targetUserId = UserHandle.USER_NULL; 150 managedProfile = null; 151 } 152 if (targetUserId == UserHandle.USER_NULL) { 153 // This covers the case where there is no parent / managed profile. 154 finish(); 155 return; 156 } 157 if (Intent.ACTION_CHOOSER.equals(intentReceived.getAction())) { 158 launchChooserActivityWithCorrectTab(intentReceived, className); 159 return; 160 } 161 162 final int callingUserId = getUserId(); 163 final Intent newIntent = canForward(intentReceived, getUserId(), targetUserId, 164 mInjector.getIPackageManager(), getContentResolver()); 165 166 if (newIntent == null) { 167 Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user " 168 + callingUserId + " to user " + targetUserId); 169 finish(); 170 return; 171 } 172 173 newIntent.prepareToLeaveUser(callingUserId); 174 final CompletableFuture<ResolveInfo> targetResolveInfoFuture = 175 mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId); 176 177 if (isPrivateProfile(callingUserId)) { 178 buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId, 179 targetUserId); 180 } else { 181 buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent, 182 callingUserId, 183 targetUserId, userMessage, managedProfile); 184 } 185 } 186 buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture, Intent intentReceived, String className, Intent newIntent, int callingUserId, int targetUserId, String userMessage, UserInfo managedProfile)187 private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture, 188 Intent intentReceived, String className, Intent newIntent, int callingUserId, 189 int targetUserId, String userMessage, UserInfo managedProfile) { 190 targetResolveInfoFuture 191 .thenApplyAsync(targetResolveInfo -> { 192 if (isResolverActivityResolveInfo(targetResolveInfo)) { 193 launchResolverActivityWithCorrectTab(intentReceived, className, newIntent, 194 callingUserId, targetUserId, false); 195 // When switching to the personal profile, automatically start the activity 196 } else if (className.equals(FORWARD_INTENT_TO_PARENT)) { 197 startActivityAsCaller(newIntent, targetUserId); 198 } 199 return targetResolveInfo; 200 }, mExecutorService) 201 .thenAcceptAsync(result -> { 202 // When switching to the personal profile, inform user after starting activity 203 if (className.equals(FORWARD_INTENT_TO_PARENT)) { 204 maybeShowDisclosure(intentReceived, result, userMessage); 205 finish(); 206 // When switching to the work profile, ask the user for consent before launching 207 } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { 208 maybeShowUserConsentMiniResolver(result, newIntent, managedProfile); 209 } 210 }, getApplicationContext().getMainExecutor()); 211 } 212 buildAndExecuteForPrivateProfile( Intent intentReceived, String className, Intent newIntent, int callingUserId, int targetUserId)213 private void buildAndExecuteForPrivateProfile( 214 Intent intentReceived, String className, Intent newIntent, int callingUserId, 215 int targetUserId) { 216 final CompletableFuture<ResolveInfo> targetResolveInfoFuture = 217 mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId); 218 targetResolveInfoFuture 219 .thenAcceptAsync(targetResolveInfo -> { 220 if (isResolverActivityResolveInfo(targetResolveInfo)) { 221 launchResolverActivityWithCorrectTab(intentReceived, className, newIntent, 222 callingUserId, targetUserId, true); 223 } else { 224 maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent, 225 targetUserId); 226 } 227 }, getApplicationContext().getMainExecutor()); 228 } 229 maybeShowUserConsentMiniResolver( ResolveInfo target, Intent launchIntent, UserInfo managedProfile)230 private void maybeShowUserConsentMiniResolver( 231 ResolveInfo target, Intent launchIntent, UserInfo managedProfile) { 232 if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { 233 finish(); 234 return; 235 } 236 237 int targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; 238 String callingPackage = getCallingPackage(); 239 boolean privilegedCallerAskedToSkipUserConsent = 240 launchIntent.getBooleanExtra( 241 EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) 242 && callingPackage != null 243 && PERMISSION_GRANTED == getPackageManager().checkPermission( 244 INTERACT_ACROSS_USERS, callingPackage); 245 246 DevicePolicyManager devicePolicyManager = 247 getSystemService(DevicePolicyManager.class); 248 ComponentName profileOwnerName = devicePolicyManager.getProfileOwnerAsUser(targetUserId); 249 boolean intentToLaunchProfileOwner = profileOwnerName != null 250 && profileOwnerName.getPackageName().equals(target.getComponentInfo().packageName); 251 252 if (privilegedCallerAskedToSkipUserConsent || intentToLaunchProfileOwner) { 253 Log.i("IntentForwarderActivity", String.format( 254 "Skipping user consent for redirection into the managed profile for intent [%s]" 255 + ", privilegedCallerAskedToSkipUserConsent=[%s]" 256 + ", intentToLaunchProfileOwner=[%s]", 257 launchIntent, privilegedCallerAskedToSkipUserConsent, 258 intentToLaunchProfileOwner)); 259 startActivityAsCaller(launchIntent, targetUserId); 260 finish(); 261 return; 262 } 263 264 Log.i("IntentForwarderActivity", String.format( 265 "Showing user consent for redirection into the managed profile for intent [%s] and " 266 + " calling package [%s]", 267 launchIntent, callingPackage)); 268 PackageManager packageManagerForTargetUser = 269 createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0) 270 .getPackageManager(); 271 buildMiniResolver(target, launchIntent, targetUserId, 272 getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)), 273 packageManagerForTargetUser); 274 275 ((Button) findViewById(R.id.button_open)).setText(getOpenInWorkButtonString(launchIntent)); 276 277 View telephonyInfo = findViewById(R.id.miniresolver_info_section); 278 279 // Additional information section is work telephony specific. Therefore, it is only shown 280 // for telephony related intents, when all sim subscriptions are in the work profile. 281 if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent)) 282 && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType() 283 == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) { 284 telephonyInfo.setVisibility(View.VISIBLE); 285 ((TextView) findViewById(R.id.miniresolver_info_section_text)) 286 .setText(getWorkTelephonyInfoSectionMessage(launchIntent)); 287 } else { 288 telephonyInfo.setVisibility(View.GONE); 289 } 290 } 291 maybeShowUserConsentMiniResolverPrivate( ResolveInfo target, Intent launchIntent, int targetUserId)292 private void maybeShowUserConsentMiniResolverPrivate( 293 ResolveInfo target, Intent launchIntent, int targetUserId) { 294 if (target == null || isIntentForwarderResolveInfo(target)) { 295 finish(); 296 return; 297 } 298 299 String callingPackage = getCallingPackage(); 300 301 Log.i("IntentForwarderActivity", String.format( 302 "Showing user consent for redirection into the main profile for intent [%s] and " 303 + " calling package [%s]", 304 launchIntent, callingPackage)); 305 PackageManager packageManagerForTargetUser = 306 createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0) 307 .getPackageManager(); 308 buildMiniResolver(target, launchIntent, targetUserId, 309 getString(R.string.miniresolver_open_in_personal, 310 target.loadLabel(packageManagerForTargetUser)), 311 packageManagerForTargetUser); 312 313 View telephonyInfo = findViewById(R.id.miniresolver_info_section); 314 telephonyInfo.setVisibility(View.VISIBLE); 315 316 if (isTextMessageIntent(launchIntent)) { 317 ((TextView) findViewById(R.id.miniresolver_info_section_text)).setText( 318 R.string.miniresolver_private_space_messages_information); 319 } else { 320 ((TextView) findViewById(R.id.miniresolver_info_section_text)).setText( 321 R.string.miniresolver_private_space_phone_information); 322 } 323 } 324 buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId, String resolverTitle, PackageManager pmForTargetUser)325 private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId, 326 String resolverTitle, PackageManager pmForTargetUser) { 327 int layoutId = R.layout.miniresolver; 328 setContentView(layoutId); 329 330 findViewById(R.id.title_container).setElevation(0); 331 332 ImageView icon = findViewById(R.id.icon); 333 icon.setImageDrawable( 334 getAppIcon(target, launchIntent, targetUserId, pmForTargetUser)); 335 336 View buttonContainer = findViewById(R.id.button_bar_container); 337 buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom()); 338 339 ((TextView) findViewById(R.id.open_cross_profile)).setText( 340 resolverTitle); 341 342 // The mini-resolver's negative button is reused in this flow to cancel the intent 343 ((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel); 344 findViewById(R.id.use_same_profile_browser).setOnClickListener(v -> finish()); 345 346 findViewById(R.id.button_open).setOnClickListener(v -> { 347 startActivityAsCaller( 348 launchIntent, 349 ActivityOptions.makeCustomAnimation( 350 getApplicationContext(), 351 R.anim.activity_open_enter, 352 R.anim.push_down_out) 353 .toBundle(), 354 /* ignoreTargetSecurity= */ false, 355 targetUserId); 356 finish(); 357 }); 358 } 359 getAppIcon( ResolveInfo target, Intent launchIntent, int targetUserId, PackageManager packageManagerForTargetUser)360 private Drawable getAppIcon( 361 ResolveInfo target, 362 Intent launchIntent, 363 int targetUserId, 364 PackageManager packageManagerForTargetUser) { 365 if (isDialerIntent(launchIntent)) { 366 // The icon for the call intent will be a generic phone icon as the target will be 367 // the telecom call handler. From the user's perspective, they are being directed 368 // to the dialer app, so use the icon from that app instead. 369 TelecomManager telecomManager = 370 getApplicationContext().getSystemService(TelecomManager.class); 371 String defaultDialerPackageName = 372 telecomManager.getDefaultDialerPackage(UserHandle.of(targetUserId)); 373 try { 374 return packageManagerForTargetUser 375 .getApplicationInfo(defaultDialerPackageName, /* flags= */ 0) 376 .loadIcon(packageManagerForTargetUser); 377 } catch (PackageManager.NameNotFoundException e) { 378 // Allow to fall-through to the icon from the target if we can't find the default 379 // dialer icon. 380 Slog.w(TAG, "Cannot load icon for default dialer package"); 381 } 382 } 383 return target.loadIcon(packageManagerForTargetUser); 384 } 385 getOpenInWorkButtonString(Intent launchIntent)386 private int getOpenInWorkButtonString(Intent launchIntent) { 387 if (isDialerIntent(launchIntent)) { 388 return R.string.miniresolver_call; 389 } 390 if (isTextMessageIntent(launchIntent)) { 391 return R.string.miniresolver_switch; 392 } 393 return R.string.whichViewApplicationLabel; 394 } 395 getOpenInWorkMessage(Intent launchIntent, CharSequence targetLabel)396 private String getOpenInWorkMessage(Intent launchIntent, CharSequence targetLabel) { 397 if (isDialerIntent(launchIntent)) { 398 return getSystemService(DevicePolicyManager.class).getResources().getString( 399 MINIRESOLVER_CALL_FROM_WORK, 400 () -> getString(R.string.miniresolver_call_in_work)); 401 } 402 if (isTextMessageIntent(launchIntent)) { 403 return getSystemService(DevicePolicyManager.class).getResources().getString( 404 MINIRESOLVER_SWITCH_TO_WORK, 405 () -> getString(R.string.miniresolver_switch_to_work)); 406 } 407 return getSystemService(DevicePolicyManager.class).getResources().getString( 408 MINIRESOLVER_OPEN_WORK, 409 () -> getString(R.string.miniresolver_open_work, targetLabel), 410 targetLabel); 411 } 412 getWorkTelephonyInfoSectionMessage(Intent launchIntent)413 private String getWorkTelephonyInfoSectionMessage(Intent launchIntent) { 414 if (isDialerIntent(launchIntent)) { 415 return getSystemService(DevicePolicyManager.class).getResources().getString( 416 MINIRESOLVER_WORK_TELEPHONY_CALL_BLOCKED_INFORMATION, 417 () -> getString(R.string.miniresolver_call_information)); 418 } 419 if (isTextMessageIntent(launchIntent)) { 420 return getSystemService(DevicePolicyManager.class).getResources().getString( 421 MINIRESOLVER_WORK_TELEPHONY_TEXT_BLOCKED_INFORMATION, 422 () -> getString(R.string.miniresolver_sms_information)); 423 } 424 return ""; 425 } 426 427 428 getForwardToPersonalMessage()429 private String getForwardToPersonalMessage() { 430 return getSystemService(DevicePolicyManager.class).getResources().getString( 431 FORWARD_INTENT_TO_PERSONAL, 432 () -> getString(com.android.internal.R.string.forward_intent_to_owner)); 433 } 434 getForwardToWorkMessage()435 private String getForwardToWorkMessage() { 436 return getSystemService(DevicePolicyManager.class).getResources().getString( 437 FORWARD_INTENT_TO_WORK, 438 () -> getString(com.android.internal.R.string.forward_intent_to_work)); 439 } 440 isIntentForwarderResolveInfo(ResolveInfo resolveInfo)441 private boolean isIntentForwarderResolveInfo(ResolveInfo resolveInfo) { 442 if (resolveInfo == null) { 443 return false; 444 } 445 ActivityInfo activityInfo = resolveInfo.activityInfo; 446 if (activityInfo == null) { 447 return false; 448 } 449 if (!"android".equals(activityInfo.packageName)) { 450 return false; 451 } 452 return activityInfo.name.equals(FORWARD_INTENT_TO_PARENT) 453 || activityInfo.name.equals(FORWARD_INTENT_TO_MANAGED_PROFILE); 454 } 455 isResolverActivityResolveInfo(@ullable ResolveInfo resolveInfo)456 private boolean isResolverActivityResolveInfo(@Nullable ResolveInfo resolveInfo) { 457 return resolveInfo != null 458 && resolveInfo.activityInfo != null 459 && RESOLVER_COMPONENT_NAME.equals(resolveInfo.activityInfo.getComponentName()); 460 } 461 maybeShowDisclosure( Intent intentReceived, ResolveInfo resolveInfo, @Nullable String message)462 private void maybeShowDisclosure( 463 Intent intentReceived, ResolveInfo resolveInfo, @Nullable String message) { 464 if (shouldShowDisclosure(resolveInfo, intentReceived) && message != null) { 465 mInjector.showToast(message, Toast.LENGTH_LONG); 466 } 467 } 468 startActivityAsCaller(Intent newIntent, int userId)469 private void startActivityAsCaller(Intent newIntent, int userId) { 470 try { 471 startActivityAsCaller( 472 newIntent, 473 /* options= */ null, 474 /* ignoreTargetSecurity= */ false, 475 userId); 476 } catch (RuntimeException e) { 477 Slog.wtf(TAG, "Unable to launch as UID " + getLaunchedFromUid() + " package " 478 + getLaunchedFromPackage() + ", while running in " 479 + ActivityThread.currentProcessName(), e); 480 } 481 } 482 launchChooserActivityWithCorrectTab(Intent intentReceived, String className)483 private void launchChooserActivityWithCorrectTab(Intent intentReceived, String className) { 484 // When showing the sharesheet, instead of forwarding to the other profile, 485 // we launch the sharesheet in the current user and select the other tab. 486 // This fixes b/152866292 where the user can not go back to the original profile 487 // when cross-profile intents are disabled. 488 int selectedProfile = findSelectedProfile(className); 489 sanitizeIntent(intentReceived); 490 intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile); 491 Intent innerIntent = intentReceived.getParcelableExtra(Intent.EXTRA_INTENT, android.content.Intent.class); 492 if (innerIntent == null) { 493 Slog.wtf(TAG, "Cannot start a chooser intent with no extra " + Intent.EXTRA_INTENT); 494 return; 495 } 496 sanitizeIntent(innerIntent); 497 startActivityAsCaller(intentReceived, null, false, getUserId()); 498 finish(); 499 } 500 launchResolverActivityWithCorrectTab(Intent intentReceived, String className, Intent newIntent, int callingUserId, int targetUserId, boolean singleTabOnly)501 private void launchResolverActivityWithCorrectTab(Intent intentReceived, String className, 502 Intent newIntent, int callingUserId, int targetUserId, boolean singleTabOnly) { 503 // When showing the intent resolver, instead of forwarding to the other profile, 504 // we launch it in the current user and select the other tab. This fixes b/155874820. 505 // 506 // In the case when there are 0 targets in the current profile and >1 apps in the other 507 // profile, the package manager launches the intent resolver in the other profile. 508 // If that's the case, we launch the resolver in the target user instead (other profile). 509 ResolveInfo callingResolveInfo = mInjector.resolveActivityAsUser( 510 newIntent, MATCH_DEFAULT_ONLY, callingUserId).join(); 511 int userId = isIntentForwarderResolveInfo(callingResolveInfo) 512 ? targetUserId : callingUserId; 513 int selectedProfile = findSelectedProfile(className); 514 sanitizeIntent(intentReceived); 515 intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile); 516 intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId)); 517 if (singleTabOnly) { 518 intentReceived.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true); 519 } 520 startActivityAsCaller(intentReceived, null, false, userId); 521 finish(); 522 } 523 findSelectedProfile(String className)524 private int findSelectedProfile(String className) { 525 if (className.equals(FORWARD_INTENT_TO_PARENT)) { 526 return ChooserActivity.PROFILE_PERSONAL; 527 } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { 528 return ChooserActivity.PROFILE_WORK; 529 } 530 return -1; 531 } 532 shouldShowDisclosure(@ullable ResolveInfo ri, Intent intent)533 private boolean shouldShowDisclosure(@Nullable ResolveInfo ri, Intent intent) { 534 if (!isDeviceProvisioned()) { 535 return false; 536 } 537 if (ri == null || ri.activityInfo == null) { 538 return true; 539 } 540 if (ri.activityInfo.applicationInfo.isSystemApp() 541 && (isDialerIntent(intent) || isTextMessageIntent(intent))) { 542 return false; 543 } 544 return !isTargetResolverOrChooserActivity(ri.activityInfo); 545 } 546 isDeviceProvisioned()547 private boolean isDeviceProvisioned() { 548 return Settings.Global.getInt(getContentResolver(), 549 Settings.Global.DEVICE_PROVISIONED, /* def= */ 0) != 0; 550 } 551 isTextMessageIntent(Intent intent)552 private boolean isTextMessageIntent(Intent intent) { 553 return (Intent.ACTION_SENDTO.equals(intent.getAction()) || isViewActionIntent(intent)) 554 && ALLOWED_TEXT_MESSAGE_SCHEMES.contains(intent.getScheme()); 555 } 556 isDialerIntent(Intent intent)557 private boolean isDialerIntent(Intent intent) { 558 return Intent.ACTION_DIAL.equals(intent.getAction()) 559 || Intent.ACTION_CALL.equals(intent.getAction()) 560 || Intent.ACTION_CALL_PRIVILEGED.equals(intent.getAction()) 561 || Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction()) 562 || (isViewActionIntent(intent) && TEL_SCHEME.equals(intent.getScheme())); 563 } 564 isViewActionIntent(Intent intent)565 private boolean isViewActionIntent(Intent intent) { 566 return Intent.ACTION_VIEW.equals(intent.getAction()) 567 && intent.hasCategory(Intent.CATEGORY_BROWSABLE); 568 } 569 isTargetResolverOrChooserActivity(ActivityInfo activityInfo)570 private boolean isTargetResolverOrChooserActivity(ActivityInfo activityInfo) { 571 if (!"android".equals(activityInfo.packageName)) { 572 return false; 573 } 574 return ResolverActivity.class.getName().equals(activityInfo.name) 575 || ChooserActivity.class.getName().equals(activityInfo.name); 576 } 577 578 /** 579 * Check whether the intent can be forwarded to target user. Return the intent used for 580 * forwarding if it can be forwarded, {@code null} otherwise. 581 */ canForward(Intent incomingIntent, int sourceUserId, int targetUserId, IPackageManager packageManager, ContentResolver contentResolver)582 static Intent canForward(Intent incomingIntent, int sourceUserId, int targetUserId, 583 IPackageManager packageManager, ContentResolver contentResolver) { 584 Intent forwardIntent = new Intent(incomingIntent); 585 forwardIntent.addFlags( 586 Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 587 sanitizeIntent(forwardIntent); 588 589 Intent intentToCheck = forwardIntent; 590 if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) { 591 return null; 592 } 593 if (forwardIntent.getSelector() != null) { 594 intentToCheck = forwardIntent.getSelector(); 595 } 596 String resolvedType = intentToCheck.resolveTypeIfNeeded(contentResolver); 597 sanitizeIntent(intentToCheck); 598 try { 599 if (packageManager.canForwardTo( 600 intentToCheck, resolvedType, sourceUserId, targetUserId)) { 601 return forwardIntent; 602 } 603 } catch (RemoteException e) { 604 Slog.e(TAG, "PackageManagerService is dead?"); 605 } 606 return null; 607 } 608 609 /** 610 * Returns the managed profile for this device or null if there is no managed profile. 611 * 612 * TODO: Remove the assumption that there is only one managed profile on the device. 613 */ getManagedProfile()614 @Nullable private UserInfo getManagedProfile() { 615 List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); 616 for (UserInfo userInfo : relatedUsers) { 617 if (userInfo.isManagedProfile()) return userInfo; 618 } 619 Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE 620 + " has been called, but there is no managed profile"); 621 return null; 622 } 623 624 /** 625 * Returns the private profile for this device or null if there is no private profile. 626 */ 627 @Nullable getPrivateProfile()628 private UserInfo getPrivateProfile() { 629 List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); 630 for (UserInfo userInfo : relatedUsers) { 631 if (userInfo.isPrivateProfile()) return userInfo; 632 } 633 return null; 634 } 635 636 /** 637 * Returns the userId of the profile parent or UserHandle.USER_NULL if there is 638 * no parent. 639 */ getProfileParent()640 private int getProfileParent() { 641 UserInfo parent = mInjector.getUserManager().getProfileParent(UserHandle.myUserId()); 642 if (parent == null) { 643 Slog.wtf(TAG, FORWARD_INTENT_TO_PARENT 644 + " has been called, but there is no parent"); 645 return UserHandle.USER_NULL; 646 } 647 return parent.id; 648 } 649 650 /** 651 * Sanitize the intent in place. 652 */ sanitizeIntent(Intent intent)653 private static void sanitizeIntent(Intent intent) { 654 // Apps should not be allowed to target a specific package/ component in the target user. 655 intent.setPackage(null); 656 intent.setComponent(null); 657 } 658 getMetricsLogger()659 protected MetricsLogger getMetricsLogger() { 660 if (mMetricsLogger == null) { 661 mMetricsLogger = new MetricsLogger(); 662 } 663 return mMetricsLogger; 664 } 665 isPrivateProfile(int userId)666 private boolean isPrivateProfile(int userId) { 667 UserInfo privateProfile = getPrivateProfile(); 668 return privateSpaceFlagsEnabled() && privateProfile != null 669 && privateProfile.id == userId; 670 } 671 privateSpaceFlagsEnabled()672 private boolean privateSpaceFlagsEnabled() { 673 return android.os.Flags.allowPrivateProfile() 674 && android.multiuser.Flags.enablePrivateSpaceFeatures() 675 && android.multiuser.Flags.enablePrivateSpaceIntentRedirection(); 676 } 677 678 @VisibleForTesting createInjector()679 protected Injector createInjector() { 680 return new InjectorImpl(); 681 } 682 683 private class InjectorImpl implements Injector { 684 685 @Override getIPackageManager()686 public IPackageManager getIPackageManager() { 687 return AppGlobals.getPackageManager(); 688 } 689 690 @Override getUserManager()691 public UserManager getUserManager() { 692 return getSystemService(UserManager.class); 693 } 694 695 @Override getPackageManager()696 public PackageManager getPackageManager() { 697 return IntentForwarderActivity.this.getPackageManager(); 698 } 699 700 @Override 701 @Nullable resolveActivityAsUser( Intent intent, int flags, int userId)702 public CompletableFuture<ResolveInfo> resolveActivityAsUser( 703 Intent intent, int flags, int userId) { 704 return CompletableFuture.supplyAsync( 705 () -> getPackageManager().resolveActivityAsUser(intent, flags, userId)); 706 } 707 708 @Override showToast(String message, int duration)709 public void showToast(String message, int duration) { 710 Toast.makeText(IntentForwarderActivity.this, message, duration).show(); 711 } 712 } 713 714 public interface Injector { getIPackageManager()715 IPackageManager getIPackageManager(); 716 getUserManager()717 UserManager getUserManager(); 718 getPackageManager()719 PackageManager getPackageManager(); 720 resolveActivityAsUser(Intent intent, int flags, int userId)721 CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId); 722 showToast(String message, int duration)723 void showToast(String message, int duration); 724 } 725 } 726