1 /* 2 * Copyright (C) 2022 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.intentresolver; 18 19 import android.annotation.Nullable; 20 import android.app.Activity; 21 import android.app.ActivityManager; 22 import android.os.UserHandle; 23 import android.os.UserManager; 24 25 import androidx.annotation.VisibleForTesting; 26 27 /** 28 * Helper class to precompute the (immutable) designations of various user handles in the system 29 * that may contribute to the current Sharesheet session. 30 */ 31 public final class AnnotatedUserHandles { 32 /** The user id of the app that started the share activity. */ 33 public final int userIdOfCallingApp; 34 35 /** 36 * The {@link UserHandle} that launched Sharesheet. 37 * TODO: I believe this would always be the handle corresponding to {@code userIdOfCallingApp} 38 * except possibly if the caller used {@link Activity#startActivityAsUser()} to launch 39 * Sharesheet as a different user than they themselves were running as. Verify and document. 40 */ 41 public final UserHandle userHandleSharesheetLaunchedAs; 42 43 /** 44 * The {@link UserHandle} that owns the "personal tab" in a tabbed share UI (or the *only* 'tab' 45 * in a non-tabbed UI). 46 * 47 * This is never a work or clone user, but may either be the root user (0) or a "secondary" 48 * multi-user profile (i.e., one that's not root, work, nor clone). This is a "secondary" 49 * profile only when that user is the active "foreground" user. 50 * 51 * In the current implementation, we can assert that this is the root user (0) any time we 52 * display a tabbed UI (i.e., any time `workProfileUserHandle` is non-null), or any time that we 53 * have a clone profile. This note is only provided for informational purposes; clients should 54 * avoid making any reliances on that assumption. 55 */ 56 public final UserHandle personalProfileUserHandle; 57 58 /** 59 * The {@link UserHandle} that owns the "work tab" in a tabbed share UI. This is (an arbitrary) 60 * one of the "managed" profiles associated with {@link personalProfileUserHandle}. 61 */ 62 @Nullable 63 public final UserHandle workProfileUserHandle; 64 65 /** 66 * The {@link UserHandle} of the clone profile belonging to {@link personalProfileUserHandle}. 67 */ 68 @Nullable 69 public final UserHandle cloneProfileUserHandle; 70 71 /** 72 * The "tab owner" user handle (i.e., either {@link personalProfileUserHandle} or 73 * {@link workProfileUserHandle}) that either matches or owns the profile of the 74 * {@link userHandleSharesheetLaunchedAs}. 75 * 76 * In the current implementation, we can assert that this is the same as 77 * `userHandleSharesheetLaunchedAs` except when the latter is the clone profile; then this is 78 * the "personal" profile owning that clone profile (which we currently know must belong to 79 * user 0, but clients should avoid making any reliances on that assumption). 80 */ 81 public final UserHandle tabOwnerUserHandleForLaunch; 82 83 /** Compute all handle designations for a new Sharesheet session in the specified activity. */ forShareActivity(Activity shareActivity)84 public static AnnotatedUserHandles forShareActivity(Activity shareActivity) { 85 // TODO: consider integrating logic for `ResolverActivity.EXTRA_CALLING_USER`? 86 UserHandle userHandleSharesheetLaunchedAs = UserHandle.of(UserHandle.myUserId()); 87 88 // ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work 89 // profile is active, we always make the personal tab from the foreground user. 90 // Outside profiles, current foreground user is potentially the same as the sharesheet 91 // process's user (UserHandle.myUserId()), so we continue to create personal tab with the 92 // current foreground user. 93 UserHandle personalProfileUserHandle = UserHandle.of(ActivityManager.getCurrentUser()); 94 95 UserManager userManager = shareActivity.getSystemService(UserManager.class); 96 97 return newBuilder() 98 .setUserIdOfCallingApp(shareActivity.getLaunchedFromUid()) 99 .setUserHandleSharesheetLaunchedAs(userHandleSharesheetLaunchedAs) 100 .setPersonalProfileUserHandle(personalProfileUserHandle) 101 .setWorkProfileUserHandle( 102 getWorkProfileForUser(userManager, personalProfileUserHandle)) 103 .setCloneProfileUserHandle( 104 getCloneProfileForUser(userManager, personalProfileUserHandle)) 105 .build(); 106 } 107 newBuilder()108 @VisibleForTesting static Builder newBuilder() { 109 return new Builder(); 110 } 111 112 /** 113 * Returns the {@link UserHandle} to use when querying resolutions for intents in a 114 * {@link ResolverListController} configured for the provided {@code userHandle}. 115 */ getQueryIntentsUser(UserHandle userHandle)116 public UserHandle getQueryIntentsUser(UserHandle userHandle) { 117 // In case launching app is in clonedProfile, and we are building the personal tab, intent 118 // resolution will be attempted as clonedUser instead of user 0. This is because intent 119 // resolution from user 0 and clonedUser is not guaranteed to return same results. 120 // We do not care about the case when personal adapter is started with non-root user 121 // (secondary user case), as clone profile is guaranteed to be non-active in that case. 122 UserHandle queryIntentsUser = userHandle; 123 if (isLaunchedAsCloneProfile() && userHandle.equals(personalProfileUserHandle)) { 124 queryIntentsUser = cloneProfileUserHandle; 125 } 126 return queryIntentsUser; 127 } 128 isLaunchedAsCloneProfile()129 private Boolean isLaunchedAsCloneProfile() { 130 return userHandleSharesheetLaunchedAs.equals(cloneProfileUserHandle); 131 } 132 AnnotatedUserHandles( int userIdOfCallingApp, UserHandle userHandleSharesheetLaunchedAs, UserHandle personalProfileUserHandle, @Nullable UserHandle workProfileUserHandle, @Nullable UserHandle cloneProfileUserHandle)133 private AnnotatedUserHandles( 134 int userIdOfCallingApp, 135 UserHandle userHandleSharesheetLaunchedAs, 136 UserHandle personalProfileUserHandle, 137 @Nullable UserHandle workProfileUserHandle, 138 @Nullable UserHandle cloneProfileUserHandle) { 139 if ((userIdOfCallingApp < 0) || UserHandle.isIsolated(userIdOfCallingApp)) { 140 throw new SecurityException("Can't start a resolver from uid " + userIdOfCallingApp); 141 } 142 143 this.userIdOfCallingApp = userIdOfCallingApp; 144 this.userHandleSharesheetLaunchedAs = userHandleSharesheetLaunchedAs; 145 this.personalProfileUserHandle = personalProfileUserHandle; 146 this.workProfileUserHandle = workProfileUserHandle; 147 this.cloneProfileUserHandle = cloneProfileUserHandle; 148 this.tabOwnerUserHandleForLaunch = 149 (userHandleSharesheetLaunchedAs == workProfileUserHandle) 150 ? workProfileUserHandle : personalProfileUserHandle; 151 } 152 153 @Nullable getWorkProfileForUser( UserManager userManager, UserHandle profileOwnerUserHandle)154 private static UserHandle getWorkProfileForUser( 155 UserManager userManager, UserHandle profileOwnerUserHandle) { 156 return userManager.getProfiles(profileOwnerUserHandle.getIdentifier()) 157 .stream() 158 .filter(info -> info.isManagedProfile()) 159 .findFirst() 160 .map(info -> info.getUserHandle()) 161 .orElse(null); 162 } 163 164 @Nullable getCloneProfileForUser( UserManager userManager, UserHandle profileOwnerUserHandle)165 private static UserHandle getCloneProfileForUser( 166 UserManager userManager, UserHandle profileOwnerUserHandle) { 167 return userManager.getProfiles(profileOwnerUserHandle.getIdentifier()) 168 .stream() 169 .filter(info -> info.isCloneProfile()) 170 .findFirst() 171 .map(info -> info.getUserHandle()) 172 .orElse(null); 173 } 174 175 @VisibleForTesting 176 static class Builder { 177 private int mUserIdOfCallingApp; 178 private UserHandle mUserHandleSharesheetLaunchedAs; 179 private UserHandle mPersonalProfileUserHandle; 180 private UserHandle mWorkProfileUserHandle; 181 private UserHandle mCloneProfileUserHandle; 182 setUserIdOfCallingApp(int id)183 public Builder setUserIdOfCallingApp(int id) { 184 mUserIdOfCallingApp = id; 185 return this; 186 } 187 setUserHandleSharesheetLaunchedAs(UserHandle user)188 public Builder setUserHandleSharesheetLaunchedAs(UserHandle user) { 189 mUserHandleSharesheetLaunchedAs = user; 190 return this; 191 } 192 setPersonalProfileUserHandle(UserHandle user)193 public Builder setPersonalProfileUserHandle(UserHandle user) { 194 mPersonalProfileUserHandle = user; 195 return this; 196 } 197 setWorkProfileUserHandle(UserHandle user)198 public Builder setWorkProfileUserHandle(UserHandle user) { 199 mWorkProfileUserHandle = user; 200 return this; 201 } 202 setCloneProfileUserHandle(UserHandle user)203 public Builder setCloneProfileUserHandle(UserHandle user) { 204 mCloneProfileUserHandle = user; 205 return this; 206 } 207 build()208 public AnnotatedUserHandles build() { 209 return new AnnotatedUserHandles( 210 mUserIdOfCallingApp, 211 mUserHandleSharesheetLaunchedAs, 212 mPersonalProfileUserHandle, 213 mWorkProfileUserHandle, 214 mCloneProfileUserHandle); 215 } 216 } 217 } 218