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