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