1 /* 2 * Copyright (C) 2014 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.tv.settings.users; 18 19 import android.appwidget.AppWidgetManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.Intent.ShortcutIconResource; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.IPackageManager; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageItemInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.content.pm.ResolveInfo; 30 import android.content.res.Resources; 31 import android.net.Uri; 32 import android.os.AsyncTask; 33 import android.os.RemoteException; 34 import android.service.dreams.DreamService; 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 com.android.tv.settings.dialog.DialogFragment.Action; 41 import com.android.tv.settings.util.UriUtils; 42 43 import java.util.ArrayList; 44 import java.util.Collections; 45 import java.util.Comparator; 46 import java.util.HashMap; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Set; 50 51 class AppLoadingTask extends AsyncTask<Void, Void, List<AppLoadingTask.SelectableAppInfo>> { 52 53 interface Listener { onPackageEnableChanged(String packageName, boolean enabled)54 void onPackageEnableChanged(String packageName, boolean enabled); 55 onActionsLoaded(ArrayList<Action> actions)56 void onActionsLoaded(ArrayList<Action> actions); 57 } 58 59 private static final boolean DEBUG = false; 60 private static final String TAG = "RestrictedProfile"; 61 62 private final Context mContext; 63 private final int mUserId; 64 private final boolean mNewUser; 65 private final PackageManager mPackageManager; 66 private final IPackageManager mIPackageManager; 67 private final Listener mListener; 68 private final PackageInfo mSysPackageInfo; 69 private final HashMap<String, Boolean> mSelectedPackages = new HashMap<String, Boolean>(); 70 private boolean mFirstTime = true; 71 72 /** 73 * Loads the list of activities that the user can enable or disable in a restricted profile. 74 * 75 * @param context context for querying the list of activities. 76 * @param userId the user ID of the user whose apps should be listed. 77 * @param newUser true if this is a newly create user. 78 * @param iPackageManager used to get application info. 79 * @param listener listener for package enable state changes. 80 */ AppLoadingTask(Context context, int userId, boolean newUser, IPackageManager iPackageManager, Listener listener)81 AppLoadingTask(Context context, int userId, boolean newUser, IPackageManager iPackageManager, 82 Listener listener) { 83 mContext = context; 84 mUserId = userId; 85 mNewUser = newUser; 86 mPackageManager = context.getPackageManager(); 87 mIPackageManager = iPackageManager; 88 mListener = listener; 89 PackageInfo sysPackageInfo = null; 90 try { 91 sysPackageInfo = mPackageManager.getPackageInfo("android", 92 PackageManager.GET_SIGNATURES); 93 } catch (NameNotFoundException nnfe) { 94 Log.wtf(TAG, "Failed to get package signatures!"); 95 } 96 mSysPackageInfo = sysPackageInfo; 97 } 98 99 @Override doInBackground(Void... params)100 protected List<SelectableAppInfo> doInBackground(Void... params) { 101 return fetchAndMergeApps(); 102 } 103 104 @Override onPostExecute(List<SelectableAppInfo> visibleApps)105 protected void onPostExecute(List<SelectableAppInfo> visibleApps) { 106 populateApps(visibleApps); 107 } 108 populateApps(List<SelectableAppInfo> visibleApps)109 private void populateApps(List<SelectableAppInfo> visibleApps) { 110 ArrayList<Action> actions = new ArrayList<Action>(); 111 Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 112 List<ResolveInfo> receivers = mPackageManager.queryBroadcastReceivers(restrictionsIntent, 113 0); 114 for (SelectableAppInfo app : visibleApps) { 115 String packageName = app.packageName; 116 if (packageName == null) { 117 if (DEBUG) { 118 Log.d(TAG, "App has no package name: " + app.appName); 119 } 120 continue; 121 } 122 final boolean isSettingsApp = packageName.equals(mContext.getPackageName()); 123 final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName); 124 boolean isAllowed = false; 125 String controllingActivity = null; 126 if (app.masterEntry != null) { 127 controllingActivity = app.masterEntry.activityName.toString(); 128 } 129 boolean hasCustomizableRestrictions = ((hasSettings || isSettingsApp) 130 && app.masterEntry == null); 131 PackageInfo pi = null; 132 try { 133 pi = mIPackageManager.getPackageInfo(packageName, 134 PackageManager.GET_UNINSTALLED_PACKAGES 135 | PackageManager.GET_SIGNATURES, mUserId); 136 } catch (RemoteException e) { 137 } 138 boolean canBeEnabledDisabled = true; 139 if (pi != null && (pi.requiredForAllUsers || isPlatformSigned(pi))) { 140 isAllowed = true; 141 canBeEnabledDisabled = false; 142 // If the app is required and has no restrictions, skip showing it 143 if (!hasSettings && !isSettingsApp) { 144 if (DEBUG) { 145 Log.d(TAG, "App is required and has no settings: " + app.appName); 146 } 147 continue; 148 } 149 // Get and populate the defaults, since the user is not going to be 150 // able to toggle this app ON (it's ON by default and immutable). 151 // Only do this for restricted profiles, not single-user restrictions 152 // Also don't do this for slave icons 153 } else if (!mNewUser && isAppEnabledForUser(pi)) { 154 isAllowed = true; 155 } 156 boolean availableForRestrictedProfile = true; 157 if (pi.requiredAccountType != null && pi.restrictedAccountType == null) { 158 availableForRestrictedProfile = false; 159 isAllowed = false; 160 canBeEnabledDisabled = false; 161 } 162 boolean canSeeRestrictedAccounts = pi.restrictedAccountType != null; 163 if (app.masterEntry != null) { 164 canBeEnabledDisabled = false; 165 isAllowed = mSelectedPackages.get(packageName); 166 } 167 onPackageEnableChanged(packageName, isAllowed); 168 if (DEBUG) { 169 Log.d(TAG, "Adding action for: " + app.appName + " has restrictions: " 170 + hasCustomizableRestrictions); 171 } 172 actions.add(UserAppRestrictionsDialogFragment.createAction(mContext, packageName, 173 app.activityName.toString(), getAppIconUri(mContext, app.info, app.iconRes), 174 canBeEnabledDisabled, isAllowed, hasCustomizableRestrictions, 175 canSeeRestrictedAccounts, availableForRestrictedProfile, controllingActivity)); 176 } 177 mListener.onActionsLoaded(actions); 178 // If this is the first time for a new profile, install/uninstall default apps for 179 // profile 180 // to avoid taking the hit in onPause(), which can cause race conditions on user switch. 181 if (mNewUser && mFirstTime) { 182 mFirstTime = false; 183 UserAppRestrictionsDialogFragment.applyUserAppsStates(mSelectedPackages, actions, 184 mIPackageManager, mUserId); 185 } 186 } 187 onPackageEnableChanged(String packageName, boolean enabled)188 private void onPackageEnableChanged(String packageName, boolean enabled) { 189 mListener.onPackageEnableChanged(packageName, enabled); 190 mSelectedPackages.put(packageName, enabled); 191 } 192 resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName)193 private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) { 194 for (ResolveInfo info : receivers) { 195 if (info.activityInfo.packageName.equals(packageName)) { 196 return true; 197 } 198 } 199 return false; 200 } 201 fetchAndMergeApps()202 private List<SelectableAppInfo> fetchAndMergeApps() { 203 List<SelectableAppInfo> visibleApps = new ArrayList<SelectableAppInfo>(); 204 205 // Find all pre-installed input methods that are marked as default and add them to an 206 // exclusion list so that they aren't presented to the user for toggling. Don't add 207 // non-default ones, as they may include other stuff that we don't need to auto-include. 208 final HashSet<String> defaultSystemImes = getDefaultSystemImes(); 209 210 // Add Settings 211 try { 212 visibleApps.add(new SelectableAppInfo(mPackageManager, 213 mPackageManager.getApplicationInfo(mContext.getPackageName(), 0))); 214 } catch (NameNotFoundException nnfe) { 215 Log.e(TAG, "Couldn't add settings item to list!", nnfe); 216 } 217 218 // Add leanback launchers 219 Intent leanbackLauncherIntent = new Intent(Intent.ACTION_MAIN); 220 leanbackLauncherIntent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER); 221 addSystemApps(visibleApps, leanbackLauncherIntent, defaultSystemImes, mUserId); 222 223 // Add widgets 224 Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 225 addSystemApps(visibleApps, widgetIntent, defaultSystemImes, mUserId); 226 227 // Add daydreams 228 Intent daydreamIntent = new Intent(DreamService.SERVICE_INTERFACE); 229 addSystemApps(visibleApps, daydreamIntent, defaultSystemImes, mUserId); 230 231 List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications( 232 PackageManager.GET_UNINSTALLED_PACKAGES); 233 addNonSystemApps(installedApps, true, visibleApps); 234 235 // Get the list of apps already installed for the user 236 try { 237 List<ApplicationInfo> userApps = mIPackageManager.getInstalledApplications( 238 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList(); 239 addNonSystemApps(userApps, false, visibleApps); 240 } catch (RemoteException re) { 241 } 242 243 // Sort the list of visible apps 244 Collections.sort(visibleApps, new AppLabelComparator()); 245 246 // Remove dupes 247 Set<String> dedupPackageSet = new HashSet<String>(); 248 for (int i = visibleApps.size() - 1; i >= 0; i--) { 249 SelectableAppInfo info = visibleApps.get(i); 250 if (DEBUG) { 251 Log.i(TAG, info.toString()); 252 } 253 String both = info.packageName + "+" + info.activityName; 254 if (!TextUtils.isEmpty(info.packageName) 255 && !TextUtils.isEmpty(info.activityName) 256 && dedupPackageSet.contains(both)) { 257 if (DEBUG) { 258 Log.d(TAG, "Removing app: " + info.appName); 259 } 260 visibleApps.remove(i); 261 } else { 262 dedupPackageSet.add(both); 263 } 264 } 265 266 // Establish master/slave relationship for entries that share a package name 267 HashMap<String, SelectableAppInfo> packageMap = new HashMap<String, 268 SelectableAppInfo>(); 269 for (SelectableAppInfo info : visibleApps) { 270 if (packageMap.containsKey(info.packageName)) { 271 info.masterEntry = packageMap.get(info.packageName); 272 } else { 273 packageMap.put(info.packageName, info); 274 } 275 } 276 return visibleApps; 277 } 278 addNonSystemApps(List<ApplicationInfo> apps, boolean disableSystemApps, List<SelectableAppInfo> visibleApps)279 private void addNonSystemApps(List<ApplicationInfo> apps, boolean disableSystemApps, 280 List<SelectableAppInfo> visibleApps) { 281 if (apps == null) { 282 return; 283 } 284 285 for (ApplicationInfo app : apps) { 286 // If it's not installed, skip 287 if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 288 continue; 289 } 290 291 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 292 && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { 293 // Downloaded app 294 visibleApps.add(new SelectableAppInfo(mPackageManager, app)); 295 } else if (disableSystemApps) { 296 try { 297 PackageInfo pi = mPackageManager.getPackageInfo(app.packageName, 0); 298 // If it's a system app that requires an account and doesn't see restricted 299 // accounts, mark for removal. It might get shown in the UI if it has an 300 // icon but will still be marked as false and immutable. 301 if (pi.requiredAccountType != null && pi.restrictedAccountType == null) { 302 onPackageEnableChanged(app.packageName, false); 303 } 304 } catch (NameNotFoundException re) { 305 } 306 } 307 } 308 } 309 310 static class SelectableAppInfo { 311 private final String packageName; 312 private final CharSequence appName; 313 private final CharSequence activityName; 314 private final ApplicationInfo info; 315 private final int iconRes; 316 private SelectableAppInfo masterEntry; 317 SelectableAppInfo(PackageManager packageManager, ResolveInfo resolveInfo)318 SelectableAppInfo(PackageManager packageManager, ResolveInfo resolveInfo) { 319 packageName = resolveInfo.activityInfo.packageName; 320 appName = resolveInfo.activityInfo.applicationInfo.loadLabel(packageManager); 321 CharSequence label = resolveInfo.activityInfo.loadLabel(packageManager); 322 activityName = (label != null) ? label : appName; 323 int activityIconRes = getIconResource(resolveInfo.activityInfo); 324 info = resolveInfo.activityInfo.applicationInfo; 325 iconRes = activityIconRes != 0 ? activityIconRes 326 : getIconResource(resolveInfo.activityInfo.applicationInfo); 327 } 328 SelectableAppInfo(PackageManager packageManager, ApplicationInfo applicationInfo)329 SelectableAppInfo(PackageManager packageManager, ApplicationInfo applicationInfo) { 330 packageName = applicationInfo.packageName; 331 appName = applicationInfo.loadLabel(packageManager); 332 activityName = appName; 333 info = applicationInfo; 334 iconRes = getIconResource(applicationInfo); 335 } 336 337 @Override toString()338 public String toString() { 339 return packageName + ": appName=" + appName + "; activityName=" + activityName 340 + "; masterEntry=" + masterEntry; 341 } 342 getIconResource(PackageItemInfo packageItemInfo)343 private int getIconResource(PackageItemInfo packageItemInfo) { 344 if (packageItemInfo.banner != 0) { 345 return packageItemInfo.banner; 346 } 347 if (packageItemInfo.logo != 0) { 348 return packageItemInfo.logo; 349 } 350 return packageItemInfo.icon; 351 } 352 } 353 354 private static class AppLabelComparator implements Comparator<SelectableAppInfo> { 355 356 @Override compare(SelectableAppInfo lhs, SelectableAppInfo rhs)357 public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) { 358 String lhsLabel = lhs.activityName.toString(); 359 String rhsLabel = rhs.activityName.toString(); 360 return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase()); 361 } 362 } 363 364 /** 365 * Find all pre-installed input methods that are marked as default and add them to an exclusion 366 * list so that they aren't presented to the user for toggling. Don't add non-default ones, as 367 * they may include other stuff that we don't need to auto-include. 368 * 369 * @return the set of default system imes 370 */ getDefaultSystemImes()371 private HashSet<String> getDefaultSystemImes() { 372 HashSet<String> defaultSystemImes = new HashSet<String>(); 373 InputMethodManager imm = (InputMethodManager) 374 mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 375 List<InputMethodInfo> imis = imm.getInputMethodList(); 376 for (InputMethodInfo imi : imis) { 377 try { 378 if (imi.isDefault(mContext) && isSystemPackage(imi.getPackageName())) { 379 defaultSystemImes.add(imi.getPackageName()); 380 } 381 } catch (Resources.NotFoundException rnfe) { 382 // Not default 383 } 384 } 385 return defaultSystemImes; 386 } 387 isSystemPackage(String packageName)388 private boolean isSystemPackage(String packageName) { 389 try { 390 final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0); 391 if (pi.applicationInfo == null) 392 return false; 393 final int flags = pi.applicationInfo.flags; 394 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 395 || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { 396 return true; 397 } 398 } catch (NameNotFoundException nnfe) { 399 // Missing package? 400 } 401 return false; 402 } 403 404 /** 405 * Add system apps that match an intent to the list, excluding any packages in the exclude list. 406 * 407 * @param visibleApps list of apps to append the new list to 408 * @param intent the intent to match 409 * @param excludePackages the set of package names to be excluded, since they're required 410 */ addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, Set<String> excludePackages, int userId)411 private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, 412 Set<String> excludePackages, int userId) { 413 final PackageManager pm = mPackageManager; 414 List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent, 415 PackageManager.GET_DISABLED_COMPONENTS 416 | PackageManager.GET_UNINSTALLED_PACKAGES); 417 for (ResolveInfo app : launchableApps) { 418 if (app.activityInfo != null && app.activityInfo.applicationInfo != null) { 419 final String packageName = app.activityInfo.packageName; 420 int flags = app.activityInfo.applicationInfo.flags; 421 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 422 || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { 423 if (DEBUG) { 424 Log.d(TAG, "Found system app: " 425 + app.activityInfo.applicationInfo.loadLabel(pm)); 426 } 427 // System app 428 // Skip excluded packages 429 if (excludePackages.contains(packageName)) { 430 if (DEBUG) { 431 Log.d(TAG, "App is an excluded ime, not adding: " 432 + app.activityInfo.applicationInfo.loadLabel(pm)); 433 } 434 continue; 435 } 436 int enabled = pm.getApplicationEnabledSetting(packageName); 437 if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED 438 || enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { 439 // Check if the app is already enabled for the target user 440 ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName, 441 0, userId); 442 if (targetUserAppInfo == null 443 || (targetUserAppInfo.flags & ApplicationInfo.FLAG_INSTALLED) 444 == 0) { 445 if (DEBUG) { 446 Log.d(TAG, "App is already something, not adding: " 447 + app.activityInfo.applicationInfo.loadLabel(pm)); 448 } 449 continue; 450 } 451 } 452 453 if (DEBUG) { 454 Log.d(TAG, "Adding system app: " 455 + app.activityInfo.applicationInfo.loadLabel(pm)); 456 } 457 visibleApps.add(new SelectableAppInfo(pm, app)); 458 } 459 } 460 } 461 } 462 getAppInfoForUser(String packageName, int flags, int userId)463 private ApplicationInfo getAppInfoForUser(String packageName, int flags, int userId) { 464 try { 465 ApplicationInfo targetUserAppInfo = mIPackageManager.getApplicationInfo(packageName, 466 flags, 467 userId); 468 return targetUserAppInfo; 469 } catch (RemoteException re) { 470 return null; 471 } 472 } 473 isPlatformSigned(PackageInfo pi)474 private boolean isPlatformSigned(PackageInfo pi) { 475 return (pi != null && pi.signatures != null && 476 mSysPackageInfo.signatures[0].equals(pi.signatures[0])); 477 } 478 isAppEnabledForUser(PackageInfo pi)479 private boolean isAppEnabledForUser(PackageInfo pi) { 480 if (pi == null) 481 return false; 482 final int flags = pi.applicationInfo.flags; 483 final int privateFlags = pi.applicationInfo.privateFlags; 484 // Return true if it is installed and not hidden 485 return ((flags & ApplicationInfo.FLAG_INSTALLED) != 0 486 && (privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0); 487 } 488 getAppIconUri(Context context, ApplicationInfo info, int iconRes)489 private static Uri getAppIconUri(Context context, ApplicationInfo info, int iconRes) { 490 String iconUri = null; 491 if (iconRes != 0) { 492 try { 493 Resources resources = context.getPackageManager() 494 .getResourcesForApplication(info); 495 ShortcutIconResource iconResource = new ShortcutIconResource(); 496 iconResource.packageName = info.packageName; 497 iconResource.resourceName = resources.getResourceName(iconRes); 498 iconUri = UriUtils.getShortcutIconResourceUri(iconResource).toString(); 499 } catch (Exception e1) { 500 Log.w("AppsBrowseInfo", e1.toString()); 501 } 502 } else { 503 iconUri = UriUtils.getAndroidResourceUri(Resources.getSystem(), 504 com.android.internal.R.drawable.sym_def_app_icon); 505 } 506 507 if (iconUri == null) { 508 iconUri = UriUtils.getAndroidResourceUri(context.getResources(), 509 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); 510 } 511 return Uri.parse(iconUri); 512 } 513 } 514