blob: 851d763ea944c598940bd4ef3981276b4587ee5e [file] [log] [blame]
/*
* Copyright (C) 2016 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.settings.applications;
import static android.webkit.Flags.updateServiceV2;
import android.Manifest;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.location.LocationManager;
import android.os.RemoteException;
import android.os.SystemConfigManager;
import android.os.UserManager;
import android.service.euicc.EuiccService;
import android.telecom.DefaultDialerManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.internal.telephony.SmsApplication;
import com.android.settings.R;
import com.android.settings.webview.WebViewUpdateServiceWrapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvider {
private static final String TAG = "AppFeatureProviderImpl";
protected final Context mContext;
private final PackageManager mPm;
private final IPackageManager mPms;
private final DevicePolicyManager mDpm;
private final UserManager mUm;
private final WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
private final SystemConfigManager mSystemConfigManager;
/** Flags to use when querying PackageManager for Euicc component implementations. */
private static final int EUICC_QUERY_FLAGS =
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
| PackageManager.GET_RESOLVED_FILTER;
public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
IPackageManager pms, DevicePolicyManager dpm) {
this(context, pm, pms, dpm, new WebViewUpdateServiceWrapper());
}
public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
IPackageManager pms, DevicePolicyManager dpm, WebViewUpdateServiceWrapper wvusWrapper) {
mContext = context.getApplicationContext();
mPm = pm;
mPms = pms;
mDpm = dpm;
mUm = UserManager.get(mContext);
mWebViewUpdateServiceWrapper = wvusWrapper;
mSystemConfigManager = context.getSystemService(SystemConfigManager.class);
}
@Override
public void calculateNumberOfPolicyInstalledApps(boolean async, NumberOfAppsCallback callback) {
final CurrentUserAndManagedProfilePolicyInstalledAppCounter counter =
new CurrentUserAndManagedProfilePolicyInstalledAppCounter(mContext, mPm, callback);
if (async) {
counter.execute();
} else {
counter.executeInForeground();
}
}
@Override
public void listPolicyInstalledApps(ListOfAppsCallback callback) {
final CurrentUserPolicyInstalledAppLister lister =
new CurrentUserPolicyInstalledAppLister(mPm, mUm, callback);
lister.execute();
}
@Override
public void calculateNumberOfAppsWithAdminGrantedPermissions(String[] permissions,
boolean async, NumberOfAppsCallback callback) {
final CurrentUserAndManagedProfileAppWithAdminGrantedPermissionsCounter counter =
new CurrentUserAndManagedProfileAppWithAdminGrantedPermissionsCounter(mContext,
permissions, mPm, mPms, mDpm, callback);
if (async) {
counter.execute();
} else {
counter.executeInForeground();
}
}
@Override
public void listAppsWithAdminGrantedPermissions(String[] permissions,
ListOfAppsCallback callback) {
final CurrentUserAppWithAdminGrantedPermissionsLister lister =
new CurrentUserAppWithAdminGrantedPermissionsLister(permissions, mPm, mPms, mDpm,
mUm, callback);
lister.execute();
}
@Override
public List<UserAppInfo> findPersistentPreferredActivities(int userId, Intent[] intents) {
final List<UserAppInfo> preferredActivities = new ArrayList<>();
final Set<UserAppInfo> uniqueApps = new ArraySet<>();
final UserInfo userInfo = mUm.getUserInfo(userId);
for (final Intent intent : intents) {
try {
final ResolveInfo resolveInfo =
mPms.findPersistentPreferredActivity(intent, userId);
if (resolveInfo != null) {
ComponentInfo componentInfo = null;
if (resolveInfo.activityInfo != null) {
componentInfo = resolveInfo.activityInfo;
} else if (resolveInfo.serviceInfo != null) {
componentInfo = resolveInfo.serviceInfo;
} else if (resolveInfo.providerInfo != null) {
componentInfo = resolveInfo.providerInfo;
}
if (componentInfo != null) {
UserAppInfo info = new UserAppInfo(userInfo, componentInfo.applicationInfo);
if (uniqueApps.add(info)) {
preferredActivities.add(info);
}
}
}
} catch (RemoteException exception) {
}
}
return preferredActivities;
}
@Override
public Set<String> getKeepEnabledPackages() {
// Find current default phone/sms app. We should keep them enabled.
final Set<String> keepEnabledPackages = new ArraySet<>();
final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(mContext);
if (!TextUtils.isEmpty(defaultDialer)) {
keepEnabledPackages.add(defaultDialer);
}
final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(
mContext, true /* updateIfNeeded */);
if (defaultSms != null) {
keepEnabledPackages.add(defaultSms.getPackageName());
}
// Keep Euicc Service enabled.
final ComponentInfo euicc = findEuiccService(mPm);
if (euicc != null) {
keepEnabledPackages.add(euicc.packageName);
}
// Keep WebView default package enabled.
if (updateServiceV2()) {
String packageName = mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName();
if (packageName != null) {
keepEnabledPackages.add(packageName);
}
}
keepEnabledPackages.addAll(getEnabledPackageAllowlist());
final LocationManager locationManager =
(LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
final String locationHistoryPackage = locationManager.getExtraLocationControllerPackage();
if (locationHistoryPackage != null) {
keepEnabledPackages.add(locationHistoryPackage);
}
keepEnabledPackages.addAll(mSystemConfigManager.getPreventUserDisablePackages());
return keepEnabledPackages;
}
private Set<String> getEnabledPackageAllowlist() {
final Set<String> keepEnabledPackages = new ArraySet<>();
// Keep Settings intelligence enabled, otherwise search feature will be disabled.
keepEnabledPackages.add(
mContext.getString(R.string.config_settingsintelligence_package_name));
// Keep Package Installer enabled.
keepEnabledPackages.add(mContext.getString(R.string.config_package_installer_package_name));
if (mPm.getWellbeingPackageName() != null) {
keepEnabledPackages.add(mPm.getWellbeingPackageName());
}
return keepEnabledPackages;
}
private static class CurrentUserAndManagedProfilePolicyInstalledAppCounter
extends InstalledAppCounter {
private NumberOfAppsCallback mCallback;
CurrentUserAndManagedProfilePolicyInstalledAppCounter(Context context,
PackageManager packageManager, NumberOfAppsCallback callback) {
super(context, PackageManager.INSTALL_REASON_POLICY, packageManager);
mCallback = callback;
}
@Override
protected void onCountComplete(int num) {
mCallback.onNumberOfAppsResult(num);
}
}
private static class CurrentUserAndManagedProfileAppWithAdminGrantedPermissionsCounter
extends AppWithAdminGrantedPermissionsCounter {
private NumberOfAppsCallback mCallback;
CurrentUserAndManagedProfileAppWithAdminGrantedPermissionsCounter(Context context,
String[] permissions, PackageManager packageManager,
IPackageManager packageManagerService,
DevicePolicyManager devicePolicyManager, NumberOfAppsCallback callback) {
super(context, permissions, packageManager, packageManagerService, devicePolicyManager);
mCallback = callback;
}
@Override
protected void onCountComplete(int num) {
mCallback.onNumberOfAppsResult(num);
}
}
private static class CurrentUserPolicyInstalledAppLister extends InstalledAppLister {
private ListOfAppsCallback mCallback;
CurrentUserPolicyInstalledAppLister(PackageManager packageManager,
UserManager userManager, ListOfAppsCallback callback) {
super(packageManager, userManager);
mCallback = callback;
}
@Override
protected void onAppListBuilt(List<UserAppInfo> list) {
mCallback.onListOfAppsResult(list);
}
}
private static class CurrentUserAppWithAdminGrantedPermissionsLister extends
AppWithAdminGrantedPermissionsLister {
private ListOfAppsCallback mCallback;
CurrentUserAppWithAdminGrantedPermissionsLister(String[] permissions,
PackageManager packageManager, IPackageManager packageManagerService,
DevicePolicyManager devicePolicyManager, UserManager userManager,
ListOfAppsCallback callback) {
super(permissions, packageManager, packageManagerService, devicePolicyManager,
userManager);
mCallback = callback;
}
@Override
protected void onAppListBuilt(List<UserAppInfo> list) {
mCallback.onListOfAppsResult(list);
}
}
/**
* Return the component info of the EuiccService to bind to, or null if none were found.
*/
@VisibleForTesting
ComponentInfo findEuiccService(PackageManager packageManager) {
final Intent intent = new Intent(EuiccService.EUICC_SERVICE_INTERFACE);
final List<ResolveInfo> resolveInfoList =
packageManager.queryIntentServices(intent, EUICC_QUERY_FLAGS);
final ComponentInfo bestComponent = findEuiccService(packageManager, resolveInfoList);
if (bestComponent == null) {
Log.w(TAG, "No valid EuiccService implementation found");
}
return bestComponent;
}
private ComponentInfo findEuiccService(
PackageManager packageManager, List<ResolveInfo> resolveInfoList) {
int bestPriority = Integer.MIN_VALUE;
ComponentInfo bestComponent = null;
if (resolveInfoList != null) {
for (ResolveInfo resolveInfo : resolveInfoList) {
if (!isValidEuiccComponent(packageManager, resolveInfo)) {
continue;
}
if (resolveInfo.filter.getPriority() > bestPriority) {
bestPriority = resolveInfo.filter.getPriority();
bestComponent = getComponentInfo(resolveInfo);
}
}
}
return bestComponent;
}
private boolean isValidEuiccComponent(
PackageManager packageManager, ResolveInfo resolveInfo) {
final ComponentInfo componentInfo = getComponentInfo(resolveInfo);
final String packageName = componentInfo.packageName;
// Verify that the app is privileged (via granting of a privileged permission).
if (packageManager.checkPermission(
Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS, packageName)
!= PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "Package " + packageName
+ " does not declare WRITE_EMBEDDED_SUBSCRIPTIONS");
return false;
}
// Verify that only the system can access the component.
final String permission;
if (componentInfo instanceof ServiceInfo) {
permission = ((ServiceInfo) componentInfo).permission;
} else if (componentInfo instanceof ActivityInfo) {
permission = ((ActivityInfo) componentInfo).permission;
} else {
throw new IllegalArgumentException("Can only verify services/activities");
}
if (!TextUtils.equals(permission, Manifest.permission.BIND_EUICC_SERVICE)) {
Log.e(TAG, "Package " + packageName
+ " does not require the BIND_EUICC_SERVICE permission");
return false;
}
// Verify that the component declares a priority.
if (resolveInfo.filter == null || resolveInfo.filter.getPriority() == 0) {
Log.e(TAG, "Package " + packageName + " does not specify a priority");
return false;
}
return true;
}
private ComponentInfo getComponentInfo(ResolveInfo resolveInfo) {
if (resolveInfo.activityInfo != null) {
return resolveInfo.activityInfo;
}
if (resolveInfo.serviceInfo != null) {
return resolveInfo.serviceInfo;
}
if (resolveInfo.providerInfo != null) {
return resolveInfo.providerInfo;
}
throw new IllegalStateException("Missing ComponentInfo!");
}
@Override
public boolean isLongBackgroundTaskPermissionToggleSupported() {
// Since the RUN_USER_INITIATED_JOBS permission related to this controller is a normal
// app-op permission allowed by default, this should always return false - if it is ever
// converted to a special app-op permission, this should be updated.
return false;
}
}