aboutsummaryrefslogtreecommitdiff
path: root/java/src/com/android/intentresolver/AnnotatedUserHandles.java
blob: 3565e7573300dc92a8111bc29148508faa1d09f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.intentresolver;

import android.app.Activity;
import android.app.ActivityManager;
import android.os.UserHandle;
import android.os.UserManager;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

/**
 * Helper class to precompute the (immutable) designations of various user handles in the system
 * that may contribute to the current Sharesheet session.
 */
public final class AnnotatedUserHandles {
    /** The user id of the app that started the share activity. */
    public final int userIdOfCallingApp;

    /**
     * The {@link UserHandle} that launched Sharesheet.
     * TODO: I believe this would always be the handle corresponding to {@code userIdOfCallingApp}
     * except possibly if the caller used {@link Activity#startActivityAsUser} to launch
     * Sharesheet as a different user than they themselves were running as. Verify and document.
     */
    public final UserHandle userHandleSharesheetLaunchedAs;

    /**
     * The {@link UserHandle} that owns the "personal tab" in a tabbed share UI (or the *only* 'tab'
     * in a non-tabbed UI).
     *
     * This is never a work or clone user, but may either be the root user (0) or a "secondary"
     * multi-user profile (i.e., one that's not root, work, nor clone). This is a "secondary"
     * profile only when that user is the active "foreground" user.
     *
     * In the current implementation, we can assert that this is the root user (0) any time we
     * display a tabbed UI (i.e., any time `workProfileUserHandle` is non-null), or any time that we
     * have a clone profile. This note is only provided for informational purposes; clients should
     * avoid making any reliances on that assumption.
     */
    public final UserHandle personalProfileUserHandle;

    /**
     * The {@link UserHandle} that owns the "work tab" in a tabbed share UI. This is (an arbitrary)
     * one of the "managed" profiles associated with {@link #personalProfileUserHandle}.
     */
    @Nullable
    public final UserHandle workProfileUserHandle;

    /**
     * The {@link UserHandle} of the clone profile belonging to {@link #personalProfileUserHandle}.
     */
    @Nullable
    public final UserHandle cloneProfileUserHandle;

    /**
     * The "tab owner" user handle (i.e., either {@link #personalProfileUserHandle} or
     * {@link #workProfileUserHandle}) that either matches or owns the profile of the
     * {@link #userHandleSharesheetLaunchedAs}.
     *
     * In the current implementation, we can assert that this is the same as
     * `userHandleSharesheetLaunchedAs` except when the latter is the clone profile; then this is
     * the "personal" profile owning that clone profile (which we currently know must belong to
     * user 0, but clients should avoid making any reliances on that assumption).
     */
    public final UserHandle tabOwnerUserHandleForLaunch;

    /** Compute all handle designations for a new Sharesheet session in the specified activity. */
    public static AnnotatedUserHandles forShareActivity(Activity shareActivity) {
        // TODO: consider integrating logic for `ResolverActivity.EXTRA_CALLING_USER`?
        UserHandle userHandleSharesheetLaunchedAs = UserHandle.of(UserHandle.myUserId());

        // ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
        // profile is active, we always make the personal tab from the foreground user.
        // Outside profiles, current foreground user is potentially the same as the sharesheet
        // process's user (UserHandle.myUserId()), so we continue to create personal tab with the
        // current foreground user.
        UserHandle personalProfileUserHandle = UserHandle.of(ActivityManager.getCurrentUser());

        UserManager userManager = shareActivity.getSystemService(UserManager.class);

        return newBuilder()
                .setUserIdOfCallingApp(shareActivity.getLaunchedFromUid())
                .setUserHandleSharesheetLaunchedAs(userHandleSharesheetLaunchedAs)
                .setPersonalProfileUserHandle(personalProfileUserHandle)
                .setWorkProfileUserHandle(
                        getWorkProfileForUser(userManager, personalProfileUserHandle))
                .setCloneProfileUserHandle(
                        getCloneProfileForUser(userManager, personalProfileUserHandle))
                .build();
    }

    @VisibleForTesting public static Builder newBuilder() {
        return new Builder();
    }

