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