1 /*
2  * Copyright (C) 2016 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.settingslib.users;
18 
19 import android.app.AppGlobals;
20 import android.appwidget.AppWidgetManager;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ParceledListSlice;
28 import android.content.pm.ResolveInfo;
29 import android.content.res.Resources;
30 import android.graphics.drawable.Drawable;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.view.inputmethod.InputMethodInfo;
37 import android.view.inputmethod.InputMethodManager;
38 
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47 
48 public class AppRestrictionsHelper {
49     private static final boolean DEBUG = false;
50     private static final String TAG = "AppRestrictionsHelper";
51 
52     private final Context mContext;
53     private final PackageManager mPackageManager;
54     private final IPackageManager mIPm;
55     private final UserManager mUserManager;
56     private final UserHandle mUser;
57     private final boolean mRestrictedProfile;
58     private boolean mLeanback;
59 
60     HashMap<String,Boolean> mSelectedPackages = new HashMap<>();
61     private List<SelectableAppInfo> mVisibleApps;
62 
AppRestrictionsHelper(Context context, UserHandle user)63     public AppRestrictionsHelper(Context context, UserHandle user) {
64         mContext = context;
65         mPackageManager = context.getPackageManager();
66         mIPm = AppGlobals.getPackageManager();
67         mUser = user;
68         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
69         mRestrictedProfile = mUserManager.getUserInfo(mUser.getIdentifier()).isRestricted();
70     }
71 
setPackageSelected(String packageName, boolean selected)72     public void setPackageSelected(String packageName, boolean selected) {
73         mSelectedPackages.put(packageName, selected);
74     }
75 
isPackageSelected(String packageName)76     public boolean isPackageSelected(String packageName) {
77         return mSelectedPackages.get(packageName);
78     }
79 
setLeanback(boolean isLeanback)80     public void setLeanback(boolean isLeanback) {
81         mLeanback = isLeanback;
82     }
83 
getVisibleApps()84     public List<SelectableAppInfo> getVisibleApps() {
85         return mVisibleApps;
86     }
87 
applyUserAppsStates(OnDisableUiForPackageListener listener)88     public void applyUserAppsStates(OnDisableUiForPackageListener listener) {
89         final int userId = mUser.getIdentifier();
90         if (!mUserManager.getUserInfo(userId).isRestricted() && userId != UserHandle.myUserId()) {
91             Log.e(TAG, "Cannot apply application restrictions on another user!");
92             return;
93         }
94         for (Map.Entry<String,Boolean> entry : mSelectedPackages.entrySet()) {
95             String packageName = entry.getKey();
96             boolean enabled = entry.getValue();
97             applyUserAppState(packageName, enabled, listener);
98         }
99     }
100 
applyUserAppState(String packageName, boolean enabled, OnDisableUiForPackageListener listener)101     public void applyUserAppState(String packageName, boolean enabled,
102             OnDisableUiForPackageListener listener) {
103         final int userId = mUser.getIdentifier();
104         if (enabled) {
105             // Enable selected apps
106             try {
107                 ApplicationInfo info = mIPm.getApplicationInfo(packageName,
108                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
109                 if (info == null || !info.enabled
110                         || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
111                     mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier());
112                     if (DEBUG) {
113                         Log.d(TAG, "Installing " + packageName);
114                     }
115                 }
116                 if (info != null && (info.privateFlags&ApplicationInfo.PRIVATE_FLAG_HIDDEN) != 0
117                         && (info.flags&ApplicationInfo.FLAG_INSTALLED) != 0) {
118                     listener.onDisableUiForPackage(packageName);
119                     mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId);
120                     if (DEBUG) {
121                         Log.d(TAG, "Unhiding " + packageName);
122                     }
123                 }
124             } catch (RemoteException re) {
125                 // Ignore
126             }
127         } else {
128             // Blacklist all other apps, system or downloaded
129             try {
130                 ApplicationInfo info = mIPm.getApplicationInfo(packageName, 0, userId);
131                 if (info != null) {
132                     if (mRestrictedProfile) {
133                         mIPm.deletePackageAsUser(packageName, null, mUser.getIdentifier(),
134                                 PackageManager.DELETE_SYSTEM_APP);
135                         if (DEBUG) {
136                             Log.d(TAG, "Uninstalling " + packageName);
137                         }
138                     } else {
139                         listener.onDisableUiForPackage(packageName);
140                         mIPm.setApplicationHiddenSettingAsUser(packageName, true, userId);
141                         if (DEBUG) {
142                             Log.d(TAG, "Hiding " + packageName);
143                         }
144                     }
145                 }
146             } catch (RemoteException re) {
147                 // Ignore
148             }
149         }
150     }
151 
fetchAndMergeApps()152     public void fetchAndMergeApps() {
153         mVisibleApps = new ArrayList<>();
154         final PackageManager pm = mPackageManager;
155         final IPackageManager ipm = mIPm;
156 
157         final HashSet<String> excludePackages = new HashSet<>();
158         addSystemImes(excludePackages);
159 
160         // Add launchers
161         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
162         if (mLeanback) {
163             launcherIntent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);
164         } else {
165             launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
166         }
167         addSystemApps(mVisibleApps, launcherIntent, excludePackages);
168 
169         // Add widgets
170         Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
171         addSystemApps(mVisibleApps, widgetIntent, excludePackages);
172 
173         List<ApplicationInfo> installedApps = pm.getInstalledApplications(
174                 PackageManager.MATCH_UNINSTALLED_PACKAGES);
175         for (ApplicationInfo app : installedApps) {
176             // If it's not installed, skip
177             if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
178 
179             if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
180                     && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
181                 // Downloaded app
182                 SelectableAppInfo info = new SelectableAppInfo();
183                 info.packageName = app.packageName;
184                 info.appName = app.loadLabel(pm);
185                 info.activityName = info.appName;
186                 info.icon = app.loadIcon(pm);
187                 mVisibleApps.add(info);
188             } else {
189                 try {
190                     PackageInfo pi = pm.getPackageInfo(app.packageName, 0);
191                     // If it's a system app that requires an account and doesn't see restricted
192                     // accounts, mark for removal. It might get shown in the UI if it has an icon
193                     // but will still be marked as false and immutable.
194                     if (mRestrictedProfile
195                             && pi.requiredAccountType != null && pi.restrictedAccountType == null) {
196                         mSelectedPackages.put(app.packageName, false);
197                     }
198                 } catch (PackageManager.NameNotFoundException re) {
199                     // Skip
200                 }
201             }
202         }
203 
204         // Get the list of apps already installed for the user
205         List<ApplicationInfo> userApps = null;
206         try {
207             ParceledListSlice<ApplicationInfo> listSlice = ipm.getInstalledApplications(
208                     PackageManager.MATCH_UNINSTALLED_PACKAGES, mUser.getIdentifier());
209             if (listSlice != null) {
210                 userApps = listSlice.getList();
211             }
212         } catch (RemoteException re) {
213             // Ignore
214         }
215 
216         if (userApps != null) {
217             for (ApplicationInfo app : userApps) {
218                 if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
219 
220                 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
221                         && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
222                     // Downloaded app
223                     SelectableAppInfo info = new SelectableAppInfo();
224                     info.packageName = app.packageName;
225                     info.appName = app.loadLabel(pm);
226                     info.activityName = info.appName;
227                     info.icon = app.loadIcon(pm);
228                     mVisibleApps.add(info);
229                 }
230             }
231         }
232 
233         // Sort the list of visible apps
234         Collections.sort(mVisibleApps, new AppLabelComparator());
235 
236         // Remove dupes
237         Set<String> dedupPackageSet = new HashSet<String>();
238         for (int i = mVisibleApps.size() - 1; i >= 0; i--) {
239             SelectableAppInfo info = mVisibleApps.get(i);
240             if (DEBUG) Log.i(TAG, info.toString());
241             String both = info.packageName + "+" + info.activityName;
242             if (!TextUtils.isEmpty(info.packageName)
243                     && !TextUtils.isEmpty(info.activityName)
244                     && dedupPackageSet.contains(both)) {
245                 mVisibleApps.remove(i);
246             } else {
247                 dedupPackageSet.add(both);
248             }
249         }
250 
251         // Establish master/slave relationship for entries that share a package name
252         HashMap<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>();
253         for (SelectableAppInfo info : mVisibleApps) {
254             if (packageMap.containsKey(info.packageName)) {
255                 info.masterEntry = packageMap.get(info.packageName);
256             } else {
257                 packageMap.put(info.packageName, info);
258             }
259         }
260     }
261 
262     /**
263      * Find all pre-installed input methods that are marked as default
264      * and add them to an exclusion list so that they aren't
265      * presented to the user for toggling.
266      * Don't add non-default ones, as they may include other stuff that we
267      * don't need to auto-include.
268      * @param excludePackages the set of package names to append to
269      */
addSystemImes(Set<String> excludePackages)270     private void addSystemImes(Set<String> excludePackages) {
271         InputMethodManager imm = (InputMethodManager)
272                 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
273         List<InputMethodInfo> imis = imm.getInputMethodList();
274         for (InputMethodInfo imi : imis) {
275             try {
276                 if (imi.isDefault(mContext) && isSystemPackage(imi.getPackageName())) {
277                     excludePackages.add(imi.getPackageName());
278                 }
279             } catch (Resources.NotFoundException rnfe) {
280                 // Not default
281             }
282         }
283     }
284 
285     /**
286      * Add system apps that match an intent to the list, excluding any packages in the exclude list.
287      * @param visibleApps list of apps to append the new list to
288      * @param intent the intent to match
289      * @param excludePackages the set of package names to be excluded, since they're required
290      */
addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, Set<String> excludePackages)291     private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent,
292             Set<String> excludePackages) {
293         final PackageManager pm = mPackageManager;
294         List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent,
295                 PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_UNINSTALLED_PACKAGES);
296         for (ResolveInfo app : launchableApps) {
297             if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
298                 final String packageName = app.activityInfo.packageName;
299                 int flags = app.activityInfo.applicationInfo.flags;
300                 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
301                         || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
302                     // System app
303                     // Skip excluded packages
304                     if (excludePackages.contains(packageName)) continue;
305                     int enabled = pm.getApplicationEnabledSetting(packageName);
306                     if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
307                             || enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
308                         // Check if the app is already enabled for the target user
309                         ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName,
310                                 0, mUser);
311                         if (targetUserAppInfo == null
312                                 || (targetUserAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
313                             continue;
314                         }
315                     }
316                     SelectableAppInfo info = new SelectableAppInfo();
317                     info.packageName = app.activityInfo.packageName;
318                     info.appName = app.activityInfo.applicationInfo.loadLabel(pm);
319                     info.icon = app.activityInfo.loadIcon(pm);
320                     info.activityName = app.activityInfo.loadLabel(pm);
321                     if (info.activityName == null) info.activityName = info.appName;
322 
323                     visibleApps.add(info);
324                 }
325             }
326         }
327     }
328 
isSystemPackage(String packageName)329     private boolean isSystemPackage(String packageName) {
330         try {
331             final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
332             if (pi.applicationInfo == null) return false;
333             final int flags = pi.applicationInfo.flags;
334             if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
335                     || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
336                 return true;
337             }
338         } catch (PackageManager.NameNotFoundException nnfe) {
339             // Missing package?
340         }
341         return false;
342     }
343 
getAppInfoForUser(String packageName, int flags, UserHandle user)344     private ApplicationInfo getAppInfoForUser(String packageName, int flags, UserHandle user) {
345         try {
346             return mIPm.getApplicationInfo(packageName, flags, user.getIdentifier());
347         } catch (RemoteException re) {
348             return null;
349         }
350     }
351 
352     public interface OnDisableUiForPackageListener {
onDisableUiForPackage(String packageName)353         void onDisableUiForPackage(String packageName);
354     }
355 
356     public static class SelectableAppInfo {
357         public String packageName;
358         public CharSequence appName;
359         public CharSequence activityName;
360         public Drawable icon;
361         public SelectableAppInfo masterEntry;
362 
363         @Override
toString()364         public String toString() {
365             return packageName + ": appName=" + appName + "; activityName=" + activityName
366                     + "; icon=" + icon + "; masterEntry=" + masterEntry;
367         }
368     }
369 
370     private static class AppLabelComparator implements Comparator<SelectableAppInfo> {
371 
372         @Override
compare(SelectableAppInfo lhs, SelectableAppInfo rhs)373         public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
374             String lhsLabel = lhs.activityName.toString();
375             String rhsLabel = rhs.activityName.toString();
376             return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
377         }
378     }
379 }
380