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