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