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