| /* |
| * Copyright (C) 2020 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.server.devicepolicy; |
| |
| import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; |
| |
| import android.accessibilityservice.AccessibilityServiceInfo; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.os.IBinder; |
| import android.os.ServiceManager; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.provider.Telephony; |
| import android.text.TextUtils; |
| import android.util.ArraySet; |
| import android.util.IndentingPrintWriter; |
| import android.util.Log; |
| import android.view.accessibility.AccessibilityManager; |
| import android.view.accessibility.IAccessibilityManager; |
| import android.view.inputmethod.InputMethodInfo; |
| |
| import com.android.internal.R; |
| import com.android.server.inputmethod.InputMethodManagerInternal; |
| import com.android.server.utils.Slogf; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Utility class to find what personal apps should be suspended to limit personal device use. |
| */ |
| public final class PersonalAppsSuspensionHelper { |
| private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG; |
| |
| // Flags to get all packages even if the user is still locked. |
| private static final int PACKAGE_QUERY_FLAGS = |
| PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; |
| |
| private final Context mContext; |
| private final PackageManager mPackageManager; |
| |
| /** |
| * Factory method |
| */ |
| public static PersonalAppsSuspensionHelper forUser(Context context, @UserIdInt int userId) { |
| return new PersonalAppsSuspensionHelper(context.createContextAsUser(UserHandle.of(userId), |
| /* flags= */ 0)); |
| } |
| |
| /** |
| * @param context Context for the user whose apps should to be suspended. |
| */ |
| private PersonalAppsSuspensionHelper(Context context) { |
| mContext = context; |
| mPackageManager = context.getPackageManager(); |
| } |
| |
| /** |
| * @return List of packages that should be suspended to limit personal use. |
| */ |
| String[] getPersonalAppsForSuspension() { |
| final List<PackageInfo> installedPackageInfos = |
| mPackageManager.getInstalledPackages(PACKAGE_QUERY_FLAGS); |
| final Set<String> result = new ArraySet<>(); |
| for (final PackageInfo packageInfo : installedPackageInfos) { |
| final ApplicationInfo info = packageInfo.applicationInfo; |
| if ((!info.isSystemApp() && !info.isUpdatedSystemApp()) |
| || hasLauncherIntent(packageInfo.packageName)) { |
| result.add(packageInfo.packageName); |
| } |
| } |
| result.removeAll(getCriticalPackages()); |
| result.removeAll(getSystemLauncherPackages()); |
| result.removeAll(getAccessibilityServices()); |
| result.removeAll(getInputMethodPackages()); |
| result.remove(Telephony.Sms.getDefaultSmsPackage(mContext)); |
| result.remove(getSettingsPackageName()); |
| |
| final String[] unsuspendablePackages = |
| mPackageManager.getUnsuspendablePackages(result.toArray(new String[0])); |
| for (final String pkg : unsuspendablePackages) { |
| result.remove(pkg); |
| } |
| |
| if (Log.isLoggable(LOG_TAG, Log.INFO)) { |
| Slogf.i(LOG_TAG, "Packages subject to suspension: %s", String.join(",", result)); |
| } |
| return result.toArray(new String[0]); |
| } |
| |
| private List<String> getSystemLauncherPackages() { |
| final List<String> result = new ArrayList<>(); |
| final Intent intent = new Intent(Intent.ACTION_MAIN); |
| intent.addCategory(Intent.CATEGORY_HOME); |
| final List<ResolveInfo> matchingActivities = |
| mPackageManager.queryIntentActivities(intent, PACKAGE_QUERY_FLAGS); |
| for (final ResolveInfo resolveInfo : matchingActivities) { |
| if (resolveInfo.activityInfo == null |
| || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) { |
| Slogf.wtf(LOG_TAG, "Could not find package name for launcher app %s", resolveInfo); |
| continue; |
| } |
| final String packageName = resolveInfo.activityInfo.packageName; |
| try { |
| final ApplicationInfo applicationInfo = |
| mPackageManager.getApplicationInfo(packageName, PACKAGE_QUERY_FLAGS); |
| if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) { |
| result.add(packageName); |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| Slogf.e(LOG_TAG, "Could not find application info for launcher app: %s", |
| packageName); |
| } |
| } |
| return result; |
| } |
| |
| private List<String> getAccessibilityServices() { |
| final List<AccessibilityServiceInfo> accessibilityServiceInfos; |
| // Not using AccessibilityManager.getInstance because that guesses |
| // at the user you require based on callingUid and caches for a given |
| // process. |
| final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); |
| final IAccessibilityManager service = iBinder == null |
| ? null : IAccessibilityManager.Stub.asInterface(iBinder); |
| final AccessibilityManager am = |
| new AccessibilityManager(mContext, service, mContext.getUserId()); |
| try { |
| accessibilityServiceInfos = am.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK); |
| } finally { |
| am.removeClient(); |
| } |
| |
| final List<String> result = new ArrayList<>(); |
| for (final AccessibilityServiceInfo serviceInfo : accessibilityServiceInfos) { |
| final ComponentName componentName = |
| ComponentName.unflattenFromString(serviceInfo.getId()); |
| if (componentName != null) { |
| result.add(componentName.getPackageName()); |
| } |
| } |
| return result; |
| } |
| |
| private List<String> getInputMethodPackages() { |
| final List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get() |
| .getEnabledInputMethodListAsUser(mContext.getUserId()); |
| final List<String> result = new ArrayList<>(); |
| for (final InputMethodInfo info : enabledImes) { |
| result.add(info.getPackageName()); |
| } |
| return result; |
| } |
| |
| @Nullable |
| private String getSettingsPackageName() { |
| final Intent intent = new Intent(Settings.ACTION_SETTINGS); |
| intent.addCategory(Intent.CATEGORY_DEFAULT); |
| final ResolveInfo resolveInfo = |
| mPackageManager.resolveActivity(intent, PACKAGE_QUERY_FLAGS); |
| if (resolveInfo != null) { |
| return resolveInfo.activityInfo.packageName; |
| } |
| return null; |
| } |
| |
| private List<String> getCriticalPackages() { |
| return Arrays.asList(mContext.getResources() |
| .getStringArray(R.array.config_packagesExemptFromSuspension)); |
| } |
| |
| private boolean hasLauncherIntent(String packageName) { |
| final Intent intentToResolve = new Intent(Intent.ACTION_MAIN); |
| intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); |
| intentToResolve.setPackage(packageName); |
| final List<ResolveInfo> resolveInfos = |
| mPackageManager.queryIntentActivities(intentToResolve, PACKAGE_QUERY_FLAGS); |
| return resolveInfos != null && !resolveInfos.isEmpty(); |
| } |
| |
| |
| void dump(IndentingPrintWriter pw) { |
| pw.println("PersonalAppsSuspensionHelper"); |
| pw.increaseIndent(); |
| |
| DevicePolicyManagerService.dumpApps(pw, "critical packages", getCriticalPackages()); |
| DevicePolicyManagerService.dumpApps(pw, "launcher packages", getSystemLauncherPackages()); |
| DevicePolicyManagerService.dumpApps(pw, "accessibility services", |
| getAccessibilityServices()); |
| DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages()); |
| pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext)); |
| pw.printf("Settings package: %s\n", getSettingsPackageName()); |
| DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension", |
| getPersonalAppsForSuspension()); |
| |
| pw.decreaseIndent(); |
| } |
| } |