public static final String EXTRA_PIN_ITEM_REQUEST =
"android.content.pm.extra.PIN_ITEM_REQUEST";
- private Context mContext;
- private ILauncherApps mService;
- private PackageManager mPm;
+ private final Context mContext;
+ private final ILauncherApps mService;
+ private final PackageManager mPm;
+ private final UserManager mUserManager;
private List<CallbackMessageHandler> mCallbacks
= new ArrayList<CallbackMessageHandler>();
mContext = context;
mService = service;
mPm = context.getPackageManager();
+ mUserManager = context.getSystemService(UserManager.class);
}
/** @hide */
}
/**
+ * Show an error log on logcat, when the calling user is a managed profile, and the target
+ * user is different from the calling user, in order to help developers to detect it.
+ */
+ private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) {
+ if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) {
+ Log.e(TAG, "Accessing other profiles/users from managed profile is no longer allowed.");
+ }
+ }
+
+ /**
* Return a list of profiles that the caller can access via the {@link LauncherApps} APIs.
*
* <p>If the caller is running on a managed profile, it'll return only the current profile.
* Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
*/
public List<UserHandle> getProfiles() {
- final UserManager um = mContext.getSystemService(UserManager.class);
- if (um.isManagedProfile()) {
+ if (mUserManager.isManagedProfile()) {
// If it's a managed profile, only return the current profile.
final List result = new ArrayList(1);
result.add(android.os.Process.myUserHandle());
return result;
} else {
- return um.getUserProfiles();
+ return mUserManager.getUserProfiles();
}
}
* @return List of launchable activities. Can be an empty list but will not be null.
*/
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
packageName, user), user);
* @return An activity info object if there is a match.
*/
public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
ActivityInfo ai = mService.resolveActivity(mContext.getPackageName(),
intent.getComponent(), user);
*/
public void startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds,
Bundle opts) {
+ logErrorForInvalidProfileAccess(user);
if (DEBUG) {
Log.i(TAG, "StartMainActivity " + component + " " + user.getIdentifier());
}
*/
public void startAppDetailsActivity(ComponentName component, UserHandle user,
Rect sourceBounds, Bundle opts) {
+ logErrorForInvalidProfileAccess(user);
try {
mService.showAppDetailsAsUser(mContext.getPackageName(),
component, sourceBounds, opts, user);
*/
public List<LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String packageName,
@NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
return convertToActivityList(mService.getShortcutConfigActivities(
mContext.getPackageName(), packageName, user),
* @see Intent#ACTION_CREATE_SHORTCUT
* @see android.app.Activity#startIntentSenderForResult
*/
+ @Nullable
public IntentSender getShortcutConfigActivityIntent(@NonNull LauncherActivityInfo info) {
try {
return mService.getShortcutConfigActivityIntent(
* @return true if the package exists and is enabled.
*/
public boolean isPackageEnabled(String packageName, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
return mService.isPackageEnabled(mContext.getPackageName(), packageName, user);
} catch (RemoteException re) {
*/
public ApplicationInfo getApplicationInfo(String packageName, @ApplicationInfoFlags int flags,
UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
return mService.getApplicationInfo(mContext.getPackageName(), packageName, flags, user);
} catch (RemoteException re) {
* @return true if the activity exists and is enabled.
*/
public boolean isActivityEnabled(ComponentName component, UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
return mService.isActivityEnabled(mContext.getPackageName(), component, user);
} catch (RemoteException re) {
@Nullable
public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
@NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
return mService.getShortcuts(mContext.getPackageName(),
query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
*/
public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
@NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
try {
mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
} catch (RemoteException e) {
public void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
@NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+
startShortcut(packageName, shortcutId, sourceBounds, startActivityOptions,
user.getIdentifier());
}
import com.android.server.SystemService;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
}
}
- /**
- * Checks if the caller is in the same group as the userToCheck.
- */
- private void ensureInUserProfiles(
- String callingPackage, UserHandle userToCheck, String message) {
- ensureInUserProfiles(callingPackage, userToCheck.getIdentifier(), message);
+ /** See {@link #canAccessProfile(String, int, String)} */
+ private boolean canAccessProfile(
+ String callingPackage, UserHandle targetUser, String message) {
+ return canAccessProfile(callingPackage, targetUser.getIdentifier(), message);
}
- private void ensureInUserProfiles(String callingPackage, int targetUserId, String message) {
+ /**
+ * Checks if the calling user is in the same group as {@code targetUser}, and allowed
+ * to access it.
+ *
+ * @return TRUE if the calling user can access {@code targetUserId}. FALSE if not *but
+ * they're still in the same profile group*.
+ *
+ * @throws SecurityException if the calling user and {@code targetUser} are not in the same
+ * group.
+ */
+ private boolean canAccessProfile(String callingPackage, int targetUserId, String message) {
final int callingUserId = injectCallingUserId();
- if (targetUserId == callingUserId) return;
+ if (targetUserId == callingUserId) return true;
long ident = injectClearCallingIdentity();
try {
UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
if (callingUserInfo.isManagedProfile()) {
- // TODO: Make it SecurityException. See b/34650921
- // throw new SecurityException(message + " for another profile " + targetUserId);
-
- // TODO: Report caller package name.
Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile "
+ targetUserId + " from " + callingUserId);
+
+ return false;
}
UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
} finally {
injectRestoreCallingIdentity(ident);
}
+ return true;
}
@VisibleForTesting // We override it in unit tests
public ActivityInfo resolveActivity(
String callingPackage, ComponentName component, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(callingPackage, user, "Cannot resolve activity");
+ if (!canAccessProfile(callingPackage, user, "Cannot resolve activity")) {
+ return null;
+ }
if (!isUserEnabled(user)) {
return null;
}
private ParceledListSlice<ResolveInfo> queryActivitiesForUser(String callingPackage,
Intent intent, UserHandle user) {
- ensureInUserProfiles(callingPackage, user, "Cannot retrieve activities");
+ if (!canAccessProfile(callingPackage, user, "Cannot retrieve activities")) {
+ return null;
+ }
if (!isUserEnabled(user)) {
return null;
}
@Override
public IntentSender getShortcutConfigActivityIntent(String callingPackage,
ComponentName component, UserHandle user) throws RemoteException {
- ensureShortcutPermission(callingPackage, user);
+ ensureShortcutPermission(callingPackage);
+ if (!canAccessProfile(callingPackage, user, "Cannot check package")) {
+ return null;
+ }
Preconditions.checkNotNull(component);
Preconditions.checkArgument(isUserEnabled(user), "User not enabled");
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
final long identity = Binder.clearCallingIdentity();
try {
- return PendingIntent.getActivityAsUser(
+ final PendingIntent pi = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
- null, user)
- .getIntentSender();
+ null, user);
+ return pi == null ? null : pi.getIntentSender();
} finally {
Binder.restoreCallingIdentity(identity);
}
@Override
public boolean isPackageEnabled(String callingPackage, String packageName, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(callingPackage, user, "Cannot check package");
+ if (!canAccessProfile(callingPackage, user, "Cannot check package")) {
+ return false;
+ }
if (!isUserEnabled(user)) {
return false;
}
public ApplicationInfo getApplicationInfo(
String callingPackage, String packageName, int flags, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(callingPackage, user, "Cannot check package");
+ if (!canAccessProfile(callingPackage, user, "Cannot check package")) {
+ return null;
+ }
if (!isUserEnabled(user)) {
return null;
}
}
}
- private void ensureShortcutPermission(@NonNull String callingPackage, UserHandle user) {
- ensureShortcutPermission(callingPackage, user.getIdentifier());
- }
-
- private void ensureShortcutPermission(@NonNull String callingPackage, int userId) {
+ private void ensureShortcutPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
- ensureInUserProfiles(callingPackage, userId, "Cannot access shortcuts");
-
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
callingPackage)) {
throw new SecurityException("Caller can't access shortcut information");
@Override
public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
String packageName, List shortcutIds, ComponentName componentName, int flags,
- UserHandle user) {
- ensureShortcutPermission(callingPackage, user);
- if (!isUserEnabled(user)) {
- return new ParceledListSlice<>(new ArrayList(0));
+ UserHandle targetUser) {
+ ensureShortcutPermission(callingPackage);
+ if (!canAccessProfile(callingPackage, targetUser, "Cannot get shortcuts")
+ || !isUserEnabled(targetUser)) {
+ return new ParceledListSlice<>(Collections.EMPTY_LIST);
}
if (shortcutIds != null && packageName == null) {
throw new IllegalArgumentException(
return new ParceledListSlice<>((List<ShortcutInfo>)
mShortcutServiceInternal.getShortcuts(getCallingUserId(),
callingPackage, changedSince, packageName, shortcutIds,
- componentName, flags, user.getIdentifier()));
+ componentName, flags, targetUser.getIdentifier()));
}
@Override
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
- UserHandle user) {
- ensureShortcutPermission(callingPackage, user);
- if (!isUserEnabled(user)) {
+ UserHandle targetUser) {
+ ensureShortcutPermission(callingPackage);
+ if (!canAccessProfile(callingPackage, targetUser, "Cannot pin shortcuts")) {
+ return;
+ }
+ if (!isUserEnabled(targetUser)) {
throw new IllegalStateException("Cannot pin shortcuts for disabled profile "
- + user);
+ + targetUser);
}
mShortcutServiceInternal.pinShortcuts(getCallingUserId(),
- callingPackage, packageName, ids, user.getIdentifier());
+ callingPackage, packageName, ids, targetUser.getIdentifier());
}
@Override
public int getShortcutIconResId(String callingPackage, String packageName, String id,
- int userId) {
- ensureShortcutPermission(callingPackage, userId);
- if (!isUserEnabled(userId)) {
+ int targetUserId) {
+ ensureShortcutPermission(callingPackage);
+ if (!canAccessProfile(callingPackage, targetUserId, "Cannot access shortcuts")) {
+ return 0;
+ }
+ if (!isUserEnabled(targetUserId)) {
return 0;
}
return mShortcutServiceInternal.getShortcutIconResId(getCallingUserId(),
- callingPackage, packageName, id, userId);
+ callingPackage, packageName, id, targetUserId);
}
@Override
public ParcelFileDescriptor getShortcutIconFd(String callingPackage,
- String packageName, String id, int userId) {
- ensureShortcutPermission(callingPackage, userId);
- if (!isUserEnabled(userId)) {
+ String packageName, String id, int targetUserId) {
+ ensureShortcutPermission(callingPackage);
+ if (!canAccessProfile(callingPackage, targetUserId, "Cannot access shortcuts")) {
+ return null;
+ }
+ if (!isUserEnabled(targetUserId)) {
return null;
}
return mShortcutServiceInternal.getShortcutIconFd(getCallingUserId(),
- callingPackage, packageName, id, userId);
+ callingPackage, packageName, id, targetUserId);
}
@Override
@Override
public boolean startShortcut(String callingPackage, String packageName, String shortcutId,
- Rect sourceBounds, Bundle startActivityOptions, int userId) {
+ Rect sourceBounds, Bundle startActivityOptions, int targetUserId) {
verifyCallingPackage(callingPackage);
- ensureInUserProfiles(callingPackage, userId, "Cannot start activity");
-
- if (!isUserEnabled(userId)) {
+ if (!canAccessProfile(callingPackage, targetUserId, "Cannot start activity")) {
+ return false;
+ }
+ if (!canAccessProfile(callingPackage, targetUserId, "Cannot access shortcuts")) {
+ return false;
+ }
+ if (!isUserEnabled(targetUserId)) {
throw new IllegalStateException("Cannot start a shortcut for disabled profile "
- + userId);
+ + targetUserId);
}
// Even without the permission, pinned shortcuts are always launchable.
if (!mShortcutServiceInternal.isPinnedByCaller(getCallingUserId(),
- callingPackage, packageName, shortcutId, userId)) {
- ensureShortcutPermission(callingPackage, userId);
+ callingPackage, packageName, shortcutId, targetUserId)) {
+ ensureShortcutPermission(callingPackage);
}
final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
- getCallingUserId(), callingPackage, packageName, shortcutId, userId);
+ getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId);
if (intents == null || intents.length == 0) {
return false;
}
intents[0].setSourceBounds(sourceBounds);
return startShortcutIntentsAsPublisher(
- intents, packageName, startActivityOptions, userId);
+ intents, packageName, startActivityOptions, targetUserId);
}
private boolean startShortcutIntentsAsPublisher(@NonNull Intent[] intents,
public boolean isActivityEnabled(
String callingPackage, ComponentName component, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(callingPackage , user, "Cannot check component");
+ if (!canAccessProfile(callingPackage , user, "Cannot check component")) {
+ return false;
+ }
if (!isUserEnabled(user)) {
return false;
}
public void startActivityAsUser(String callingPackage,
ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
- ensureInUserProfiles(callingPackage, user, "Cannot start activity");
+ if (!canAccessProfile(callingPackage, user, "Cannot start activity")) {
+ return;
+ }
if (!isUserEnabled(user)) {
throw new IllegalStateException("Cannot start activity for disabled profile " + user);
}
@Override
public void showAppDetailsAsUser(String callingPackage, ComponentName component,
Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException {
- ensureInUserProfiles(callingPackage, user, "Cannot show app details");
+ if (!canAccessProfile(callingPackage, user, "Cannot show app details")) {
+ return;
+ }
if (!isUserEnabled(user)) {
throw new IllegalStateException("Cannot show app details for disabled profile "
+ user);
private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser,
String debugMsg) {
if (user.getIdentifier() == listeningUser.getIdentifier()) {
- if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg);
+ if (DEBUG) Log.d(TAG, "Delivering msg to same user: " + debugMsg);
return true;
}
+ if (mUm.isManagedProfile(listeningUser.getIdentifier())) {
+ if (DEBUG) Log.d(TAG, "Managed profile can't see other profiles: " + debugMsg);
+ return false;
+ }
long ident = injectClearCallingIdentity();
try {
UserInfo userInfo = mUm.getUserInfo(user.getIdentifier());
protected static final int USER_0 = UserHandle.USER_SYSTEM;
protected static final int USER_10 = 10;
protected static final int USER_11 = 11;
- protected static final int USER_P0 = 20; // profile of user 0
+ protected static final int USER_P0 = 20; // profile of user 0 (MANAGED_PROFILE *not* set)
+ protected static final int USER_P1 = 21; // another profile of user 0 (MANAGED_PROFILE set)
protected static final UserHandle HANDLE_USER_0 = UserHandle.of(USER_0);
protected static final UserHandle HANDLE_USER_10 = UserHandle.of(USER_10);
protected static final UserHandle HANDLE_USER_11 = UserHandle.of(USER_11);
protected static final UserHandle HANDLE_USER_P0 = UserHandle.of(USER_P0);
+ protected static final UserHandle HANDLE_USER_P1 = UserHandle.of(USER_P1);
protected static final UserInfo USER_INFO_0 = withProfileGroupId(
new UserInfo(USER_0, "user0",
protected static final UserInfo USER_INFO_P0 = withProfileGroupId(
new UserInfo(USER_P0, "userP0", UserInfo.FLAG_INITIALIZED), 0);
+ protected static final UserInfo USER_INFO_P1 = withProfileGroupId(
+ new UserInfo(USER_P1, "userP1",
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE), 0);
+
protected BiPredicate<String, Integer> mDefaultLauncherChecker =
(callingPackage, userId) ->
LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
mUserInfos.put(USER_10, USER_INFO_10);
mUserInfos.put(USER_11, USER_INFO_11);
mUserInfos.put(USER_P0, USER_INFO_P0);
+ mUserInfos.put(USER_P1, USER_INFO_P1);
// Set up isUserRunning and isUserUnlocked.
when(mMockUserManager.isUserRunning(anyInt())).thenAnswer(new AnswerWithSystemCheck<>(
assertNotNull(parent);
return parent;
}));
+ when(mMockUserManager.isManagedProfile(anyInt()))
+ .thenAnswer(new AnswerWithSystemCheck<>(inv -> {
+ final int userId = (Integer) inv.getArguments()[0];
+ final UserInfo ui = mUserInfos.get(userId);
+ assertNotNull(ui);
+ return ui.isManagedProfile();
+ }));
when(mMockActivityManagerInternal.getUidProcessState(anyInt())).thenReturn(
ActivityManager.PROCESS_STATE_CACHED_EMPTY);
mRunningUsers.put(USER_10, false);
mRunningUsers.put(USER_11, false);
mRunningUsers.put(USER_P0, true);
+ mRunningUsers.put(USER_P1, true);
// Unlock all users by default.
mUnlockedUsers.put(USER_0, true);
mUnlockedUsers.put(USER_10, true);
mUnlockedUsers.put(USER_11, true);
mUnlockedUsers.put(USER_P0, true);
+ mUnlockedUsers.put(USER_P1, true);
// Set up resources
setUpAppResources();
list("s1", "s2", "s3"), HANDLE_USER_10);
});
+ // First, make sure managed profile can't see other profiles.
+ runWithCaller(LAUNCHER_1, USER_P1, () -> {
+
+ final ShortcutQuery q = new ShortcutQuery().setQueryFlags(
+ ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_PINNED
+ | ShortcutQuery.FLAG_MATCH_MANIFEST);
+
+ // No shortcuts are visible.
+ assertWith(mLauncherApps.getShortcuts(q, HANDLE_USER_0)).isEmpty();
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
+
+ // Should have no effects.
+ assertWith(mLauncherApps.getShortcuts(q, HANDLE_USER_0)).isEmpty();
+
+ assertShortcutNotLaunched(CALLING_PACKAGE_1, "s1", USER_0);
+ });
+
// Cross profile pinning.
final int PIN_AND_DYNAMIC = ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC;
final LauncherApps.Callback c0_4 = mock(LauncherApps.Callback.class);
final LauncherApps.Callback cP0_1 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback cP1_1 = mock(LauncherApps.Callback.class);
final LauncherApps.Callback c10_1 = mock(LauncherApps.Callback.class);
final LauncherApps.Callback c10_2 = mock(LauncherApps.Callback.class);
final LauncherApps.Callback c11_1 = mock(LauncherApps.Callback.class);
return LAUNCHER_2.equals(pkg);
case USER_P0:
return LAUNCHER_1.equals(pkg);
+ case USER_P1:
+ return LAUNCHER_1.equals(pkg);
case USER_10:
return LAUNCHER_1.equals(pkg);
case USER_11:
runWithCaller(LAUNCHER_3, USER_0, () -> mLauncherApps.registerCallback(c0_3, h));
runWithCaller(LAUNCHER_4, USER_0, () -> mLauncherApps.registerCallback(c0_4, h));
runWithCaller(LAUNCHER_1, USER_P0, () -> mLauncherApps.registerCallback(cP0_1, h));
+ runWithCaller(LAUNCHER_1, USER_P1, () -> mLauncherApps.registerCallback(cP1_1, h));
runWithCaller(LAUNCHER_1, USER_10, () -> mLauncherApps.registerCallback(c10_1, h));
runWithCaller(LAUNCHER_2, USER_10, () -> mLauncherApps.registerCallback(c10_2, h));
runWithCaller(LAUNCHER_1, USER_11, () -> mLauncherApps.registerCallback(c11_1, h));
assertCallbackNotReceived(c11_1);
assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3");
assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
+ assertCallbackNotReceived(cP1_1);
// User 0, different package.
assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_3, "s1", "s2", "s3", "s4");
assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_3,
"s1", "s2", "s3", "s4", "s5", "s6");
+ assertCallbackNotReceived(cP1_1);
// Work profile.
resetAll(all);
assertCallbackNotReceived(c11_1);
assertCallbackReceived(c0_2, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s5");
assertCallbackReceived(cP0_1, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
+ assertCallbackNotReceived(cP1_1);
// Normal secondary user.
mRunningUsers.put(USER_10, true);
assertCallbackNotReceived(c11_1);
assertCallbackReceived(c10_1, HANDLE_USER_10, CALLING_PACKAGE_1,
"x1", "x2", "x3", "x4", "x5");
+ assertCallbackNotReceived(cP1_1);
}
// === Test for persisting ===