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