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