1 /*
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  */
16 package com.android.settings.applications.specialaccess.pictureinpicture;
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.ActivityInfo;
24 import android.content.pm.ApplicationInfo;
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.annotation.VisibleForTesting;
36 import androidx.preference.Preference;
37 import androidx.preference.Preference.OnPreferenceClickListener;
38 import androidx.preference.PreferenceScreen;
39 
40 import com.android.settings.R;
41 import com.android.settings.applications.AppInfoBase;
42 import com.android.settings.search.BaseSearchIndexProvider;
43 import com.android.settings.widget.EmptyTextSettings;
44 import com.android.settingslib.search.SearchIndexable;
45 import com.android.settingslib.widget.AppPreference;
46 
47 import java.text.Collator;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.List;
52 
53 @SearchIndexable
54 public class PictureInPictureSettings extends EmptyTextSettings {
55 
56     @VisibleForTesting
57     static final List<String> IGNORE_PACKAGE_LIST = new ArrayList<>();
58 
59     static {
60         IGNORE_PACKAGE_LIST.add("com.android.systemui");
61     }
62 
63     /**
64      * Comparator by name, then user id.
65      * {@see PackageItemInfo#DisplayNameComparator}
66      */
67     static class AppComparator implements Comparator<Pair<ApplicationInfo, Integer>> {
68 
69         private final Collator mCollator = Collator.getInstance();
70         private final PackageManager mPm;
71 
AppComparator(PackageManager pm)72         public AppComparator(PackageManager pm) {
73             mPm = pm;
74         }
75 
compare(Pair<ApplicationInfo, Integer> a, Pair<ApplicationInfo, Integer> b)76         public final int compare(Pair<ApplicationInfo, Integer> a,
77                 Pair<ApplicationInfo, Integer> b) {
78             CharSequence sa = a.first.loadLabel(mPm);
79             if (sa == null) sa = a.first.name;
80             CharSequence sb = b.first.loadLabel(mPm);
81             if (sb == null) sb = b.first.name;
82             int nameCmp = mCollator.compare(sa.toString(), sb.toString());
83             if (nameCmp != 0) {
84                 return nameCmp;
85             } else {
86                 return a.second - b.second;
87             }
88         }
89     }
90 
91     private Context mContext;
92     private PackageManager mPackageManager;
93     private UserManager mUserManager;
94     private IconDrawableFactory mIconDrawableFactory;
95 
96     /**
97      * @return true if the package has any activities that declare that they support
98      * picture-in-picture.
99      */
100 
checkPackageHasPictureInPictureActivities(String packageName, ActivityInfo[] activities)101     public static boolean checkPackageHasPictureInPictureActivities(String packageName,
102             ActivityInfo[] activities) {
103         // Skip if it's in the ignored list
104         if (IGNORE_PACKAGE_LIST.contains(packageName)) {
105             return false;
106         }
107 
108         // Iterate through all the activities and check if it is resizeable and supports
109         // picture-in-picture
110         if (activities != null) {
111             for (int i = activities.length - 1; i >= 0; i--) {
112                 if (activities[i].supportsPictureInPicture()) {
113                     return true;
114                 }
115             }
116         }
117         return false;
118     }
119 
PictureInPictureSettings()120     public PictureInPictureSettings() {
121         // Do nothing
122     }
123 
PictureInPictureSettings(PackageManager pm, UserManager um)124     public PictureInPictureSettings(PackageManager pm, UserManager um) {
125         mPackageManager = pm;
126         mUserManager = um;
127     }
128 
129     @Override
onCreate(Bundle icicle)130     public void onCreate(Bundle icicle) {
131         super.onCreate(icicle);
132 
133         mContext = getActivity();
134         mPackageManager = mContext.getPackageManager();
135         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
136         mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
137     }
138 
139     @Override
onResume()140     public void onResume() {
141         super.onResume();
142 
143         // Clear the prefs
144         final PreferenceScreen screen = getPreferenceScreen();
145         screen.removeAll();
146 
147         // Fetch the set of applications for each profile which have at least one activity that
148         // declare that they support picture-in-picture
149         final ArrayList<Pair<ApplicationInfo, Integer>> pipApps =
150                 collectPipApps(UserHandle.myUserId());
151         Collections.sort(pipApps, new AppComparator(mPackageManager));
152 
153         // Rebuild the list of prefs
154         final Context prefContext = getPrefContext();
155         for (final Pair<ApplicationInfo, Integer> appData : pipApps) {
156             final ApplicationInfo appInfo = appData.first;
157             final int userId = appData.second;
158             final UserHandle user = UserHandle.of(userId);
159             final String packageName = appInfo.packageName;
160             final CharSequence label = appInfo.loadLabel(mPackageManager);
161 
162             final Preference pref = new AppPreference(prefContext);
163             pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, userId));
164             pref.setTitle(mPackageManager.getUserBadgedLabel(label, user));
165             pref.setSummary(PictureInPictureDetails.getPreferenceSummary(prefContext,
166                     appInfo.uid, packageName));
167             pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
168                 @Override
169                 public boolean onPreferenceClick(Preference preference) {
170                     AppInfoBase.startAppInfoFragment(PictureInPictureDetails.class,
171                             R.string.picture_in_picture_app_detail_title, packageName, appInfo.uid,
172                             PictureInPictureSettings.this, -1, getMetricsCategory());
173                     return true;
174                 }
175             });
176             screen.addPreference(pref);
177         }
178     }
179 
180     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)181     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
182         super.onViewCreated(view, savedInstanceState);
183         setEmptyText(R.string.picture_in_picture_empty_text);
184     }
185 
186     @Override
getPreferenceScreenResId()187     protected int getPreferenceScreenResId() {
188         return R.xml.picture_in_picture_settings;
189     }
190 
191     @Override
getMetricsCategory()192     public int getMetricsCategory() {
193         return SettingsEnums.SETTINGS_MANAGE_PICTURE_IN_PICTURE;
194     }
195 
196     /**
197      * @return the list of applications for the given user and all their profiles that have
198      * activities which support PiP.
199      */
collectPipApps(int userId)200     ArrayList<Pair<ApplicationInfo, Integer>> collectPipApps(int userId) {
201         final ArrayList<Pair<ApplicationInfo, Integer>> pipApps = new ArrayList<>();
202         final ArrayList<Integer> userIds = new ArrayList<>();
203         for (UserInfo user : mUserManager.getProfiles(userId)) {
204             userIds.add(user.id);
205         }
206 
207         for (int id : userIds) {
208             final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser(
209                     GET_ACTIVITIES, id);
210             for (PackageInfo packageInfo : installedPackages) {
211                 if (checkPackageHasPictureInPictureActivities(packageInfo.packageName,
212                         packageInfo.activities)) {
213                     pipApps.add(new Pair<>(packageInfo.applicationInfo, id));
214                 }
215             }
216         }
217         return pipApps;
218     }
219 
220     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
221             new BaseSearchIndexProvider(R.xml.picture_in_picture_settings);
222 }
223