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