1 /* 2 * Copyright 2021 Google LLC 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 * https://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 package com.google.android.enterprise.connectedapps; 17 18 import static com.google.common.collect.ImmutableList.toImmutableList; 19 20 import android.app.admin.DevicePolicyManager; 21 import android.content.Context; 22 import android.content.pm.CrossProfileApps; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.os.Build.VERSION; 26 import android.os.Build.VERSION_CODES; 27 import android.os.UserHandle; 28 import android.os.UserManager; 29 import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 import org.checkerframework.checker.nullness.qual.Nullable; 34 35 /** Utility methods for acting on profiles. These methods should only be used by the SDK. */ 36 class CrossProfileSDKUtilities { 37 private static boolean isRunningOnWorkProfileCached = false; 38 private static boolean isRunningOnWorkProfile = false; 39 isRunningOnWorkProfile(Context context)40 static boolean isRunningOnWorkProfile(Context context) { 41 if (!isRunningOnWorkProfileCached) { 42 calculateIsRunningOnWorkProfile(context); 43 } 44 return isRunningOnWorkProfile; 45 } 46 47 /** 48 * Set the {@code isRunningOnWorkProfile} field and return whether or not we can cache this value. 49 */ calculateIsRunningOnWorkProfile(Context context)50 private static void calculateIsRunningOnWorkProfile(Context context) { 51 UserManager userManager = context.getSystemService(UserManager.class); 52 isRunningOnWorkProfileCached = true; // By default we cache the result of this calculation 53 54 if (VERSION.SDK_INT >= VERSION_CODES.R) { 55 isRunningOnWorkProfile = userManager.isManagedProfile(); 56 return; 57 } 58 if (userManager.getUserProfiles().size() < 2) { 59 // This accounts for situations where a personal profile has management. 60 isRunningOnWorkProfile = false; 61 // we can't cache it as this case is also entered if we are on the work profile but it's not 62 // fully configured 63 isRunningOnWorkProfileCached = false; 64 return; 65 } 66 67 DevicePolicyManager devicePolicyManager = context.getSystemService(DevicePolicyManager.class); 68 PackageManager packageManager = context.getPackageManager(); 69 70 isRunningOnWorkProfile = false; 71 for (PackageInfo pkg : packageManager.getInstalledPackages(/* flags= */ 0)) { 72 if (devicePolicyManager.isProfileOwnerApp(pkg.packageName)) { 73 isRunningOnWorkProfile = true; 74 return; 75 } 76 } 77 } 78 isRunningOnPersonalProfile(Context context)79 static boolean isRunningOnPersonalProfile(Context context) { 80 if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { 81 UserManager userManager = context.getSystemService(UserManager.class); 82 return !userManager.isProfile(); 83 } 84 return !isRunningOnWorkProfile(context); 85 } 86 87 /** 88 * Check if a user is either the personal user or the managed work profile. 89 * 90 * <p>If the user is not a profile, it is assumed to be the personal user. 91 */ isPersonalOrWorkProfile(CrossProfileApps crossProfileApps, UserHandle userHandle)92 static boolean isPersonalOrWorkProfile(CrossProfileApps crossProfileApps, UserHandle userHandle) { 93 if (VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) { 94 return crossProfileApps.isManagedProfile(userHandle) 95 || !crossProfileApps.isProfile(userHandle); 96 } 97 throw new UnsupportedOperationException("isPersonalOrWorkProfile is not supported on this SDK"); 98 } 99 100 /** 101 * Deterministically select the user to bind to. 102 * 103 * <p>This will ensure that on a device with multiple profiles, we bind to the same one 104 * consistently. 105 */ 106 @Nullable selectUserHandleToBind(Context context, List<UserHandle> userHandles)107 static UserHandle selectUserHandleToBind(Context context, List<UserHandle> userHandles) { 108 if (VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) { 109 CrossProfileApps crossProfileApps = context.getSystemService(CrossProfileApps.class); 110 userHandles = 111 userHandles.stream() 112 .filter(userHandle -> isPersonalOrWorkProfile(crossProfileApps, userHandle)) 113 .collect(toImmutableList()); 114 } 115 116 if (userHandles.isEmpty()) { 117 return null; 118 } 119 120 UserManager userManager = context.getSystemService(UserManager.class); 121 122 return Collections.min( 123 userHandles, 124 (o1, o2) -> 125 (int) 126 (userManager.getSerialNumberForUser(o1) - userManager.getSerialNumberForUser(o2))); 127 } 128 129 /** Filter out users according to the passed {@link AvailabilityRestrictions}. */ filterUsersByAvailabilityRestrictions( Context context, List<UserHandle> userHandles, AvailabilityRestrictions availabilityRestrictions)130 static List<UserHandle> filterUsersByAvailabilityRestrictions( 131 Context context, 132 List<UserHandle> userHandles, 133 AvailabilityRestrictions availabilityRestrictions) { 134 List<UserHandle> filteredUserHandles = new ArrayList<>(); 135 UserManager userManager = context.getSystemService(UserManager.class); 136 137 for (UserHandle userHandle : userHandles) { 138 if (!userManager.isUserRunning(userHandle)) { 139 continue; 140 } 141 if (userManager.isQuietModeEnabled(userHandle)) { 142 continue; 143 } 144 if (availabilityRestrictions.requireUnlocked && !userManager.isUserUnlocked(userHandle)) { 145 continue; 146 } 147 148 filteredUserHandles.add(userHandle); 149 } 150 151 return filteredUserHandles; 152 } 153 154 /** Should only be used during tests where the profile state may change during a single run. */ clearCache()155 static void clearCache() { 156 isRunningOnWorkProfileCached = false; 157 } 158 CrossProfileSDKUtilities()159 private CrossProfileSDKUtilities() {} 160 } 161