1 /*
2  * Copyright (C) 2020 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  */
16 package com.android.settings.applications.specialaccess.interactacrossprofiles;
17 
18 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_WORK_AND_PERSONAL_APPS_TITLE;
19 
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.CrossProfileApps;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.UserInfo;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.util.IconDrawableFactory;
31 import android.util.Pair;
32 import android.view.View;
33 
34 import androidx.annotation.Nullable;
35 import androidx.preference.Preference;
36 import androidx.preference.Preference.OnPreferenceClickListener;
37 import androidx.preference.PreferenceScreen;
38 
39 import com.android.settings.R;
40 import com.android.settings.applications.AppInfoBase;
41 import com.android.settings.search.BaseSearchIndexProvider;
42 import com.android.settings.widget.EmptyTextSettings;
43 import com.android.settingslib.search.SearchIndexable;
44 import com.android.settingslib.widget.AppPreference;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 @SearchIndexable
50 public class InteractAcrossProfilesSettings extends EmptyTextSettings {
51     private Context mContext;
52     private PackageManager mPackageManager;
53     private UserManager mUserManager;
54     private CrossProfileApps mCrossProfileApps;
55     private IconDrawableFactory mIconDrawableFactory;
56 
57     @Override
onCreate(Bundle icicle)58     public void onCreate(Bundle icicle) {
59         super.onCreate(icicle);
60 
61         mContext = getContext();
62         mPackageManager = mContext.getPackageManager();
63         mUserManager = mContext.getSystemService(UserManager.class);
64         mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
65         mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
66     }
67 
68     @Override
onResume()69     public void onResume() {
70         super.onResume();
71 
72         final PreferenceScreen screen = getPreferenceScreen();
73         screen.removeAll();
74 
75         replaceEnterprisePreferenceScreenTitle(CONNECTED_WORK_AND_PERSONAL_APPS_TITLE,
76                 R.string.interact_across_profiles_title);
77 
78         final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps =
79                 collectConfigurableApps(mPackageManager, mUserManager, mCrossProfileApps);
80 
81         final Context prefContext = getPrefContext();
82         for (final Pair<ApplicationInfo, UserHandle> appData : crossProfileApps) {
83             final ApplicationInfo appInfo = appData.first;
84             final UserHandle user = appData.second;
85             final String packageName = appInfo.packageName;
86             final CharSequence label = appInfo.loadLabel(mPackageManager);
87 
88             final Preference pref = new AppPreference(prefContext);
89             pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, user.getIdentifier()));
90             pref.setTitle(mPackageManager.getUserBadgedLabel(label, user));
91             pref.setSummary(InteractAcrossProfilesDetails.getPreferenceSummary(
92                     prefContext, packageName));
93             pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
94                 @Override
95                 public boolean onPreferenceClick(Preference preference) {
96                     AppInfoBase.startAppInfoFragment(InteractAcrossProfilesDetails.class,
97                             mDevicePolicyManager.getResources().getString(
98                                     CONNECTED_WORK_AND_PERSONAL_APPS_TITLE,
99                                     () -> getString(R.string.interact_across_profiles_title)),
100                             packageName,
101                             appInfo.uid,
102                             InteractAcrossProfilesSettings.this/* source */,
103                             -1/* request */,
104                             getMetricsCategory());
105                     return true;
106                 }
107             });
108             screen.addPreference(pref);
109         }
110     }
111 
112     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)113     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
114         super.onViewCreated(view, savedInstanceState);
115         setEmptyText(R.string.interact_across_profiles_empty_text);
116     }
117 
118     @Override
getPreferenceScreenResId()119     protected int getPreferenceScreenResId() {
120         return R.xml.interact_across_profiles;
121     }
122 
123     @Override
getMetricsCategory()124     public int getMetricsCategory() {
125         return SettingsEnums.INTERACT_ACROSS_PROFILES;
126     }
127 
128     /**
129      * @return the list of applications for the personal profile in the calling user's profile group
130      * that can configure interact across profiles.
131      */
collectConfigurableApps( PackageManager packageManager, UserManager userManager, CrossProfileApps crossProfileApps)132     static ArrayList<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps(
133             PackageManager packageManager, UserManager userManager,
134             CrossProfileApps crossProfileApps) {
135         final UserHandle workProfile = getWorkProfile(userManager);
136         if (workProfile == null) {
137             return new ArrayList<>();
138         }
139         final UserHandle personalProfile = userManager.getProfileParent(workProfile);
140         if (personalProfile == null) {
141             return new ArrayList<>();
142         }
143 
144         final ArrayList<Pair<ApplicationInfo, UserHandle>> apps = new ArrayList<>();
145         for (PackageInfo packageInfo : getAllInstalledPackages(
146                 packageManager, personalProfile, workProfile)) {
147             if (crossProfileApps.canUserAttemptToConfigureInteractAcrossProfiles(
148                     packageInfo.packageName)) {
149                 apps.add(new Pair<>(packageInfo.applicationInfo, personalProfile));
150             }
151         }
152         return apps;
153     }
154 
getAllInstalledPackages( PackageManager packageManager, UserHandle personalProfile, UserHandle workProfile)155     private static List<PackageInfo> getAllInstalledPackages(
156             PackageManager packageManager, UserHandle personalProfile, UserHandle workProfile) {
157         List<PackageInfo> personalPackages = packageManager.getInstalledPackagesAsUser(
158                 /* flags= */ 0, personalProfile.getIdentifier());
159         List<PackageInfo> workPackages = packageManager.getInstalledPackagesAsUser(
160                 /* flags= */ 0, workProfile.getIdentifier());
161         List<PackageInfo> allPackages = new ArrayList<>(personalPackages);
162         for (PackageInfo workPackage : workPackages) {
163             if (allPackages.stream().noneMatch(
164                     p -> workPackage.packageName.equals(p.packageName))) {
165                 allPackages.add(workPackage);
166             }
167         }
168         return allPackages;
169     }
170 
171     /**
172      * @return the number of applications that can interact across profiles.
173      */
getNumberOfEnabledApps( Context context, PackageManager packageManager, UserManager userManager, CrossProfileApps crossProfileApps)174     static int getNumberOfEnabledApps(
175             Context context, PackageManager packageManager, UserManager userManager,
176             CrossProfileApps crossProfileApps) {
177         UserHandle workProfile = getWorkProfile(userManager);
178         if (workProfile == null) {
179             return 0;
180         }
181         UserHandle personalProfile = userManager.getProfileParent(workProfile);
182         if (personalProfile == null) {
183             return 0;
184         }
185         final ArrayList<Pair<ApplicationInfo, UserHandle>> apps =
186                 collectConfigurableApps(packageManager, userManager, crossProfileApps);
187         apps.removeIf(
188                 app -> !InteractAcrossProfilesDetails.isInteractAcrossProfilesEnabled(
189                         context, app.first.packageName)
190                         || !crossProfileApps.canConfigureInteractAcrossProfiles(
191                                 app.first.packageName));
192         return apps.size();
193     }
194 
195     /**
196      * Returns the work profile in the profile group of the calling user.
197      * Returns null if not found.
198      */
199     @Nullable
getWorkProfile(UserManager userManager)200     static UserHandle getWorkProfile(UserManager userManager) {
201         for (UserInfo user : userManager.getProfiles(UserHandle.myUserId())) {
202             if (userManager.isManagedProfile(user.id)) {
203                 return user.getUserHandle();
204             }
205         }
206         return null;
207     }
208 
209     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
210             new BaseSearchIndexProvider(R.xml.interact_across_profiles);
211 }
212