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 AppRestrictionsHelper(Context context, UserHandle user)65 public AppRestrictionsHelper(Context context, UserHandle user) { 66 this(new Injector(context, user)); 67 } 68 69 @VisibleForTesting AppRestrictionsHelper(Injector injector)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 setPackageSelected(String packageName, boolean selected)80 public void setPackageSelected(String packageName, boolean selected) { 81 mSelectedPackages.put(packageName, selected); 82 } 83 isPackageSelected(String packageName)84 public boolean isPackageSelected(String packageName) { 85 return mSelectedPackages.get(packageName); 86 } 87 setLeanback(boolean isLeanback)88 public void setLeanback(boolean isLeanback) { 89 mLeanback = isLeanback; 90 } 91 getVisibleApps()92 public List<SelectableAppInfo> getVisibleApps() { 93 return mVisibleApps; 94 } 95 applyUserAppsStates(OnDisableUiForPackageListener listener)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 applyUserAppState(String packageName, boolean enabled, OnDisableUiForPackageListener listener)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 fetchAndMergeApps()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 */ addSystemImes(Set<String> excludePackages)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 */ addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, Set<String> excludePackages)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 isSystemPackage(String packageName)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 getAppInfoForUser(String packageName, int flags, UserHandle user)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 { onDisableUiForPackage(String packageName)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 toString()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 compare(SelectableAppInfo lhs, SelectableAppInfo rhs)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 Injector(Context context, UserHandle user)394 Injector(Context context, UserHandle user) { 395 mContext = context; 396 mUser = user; 397 } 398 getContext()399 Context getContext() { 400 return mContext; 401 } 402 getUser()403 UserHandle getUser() { 404 return mUser; 405 } 406 getPackageManager()407 PackageManager getPackageManager() { 408 return mContext.getPackageManager(); 409 } 410 getIPackageManager()411 IPackageManager getIPackageManager() { 412 return AppGlobals.getPackageManager(); 413 } 414 getUserManager()415 UserManager getUserManager() { 416 return mContext.getSystemService(UserManager.class); 417 } 418 getInputMethodList()419 List<InputMethodInfo> getInputMethodList() { 420 InputMethodManager imm = (InputMethodManager) getContext().getSystemService( 421 Context.INPUT_METHOD_SERVICE); 422 return imm.getInputMethodList(); 423 } 424 } 425 } 426