• 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.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
20 
21 import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER;
22 import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE;
23 
24 import android.annotation.Nullable;
25 import android.annotation.StringRes;
26 import android.app.Activity;
27 import android.app.ActivityThread;
28 import android.app.AppGlobals;
29 import android.app.admin.DevicePolicyManager;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.ComponentName;
32 import android.content.ContentResolver;
33 import android.content.Intent;
34 import android.content.pm.ActivityInfo;
35 import android.content.pm.IPackageManager;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.pm.UserInfo;
39 import android.metrics.LogMaker;
40 import android.os.Build;
41 import android.os.Bundle;
42 import android.os.RemoteException;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.provider.Settings;
46 import android.util.Slog;
47 import android.widget.Toast;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.logging.MetricsLogger;
51 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
52 
53 import java.util.Arrays;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Set;
57 import java.util.concurrent.CompletableFuture;
58 import java.util.concurrent.ExecutorService;
59 import java.util.concurrent.Executors;
60 
61 /**
62  * This is used in conjunction with
63  * {@link DevicePolicyManager#addCrossProfileIntentFilter} to enable intents to
64  * be passed in and out of a managed profile.
65  */
66 public class IntentForwarderActivity extends Activity  {
67     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
68     public static String TAG = "IntentForwarderActivity";
69 
70     public static String FORWARD_INTENT_TO_PARENT
71             = "com.android.internal.app.ForwardIntentToParent";
72 
73     public static String FORWARD_INTENT_TO_MANAGED_PROFILE
74             = "com.android.internal.app.ForwardIntentToManagedProfile";
75 
76     private static final Set<String> ALLOWED_TEXT_MESSAGE_SCHEMES
77             = new HashSet<>(Arrays.asList("sms", "smsto", "mms", "mmsto"));
78 
79     private static final String TEL_SCHEME = "tel";
80 
81     private static final ComponentName RESOLVER_COMPONENT_NAME =
82             new ComponentName("android", ResolverActivity.class.getName());
83 
84     private Injector mInjector;
85 
86     private MetricsLogger mMetricsLogger;
87     protected ExecutorService mExecutorService;
88 
89     @Override
onDestroy()90     protected void onDestroy() {
91         super.onDestroy();
92         mExecutorService.shutdown();
93     }
94 
95     @Override
onCreate(Bundle savedInstanceState)96     protected void onCreate(Bundle savedInstanceState) {
97         super.onCreate(savedInstanceState);
98         mInjector = createInjector();
99         mExecutorService = Executors.newSingleThreadExecutor();
100 
101         Intent intentReceived = getIntent();
102         String className = intentReceived.getComponent().getClassName();
103         final int targetUserId;
104         final int userMessageId;
105         if (className.equals(FORWARD_INTENT_TO_PARENT)) {
106             userMessageId = com.android.internal.R.string.forward_intent_to_owner;
107             targetUserId = getProfileParent();
108 
109             getMetricsLogger().write(
110                     new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE)
111                     .setSubtype(MetricsEvent.PARENT_PROFILE));
112         } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
113             userMessageId = com.android.internal.R.string.forward_intent_to_work;
114             targetUserId = getManagedProfile();
115 
116             getMetricsLogger().write(
117                     new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE)
118                     .setSubtype(MetricsEvent.MANAGED_PROFILE));
119         } else {
120             Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly");
121             userMessageId = -1;
122             targetUserId = UserHandle.USER_NULL;
123         }
124         if (targetUserId == UserHandle.USER_NULL) {
125             // This covers the case where there is no parent / managed profile.
126             finish();
127             return;
128         }
129         if (Intent.ACTION_CHOOSER.equals(intentReceived.getAction())) {
130             launchChooserActivityWithCorrectTab(intentReceived, className);
131             return;
132         }
133 
134         final int callingUserId = getUserId();
135         final Intent newIntent = canForward(intentReceived, getUserId(), targetUserId,
136                 mInjector.getIPackageManager(), getContentResolver());
137 
138         if (newIntent == null) {
139             Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user "
140                     + callingUserId + " to user " + targetUserId);
141             finish();
142             return;
143         }
144 
145         newIntent.prepareToLeaveUser(callingUserId);
146         final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
147                 mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
148         targetResolveInfoFuture
149                 .thenApplyAsync(targetResolveInfo -> {
150                     if (isResolverActivityResolveInfo(targetResolveInfo)) {
151                         launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
152                                 callingUserId, targetUserId);
153                         return targetResolveInfo;
154                     }
155                     startActivityAsCaller(newIntent, targetUserId);
156                     return targetResolveInfo;
157                 }, mExecutorService)
158                 .thenAcceptAsync(result -> {
159                     maybeShowDisclosure(intentReceived, result, userMessageId);
160                     finish();
161                 }, getApplicationContext().getMainExecutor());
162     }
163 
isIntentForwarderResolveInfo(ResolveInfo resolveInfo)164     private boolean isIntentForwarderResolveInfo(ResolveInfo resolveInfo) {
165         if (resolveInfo == null) {
166             return false;
167         }
168         ActivityInfo activityInfo = resolveInfo.activityInfo;
169         if (activityInfo == null) {
170             return false;
171         }
172         if (!"android".equals(activityInfo.packageName)) {
173             return false;
174         }
175         return activityInfo.name.equals(FORWARD_INTENT_TO_PARENT)
176                 || activityInfo.name.equals(FORWARD_INTENT_TO_MANAGED_PROFILE);
177     }
178 
isResolverActivityResolveInfo(@ullable ResolveInfo resolveInfo)179     private boolean isResolverActivityResolveInfo(@Nullable ResolveInfo resolveInfo) {
180         return resolveInfo != null
181                 && resolveInfo.activityInfo != null
182                 && RESOLVER_COMPONENT_NAME.equals(resolveInfo.activityInfo.getComponentName());
183     }
184 
maybeShowDisclosure( Intent intentReceived, ResolveInfo resolveInfo, int messageId)185     private void maybeShowDisclosure(
186             Intent intentReceived, ResolveInfo resolveInfo, int messageId) {
187         if (shouldShowDisclosure(resolveInfo, intentReceived)) {
188             mInjector.showToast(messageId, Toast.LENGTH_LONG);
189         }
190     }
191 
startActivityAsCaller(Intent newIntent, int userId)192     private void startActivityAsCaller(Intent newIntent, int userId) {
193         try {
194             startActivityAsCaller(
195                     newIntent,
196                     /* options= */ null,
197                     /* permissionToken= */ null,
198                     /* ignoreTargetSecurity= */ false,
199                     userId);
200         } catch (RuntimeException e) {
201             Slog.wtf(TAG, "Unable to launch as UID " + getLaunchedFromUid() + " package "
202                     + getLaunchedFromPackage() + ", while running in "
203                     + ActivityThread.currentProcessName(), e);
204         }
205     }
206 
launchChooserActivityWithCorrectTab(Intent intentReceived, String className)207     private void launchChooserActivityWithCorrectTab(Intent intentReceived, String className) {
208         // When showing the sharesheet, instead of forwarding to the other profile,
209         // we launch the sharesheet in the current user and select the other tab.
210         // This fixes b/152866292 where the user can not go back to the original profile
211         // when cross-profile intents are disabled.
212         int selectedProfile = findSelectedProfile(className);
213         sanitizeIntent(intentReceived);
214         intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
215         Intent innerIntent = intentReceived.getParcelableExtra(Intent.EXTRA_INTENT);
216         if (innerIntent == null) {
217             Slog.wtf(TAG, "Cannot start a chooser intent with no extra " + Intent.EXTRA_INTENT);
218             return;
219         }
220         sanitizeIntent(innerIntent);
221         startActivityAsCaller(intentReceived, null, null, false, getUserId());
222         finish();
223     }
224 
launchResolverActivityWithCorrectTab(Intent intentReceived, String className, Intent newIntent, int callingUserId, int targetUserId)225     private void launchResolverActivityWithCorrectTab(Intent intentReceived, String className,
226             Intent newIntent, int callingUserId, int targetUserId) {
227         // When showing the intent resolver, instead of forwarding to the other profile,
228         // we launch it in the current user and select the other tab. This fixes b/155874820.
229         //
230         // In the case when there are 0 targets in the current profile and >1 apps in the other
231         // profile, the package manager launches the intent resolver in the other profile.
232         // If that's the case, we launch the resolver in the target user instead (other profile).
233         ResolveInfo callingResolveInfo = mInjector.resolveActivityAsUser(
234                 newIntent, MATCH_DEFAULT_ONLY, callingUserId).join();
235         int userId = isIntentForwarderResolveInfo(callingResolveInfo)
236                 ? targetUserId : callingUserId;
237         int selectedProfile = findSelectedProfile(className);
238         sanitizeIntent(intentReceived);
239         intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
240         intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId));
241         startActivityAsCaller(intentReceived, null, null, false, userId);
242         finish();
243     }
244 
findSelectedProfile(String className)245     private int findSelectedProfile(String className) {
246         if (className.equals(FORWARD_INTENT_TO_PARENT)) {
247             return ChooserActivity.PROFILE_PERSONAL;
248         } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
249             return ChooserActivity.PROFILE_WORK;
250         }
251         return -1;
252     }
253 
shouldShowDisclosure(@ullable ResolveInfo ri, Intent intent)254     private boolean shouldShowDisclosure(@Nullable ResolveInfo ri, Intent intent) {
255         if (!isDeviceProvisioned()) {
256             return false;
257         }
258         if (ri == null || ri.activityInfo == null) {
259             return true;
260         }
261         if (ri.activityInfo.applicationInfo.isSystemApp()
262                 && (isDialerIntent(intent) || isTextMessageIntent(intent))) {
263             return false;
264         }
265         return !isTargetResolverOrChooserActivity(ri.activityInfo);
266     }
267 
isDeviceProvisioned()268     private boolean isDeviceProvisioned() {
269         return Settings.Global.getInt(getContentResolver(),
270                 Settings.Global.DEVICE_PROVISIONED, /* def= */ 0) != 0;
271     }
272 
isTextMessageIntent(Intent intent)273     private boolean isTextMessageIntent(Intent intent) {
274         return (Intent.ACTION_SENDTO.equals(intent.getAction()) || isViewActionIntent(intent))
275                 && ALLOWED_TEXT_MESSAGE_SCHEMES.contains(intent.getScheme());
276     }
277 
isDialerIntent(Intent intent)278     private boolean isDialerIntent(Intent intent) {
279         return Intent.ACTION_DIAL.equals(intent.getAction())
280                 || Intent.ACTION_CALL.equals(intent.getAction())
281                 || Intent.ACTION_CALL_PRIVILEGED.equals(intent.getAction())
282                 || Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction())
283                 || (isViewActionIntent(intent) && TEL_SCHEME.equals(intent.getScheme()));
284     }
285 
isViewActionIntent(Intent intent)286     private boolean isViewActionIntent(Intent intent) {
287         return Intent.ACTION_VIEW.equals(intent.getAction())
288                 && intent.hasCategory(Intent.CATEGORY_BROWSABLE);
289     }
290 
isTargetResolverOrChooserActivity(ActivityInfo activityInfo)291     private boolean isTargetResolverOrChooserActivity(ActivityInfo activityInfo) {
292         if (!"android".equals(activityInfo.packageName)) {
293             return false;
294         }
295         return ResolverActivity.class.getName().equals(activityInfo.name)
296             || ChooserActivity.class.getName().equals(activityInfo.name);
297     }
298 
299     /**
300      * Check whether the intent can be forwarded to target user. Return the intent used for
301      * forwarding if it can be forwarded, {@code null} otherwise.
302      */
canForward(Intent incomingIntent, int sourceUserId, int targetUserId, IPackageManager packageManager, ContentResolver contentResolver)303     static Intent canForward(Intent incomingIntent, int sourceUserId, int targetUserId,
304             IPackageManager packageManager, ContentResolver contentResolver)  {
305         Intent forwardIntent = new Intent(incomingIntent);
306         forwardIntent.addFlags(
307                 Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
308         sanitizeIntent(forwardIntent);
309 
310         Intent intentToCheck = forwardIntent;
311         if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) {
312             return null;
313         }
314         if (forwardIntent.getSelector() != null) {
315             intentToCheck = forwardIntent.getSelector();
316         }
317         String resolvedType = intentToCheck.resolveTypeIfNeeded(contentResolver);
318         sanitizeIntent(intentToCheck);
319         try {
320             if (packageManager.canForwardTo(
321                     intentToCheck, resolvedType, sourceUserId, targetUserId)) {
322                 return forwardIntent;
323             }
324         } catch (RemoteException e) {
325             Slog.e(TAG, "PackageManagerService is dead?");
326         }
327         return null;
328     }
329 
330     /**
331      * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is
332      * no managed profile.
333      *
334      * TODO: Remove the assumption that there is only one managed profile
335      * on the device.
336      */
getManagedProfile()337     private int getManagedProfile() {
338         List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
339         for (UserInfo userInfo : relatedUsers) {
340             if (userInfo.isManagedProfile()) return userInfo.id;
341         }
342         Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE
343                 + " has been called, but there is no managed profile");
344         return UserHandle.USER_NULL;
345     }
346 
347     /**
348      * Returns the userId of the profile parent or UserHandle.USER_NULL if there is
349      * no parent.
350      */
getProfileParent()351     private int getProfileParent() {
352         UserInfo parent = mInjector.getUserManager().getProfileParent(UserHandle.myUserId());
353         if (parent == null) {
354             Slog.wtf(TAG, FORWARD_INTENT_TO_PARENT
355                     + " has been called, but there is no parent");
356             return UserHandle.USER_NULL;
357         }
358         return parent.id;
359     }
360 
361     /**
362      * Sanitize the intent in place.
363      */
sanitizeIntent(Intent intent)364     private static void sanitizeIntent(Intent intent) {
365         // Apps should not be allowed to target a specific package/ component in the target user.
366         intent.setPackage(null);
367         intent.setComponent(null);
368     }
369 
getMetricsLogger()370     protected MetricsLogger getMetricsLogger() {
371         if (mMetricsLogger == null) {
372             mMetricsLogger = new MetricsLogger();
373         }
374         return mMetricsLogger;
375     }
376 
377     @VisibleForTesting
createInjector()378     protected Injector createInjector() {
379         return new InjectorImpl();
380     }
381 
382     private class InjectorImpl implements Injector {
383 
384         @Override
getIPackageManager()385         public IPackageManager getIPackageManager() {
386             return AppGlobals.getPackageManager();
387         }
388 
389         @Override
getUserManager()390         public UserManager getUserManager() {
391             return getSystemService(UserManager.class);
392         }
393 
394         @Override
getPackageManager()395         public PackageManager getPackageManager() {
396             return IntentForwarderActivity.this.getPackageManager();
397         }
398 
399         @Override
400         @Nullable
resolveActivityAsUser( Intent intent, int flags, int userId)401         public CompletableFuture<ResolveInfo> resolveActivityAsUser(
402                 Intent intent, int flags, int userId) {
403             return CompletableFuture.supplyAsync(
404                     () -> getPackageManager().resolveActivityAsUser(intent, flags, userId));
405         }
406 
407         @Override
showToast(int messageId, int duration)408         public void showToast(int messageId, int duration) {
409             Toast.makeText(IntentForwarderActivity.this, getString(messageId), duration).show();
410         }
411     }
412 
413     public interface Injector {
getIPackageManager()414         IPackageManager getIPackageManager();
415 
getUserManager()416         UserManager getUserManager();
417 
getPackageManager()418         PackageManager getPackageManager();
419 
resolveActivityAsUser(Intent intent, int flags, int userId)420         CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId);
421 
showToast(@tringRes int messageId, int duration)422         void showToast(@StringRes int messageId, int duration);
423     }
424 }
425