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