/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.settingslib.users; import android.app.AppGlobals; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class AppRestrictionsHelper { private static final boolean DEBUG = false; private static final String TAG = "AppRestrictionsHelper"; private final Injector mInjector; private final Context mContext; private final PackageManager mPackageManager; private final IPackageManager mIPm; private final UserManager mUserManager; private final UserHandle mUser; private final boolean mRestrictedProfile; private boolean mLeanback; HashMap mSelectedPackages = new HashMap<>(); private List mVisibleApps; public AppRestrictionsHelper(Context context, UserHandle user) { this(new Injector(context, user)); } @VisibleForTesting AppRestrictionsHelper(Injector injector) { mInjector = injector; mContext = mInjector.getContext(); mPackageManager = mInjector.getPackageManager(); mIPm = mInjector.getIPackageManager(); mUser = mInjector.getUser(); mUserManager = mInjector.getUserManager(); mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted(); } public void setPackageSelected(String packageName, boolean selected) { mSelectedPackages.put(packageName, selected); } public boolean isPackageSelected(String packageName) { return mSelectedPackages.get(packageName); } public void setLeanback(boolean isLeanback) { mLeanback = isLeanback; } public List getVisibleApps() { return mVisibleApps; } public void applyUserAppsStates(OnDisableUiForPackageListener listener) { if (!mRestrictedProfile && mUser.getIdentifier() != UserHandle.myUserId()) { Log.e(TAG, "Cannot apply application restrictions on another user!"); return; } for (Map.Entry entry : mSelectedPackages.entrySet()) { String packageName = entry.getKey(); boolean enabled = entry.getValue(); applyUserAppState(packageName, enabled, listener); } } public void applyUserAppState(String packageName, boolean enabled, OnDisableUiForPackageListener listener) { final int userId = mUser.getIdentifier(); if (enabled) { // Enable selected apps try { ApplicationInfo info = mIPm.getApplicationInfo(packageName, PackageManager.MATCH_ANY_USER, userId); if (info == null || !info.enabled || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier(), 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN); if (DEBUG) { Log.d(TAG, "Installing " + packageName); } } if (info != null && (info.privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) != 0 && (info.flags&ApplicationInfo.FLAG_INSTALLED) != 0) { listener.onDisableUiForPackage(packageName); mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId); if (DEBUG) { Log.d(TAG, "Unhiding " + packageName); } } } catch (RemoteException re) { // Ignore } } else { // Blacklist all other apps, system or downloaded try { ApplicationInfo info = mIPm.getApplicationInfo(packageName, 0, userId); if (info != null) { if (mRestrictedProfile) { mPackageManager.deletePackageAsUser(packageName, null, PackageManager.DELETE_SYSTEM_APP, mUser.getIdentifier()); if (DEBUG) { Log.d(TAG, "Uninstalling " + packageName); } } else { listener.onDisableUiForPackage(packageName); mIPm.setApplicationHiddenSettingAsUser(packageName, true, userId); if (DEBUG) { Log.d(TAG, "Hiding " + packageName); } } } } catch (RemoteException re) { // Ignore } } } public void fetchAndMergeApps() { mVisibleApps = new ArrayList<>(); final PackageManager pm = mPackageManager; final IPackageManager ipm = mIPm; final HashSet excludePackages = new HashSet<>(); addSystemImes(excludePackages); // Add launchers Intent launcherIntent = new Intent(Intent.ACTION_MAIN); if (mLeanback) { launcherIntent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER); } else { launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); } addSystemApps(mVisibleApps, launcherIntent, excludePackages); // Add widgets Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); addSystemApps(mVisibleApps, widgetIntent, excludePackages); List installedApps = pm.getInstalledApplications( PackageManager.MATCH_ANY_USER); for (ApplicationInfo app : installedApps) { // If it's not installed, skip if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue; if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { // Downloaded app SelectableAppInfo info = new SelectableAppInfo(); info.packageName = app.packageName; info.appName = app.loadLabel(pm); info.activityName = info.appName; info.icon = app.loadIcon(pm); mVisibleApps.add(info); } else { try { PackageInfo pi = pm.getPackageInfo(app.packageName, 0); // If it's a system app that requires an account and doesn't see restricted // accounts, mark for removal. It might get shown in the UI if it has an icon // but will still be marked as false and immutable. if (mRestrictedProfile && pi.requiredAccountType != null && pi.restrictedAccountType == null) { mSelectedPackages.put(app.packageName, false); } } catch (PackageManager.NameNotFoundException re) { // Skip } } } // Get the list of apps already installed for the user List userApps = null; try { ParceledListSlice listSlice = ipm.getInstalledApplications( PackageManager.MATCH_UNINSTALLED_PACKAGES, mUser.getIdentifier()); if (listSlice != null) { userApps = listSlice.getList(); } } catch (RemoteException re) { // Ignore } if (userApps != null) { for (ApplicationInfo app : userApps) { if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue; if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { // Downloaded app SelectableAppInfo info = new SelectableAppInfo(); info.packageName = app.packageName; info.appName = app.loadLabel(pm); info.activityName = info.appName; info.icon = app.loadIcon(pm); mVisibleApps.add(info); } } } // Sort the list of visible apps Collections.sort(mVisibleApps, new AppLabelComparator()); // Remove dupes Set dedupPackageSet = new HashSet(); for (int i = mVisibleApps.size() - 1; i >= 0; i--) { SelectableAppInfo info = mVisibleApps.get(i); if (DEBUG) Log.i(TAG, info.toString()); String both = info.packageName + "+" + info.activityName; if (!TextUtils.isEmpty(info.packageName) && !TextUtils.isEmpty(info.activityName) && dedupPackageSet.contains(both)) { mVisibleApps.remove(i); } else { dedupPackageSet.add(both); } } // Establish master/slave relationship for entries that share a package name HashMap packageMap = new HashMap(); for (SelectableAppInfo info : mVisibleApps) { if (packageMap.containsKey(info.packageName)) { info.masterEntry = packageMap.get(info.packageName); } else { packageMap.put(info.packageName, info); } } } /** * Find all pre-installed input methods that are marked as default * and add them to an exclusion list so that they aren't * presented to the user for toggling. * Don't add non-default ones, as they may include other stuff that we * don't need to auto-include. * @param excludePackages the set of package names to append to */ private void addSystemImes(Set excludePackages) { List imis = mInjector.getInputMethodList(); for (InputMethodInfo imi : imis) { try { if (imi.isDefault(mContext) && isSystemPackage(imi.getPackageName())) { excludePackages.add(imi.getPackageName()); } } catch (Resources.NotFoundException rnfe) { // Not default } } } /** * Add system apps that match an intent to the list, excluding any packages in the exclude list. * @param visibleApps list of apps to append the new list to * @param intent the intent to match * @param excludePackages the set of package names to be excluded, since they're required */ private void addSystemApps(List visibleApps, Intent intent, Set excludePackages) { final PackageManager pm = mPackageManager; List launchableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_UNINSTALLED_PACKAGES); for (ResolveInfo app : launchableApps) { if (app.activityInfo != null && app.activityInfo.applicationInfo != null) { final String packageName = app.activityInfo.packageName; int flags = app.activityInfo.applicationInfo.flags; if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { // System app // Skip excluded packages if (excludePackages.contains(packageName)) continue; int enabled = pm.getApplicationEnabledSetting(packageName); if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED || enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { // Check if the app is already enabled for the target user ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName, 0, mUser); if (targetUserAppInfo == null || (targetUserAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { continue; } } SelectableAppInfo info = new SelectableAppInfo(); info.packageName = app.activityInfo.packageName; info.appName = app.activityInfo.applicationInfo.loadLabel(pm); info.icon = app.activityInfo.loadIcon(pm); info.activityName = app.activityInfo.loadLabel(pm); if (info.activityName == null) info.activityName = info.appName; visibleApps.add(info); } } } } private boolean isSystemPackage(String packageName) { try { final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0); if (pi.applicationInfo == null) return false; final int flags = pi.applicationInfo.flags; if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { return true; } } catch (PackageManager.NameNotFoundException nnfe) { // Missing package? } return false; } private ApplicationInfo getAppInfoForUser(String packageName, int flags, UserHandle user) { try { return mIPm.getApplicationInfo(packageName, flags, user.getIdentifier()); } catch (RemoteException re) { return null; } } public interface OnDisableUiForPackageListener { void onDisableUiForPackage(String packageName); } public static class SelectableAppInfo { public String packageName; public CharSequence appName; public CharSequence activityName; public Drawable icon; public SelectableAppInfo masterEntry; @Override public String toString() { return packageName + ": appName=" + appName + "; activityName=" + activityName + "; icon=" + icon + "; masterEntry=" + masterEntry; } } private static class AppLabelComparator implements Comparator { @Override public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) { String lhsLabel = lhs.activityName.toString(); String rhsLabel = rhs.activityName.toString(); return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase()); } } /** * Unit test will subclass it to inject mocks. */ @VisibleForTesting static class Injector { private Context mContext; private UserHandle mUser; Injector(Context context, UserHandle user) { mContext = context; mUser = user; } Context getContext() { return mContext; } UserHandle getUser() { return mUser; } PackageManager getPackageManager() { return mContext.getPackageManager(); } IPackageManager getIPackageManager() { return AppGlobals.getPackageManager(); } UserManager getUserManager() { return mContext.getSystemService(UserManager.class); } List getInputMethodList() { InputMethodManager imm = (InputMethodManager) getContext().getSystemService( Context.INPUT_METHOD_SERVICE); return imm.getInputMethodList(); } } }