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 AppRestrictionsHelper(Context context, UserHandle user)66 public AppRestrictionsHelper(Context context, UserHandle user) { 67 this(new Injector(context, user)); 68 } 69 70 @VisibleForTesting AppRestrictionsHelper(Injector injector)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 setPackageSelected(String packageName, boolean selected)81 public void setPackageSelected(String packageName, boolean selected) { 82 mSelectedPackages.put(packageName, selected); 83 } 84 isPackageSelected(String packageName)85 public boolean isPackageSelected(String packageName) { 86 return mSelectedPackages.get(packageName); 87 } 88 setLeanback(boolean isLeanback)89 public void setLeanback(boolean isLeanback) { 90 mLeanback = isLeanback; 91 } 92 getVisibleApps()93 public List<SelectableAppInfo> getVisibleApps() { 94 return mVisibleApps; 95 } 96 applyUserAppsStates(OnDisableUiForPackageListener listener)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 applyUserAppState(String packageName, boolean enabled, OnDisableUiForPackageListener listener)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 // Denylist 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 fetchAndMergeApps()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 primary/secondary 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.primaryEntry = 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 */ addSystemImes(Set<String> excludePackages)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 */ addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, Set<String> excludePackages)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 isSystemPackage(String packageName)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 getAppInfoForUser(String packageName, int flags, UserHandle user)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 { onDisableUiForPackage(String packageName)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 primaryEntry; 370 371 @Override toString()372 public String toString() { 373 return packageName + ": appName=" + appName + "; activityName=" + activityName 374 + "; icon=" + icon + "; primaryEntry=" + primaryEntry; 375 } 376 } 377 378 private static class AppLabelComparator implements Comparator<SelectableAppInfo> { 379 380 @Override compare(SelectableAppInfo lhs, SelectableAppInfo rhs)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 Injector(Context context, UserHandle user)396 Injector(Context context, UserHandle user) { 397 mContext = context; 398 mUser = user; 399 } 400 getContext()401 Context getContext() { 402 return mContext; 403 } 404 getUser()405 UserHandle getUser() { 406 return mUser; 407 } 408 getPackageManager()409 PackageManager getPackageManager() { 410 return mContext.getPackageManager(); 411 } 412 getIPackageManager()413 IPackageManager getIPackageManager() { 414 return AppGlobals.getPackageManager(); 415 } 416 getUserManager()417 UserManager getUserManager() { 418 return mContext.getSystemService(UserManager.class); 419 } 420 getInputMethodList()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