| /* |
| * 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 androidx.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<String,Boolean> mSelectedPackages = new HashMap<>(); |
| private List<SelectableAppInfo> 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<SelectableAppInfo> 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<String,Boolean> 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<String> 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<ApplicationInfo> 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<ApplicationInfo> userApps = null; |
| try { |
| ParceledListSlice<ApplicationInfo> 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<String> dedupPackageSet = new HashSet<String>(); |
| 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<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>(); |
| 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<String> excludePackages) { |
| List<InputMethodInfo> 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<SelectableAppInfo> visibleApps, Intent intent, |
| Set<String> excludePackages) { |
| final PackageManager pm = mPackageManager; |
| List<ResolveInfo> 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<SelectableAppInfo> { |
| |
| @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<InputMethodInfo> getInputMethodList() { |
| InputMethodManager imm = (InputMethodManager) getContext().getSystemService( |
| Context.INPUT_METHOD_SERVICE); |
| return imm.getInputMethodList(); |
| } |
| } |
| } |