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