blob: 10ac95cc2e53ad15d68cb3a923137f659f013034 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.settings.users;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.RemoteException;
import android.service.dreams.DreamService;
import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import com.android.tv.settings.dialog.DialogFragment.Action;
import com.android.tv.settings.util.UriUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class AppLoadingTask extends AsyncTask<Void, Void, List<AppLoadingTask.SelectableAppInfo>> {
interface Listener {
void onPackageEnableChanged(String packageName, boolean enabled);
void onActionsLoaded(ArrayList<Action> actions);
}
private static final boolean DEBUG = false;
private static final String TAG = "RestrictedProfile";
private final Context mContext;
private final int mUserId;
private final boolean mNewUser;
private final PackageManager mPackageManager;
private final IPackageManager mIPackageManager;
private final Listener mListener;
private final PackageInfo mSysPackageInfo;
private final HashMap<String, Boolean> mSelectedPackages = new HashMap<String, Boolean>();
private boolean mFirstTime = true;
/**
* Loads the list of activities that the user can enable or disable in a restricted profile.
*
* @param context context for querying the list of activities.
* @param userId the user ID of the user whose apps should be listed.
* @param newUser true if this is a newly create user.
* @param iPackageManager used to get application info.
* @param listener listener for package enable state changes.
*/
AppLoadingTask(Context context, int userId, boolean newUser, IPackageManager iPackageManager,
Listener listener) {
mContext = context;
mUserId = userId;
mNewUser = newUser;
mPackageManager = context.getPackageManager();
mIPackageManager = iPackageManager;
mListener = listener;
PackageInfo sysPackageInfo = null;
try {
sysPackageInfo = mPackageManager.getPackageInfo("android",
PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException nnfe) {
Log.wtf(TAG, "Failed to get package signatures!");
}
mSysPackageInfo = sysPackageInfo;
}
@Override
protected List<SelectableAppInfo> doInBackground(Void... params) {
return fetchAndMergeApps();
}
@Override
protected void onPostExecute(List<SelectableAppInfo> visibleApps) {
populateApps(visibleApps);
}
private void populateApps(List<SelectableAppInfo> visibleApps) {
ArrayList<Action> actions = new ArrayList<Action>();
Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
List<ResolveInfo> receivers = mPackageManager.queryBroadcastReceivers(restrictionsIntent,
0);
for (SelectableAppInfo app : visibleApps) {
String packageName = app.packageName;
if (packageName == null) {
if (DEBUG) {
Log.d(TAG, "App has no package name: " + app.appName);
}
continue;
}
final boolean isSettingsApp = packageName.equals(mContext.getPackageName());
final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName);
boolean isAllowed = false;
String controllingActivity = null;
if (app.masterEntry != null) {
controllingActivity = app.masterEntry.activityName.toString();
}
boolean hasCustomizableRestrictions = ((hasSettings || isSettingsApp)
&& app.masterEntry == null);
PackageInfo pi = null;
try {
pi = mIPackageManager.getPackageInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES
| PackageManager.GET_SIGNATURES, mUserId);
} catch (RemoteException e) {
}
boolean canBeEnabledDisabled = true;
if (pi != null && (pi.requiredForAllUsers || isPlatformSigned(pi))) {
isAllowed = true;
canBeEnabledDisabled = false;
// If the app is required and has no restrictions, skip showing it
if (!hasSettings && !isSettingsApp) {
if (DEBUG) {
Log.d(TAG, "App is required and has no settings: " + app.appName);
}
continue;
}
// Get and populate the defaults, since the user is not going to be
// able to toggle this app ON (it's ON by default and immutable).
// Only do this for restricted profiles, not single-user restrictions
// Also don't do this for slave icons
} else if (!mNewUser && isAppEnabledForUser(pi)) {
isAllowed = true;
}
boolean availableForRestrictedProfile = true;
if (pi.requiredAccountType != null && pi.restrictedAccountType == null) {
availableForRestrictedProfile = false;
isAllowed = false;
canBeEnabledDisabled = false;
}
boolean canSeeRestrictedAccounts = pi.restrictedAccountType != null;
if (app.masterEntry != null) {
canBeEnabledDisabled = false;
isAllowed = mSelectedPackages.get(packageName);
}
onPackageEnableChanged(packageName, isAllowed);
if (DEBUG) {
Log.d(TAG, "Adding action for: " + app.appName + " has restrictions: "
+ hasCustomizableRestrictions);
}
actions.add(UserAppRestrictionsDialogFragment.createAction(mContext, packageName,
app.activityName.toString(), getAppIconUri(mContext, app.info, app.iconRes),
canBeEnabledDisabled, isAllowed, hasCustomizableRestrictions,
canSeeRestrictedAccounts, availableForRestrictedProfile, controllingActivity));
}
mListener.onActionsLoaded(actions);
// If this is the first time for a new profile, install/uninstall default apps for
// profile
// to avoid taking the hit in onPause(), which can cause race conditions on user switch.
if (mNewUser && mFirstTime) {
mFirstTime = false;
UserAppRestrictionsDialogFragment.applyUserAppsStates(mSelectedPackages, actions,
mIPackageManager, mUserId);
}
}
private void onPackageEnableChanged(String packageName, boolean enabled) {
mListener.onPackageEnableChanged(packageName, enabled);
mSelectedPackages.put(packageName, enabled);
}
private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) {
for (ResolveInfo info : receivers) {
if (info.activityInfo.packageName.equals(packageName)) {
return true;
}
}
return false;
}
private List<SelectableAppInfo> fetchAndMergeApps() {
List<SelectableAppInfo> visibleApps = new ArrayList<SelectableAppInfo>();
// Find all pre-installed input methods that are marked as default and add them to an
// exclusion list so that they aren't presented to the user for toggling. Don't add
// non-default ones, as they may include other stuff that we don't need to auto-include.
final HashSet<String> defaultSystemImes = getDefaultSystemImes();
// Add Settings
try {
visibleApps.add(new SelectableAppInfo(mPackageManager,
mPackageManager.getApplicationInfo(mContext.getPackageName(), 0)));
} catch (NameNotFoundException nnfe) {
Log.e(TAG, "Couldn't add settings item to list!", nnfe);
}
// Add leanback launchers
Intent leanbackLauncherIntent = new Intent(Intent.ACTION_MAIN);
leanbackLauncherIntent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);
addSystemApps(visibleApps, leanbackLauncherIntent, defaultSystemImes, mUserId);
// Add widgets
Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
addSystemApps(visibleApps, widgetIntent, defaultSystemImes, mUserId);
// Add daydreams
Intent daydreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
addSystemApps(visibleApps, daydreamIntent, defaultSystemImes, mUserId);
List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
PackageManager.GET_UNINSTALLED_PACKAGES);
addNonSystemApps(installedApps, true, visibleApps);
// Get the list of apps already installed for the user
try {
List<ApplicationInfo> userApps = mIPackageManager.getInstalledApplications(
PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList();
addNonSystemApps(userApps, false, visibleApps);
} catch (RemoteException re) {
}
// Sort the list of visible apps
Collections.sort(visibleApps, new AppLabelComparator());
// Remove dupes
Set<String> dedupPackageSet = new HashSet<String>();
for (int i = visibleApps.size() - 1; i >= 0; i--) {
SelectableAppInfo info = visibleApps.get(i);
if (DEBUG) {
Log.i(TAG, info.toString());
}
String both = info.packageName + "+" + info.activityName;
if (!TextUtils.isEmpty(info.packageName)
&& !TextUtils.isEmpty(info.activityName)
&& dedupPackageSet.contains(both)) {
if (DEBUG) {
Log.d(TAG, "Removing app: " + info.appName);
}
visibleApps.remove(i);
} else {
dedupPackageSet.add(both);
}
}
// Establish master/slave relationship for entries that share a package name
HashMap<String, SelectableAppInfo> packageMap = new HashMap<String,
SelectableAppInfo>();
for (SelectableAppInfo info : visibleApps) {
if (packageMap.containsKey(info.packageName)) {
info.masterEntry = packageMap.get(info.packageName);
} else {
packageMap.put(info.packageName, info);
}
}
return visibleApps;
}
private void addNonSystemApps(List<ApplicationInfo> apps, boolean disableSystemApps,
List<SelectableAppInfo> visibleApps) {
if (apps == null) {
return;
}
for (ApplicationInfo app : apps) {
// If it's not installed, skip
if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
continue;
}
if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
&& (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
// Downloaded app
visibleApps.add(new SelectableAppInfo(mPackageManager, app));
} else if (disableSystemApps) {
try {
PackageInfo pi = mPackageManager.getPackageInfo(app.packageName, 0);
// If it's a system app that requires an account and doesn't see restricted
// accounts, mark for removal. It might get shown in the UI if it has an
// icon but will still be marked as false and immutable.
if (pi.requiredAccountType != null && pi.restrictedAccountType == null) {
onPackageEnableChanged(app.packageName, false);
}
} catch (NameNotFoundException re) {
}
}
}
}
static class SelectableAppInfo {
private final String packageName;
private final CharSequence appName;
private final CharSequence activityName;
private final ApplicationInfo info;
private final int iconRes;
private SelectableAppInfo masterEntry;
SelectableAppInfo(PackageManager packageManager, ResolveInfo resolveInfo) {
packageName = resolveInfo.activityInfo.packageName;
appName = resolveInfo.activityInfo.applicationInfo.loadLabel(packageManager);
CharSequence label = resolveInfo.activityInfo.loadLabel(packageManager);
activityName = (label != null) ? label : appName;
int activityIconRes = getIconResource(resolveInfo.activityInfo);
info = resolveInfo.activityInfo.applicationInfo;
iconRes = activityIconRes != 0 ? activityIconRes
: getIconResource(resolveInfo.activityInfo.applicationInfo);
}
SelectableAppInfo(PackageManager packageManager, ApplicationInfo applicationInfo) {
packageName = applicationInfo.packageName;
appName = applicationInfo.loadLabel(packageManager);
activityName = appName;
info = applicationInfo;
iconRes = getIconResource(applicationInfo);
}
@Override
public String toString() {
return packageName + ": appName=" + appName + "; activityName=" + activityName
+ "; masterEntry=" + masterEntry;
}
private int getIconResource(PackageItemInfo packageItemInfo) {
if (packageItemInfo.banner != 0) {
return packageItemInfo.banner;
}
if (packageItemInfo.logo != 0) {
return packageItemInfo.logo;
}
return packageItemInfo.icon;
}
}
private static class AppLabelComparator implements Comparator<SelectableAppInfo> {
@Override
public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
String lhsLabel = lhs.activityName.toString();
String rhsLabel = rhs.activityName.toString();
return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
}
}
/**
* Find all pre-installed input methods that are marked as default and add them to an exclusion
* list so that they aren't presented to the user for toggling. Don't add non-default ones, as
* they may include other stuff that we don't need to auto-include.
*
* @return the set of default system imes
*/
private HashSet<String> getDefaultSystemImes() {
HashSet<String> defaultSystemImes = new HashSet<String>();
InputMethodManager imm = (InputMethodManager)
mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
List<InputMethodInfo> imis = imm.getInputMethodList();
for (InputMethodInfo imi : imis) {
try {
if (imi.isDefault(mContext) && isSystemPackage(imi.getPackageName())) {
defaultSystemImes.add(imi.getPackageName());
}
} catch (Resources.NotFoundException rnfe) {
// Not default
}
}
return defaultSystemImes;
}
private boolean isSystemPackage(String packageName) {
try {
final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
if (pi.applicationInfo == null)
return false;
final int flags = pi.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
|| (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
return true;
}
} catch (NameNotFoundException nnfe) {
// Missing package?
}
return false;
}
/**
* Add system apps that match an intent to the list, excluding any packages in the exclude list.
*
* @param visibleApps list of apps to append the new list to
* @param intent the intent to match
* @param excludePackages the set of package names to be excluded, since they're required
*/
private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent,
Set<String> excludePackages, int userId) {
final PackageManager pm = mPackageManager;
List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent,
PackageManager.GET_DISABLED_COMPONENTS
| PackageManager.GET_UNINSTALLED_PACKAGES);
for (ResolveInfo app : launchableApps) {
if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
final String packageName = app.activityInfo.packageName;
int flags = app.activityInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
|| (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
if (DEBUG) {
Log.d(TAG, "Found system app: "
+ app.activityInfo.applicationInfo.loadLabel(pm));
}
// System app
// Skip excluded packages
if (excludePackages.contains(packageName)) {
if (DEBUG) {
Log.d(TAG, "App is an excluded ime, not adding: "
+ app.activityInfo.applicationInfo.loadLabel(pm));
}
continue;
}
int enabled = pm.getApplicationEnabledSetting(packageName);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
// Check if the app is already enabled for the target user
ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName,
0, userId);
if (targetUserAppInfo == null
|| (targetUserAppInfo.flags & ApplicationInfo.FLAG_INSTALLED)
== 0) {
if (DEBUG) {
Log.d(TAG, "App is already something, not adding: "
+ app.activityInfo.applicationInfo.loadLabel(pm));
}
continue;
}
}
if (DEBUG) {
Log.d(TAG, "Adding system app: "
+ app.activityInfo.applicationInfo.loadLabel(pm));
}
visibleApps.add(new SelectableAppInfo(pm, app));
}
}
}
}
private ApplicationInfo getAppInfoForUser(String packageName, int flags, int userId) {
try {
ApplicationInfo targetUserAppInfo = mIPackageManager.getApplicationInfo(packageName,
flags,
userId);
return targetUserAppInfo;
} catch (RemoteException re) {
return null;
}
}
private boolean isPlatformSigned(PackageInfo pi) {
return (pi != null && pi.signatures != null &&
mSysPackageInfo.signatures[0].equals(pi.signatures[0]));
}
private boolean isAppEnabledForUser(PackageInfo pi) {
if (pi == null)
return false;
final int flags = pi.applicationInfo.flags;
final int privateFlags = pi.applicationInfo.privateFlags;
// Return true if it is installed and not hidden
return ((flags & ApplicationInfo.FLAG_INSTALLED) != 0
&& (privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0);
}
private static Uri getAppIconUri(Context context, ApplicationInfo info, int iconRes) {
String iconUri = null;
if (iconRes != 0) {
try {
Resources resources = context.getPackageManager()
.getResourcesForApplication(info);
ShortcutIconResource iconResource = new ShortcutIconResource();
iconResource.packageName = info.packageName;
iconResource.resourceName = resources.getResourceName(iconRes);
iconUri = UriUtils.getShortcutIconResourceUri(iconResource).toString();
} catch (Exception e1) {
Log.w("AppsBrowseInfo", e1.toString());
}
} else {
iconUri = UriUtils.getAndroidResourceUri(Resources.getSystem(),
com.android.internal.R.drawable.sym_def_app_icon);
}
if (iconUri == null) {
iconUri = UriUtils.getAndroidResourceUri(context.getResources(),
com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
}
return Uri.parse(iconUri);
}
}