• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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