You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
9.3 KiB
218 lines
9.3 KiB
/*
|
|
* 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.annotation.Nullable;
|
|
import android.app.Activity;
|
|
import android.app.ActivityManager;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
|
|
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 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
|
|
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);
|
|
}
|
|
}
|
|
}
|