blob: b7afe3f71fece8323ae84524832e810a1ff6be6d [file] [log] [blame]
Tony Mak1b708e62017-10-12 10:59:11 +01001/*
2 * Copyright (C) 2017 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 */
Tony Makb0d22622018-01-18 12:49:49 +000016package android.content.pm;
Tony Mak1b708e62017-10-12 10:59:11 +010017
18import android.annotation.NonNull;
kholoud mohamed5b2dfe32020-01-02 11:55:22 +000019import android.annotation.Nullable;
Varun Shahacad1382018-11-28 11:56:08 -080020import android.annotation.RequiresPermission;
21import android.annotation.SystemApi;
Alex Kershaw6fb8a022020-01-09 11:33:56 +000022import android.app.AppOpsManager.Mode;
Tony Mak1b708e62017-10-12 10:59:11 +010023import android.content.ComponentName;
24import android.content.Context;
kholoud mohamed5b2dfe32020-01-02 11:55:22 +000025import android.content.Intent;
Tony Mak82465132017-11-29 21:00:30 +000026import android.content.res.Resources;
Tony Mak82465132017-11-29 21:00:30 +000027import android.graphics.drawable.Drawable;
kholoud mohamed5b2dfe32020-01-02 11:55:22 +000028import android.net.Uri;
Tony Mak1b708e62017-10-12 10:59:11 +010029import android.os.RemoteException;
30import android.os.UserHandle;
31import android.os.UserManager;
kholoud mohamed5b2dfe32020-01-02 11:55:22 +000032import android.provider.Settings;
Tony Mak1b708e62017-10-12 10:59:11 +010033
Tony Mak82465132017-11-29 21:00:30 +000034import com.android.internal.R;
35import com.android.internal.util.UserIcons;
36
Alex Kershaw4b0197d2020-01-15 20:00:05 +000037import java.util.Collection;
Tony Mak1b708e62017-10-12 10:59:11 +010038import java.util.List;
kholoud mohamed946df392019-12-12 17:43:32 +000039import java.util.Set;
Alex Kershaw4b0197d2020-01-15 20:00:05 +000040import java.util.stream.Collectors;
Tony Mak1b708e62017-10-12 10:59:11 +010041
42/**
43 * Class for handling cross profile operations. Apps can use this class to interact with its
44 * instance in any profile that is in {@link #getTargetUserProfiles()}. For example, app can
45 * use this class to start its main activity in managed profile.
46 */
47public class CrossProfileApps {
Alex Kershaw6fb8a022020-01-09 11:33:56 +000048
49 /**
50 * Broadcast signalling that the receiving app's ability to interact across profiles has
51 * changed, as defined by the return value of {@link #canInteractAcrossProfiles()}.
52 *
53 * <p>Apps that have set the {@code android:crossProfile} manifest attribute to {@code true}
54 * can receive this broadcast in manifest broadcast receivers. Otherwise, it can only be
55 * received by dynamically-registered broadcast receivers.
56 */
57 public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED =
58 "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED";
59
Tony Mak1b708e62017-10-12 10:59:11 +010060 private final Context mContext;
61 private final ICrossProfileApps mService;
Tony Mak82465132017-11-29 21:00:30 +000062 private final UserManager mUserManager;
63 private final Resources mResources;
Tony Mak1b708e62017-10-12 10:59:11 +010064
65 /** @hide */
66 public CrossProfileApps(Context context, ICrossProfileApps service) {
67 mContext = context;
68 mService = service;
Tony Mak82465132017-11-29 21:00:30 +000069 mUserManager = context.getSystemService(UserManager.class);
70 mResources = context.getResources();
Tony Mak1b708e62017-10-12 10:59:11 +010071 }
72
73 /**
74 * Starts the specified main activity of the caller package in the specified profile.
75 *
76 * @param component The ComponentName of the activity to launch, it must be exported and has
77 * action {@link android.content.Intent#ACTION_MAIN}, category
78 * {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
79 * be thrown.
Tony Makb0d22622018-01-18 12:49:49 +000080 * @param targetUser The UserHandle of the profile, must be one of the users returned by
Tony Mak1b708e62017-10-12 10:59:11 +010081 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
82 * be thrown.
Tony Mak1b708e62017-10-12 10:59:11 +010083 */
Tony Makb0d22622018-01-18 12:49:49 +000084 public void startMainActivity(@NonNull ComponentName component,
85 @NonNull UserHandle targetUser) {
Tony Mak1b708e62017-10-12 10:59:11 +010086 try {
Tony Makde32b832018-04-30 15:11:57 +010087 mService.startActivityAsUser(
88 mContext.getIApplicationThread(),
89 mContext.getPackageName(),
Philip P. Moltmann9c5226f2020-01-10 08:53:43 -080090 mContext.getFeatureId(),
Tony Makde32b832018-04-30 15:11:57 +010091 component,
Varun Shahacad1382018-11-28 11:56:08 -080092 targetUser.getIdentifier(),
93 true);
94 } catch (RemoteException ex) {
95 throw ex.rethrowFromSystemServer();
96 }
97 }
98
99 /**
Jonathan Scottd72fc8d2020-01-20 16:22:58 +0000100 * Starts the specified activity of the caller package in the specified profile.
101 *
102 * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
103 * permission and both the caller and target user profiles must be in the same profile group.
104 *
105 * @param intent The intent to launch. A component in the caller package must be specified.
106 * @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by
107 * {@link #getTargetUserProfiles()} if different to the calling user, otherwise a
108 * {@link SecurityException} will be thrown.
109 */
110 @RequiresPermission(anyOf = {
111 android.Manifest.permission.INTERACT_ACROSS_PROFILES,
112 android.Manifest.permission.INTERACT_ACROSS_USERS})
113 public void startActivity(@NonNull Intent intent, @NonNull UserHandle targetUser) {
114 try {
115 mService.startActivityAsUserByIntent(
116 mContext.getIApplicationThread(),
117 mContext.getPackageName(),
Philip P. Moltmann9c5226f2020-01-10 08:53:43 -0800118 mContext.getFeatureId(),
Jonathan Scottd72fc8d2020-01-20 16:22:58 +0000119 intent,
120 targetUser.getIdentifier());
121 } catch (RemoteException ex) {
122 throw ex.rethrowFromSystemServer();
123 }
124 }
125
126 /**
Varun Shahbdb849f2019-01-16 12:06:18 -0800127 * Starts the specified activity of the caller package in the specified profile. Unlike
128 * {@link #startMainActivity}, this can start any activity of the caller package, not just
129 * the main activity.
130 * The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
131 * permission and both the caller and target user profiles must be in the same profile group.
Varun Shahacad1382018-11-28 11:56:08 -0800132 *
133 * @param component The ComponentName of the activity to launch. It must be exported.
134 * @param targetUser The UserHandle of the profile, must be one of the users returned by
135 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
136 * be thrown.
137 * @hide
138 */
139 @SystemApi
140 @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
Varun Shahbdb849f2019-01-16 12:06:18 -0800141 public void startActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) {
Varun Shahacad1382018-11-28 11:56:08 -0800142 try {
143 mService.startActivityAsUser(mContext.getIApplicationThread(),
Philip P. Moltmann9c5226f2020-01-10 08:53:43 -0800144 mContext.getPackageName(), mContext.getFeatureId(), component,
145 targetUser.getIdentifier(), false);
Tony Mak1b708e62017-10-12 10:59:11 +0100146 } catch (RemoteException ex) {
147 throw ex.rethrowFromSystemServer();
148 }
149 }
150
151 /**
152 * Return a list of user profiles that that the caller can use when calling other APIs in this
153 * class.
154 * <p>
155 * A user profile would be considered as a valid target user profile, provided that:
156 * <ul>
157 * <li>It gets caller app installed</li>
158 * <li>It is not equal to the calling user</li>
159 * <li>It is in the same profile group of calling user profile</li>
160 * <li>It is enabled</li>
161 * </ul>
162 *
163 * @see UserManager#getUserProfiles()
164 */
165 public @NonNull List<UserHandle> getTargetUserProfiles() {
166 try {
167 return mService.getTargetUserProfiles(mContext.getPackageName());
168 } catch (RemoteException ex) {
169 throw ex.rethrowFromSystemServer();
170 }
171 }
Tony Mak82465132017-11-29 21:00:30 +0000172
173 /**
174 * Return a label that calling app can show to user for the semantic of profile switching --
175 * launching its own activity in specified user profile. For example, it may return
176 * "Switch to work" if the given user handle is the managed profile one.
177 *
178 * @param userHandle The UserHandle of the target profile, must be one of the users returned by
179 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
180 * be thrown.
181 * @return a label that calling app can show user for the semantic of launching its own
182 * activity in the specified user profile.
183 *
Aurimas Liutikas7f695332018-05-31 21:07:32 -0700184 * @see #startMainActivity(ComponentName, UserHandle)
Tony Mak82465132017-11-29 21:00:30 +0000185 */
186 public @NonNull CharSequence getProfileSwitchingLabel(@NonNull UserHandle userHandle) {
187 verifyCanAccessUser(userHandle);
188
189 final int stringRes = mUserManager.isManagedProfile(userHandle.getIdentifier())
190 ? R.string.managed_profile_label
191 : R.string.user_owner_label;
192 return mResources.getString(stringRes);
193 }
194
195 /**
Tony Makb0d22622018-01-18 12:49:49 +0000196 * Return a drawable that calling app can show to user for the semantic of profile switching --
Tony Mak82465132017-11-29 21:00:30 +0000197 * launching its own activity in specified user profile. For example, it may return a briefcase
198 * icon if the given user handle is the managed profile one.
199 *
200 * @param userHandle The UserHandle of the target profile, must be one of the users returned by
201 * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
202 * be thrown.
203 * @return an icon that calling app can show user for the semantic of launching its own
204 * activity in specified user profile.
205 *
Tony Makb0d22622018-01-18 12:49:49 +0000206 * @see #startMainActivity(ComponentName, UserHandle)
Tony Mak82465132017-11-29 21:00:30 +0000207 */
Tony Makb0d22622018-01-18 12:49:49 +0000208 public @NonNull Drawable getProfileSwitchingIconDrawable(@NonNull UserHandle userHandle) {
Tony Mak82465132017-11-29 21:00:30 +0000209 verifyCanAccessUser(userHandle);
210
211 final boolean isManagedProfile =
212 mUserManager.isManagedProfile(userHandle.getIdentifier());
213 if (isManagedProfile) {
214 return mResources.getDrawable(R.drawable.ic_corp_badge, null);
215 } else {
216 return UserIcons.getDefaultUserIcon(
217 mResources, UserHandle.USER_SYSTEM, true /* light */);
218 }
219 }
220
kholoud mohamed946df392019-12-12 17:43:32 +0000221 /**
222 * Returns whether the calling package can request to interact across profiles.
223 *
224 * <p>The package's current ability to interact across profiles can be checked with
225 * {@link #canInteractAcrossProfiles()}.
226 *
227 * <p>Specifically, returns whether the following are all true:
228 * <ul>
229 * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
230 * <li>The calling app has requested</li>
231 * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.
232 * <li>The calling package has either been whitelisted by default by the OEM or has been
233 * explicitly whitelisted by the admin via
234 * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
235 * </li>
236 * </ul>
237 *
238 * @return true if the calling package can request to interact across profiles.
239 */
240 public boolean canRequestInteractAcrossProfiles() {
241 try {
242 return mService.canRequestInteractAcrossProfiles(mContext.getPackageName());
243 } catch (RemoteException ex) {
244 throw ex.rethrowFromSystemServer();
245 }
246 }
247
248 /**
249 * Returns whether the calling package can interact across profiles.
250 *
251 * <p>The package's current ability to request to interact across profiles can be checked with
252 * {@link #canRequestInteractAcrossProfiles()}.
253 *
254 * <p>Specifically, returns whether the following are all true:
255 * <ul>
256 * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
257 * <li>The user has previously consented to cross-profile communication for the calling
258 * package.</li>
259 * <li>The calling package has either been whitelisted by default by the OEM or has been
260 * explicitly whitelisted by the admin via
261 * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
262 * </li>
263 * </ul>
264 *
265 * @return true if the calling package can interact across profiles.
266 * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
267 * calling UID.
268 */
269 public boolean canInteractAcrossProfiles() {
270 try {
271 return mService.canInteractAcrossProfiles(mContext.getPackageName());
272 } catch (RemoteException ex) {
273 throw ex.rethrowFromSystemServer();
274 }
275 }
276
kholoud mohamed5b2dfe32020-01-02 11:55:22 +0000277 /**
278 * Returns an {@link Intent} to open the settings page that allows the user to decide whether
279 * the calling app can interact across profiles. The current state is given by
280 * {@link #canInteractAcrossProfiles()}.
281 *
282 * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
283 *
284 * @return an {@link Intent} to open the settings page that allows the user to decide whether
285 * the app can interact across profiles
286 *
287 * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
288 * calling UID.
289 */
290 public @Nullable Intent createRequestInteractAcrossProfilesIntent() {
291 if (!canRequestInteractAcrossProfiles()) {
292 return null;
293 }
294 final Intent settingsIntent = new Intent();
295 settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
296 final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
297 settingsIntent.setData(packageUri);
298 return settingsIntent;
299 }
300
Alex Kershaw6fb8a022020-01-09 11:33:56 +0000301 /**
302 * Sets the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} that is
303 * configurable by users in Settings. This configures it for the profile group of the calling
304 * package.
305 *
Alex Kershaw4b0197d2020-01-15 20:00:05 +0000306 * <p>Before calling, check {@link #canConfigureInteractAcrossProfiles(String)} and do not call
307 * if it is {@code false}. If presenting a user interface, do not allow the user to configure
308 * the app-op in that case.
Alex Kershaw6fb8a022020-01-09 11:33:56 +0000309 *
310 * <p>The underlying app-op {@link android.app.AppOpsManager#OP_INTERACT_ACROSS_PROFILES} should
311 * never be set directly. This method ensures that the app-op is kept in sync for the app across
312 * each user in the profile group and that those apps are sent a broadcast when their ability to
313 * interact across profiles changes.
314 *
Alex Kershaw4b0197d2020-01-15 20:00:05 +0000315 * <p>This method should be used directly whenever a user's action results in a change in an
316 * app's ability to interact across profiles, as defined by the return value of {@link
317 * #canInteractAcrossProfiles()}. This includes user consent changes in Settings or during
318 * provisioning.
319 *
320 * <p>If other changes could have affected the app's ability to interact across profiles, as
321 * defined by the return value of {@link #canInteractAcrossProfiles()}, such as changes to the
322 * admin or OEM consent whitelists, then {@link
323 * #resetInteractAcrossProfilesAppOpsIfInvalid(List)} should be used.
Alex Kershaw6fb8a022020-01-09 11:33:56 +0000324 *
325 * @hide
326 */
327 @RequiresPermission(
328 allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
329 android.Manifest.permission.INTERACT_ACROSS_USERS})
330 public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) {
331 try {
332 mService.setInteractAcrossProfilesAppOp(packageName, newMode);
333 } catch (RemoteException ex) {
334 throw ex.rethrowFromSystemServer();
335 }
336 }
337
Alex Kershaw4b0197d2020-01-15 20:00:05 +0000338 /**
339 * Returns whether the given package can have its ability to interact across profiles configured
340 * by the user. This means that every other condition to interact across profiles has been set.
341 *
342 * <p>This differs from {@link #canRequestInteractAcrossProfiles()} since it will not return
343 * {@code false} simply when the target profile is disabled.
344 *
345 * @hide
346 */
347 public boolean canConfigureInteractAcrossProfiles(@NonNull String packageName) {
348 try {
349 return mService.canConfigureInteractAcrossProfiles(packageName);
350 } catch (RemoteException ex) {
351 throw ex.rethrowFromSystemServer();
352 }
353 }
354
355 /**
356 * For each of the packages defined in {@code previousCrossProfilePackages} but not included in
357 * {@code newCrossProfilePackages}, resets the app-op for {@link android.Manifest.permission
358 * #INTERACT_ACROSS_PROFILES} back to its default value if it can no longer be configured by
359 * users in Settings, as defined by {@link #canConfigureInteractAcrossProfiles(String)}.
360 *
361 * <p>This method should be used whenever an app's ability to interact across profiles could
362 * have changed as a result of non-user actions, such as changes to admin or OEM consent
363 * whitelists.
364 *
365 * @hide
366 */
367 @RequiresPermission(
368 allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
369 android.Manifest.permission.INTERACT_ACROSS_USERS})
370 public void resetInteractAcrossProfilesAppOps(
371 @NonNull Collection<String> previousCrossProfilePackages,
372 @NonNull Set<String> newCrossProfilePackages) {
373 if (previousCrossProfilePackages.isEmpty()) {
374 return;
375 }
376 final List<String> unsetCrossProfilePackages =
377 previousCrossProfilePackages.stream()
378 .filter(packageName -> !newCrossProfilePackages.contains(packageName))
379 .collect(Collectors.toList());
380 if (unsetCrossProfilePackages.isEmpty()) {
381 return;
382 }
383 try {
384 mService.resetInteractAcrossProfilesAppOps(unsetCrossProfilePackages);
385 } catch (RemoteException ex) {
386 throw ex.rethrowFromSystemServer();
387 }
388 }
389
Tony Mak82465132017-11-29 21:00:30 +0000390 private void verifyCanAccessUser(UserHandle userHandle) {
391 if (!getTargetUserProfiles().contains(userHandle)) {
392 throw new SecurityException("Not allowed to access " + userHandle);
393 }
394 }
Tony Mak1b708e62017-10-12 10:59:11 +0100395}