    /**
     * Returns the {@link UserHandle} to use when querying resolutions for intents in a
     * {@link ResolverListController} configured for the provided {@code userHandle}.
     */
    public UserHandle getQueryIntentsUser(UserHandle userHandle) {
        // In case launching app is in clonedProfile, and we are building the personal tab, intent
        // resolution will be attempted as clonedUser instead of user 0. This is because intent
        // resolution from user 0 and clonedUser is not guaranteed to return same results.
        // We do not care about the case when personal adapter is started with non-root user
        // (secondary user case), as clone profile is guaranteed to be non-active in that case.
        UserHandle queryIntentsUser = userHandle;
        if (isLaunchedAsCloneProfile() && userHandle.equals(personalProfileUserHandle)) {
            queryIntentsUser = cloneProfileUserHandle;
        }
        return queryIntentsUser;
    }

    private Boolean isLaunchedAsCloneProfile() {
        return userHandleSharesheetLaunchedAs.equals(cloneProfileUserHandle);
    }

    private AnnotatedUserHandles(
            int userIdOfCallingApp,
            UserHandle userHandleSharesheetLaunchedAs,
            UserHandle personalProfileUserHandle,
            @Nullable UserHandle workProfileUserHandle,
            @Nullable UserHandle cloneProfileUserHandle) {
        if ((userIdOfCallingApp < 0) || UserHandle.isIsolated(userIdOfCallingApp)) {
            throw new SecurityException("Can't start a resolver from uid " + userIdOfCallingApp);
        }

        this.userIdOfCallingApp = userIdOfCallingApp;
        this.userHandleSharesheetLaunchedAs = userHandleSharesheetLaunchedAs;
        this.personalProfileUserHandle = personalProfileUserHandle;
        this.workProfileUserHandle = workProfileUserHandle;
        this.cloneProfileUserHandle = cloneProfileUserHandle;
        this.tabOwnerUserHandleForLaunch =
                (userHandleSharesheetLaunchedAs == workProfileUserHandle)
                    ? workProfileUserHandle : personalProfileUserHandle;
    }

    @Nullable
    private static UserHandle getWorkProfileForUser(
            UserManager userManager, UserHandle profileOwnerUserHandle) {
        return userManager.getProfiles(profileOwnerUserHandle.getIdentifier())
                .stream()
                .filter(info -> info.isManagedProfile())
                .findFirst()
                .map(info -> info.getUserHandle())
                .orElse(null);
    }

    @Nullable
    private static UserHandle getCloneProfileForUser(
            UserManager userManager, UserHandle profileOwnerUserHandle) {
        return userManager.getProfiles(profileOwnerUserHandle.getIdentifier())
                .stream()
                .filter(info -> info.isCloneProfile())
                .findFirst()
                .map(info -> info.getUserHandle())
                .orElse(null);
    }

    @VisibleForTesting
    public static class Builder {
        private int mUserIdOfCallingApp;
        private UserHandle mUserHandleSharesheetLaunchedAs;
        private UserHandle mPersonalProfileUserHandle;
        private UserHandle mWorkProfileUserHandle;
        private UserHandle mCloneProfileUserHandle;

        public Builder setUserIdOfCallingApp(int id) {
            mUserIdOfCallingApp = id;
            return this;
        }

        public Builder setUserHandleSharesheetLaunchedAs(UserHandle user) {
            mUserHandleSharesheetLaunchedAs = user;
            return this;
        }

        public Builder setPersonalProfileUserHandle(UserHandle user) {
            mPersonalProfileUserHandle = user;
            return this;
        }

        public Builder setWorkProfileUserHandle(UserHandle user) {
            mWorkProfileUserHandle = user;
            return this;
        }

        public Builder setCloneProfileUserHandle(UserHandle user) {
            mCloneProfileUserHandle = user;
            return this;
        }

        public AnnotatedUserHandles build() {
            return new AnnotatedUserHandles(
                    mUserIdOfCallingApp,
                    mUserHandleSharesheetLaunchedAs,
                    mPersonalProfileUserHandle,
                    mWorkProfileUserHandle,
                    mCloneProfileUserHandle);
        }
    }
}