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.content.pm.PackageManager.GET_ACTIVITIES;
19 
20 import android.annotation.Nullable;
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.CrossProfileApps;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.UserInfo;
28 import android.os.Bundle;
29 import android.os.UserHandle;
30 import android.os.UserManager;
31 import android.util.IconDrawableFactory;
32 import android.util.Pair;
33 import android.view.View;
34 
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.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         final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps =
76                 collectConfigurableApps(mPackageManager, mUserManager, mCrossProfileApps);
77 
78         final Context prefContext = getPrefContext();
79         for (final Pair<ApplicationInfo, UserHandle> appData : crossProfileApps) {
80             final ApplicationInfo appInfo = appData.first;
81             final UserHandle user = appData.second;
82             final String packageName = appInfo.packageName;
83             final CharSequence label = appInfo.loadLabel(mPackageManager);
84 
85             final Preference pref = new AppPreference(prefContext);
86             pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, user.getIdentifier()));
87             pref.setTitle(mPackageManager.getUserBadgedLabel(label, user));
88             pref.setSummary(InteractAcrossProfilesDetails.getPreferenceSummary(
89                     prefContext, packageName));
90             pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
91                 @Override
92                 public boolean onPreferenceClick(Preference preference) {
93                     AppInfoBase.startAppInfoFragment(InteractAcrossProfilesDetails.class,
94                             R.string.interact_across_profiles_title,
95                             packageName,
96                             appInfo.uid,
97                             InteractAcrossProfilesSettings.this/* source */,
98                             -1/* request */,
99                             getMetricsCategory());
100                     return true;
101                 }
102             });
103             screen.addPreference(pref);
104         }
105     }
106 
107     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)108     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
109         super.onViewCreated(view, savedInstanceState);
110         setEmptyText(R.string.interact_across_profiles_empty_text);
111     }
112 
113     @Override
getPreferenceScreenResId()114     protected int getPreferenceScreenResId() {
115         return R.xml.interact_across_profiles;
116     }
117 
118     @Override
getMetricsCategory()119     public int getMetricsCategory() {
120         return SettingsEnums.INTERACT_ACROSS_PROFILES;
121     }
122 
123     /**
124      * @return the list of applications for the personal profile in the calling user's profile group
125      * that can configure interact across profiles.
126      */
collectConfigurableApps( PackageManager packageManager, UserManager userManager, CrossProfileApps crossProfileApps)127     static ArrayList<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps(
128             PackageManager packageManager, UserManager userManager,
129             CrossProfileApps crossProfileApps) {
130         final UserHandle workProfile = getWorkProfile(userManager);
131         if (workProfile == null) {
132             return new ArrayList<>();
133         }
134         final UserHandle personalProfile = userManager.getProfileParent(workProfile);
135         if (personalProfile == null) {
136             return new ArrayList<>();
137         }
138 
139         final ArrayList<Pair<ApplicationInfo, UserHandle>> apps = new ArrayList<>();
140         for (PackageInfo packageInfo : getAllInstalledPackages(
141                 packageManager, personalProfile, workProfile)) {
142             if (crossProfileApps.canUserAttemptToConfigureInteractAcrossProfiles(
143                     packageInfo.packageName)) {
144                 apps.add(new Pair<>(packageInfo.applicationInfo, personalProfile));
145             }
146         }
147         return apps;
148     }
149 
getAllInstalledPackages( PackageManager packageManager, UserHandle personalProfile, UserHandle workProfile)150     private static List<PackageInfo> getAllInstalledPackages(
151             PackageManager packageManager, UserHandle personalProfile, UserHandle workProfile) {
152         List<PackageInfo> personalPackages = packageManager.getInstalledPackagesAsUser(
153                 GET_ACTIVITIES, personalProfile.getIdentifier());
154         List<PackageInfo> workPackages = packageManager.getInstalledPackagesAsUser(
155                 GET_ACTIVITIES, workProfile.getIdentifier());
156         List<PackageInfo> allPackages = new ArrayList<>(personalPackages);
157         for (PackageInfo workPackage : workPackages) {
158             if (allPackages.stream().noneMatch(
159                     p -> workPackage.packageName.equals(p.packageName))) {
160                 allPackages.add(workPackage);
161             }
162         }
163         return allPackages;
164     }
165 
166     /**
167      * @return the number of applications that can interact across profiles.
168      */
getNumberOfEnabledApps( Context context, PackageManager packageManager, UserManager userManager, CrossProfileApps crossProfileApps)169     static int getNumberOfEnabledApps(
170             Context context, PackageManager packageManager, UserManager userManager,
171             CrossProfileApps crossProfileApps) {
172         UserHandle workProfile = getWorkProfile(userManager);
173         if (workProfile == null) {
174             return 0;
175         }
176         UserHandle personalProfile = userManager.getProfileParent(workProfile);
177         if (personalProfile == null) {
178             return 0;
179         }
180         final ArrayList<Pair<ApplicationInfo, UserHandle>> apps =
181                 collectConfigurableApps(packageManager, userManager, crossProfileApps);
182         apps.removeIf(
183                 app -> !InteractAcrossProfilesDetails.isInteractAcrossProfilesEnabled(
184                         context, app.first.packageName)
185                         || !crossProfileApps.canConfigureInteractAcrossProfiles(
186                                 app.first.packageName));
187         return apps.size();
188     }
189 
190     /**
191      * Returns the work profile in the profile group of the calling user.
192      * Returns null if not found.
193      */
194     @Nullable
getWorkProfile(UserManager userManager)195     static UserHandle getWorkProfile(UserManager userManager) {
196         for (UserInfo user : userManager.getProfiles(UserHandle.myUserId())) {
197             if (userManager.isManagedProfile(user.id)) {
198                 return user.getUserHandle();
199             }
200         }
201         return null;
202     }
203 
204     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
205             new BaseSearchIndexProvider(R.xml.interact_across_profiles);
206 }
207