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