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