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