• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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