1 /*
2  * Copyright (C) 2020 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.server.devicepolicy;
18 
19 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
20 
21 import android.accessibilityservice.AccessibilityServiceInfo;
22 import android.annotation.Nullable;
23 import android.annotation.UserIdInt;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.os.IBinder;
32 import android.os.ServiceManager;
33 import android.os.UserHandle;
34 import android.provider.Settings;
35 import android.provider.Telephony;
36 import android.text.TextUtils;
37 import android.util.ArraySet;
38 import android.util.IndentingPrintWriter;
39 import android.util.Log;
40 import android.view.accessibility.AccessibilityManager;
41 import android.view.accessibility.IAccessibilityManager;
42 import android.view.inputmethod.InputMethodInfo;
43 
44 import com.android.internal.R;
45 import com.android.server.inputmethod.InputMethodManagerInternal;
46 import com.android.server.utils.Slogf;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.List;
51 import java.util.Set;
52 
53 /**
54  * Utility class to find what personal apps should be suspended to limit personal device use.
55  */
56 public final class PersonalAppsSuspensionHelper {
57     private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
58 
59     // Flags to get all packages even if the user is still locked.
60     private static final int PACKAGE_QUERY_FLAGS =
61             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
62 
63     private final Context mContext;
64     private final PackageManager mPackageManager;
65 
66     /**
67      * Factory method
68      */
forUser(Context context, @UserIdInt int userId)69     public static PersonalAppsSuspensionHelper forUser(Context context, @UserIdInt int userId) {
70         return new PersonalAppsSuspensionHelper(context.createContextAsUser(UserHandle.of(userId),
71                 /* flags= */ 0));
72     }
73 
74     /**
75      * @param context Context for the user whose apps should to be suspended.
76      */
PersonalAppsSuspensionHelper(Context context)77     private PersonalAppsSuspensionHelper(Context context) {
78         mContext = context;
79         mPackageManager = context.getPackageManager();
80     }
81 
82     /**
83      * @return List of packages that should be suspended to limit personal use.
84      */
getPersonalAppsForSuspension()85     String[] getPersonalAppsForSuspension() {
86         final List<PackageInfo> installedPackageInfos =
87                 mPackageManager.getInstalledPackages(PACKAGE_QUERY_FLAGS);
88         final Set<String> result = new ArraySet<>();
89         for (final PackageInfo packageInfo : installedPackageInfos) {
90             final ApplicationInfo info = packageInfo.applicationInfo;
91             if ((!info.isSystemApp() && !info.isUpdatedSystemApp())
92                     || hasLauncherIntent(packageInfo.packageName)) {
93                 result.add(packageInfo.packageName);
94             }
95         }
96         result.removeAll(getCriticalPackages());
97         result.removeAll(getSystemLauncherPackages());
98         result.removeAll(getAccessibilityServices());
99         result.removeAll(getInputMethodPackages());
100         result.remove(Telephony.Sms.getDefaultSmsPackage(mContext));
101         result.remove(getSettingsPackageName());
102 
103         final String[] unsuspendablePackages =
104                 mPackageManager.getUnsuspendablePackages(result.toArray(new String[0]));
105         for (final String pkg : unsuspendablePackages) {
106             result.remove(pkg);
107         }
108 
109         if (Log.isLoggable(LOG_TAG, Log.INFO)) {
110             Slogf.i(LOG_TAG, "Packages subject to suspension: %s", String.join(",", result));
111         }
112         return result.toArray(new String[0]);
113     }
114 
getSystemLauncherPackages()115     private List<String> getSystemLauncherPackages() {
116         final List<String> result = new ArrayList<>();
117         final Intent intent = new Intent(Intent.ACTION_MAIN);
118         intent.addCategory(Intent.CATEGORY_HOME);
119         final List<ResolveInfo> matchingActivities =
120                 mPackageManager.queryIntentActivities(intent, PACKAGE_QUERY_FLAGS);
121         for (final ResolveInfo resolveInfo : matchingActivities) {
122             if (resolveInfo.activityInfo == null
123                     || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) {
124                 Slogf.wtf(LOG_TAG, "Could not find package name for launcher app %s", resolveInfo);
125                 continue;
126             }
127             final String packageName = resolveInfo.activityInfo.packageName;
128             try {
129                 final ApplicationInfo applicationInfo =
130                         mPackageManager.getApplicationInfo(packageName, PACKAGE_QUERY_FLAGS);
131                 if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) {
132                     result.add(packageName);
133                 }
134             } catch (PackageManager.NameNotFoundException e) {
135                 Slogf.e(LOG_TAG, "Could not find application info for launcher app: %s",
136                         packageName);
137             }
138         }
139         return result;
140     }
141 
getAccessibilityServices()142     private List<String> getAccessibilityServices() {
143         final List<AccessibilityServiceInfo> accessibilityServiceInfos;
144         // Not using AccessibilityManager.getInstance because that guesses
145         // at the user you require based on callingUid and caches for a given
146         // process.
147         final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
148         final IAccessibilityManager service = iBinder == null
149                 ? null : IAccessibilityManager.Stub.asInterface(iBinder);
150         final AccessibilityManager am =
151                 new AccessibilityManager(mContext, service, mContext.getUserId());
152         try {
153             accessibilityServiceInfos = am.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
154         } finally {
155             am.removeClient();
156         }
157 
158         final List<String> result = new ArrayList<>();
159         for (final AccessibilityServiceInfo serviceInfo : accessibilityServiceInfos) {
160             final ComponentName componentName =
161                     ComponentName.unflattenFromString(serviceInfo.getId());
162             if (componentName != null) {
163                 result.add(componentName.getPackageName());
164             }
165         }
166         return result;
167     }
168 
getInputMethodPackages()169     private List<String> getInputMethodPackages() {
170         final List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
171                 .getEnabledInputMethodListAsUser(mContext.getUserId());
172         final List<String> result = new ArrayList<>();
173         for (final InputMethodInfo info : enabledImes) {
174             result.add(info.getPackageName());
175         }
176         return result;
177     }
178 
179     @Nullable
getSettingsPackageName()180     private String getSettingsPackageName() {
181         final Intent intent = new Intent(Settings.ACTION_SETTINGS);
182         intent.addCategory(Intent.CATEGORY_DEFAULT);
183         final ResolveInfo resolveInfo =
184                 mPackageManager.resolveActivity(intent, PACKAGE_QUERY_FLAGS);
185         if (resolveInfo != null) {
186             return resolveInfo.activityInfo.packageName;
187         }
188         return null;
189     }
190 
getCriticalPackages()191     private List<String> getCriticalPackages() {
192         return Arrays.asList(mContext.getResources()
193                 .getStringArray(R.array.config_packagesExemptFromSuspension));
194     }
195 
hasLauncherIntent(String packageName)196     private boolean hasLauncherIntent(String packageName) {
197         final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
198         intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
199         intentToResolve.setPackage(packageName);
200         final List<ResolveInfo> resolveInfos =
201                 mPackageManager.queryIntentActivities(intentToResolve, PACKAGE_QUERY_FLAGS);
202         return resolveInfos != null && !resolveInfos.isEmpty();
203     }
204 
205 
dump(IndentingPrintWriter pw)206     void dump(IndentingPrintWriter pw) {
207         pw.println("PersonalAppsSuspensionHelper");
208         pw.increaseIndent();
209 
210         DevicePolicyManagerService.dumpApps(pw, "critical packages", getCriticalPackages());
211         DevicePolicyManagerService.dumpApps(pw, "launcher packages", getSystemLauncherPackages());
212         DevicePolicyManagerService.dumpApps(pw, "accessibility services",
213                 getAccessibilityServices());
214         DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages());
215         pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext));
216         pw.printf("Settings package: %s\n", getSettingsPackageName());
217         DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension",
218                 getPersonalAppsForSuspension());
219 
220         pw.decreaseIndent();
221     }
222 }
223