1 /* 2 * Copyright (C) 2017 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 package android.content.pm; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.annotation.RequiresPermission; 21 import android.annotation.SystemApi; 22 import android.annotation.TestApi; 23 import android.app.Activity; 24 import android.app.AppOpsManager.Mode; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.res.Resources; 29 import android.graphics.drawable.Drawable; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.RemoteException; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.provider.Settings; 36 37 import com.android.internal.R; 38 import com.android.internal.util.UserIcons; 39 40 import java.util.Collection; 41 import java.util.List; 42 import java.util.Set; 43 import java.util.stream.Collectors; 44 45 /** 46 * Class for handling cross profile operations. Apps can use this class to interact with its 47 * instance in any profile that is in {@link #getTargetUserProfiles()}. For example, app can 48 * use this class to start its main activity in managed profile. 49 */ 50 public class CrossProfileApps { 51 52 /** 53 * Broadcast signalling that the receiving app's permission to interact across profiles has 54 * changed. This includes the user, admin, or OEM changing their consent such that the 55 * permission for the app to interact across profiles has changed. 56 * 57 * <p>This broadcast is not sent when other circumstances result in a change to being able to 58 * interact across profiles in practice, such as the profile being turned off or removed, apps 59 * being uninstalled, etc. The methods {@link #canInteractAcrossProfiles()} and {@link 60 * #canRequestInteractAcrossProfiles()} can be used by apps prior to attempting to interact 61 * across profiles or attempting to request user consent to interact across profiles. 62 * 63 * <p>Apps that have set the {@code android:crossProfile} manifest attribute to {@code true} 64 * can receive this broadcast in manifest broadcast receivers. Otherwise, it can only be 65 * received by dynamically-registered broadcast receivers. 66 */ 67 public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED = 68 "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"; 69 70 private final Context mContext; 71 private final ICrossProfileApps mService; 72 private final UserManager mUserManager; 73 private final Resources mResources; 74 75 /** @hide */ CrossProfileApps(Context context, ICrossProfileApps service)76 public CrossProfileApps(Context context, ICrossProfileApps service) { 77 mContext = context; 78 mService = service; 79 mUserManager = context.getSystemService(UserManager.class); 80 mResources = context.getResources(); 81 } 82 83 /** 84 * Starts the specified main activity of the caller package in the specified profile. 85 * 86 * @param component The ComponentName of the activity to launch, it must be exported and has 87 * action {@link android.content.Intent#ACTION_MAIN}, category 88 * {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will 89 * be thrown. 90 * @param targetUser The UserHandle of the profile, must be one of the users returned by 91 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will 92 * be thrown. 93 */ startMainActivity(@onNull ComponentName component, @NonNull UserHandle targetUser)94 public void startMainActivity(@NonNull ComponentName component, 95 @NonNull UserHandle targetUser) { 96 try { 97 mService.startActivityAsUser( 98 mContext.getIApplicationThread(), 99 mContext.getPackageName(), 100 mContext.getAttributionTag(), 101 component, 102 targetUser.getIdentifier(), 103 true); 104 } catch (RemoteException ex) { 105 throw ex.rethrowFromSystemServer(); 106 } 107 } 108 109 /** 110 * Starts the specified activity of the caller package in the specified profile. 111 * 112 * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}, 113 * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}, or {@code 114 * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. Both the caller and 115 * target user profiles must be in the same profile group. The target user must be a valid user 116 * returned from {@link #getTargetUserProfiles()}. 117 * 118 * @param intent The intent to launch. A component in the caller package must be specified. 119 * @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by 120 * {@link #getTargetUserProfiles()} if different to the calling user, otherwise a 121 * {@link SecurityException} will be thrown. 122 * @param callingActivity The activity to start the new activity from for the purposes of 123 * passing back any result and deciding which task the new activity should belong to. If 124 * {@code null}, the activity will always be started in a new task and no result will be 125 * returned. 126 */ 127 @RequiresPermission(anyOf = { 128 android.Manifest.permission.INTERACT_ACROSS_PROFILES, 129 android.Manifest.permission.INTERACT_ACROSS_USERS}) startActivity( @onNull Intent intent, @NonNull UserHandle targetUser, @Nullable Activity callingActivity)130 public void startActivity( 131 @NonNull Intent intent, 132 @NonNull UserHandle targetUser, 133 @Nullable Activity callingActivity) { 134 startActivity(intent, targetUser, callingActivity, /* options= */ null); 135 } 136 137 /** 138 * Starts the specified activity of the caller package in the specified profile. 139 * 140 * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}, 141 * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}, or {@code 142 * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. Both the caller and 143 * target user profiles must be in the same profile group. The target user must be a valid user 144 * returned from {@link #getTargetUserProfiles()}. 145 * 146 * @param intent The intent to launch. A component in the caller package must be specified. 147 * @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by 148 * {@link #getTargetUserProfiles()} if different to the calling user, otherwise a 149 * {@link SecurityException} will be thrown. 150 * @param callingActivity The activity to start the new activity from for the purposes of 151 * passing back any result and deciding which task the new activity should belong to. If 152 * {@code null}, the activity will always be started in a new task and no result will be 153 * returned. 154 * @param options The activity options or {@code null}. See {@link android.app.ActivityOptions}. 155 */ 156 @RequiresPermission(anyOf = { 157 android.Manifest.permission.INTERACT_ACROSS_PROFILES, 158 android.Manifest.permission.INTERACT_ACROSS_USERS}) startActivity( @onNull Intent intent, @NonNull UserHandle targetUser, @Nullable Activity callingActivity, @Nullable Bundle options)159 public void startActivity( 160 @NonNull Intent intent, 161 @NonNull UserHandle targetUser, 162 @Nullable Activity callingActivity, 163 @Nullable Bundle options) { 164 try { 165 mService.startActivityAsUserByIntent( 166 mContext.getIApplicationThread(), 167 mContext.getPackageName(), 168 mContext.getAttributionTag(), 169 intent, 170 targetUser.getIdentifier(), 171 callingActivity != null ? callingActivity.getActivityToken() : null, 172 options); 173 } catch (RemoteException ex) { 174 throw ex.rethrowFromSystemServer(); 175 } 176 } 177 178 /** 179 * Starts the specified activity of the caller package in the specified profile. Unlike 180 * {@link #startMainActivity}, this can start any activity of the caller package, not just 181 * the main activity. 182 * The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} 183 * permission and both the caller and target user profiles must be in the same profile group. 184 * 185 * @param component The ComponentName of the activity to launch. It must be exported. 186 * @param targetUser The UserHandle of the profile, must be one of the users returned by 187 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will 188 * be thrown. 189 * @hide 190 */ 191 @SystemApi 192 @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) startActivity(@onNull ComponentName component, @NonNull UserHandle targetUser)193 public void startActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) { 194 try { 195 mService.startActivityAsUser(mContext.getIApplicationThread(), 196 mContext.getPackageName(), mContext.getAttributionTag(), component, 197 targetUser.getIdentifier(), false); 198 } catch (RemoteException ex) { 199 throw ex.rethrowFromSystemServer(); 200 } 201 } 202 203 /** 204 * Return a list of user profiles that that the caller can use when calling other APIs in this 205 * class. 206 * <p> 207 * A user profile would be considered as a valid target user profile, provided that: 208 * <ul> 209 * <li>It gets caller app installed</li> 210 * <li>It is not equal to the calling user</li> 211 * <li>It is in the same profile group of calling user profile</li> 212 * <li>It is enabled</li> 213 * </ul> 214 * 215 * @see UserManager#getUserProfiles() 216 */ getTargetUserProfiles()217 public @NonNull List<UserHandle> getTargetUserProfiles() { 218 try { 219 return mService.getTargetUserProfiles(mContext.getPackageName()); 220 } catch (RemoteException ex) { 221 throw ex.rethrowFromSystemServer(); 222 } 223 } 224 225 /** 226 * Return a label that calling app can show to user for the semantic of profile switching -- 227 * launching its own activity in specified user profile. For example, it may return 228 * "Switch to work" if the given user handle is the managed profile one. 229 * 230 * @param userHandle The UserHandle of the target profile, must be one of the users returned by 231 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will 232 * be thrown. 233 * @return a label that calling app can show user for the semantic of launching its own 234 * activity in the specified user profile. 235 * 236 * @see #startMainActivity(ComponentName, UserHandle) 237 */ getProfileSwitchingLabel(@onNull UserHandle userHandle)238 public @NonNull CharSequence getProfileSwitchingLabel(@NonNull UserHandle userHandle) { 239 verifyCanAccessUser(userHandle); 240 241 final int stringRes = mUserManager.isManagedProfile(userHandle.getIdentifier()) 242 ? R.string.managed_profile_label 243 : R.string.user_owner_label; 244 return mResources.getString(stringRes); 245 } 246 247 /** 248 * Return a drawable that calling app can show to user for the semantic of profile switching -- 249 * launching its own activity in specified user profile. For example, it may return a briefcase 250 * icon if the given user handle is the managed profile one. 251 * 252 * @param userHandle The UserHandle of the target profile, must be one of the users returned by 253 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will 254 * be thrown. 255 * @return an icon that calling app can show user for the semantic of launching its own 256 * activity in specified user profile. 257 * 258 * @see #startMainActivity(ComponentName, UserHandle) 259 */ getProfileSwitchingIconDrawable(@onNull UserHandle userHandle)260 public @NonNull Drawable getProfileSwitchingIconDrawable(@NonNull UserHandle userHandle) { 261 verifyCanAccessUser(userHandle); 262 263 final boolean isManagedProfile = 264 mUserManager.isManagedProfile(userHandle.getIdentifier()); 265 if (isManagedProfile) { 266 return mResources.getDrawable(R.drawable.ic_corp_badge, null); 267 } else { 268 return UserIcons.getDefaultUserIcon( 269 mResources, UserHandle.USER_SYSTEM, true /* light */); 270 } 271 } 272 273 /** 274 * Returns whether the calling package can request to navigate the user to 275 * the relevant settings page to request user consent to interact across profiles. 276 * 277 * <p>If {@code true}, the navigation intent can be obtained via {@link 278 * #createRequestInteractAcrossProfilesIntent()}. The package can then listen to {@link 279 * #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts. 280 * 281 * <p>Specifically, returns whether the following are all true: 282 * <ul> 283 * <li>{@code UserManager#getEnabledProfileIds(int)} returns at least one other profile for the 284 * calling user.</li> 285 * <li>The calling app has requested 286 * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.</li> 287 * <li>The calling app is not a profile owner within the profile group of the calling user.</li> 288 * </ul> 289 * 290 * <p>Note that in order for the user to be able to grant the consent, the requesting package 291 * must be allowlisted by the admin or the OEM and installed in the other profile. If this is 292 * not the case the user will be shown a message explaining why they can't grant the consent. 293 * 294 * <p>Note that user consent could already be granted if given a return value of {@code true}. 295 * The package's current ability to interact across profiles can be checked with {@link 296 * #canInteractAcrossProfiles()}. 297 * 298 * @return true if the calling package can request to interact across profiles. 299 */ canRequestInteractAcrossProfiles()300 public boolean canRequestInteractAcrossProfiles() { 301 try { 302 return mService.canRequestInteractAcrossProfiles(mContext.getPackageName()); 303 } catch (RemoteException ex) { 304 throw ex.rethrowFromSystemServer(); 305 } 306 } 307 308 /** 309 * Returns whether the calling package can interact across profiles. 310 311 * <p>Specifically, returns whether the following are all true: 312 * <ul> 313 * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> 314 * <li>The user has previously consented to cross-profile communication for the calling 315 * package.</li> 316 * <li>The calling package has either been allowlisted by default by the OEM or has been 317 * explicitly allowlisted by the admin via 318 * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}. 319 * </li> 320 * </ul> 321 * 322 * <p>If {@code false}, the package's current ability to request user consent to interact across 323 * profiles can be checked with {@link #canRequestInteractAcrossProfiles()}. If {@code true}, 324 * user consent can be obtained via {@link #createRequestInteractAcrossProfilesIntent()}. The 325 * package can then listen to {@link #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts. 326 * 327 * @return true if the calling package can interact across profiles. 328 * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the 329 * calling UID. 330 */ canInteractAcrossProfiles()331 public boolean canInteractAcrossProfiles() { 332 try { 333 return mService.canInteractAcrossProfiles(mContext.getPackageName()); 334 } catch (RemoteException ex) { 335 throw ex.rethrowFromSystemServer(); 336 } 337 } 338 339 /** 340 * Returns an {@link Intent} to open the settings page that allows the user to decide whether 341 * the calling app can interact across profiles. 342 * 343 * <p>The method {@link #canRequestInteractAcrossProfiles()} must be returning {@code true}. 344 * 345 * <p>Note that the user may already have given consent and the app may already be able to 346 * interact across profiles, even if {@link #canRequestInteractAcrossProfiles()} is {@code 347 * true}. The current ability to interact across profiles is given by {@link 348 * #canInteractAcrossProfiles()}. 349 * 350 * @return an {@link Intent} to open the settings page that allows the user to decide whether 351 * the app can interact across profiles 352 * 353 * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the 354 * calling UID, or {@link #canRequestInteractAcrossProfiles()} is {@code false}. 355 */ createRequestInteractAcrossProfilesIntent()356 public @NonNull Intent createRequestInteractAcrossProfilesIntent() { 357 if (!canRequestInteractAcrossProfiles()) { 358 throw new SecurityException( 359 "The calling package can not request to interact across profiles."); 360 } 361 final Intent settingsIntent = new Intent(); 362 settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS); 363 final Uri packageUri = Uri.parse("package:" + mContext.getPackageName()); 364 settingsIntent.setData(packageUri); 365 return settingsIntent; 366 } 367 368 /** 369 * Sets the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} that is 370 * configurable by users in Settings. This configures it for the profile group of the calling 371 * package. 372 * 373 * <p>Before calling, check {@link #canConfigureInteractAcrossProfiles(String)} and do not call 374 * if it is {@code false}. If presenting a user interface, do not allow the user to configure 375 * the app-op in that case. 376 * 377 * <p>The underlying app-op {@link android.app.AppOpsManager#OP_INTERACT_ACROSS_PROFILES} should 378 * never be set directly. This method ensures that the app-op is kept in sync for the app across 379 * each user in the profile group and that those apps are sent a broadcast when their ability to 380 * interact across profiles changes. 381 * 382 * <p>This method should be used directly whenever a user's action results in a change in an 383 * app's ability to interact across profiles, as defined by the return value of {@link 384 * #canInteractAcrossProfiles()}. This includes user consent changes in Settings or during 385 * provisioning. 386 * 387 * <p>If other changes could have affected the app's ability to interact across profiles, as 388 * defined by the return value of {@link #canInteractAcrossProfiles()}, such as changes to the 389 * admin or OEM consent whitelists, then {@link #resetInteractAcrossProfilesAppOps(Collection, 390 * Set)} should be used. 391 * 392 * <p>If the caller does not have the {@link android.Manifest.permission 393 * #CONFIGURE_INTERACT_ACROSS_PROFILES} permission, then they must have the permissions that 394 * would have been required to call {@link android.app.AppOpsManager#setMode(int, int, String, 395 * int)}, which includes {@link android.Manifest.permission#MANAGE_APP_OPS_MODES}. 396 * 397 * <p>Also requires either {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or {@link 398 * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. 399 * 400 * @hide 401 */ 402 @RequiresPermission( 403 allOf={android.Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES, 404 android.Manifest.permission.INTERACT_ACROSS_USERS}) setInteractAcrossProfilesAppOp(@onNull String packageName, @Mode int newMode)405 public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) { 406 try { 407 mService.setInteractAcrossProfilesAppOp(packageName, newMode); 408 } catch (RemoteException ex) { 409 throw ex.rethrowFromSystemServer(); 410 } 411 } 412 413 /** 414 * Returns whether the given package can have its ability to interact across profiles configured 415 * by the user. This means that every other condition to interact across profiles has been set. 416 * 417 * <p>This differs from {@link #canRequestInteractAcrossProfiles()} since it will not return 418 * {@code false} simply when the target profile is disabled. 419 * 420 * @hide 421 */ 422 @TestApi canConfigureInteractAcrossProfiles(@onNull String packageName)423 public boolean canConfigureInteractAcrossProfiles(@NonNull String packageName) { 424 try { 425 return mService.canConfigureInteractAcrossProfiles(packageName); 426 } catch (RemoteException ex) { 427 throw ex.rethrowFromSystemServer(); 428 } 429 } 430 431 /** 432 * Returns {@code true} if the given package has requested 433 * {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} and the user has at least one 434 * other profile in the same profile group. 435 * 436 * <p>This differs from {@link #canConfigureInteractAcrossProfiles(String)} since it will 437 * not return {@code false} if the app is not allowlisted or not installed in the other profile. 438 * 439 * <p>Note that platform-signed apps that are automatically granted the permission and are not 440 * allowlisted by the OEM will not be included in this list. 441 * 442 * @hide 443 */ canUserAttemptToConfigureInteractAcrossProfiles(String packageName)444 public boolean canUserAttemptToConfigureInteractAcrossProfiles(String packageName) { 445 try { 446 return mService.canUserAttemptToConfigureInteractAcrossProfiles(packageName); 447 } catch (RemoteException ex) { 448 throw ex.rethrowFromSystemServer(); 449 } 450 } 451 /** 452 * For each of the packages defined in {@code previousCrossProfilePackages} but not included in 453 * {@code newCrossProfilePackages}, resets the app-op for {@link android.Manifest.permission 454 * #INTERACT_ACROSS_PROFILES} back to its default value if it can no longer be configured by 455 * users in Settings, as defined by {@link #canConfigureInteractAcrossProfiles(String)}. 456 * 457 * <p>This method should be used whenever an app's ability to interact across profiles could 458 * have changed as a result of non-user actions, such as changes to admin or OEM consent 459 * whitelists. 460 * 461 * <p>If the caller does not have the {@link android.Manifest.permission 462 * #CONFIGURE_INTERACT_ACROSS_PROFILES} permission, then they must have the permissions that 463 * would have been required to call {@link android.app.AppOpsManager#setMode(int, int, String, 464 * int)}, which includes {@link android.Manifest.permission#MANAGE_APP_OPS_MODES}. 465 * 466 * <p>Also requires either {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or {@link 467 * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. 468 * 469 * @hide 470 */ 471 @RequiresPermission( 472 allOf={android.Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES, 473 android.Manifest.permission.INTERACT_ACROSS_USERS}) resetInteractAcrossProfilesAppOps( @onNull Collection<String> previousCrossProfilePackages, @NonNull Set<String> newCrossProfilePackages)474 public void resetInteractAcrossProfilesAppOps( 475 @NonNull Collection<String> previousCrossProfilePackages, 476 @NonNull Set<String> newCrossProfilePackages) { 477 if (previousCrossProfilePackages.isEmpty()) { 478 return; 479 } 480 final List<String> unsetCrossProfilePackages = 481 previousCrossProfilePackages.stream() 482 .filter(packageName -> !newCrossProfilePackages.contains(packageName)) 483 .collect(Collectors.toList()); 484 if (unsetCrossProfilePackages.isEmpty()) { 485 return; 486 } 487 try { 488 mService.resetInteractAcrossProfilesAppOps(unsetCrossProfilePackages); 489 } catch (RemoteException ex) { 490 throw ex.rethrowFromSystemServer(); 491 } 492 } 493 494 /** 495 * Clears the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} back to 496 * its default value for every package on the device. 497 * 498 * <p>This method can be used to ensure that app-op state is not left around on existing users 499 * for previously-configured profiles. 500 * 501 * <p>If the caller does not have the {@link android.Manifest.permission 502 * #CONFIGURE_INTERACT_ACROSS_PROFILES} permission, then they must have the permissions that 503 * would have been required to call {@link android.app.AppOpsManager#setMode(int, int, String, 504 * int)}, which includes {@link android.Manifest.permission#MANAGE_APP_OPS_MODES}. 505 * 506 * <p>Also requires either {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or {@link 507 * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. 508 * 509 * @hide 510 */ 511 @RequiresPermission( 512 allOf={android.Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES, 513 android.Manifest.permission.INTERACT_ACROSS_USERS}) clearInteractAcrossProfilesAppOps()514 public void clearInteractAcrossProfilesAppOps() { 515 try { 516 mService.clearInteractAcrossProfilesAppOps(); 517 } catch (RemoteException ex) { 518 throw ex.rethrowFromSystemServer(); 519 } 520 } 521 verifyCanAccessUser(UserHandle userHandle)522 private void verifyCanAccessUser(UserHandle userHandle) { 523 if (!getTargetUserProfiles().contains(userHandle)) { 524 throw new SecurityException("Not allowed to access " + userHandle); 525 } 526 } 527 } 528