AppRestrictionsHelper.java revision e662ca6b5ec066384d1082fd3f43d50cdea37c68
1/*
2 * Copyright (C) 2016 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
17package com.android.settingslib.users;
18
19import android.app.AppGlobals;
20import android.appwidget.AppWidgetManager;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.IPackageManager;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.ParceledListSlice;
28import android.content.pm.ResolveInfo;
29import android.content.res.Resources;
30import android.graphics.drawable.Drawable;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.os.UserHandle;
34import android.os.UserManager;
35import android.text.TextUtils;
36import android.util.Log;
37import android.view.inputmethod.InputMethodInfo;
38import android.view.inputmethod.InputMethodManager;
39
40import java.util.ArrayList;
41import java.util.Collections;
42import java.util.Comparator;
43import java.util.HashMap;
44import java.util.HashSet;
45import java.util.List;
46import java.util.Map;
47import java.util.Set;
48
49public class AppRestrictionsHelper {
50    private static final boolean DEBUG = false;
51    private static final String TAG = "AppRestrictionsHelper";
52
53    private final Context mContext;
54    private final PackageManager mPackageManager;
55    private final IPackageManager mIPm;
56    private final UserManager mUserManager;
57    private final UserHandle mUser;
58    private final boolean mRestrictedProfile;
59
60    HashMap<String,Boolean> mSelectedPackages = new HashMap<>();
61    private List<SelectableAppInfo> mVisibleApps;
62
63    public AppRestrictionsHelper(Context context, UserHandle user) {
64        mContext = context;
65        mPackageManager = context.getPackageManager();
66        mIPm = AppGlobals.getPackageManager();
67        mUser = user;
68        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
69        mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted();
70    }
71
72    public void setPackageSelected(String packageName, boolean selected) {
73        mSelectedPackages.put(packageName, selected);
74    }
75
76    public boolean isPackageSelected(String packageName) {
77        return mSelectedPackages.get(packageName);
78    }
79
80    public List<SelectableAppInfo> getVisibleApps() {
81        return mVisibleApps;
82    }
83
84    public void applyUserAppsStates(OnDisableUiForPackageListener listener) {
85        final int userId = mUser.getIdentifier();
86        if (!mUserManager.getUserInfo(userId).isRestricted() && userId != UserHandle.myUserId()) {
87            Log.e(TAG, "Cannot apply application restrictions on another user!");
88            return;
89        }
90        for (Map.Entry<String,Boolean> entry : mSelectedPackages.entrySet()) {
91            String packageName = entry.getKey();
92            boolean enabled = entry.getValue();
93            applyUserAppState(packageName, enabled, listener);
94        }
95    }
96
97    public void applyUserAppState(String packageName, boolean enabled,
98            OnDisableUiForPackageListener listener) {
99        final int userId = mUser.getIdentifier();
100        if (enabled) {
101            // Enable selected apps
102            try {
103                ApplicationInfo info = mIPm.getApplicationInfo(packageName,
104                        PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
105                if (info == null || !info.enabled
106                        || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
107                    mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier());
108                    if (DEBUG) {
109                        Log.d(TAG, "Installing " + packageName);
110                    }
111                }
112                if (info != null && (info.privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) != 0
113                        && (info.flags&ApplicationInfo.FLAG_INSTALLED) != 0) {
114                    listener.onDisableUiForPackage(packageName);
115                    mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId);
116                    if (DEBUG) {
117                        Log.d(TAG, "Unhiding " + packageName);
118                    }
119                }
120            } catch (RemoteException re) {
121                // Ignore
122            }
123        } else {
124            // Blacklist all other apps, system or downloaded
125            try {
126                ApplicationInfo info = mIPm.getApplicationInfo(packageName, 0, userId);
127                if (info != null) {
128                    if (mRestrictedProfile) {
129                        mIPm.deletePackageAsUser(packageName, null, mUser.getIdentifier(),
130                                PackageManager.DELETE_SYSTEM_APP);
131                        if (DEBUG) {
132                            Log.d(TAG, "Uninstalling " + packageName);
133                        }
134                    } else {
135                        listener.onDisableUiForPackage(packageName);
136                        mIPm.setApplicationHiddenSettingAsUser(packageName, true, userId);
137                        if (DEBUG) {
138                            Log.d(TAG, "Hiding " + packageName);
139                        }
140                    }
141                }
142            } catch (RemoteException re) {
143                // Ignore
144            }
145        }
146    }
147
148    public void fetchAndMergeApps() {
149        mVisibleApps = new ArrayList<>();
150        final PackageManager pm = mPackageManager;
151        final IPackageManager ipm = mIPm;
152
153        final HashSet<String> excludePackages = new HashSet<>();
154        addSystemImes(excludePackages);
155
156        // Add launchers
157        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
158        launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
159        addSystemApps(mVisibleApps, launcherIntent, excludePackages);
160
161        // Add widgets
162        Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
163        addSystemApps(mVisibleApps, widgetIntent, excludePackages);
164
165        List<ApplicationInfo> installedApps = pm.getInstalledApplications(
166                PackageManager.MATCH_UNINSTALLED_PACKAGES);
167        for (ApplicationInfo app : installedApps) {
168            // If it's not installed, skip
169            if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
170
171            if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
172                    && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
173                // Downloaded app
174                SelectableAppInfo info = new SelectableAppInfo();
175                info.packageName = app.packageName;
176                info.appName = app.loadLabel(pm);
177                info.activityName = info.appName;
178                info.icon = app.loadIcon(pm);
179                mVisibleApps.add(info);
180            } else {
181                try {
182                    PackageInfo pi = pm.getPackageInfo(app.packageName, 0);
183                    // If it's a system app that requires an account and doesn't see restricted
184                    // accounts, mark for removal. It might get shown in the UI if it has an icon
185                    // but will still be marked as false and immutable.
186                    if (mRestrictedProfile
187                            && pi.requiredAccountType != null && pi.restrictedAccountType == null) {
188                        mSelectedPackages.put(app.packageName, false);
189                    }
190                } catch (PackageManager.NameNotFoundException re) {
191                    // Skip
192                }
193            }
194        }
195
196        // Get the list of apps already installed for the user
197        List<ApplicationInfo> userApps = null;
198        try {
199            ParceledListSlice<ApplicationInfo> listSlice = ipm.getInstalledApplications(
200                    PackageManager.MATCH_UNINSTALLED_PACKAGES, mUser.getIdentifier());
201            if (listSlice != null) {
202                userApps = listSlice.getList();
203            }
204        } catch (RemoteException re) {
205            // Ignore
206        }
207
208        if (userApps != null) {
209            for (ApplicationInfo app : userApps) {
210                if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
211
212                if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
213                        && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
214                    // Downloaded app
215                    SelectableAppInfo info = new SelectableAppInfo();
216                    info.packageName = app.packageName;
217                    info.appName = app.loadLabel(pm);
218                    info.activityName = info.appName;
219                    info.icon = app.loadIcon(pm);
220                    mVisibleApps.add(info);
221                }
222            }
223        }
224
225        // Sort the list of visible apps
226        Collections.sort(mVisibleApps, new AppLabelComparator());
227
228        // Remove dupes
229        Set<String> dedupPackageSet = new HashSet<String>();
230        for (int i = mVisibleApps.size() - 1; i >= 0; i--) {
231            SelectableAppInfo info = mVisibleApps.get(i);
232            if (DEBUG) Log.i(TAG, info.toString());
233            String both = info.packageName + "+" + info.activityName;
234            if (!TextUtils.isEmpty(info.packageName)
235                    && !TextUtils.isEmpty(info.activityName)
236                    && dedupPackageSet.contains(both)) {
237                mVisibleApps.remove(i);
238            } else {
239                dedupPackageSet.add(both);
240            }
241        }
242
243        // Establish master/slave relationship for entries that share a package name
244        HashMap<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>();
245        for (SelectableAppInfo info : mVisibleApps) {
246            if (packageMap.containsKey(info.packageName)) {
247                info.masterEntry = packageMap.get(info.packageName);
248            } else {
249                packageMap.put(info.packageName, info);
250            }
251        }
252    }
253
254    /**
255     * Find all pre-installed input methods that are marked as default
256     * and add them to an exclusion list so that they aren't
257     * presented to the user for toggling.
258     * Don't add non-default ones, as they may include other stuff that we
259     * don't need to auto-include.
260     * @param excludePackages the set of package names to append to
261     */
262    private void addSystemImes(Set<String> excludePackages) {
263        InputMethodManager imm = (InputMethodManager)
264                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
265        List<InputMethodInfo> imis = imm.getInputMethodList();
266        for (InputMethodInfo imi : imis) {
267            try {
268                if (imi.isDefault(mContext) && isSystemPackage(imi.getPackageName())) {
269                    excludePackages.add(imi.getPackageName());
270                }
271            } catch (Resources.NotFoundException rnfe) {
272                // Not default
273            }
274        }
275    }
276
277    /**
278     * Add system apps that match an intent to the list, excluding any packages in the exclude list.
279     * @param visibleApps list of apps to append the new list to
280     * @param intent the intent to match
281     * @param excludePackages the set of package names to be excluded, since they're required
282     */
283    private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent,
284            Set<String> excludePackages) {
285        final PackageManager pm = mPackageManager;
286        List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent,
287                PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_UNINSTALLED_PACKAGES);
288        for (ResolveInfo app : launchableApps) {
289            if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
290                final String packageName = app.activityInfo.packageName;
291                int flags = app.activityInfo.applicationInfo.flags;
292                if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
293                        || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
294                    // System app
295                    // Skip excluded packages
296                    if (excludePackages.contains(packageName)) continue;
297                    int enabled = pm.getApplicationEnabledSetting(packageName);
298                    if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
299                            || enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
300                        // Check if the app is already enabled for the target user
301                        ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName,
302                                0, mUser);
303                        if (targetUserAppInfo == null
304                                || (targetUserAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
305                            continue;
306                        }
307                    }
308                    SelectableAppInfo info = new SelectableAppInfo();
309                    info.packageName = app.activityInfo.packageName;
310                    info.appName = app.activityInfo.applicationInfo.loadLabel(pm);
311                    info.icon = app.activityInfo.loadIcon(pm);
312                    info.activityName = app.activityInfo.loadLabel(pm);
313                    if (info.activityName == null) info.activityName = info.appName;
314
315                    visibleApps.add(info);
316                }
317            }
318        }
319    }
320
321    private boolean isSystemPackage(String packageName) {
322        try {
323            final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
324            if (pi.applicationInfo == null) return false;
325            final int flags = pi.applicationInfo.flags;
326            if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
327                    || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
328                return true;
329            }
330        } catch (PackageManager.NameNotFoundException nnfe) {
331            // Missing package?
332        }
333        return false;
334    }
335
336    private ApplicationInfo getAppInfoForUser(String packageName, int flags, UserHandle user) {
337        try {
338            return mIPm.getApplicationInfo(packageName, flags, user.getIdentifier());
339        } catch (RemoteException re) {
340            return null;
341        }
342    }
343
344    public interface OnDisableUiForPackageListener {
345        void onDisableUiForPackage(String packageName);
346    }
347
348    public static class SelectableAppInfo {
349        public String packageName;
350        public CharSequence appName;
351        public CharSequence activityName;
352        public Drawable icon;
353        public SelectableAppInfo masterEntry;
354
355        @Override
356        public String toString() {
357            return packageName + ": appName=" + appName + "; activityName=" + activityName
358                    + "; icon=" + icon + "; masterEntry=" + masterEntry;
359        }
360    }
361
362    private static class AppLabelComparator implements Comparator<SelectableAppInfo> {
363
364        @Override
365        public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
366            String lhsLabel = lhs.activityName.toString();
367            String rhsLabel = rhs.activityName.toString();
368            return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
369        }
370    }
371}
372