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