• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.tv.settings.users;
18 
19 import android.appwidget.AppWidgetManager;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.Intent.ShortcutIconResource;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageItemInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.ResolveInfo;
30 import android.content.res.Resources;
31 import android.net.Uri;
32 import android.os.AsyncTask;
33 import android.os.RemoteException;
34 import android.service.dreams.DreamService;
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 com.android.tv.settings.dialog.DialogFragment.Action;
41 import com.android.tv.settings.util.UriUtils;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.Comparator;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Set;
50 
51 class AppLoadingTask extends AsyncTask<Void, Void, List<AppLoadingTask.SelectableAppInfo>> {
52 
53     interface Listener {
onPackageEnableChanged(String packageName, boolean enabled)54         void onPackageEnableChanged(String packageName, boolean enabled);
55 
onActionsLoaded(ArrayList<Action> actions)56         void onActionsLoaded(ArrayList<Action> actions);
57     }
58 
59     private static final boolean DEBUG = false;
60     private static final String TAG = "RestrictedProfile";
61 
62     private final Context mContext;
63     private final int mUserId;
64     private final boolean mNewUser;
65     private final PackageManager mPackageManager;
66     private final IPackageManager mIPackageManager;
67     private final Listener mListener;
68     private final PackageInfo mSysPackageInfo;
69     private final HashMap<String, Boolean> mSelectedPackages = new HashMap<String, Boolean>();
70     private boolean mFirstTime = true;
71 
72     /**
73      * Loads the list of activities that the user can enable or disable in a restricted profile.
74      *
75      * @param context context for querying the list of activities.
76      * @param userId the user ID of the user whose apps should be listed.
77      * @param newUser true if this is a newly create user.
78      * @param iPackageManager used to get application info.
79      * @param listener listener for package enable state changes.
80      */
AppLoadingTask(Context context, int userId, boolean newUser, IPackageManager iPackageManager, Listener listener)81     AppLoadingTask(Context context, int userId, boolean newUser, IPackageManager iPackageManager,
82             Listener listener) {
83         mContext = context;
84         mUserId = userId;
85         mNewUser = newUser;
86         mPackageManager = context.getPackageManager();
87         mIPackageManager = iPackageManager;
88         mListener = listener;
89         PackageInfo sysPackageInfo = null;
90         try {
91             sysPackageInfo = mPackageManager.getPackageInfo("android",
92                     PackageManager.GET_SIGNATURES);
93         } catch (NameNotFoundException nnfe) {
94             Log.wtf(TAG, "Failed to get package signatures!");
95         }
96         mSysPackageInfo = sysPackageInfo;
97     }
98 
99     @Override
doInBackground(Void... params)100     protected List<SelectableAppInfo> doInBackground(Void... params) {
101         return fetchAndMergeApps();
102     }
103 
104     @Override
onPostExecute(List<SelectableAppInfo> visibleApps)105     protected void onPostExecute(List<SelectableAppInfo> visibleApps) {
106         populateApps(visibleApps);
107     }
108 
populateApps(List<SelectableAppInfo> visibleApps)109     private void populateApps(List<SelectableAppInfo> visibleApps) {
110         ArrayList<Action> actions = new ArrayList<Action>();
111         Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
112         List<ResolveInfo> receivers = mPackageManager.queryBroadcastReceivers(restrictionsIntent,
113                 0);
114         for (SelectableAppInfo app : visibleApps) {
115             String packageName = app.packageName;
116             if (packageName == null) {
117                 if (DEBUG) {
118                     Log.d(TAG, "App has no package name: " + app.appName);
119                 }
120                 continue;
121             }
122             final boolean isSettingsApp = packageName.equals(mContext.getPackageName());
123             final boolean hasSettings = resolveInfoListHasPackage(receivers, packageName);
124             boolean isAllowed = false;
125             String controllingActivity = null;
126             if (app.masterEntry != null) {
127                 controllingActivity = app.masterEntry.activityName.toString();
128             }
129             boolean hasCustomizableRestrictions = ((hasSettings || isSettingsApp)
130                     && app.masterEntry == null);
131             PackageInfo pi = null;
132             try {
133                 pi = mIPackageManager.getPackageInfo(packageName,
134                         PackageManager.GET_UNINSTALLED_PACKAGES
135                         | PackageManager.GET_SIGNATURES, mUserId);
136             } catch (RemoteException e) {
137             }
138             boolean canBeEnabledDisabled = true;
139             if (pi != null && (pi.requiredForAllUsers || isPlatformSigned(pi))) {
140                 isAllowed = true;
141                 canBeEnabledDisabled = false;
142                 // If the app is required and has no restrictions, skip showing it
143                 if (!hasSettings && !isSettingsApp) {
144                     if (DEBUG) {
145                         Log.d(TAG, "App is required and has no settings: " + app.appName);
146                     }
147                     continue;
148                 }
149                 // Get and populate the defaults, since the user is not going to be
150                 // able to toggle this app ON (it's ON by default and immutable).
151                 // Only do this for restricted profiles, not single-user restrictions
152                 // Also don't do this for slave icons
153             } else if (!mNewUser && isAppEnabledForUser(pi)) {
154                 isAllowed = true;
155             }
156             boolean availableForRestrictedProfile = true;
157             if (pi.requiredAccountType != null && pi.restrictedAccountType == null) {
158                 availableForRestrictedProfile = false;
159                 isAllowed = false;
160                 canBeEnabledDisabled = false;
161             }
162             boolean canSeeRestrictedAccounts = pi.restrictedAccountType != null;
163             if (app.masterEntry != null) {
164                 canBeEnabledDisabled = false;
165                 isAllowed = mSelectedPackages.get(packageName);
166             }
167             onPackageEnableChanged(packageName, isAllowed);
168             if (DEBUG) {
169                 Log.d(TAG, "Adding action for: " + app.appName + " has restrictions: "
170                         + hasCustomizableRestrictions);
171             }
172             actions.add(UserAppRestrictionsDialogFragment.createAction(mContext, packageName,
173                     app.activityName.toString(), getAppIconUri(mContext, app.info, app.iconRes),
174                     canBeEnabledDisabled, isAllowed, hasCustomizableRestrictions,
175                     canSeeRestrictedAccounts, availableForRestrictedProfile, controllingActivity));
176         }
177         mListener.onActionsLoaded(actions);
178         // If this is the first time for a new profile, install/uninstall default apps for
179         // profile
180         // to avoid taking the hit in onPause(), which can cause race conditions on user switch.
181         if (mNewUser && mFirstTime) {
182             mFirstTime = false;
183             UserAppRestrictionsDialogFragment.applyUserAppsStates(mSelectedPackages, actions,
184                     mIPackageManager, mUserId);
185         }
186     }
187 
onPackageEnableChanged(String packageName, boolean enabled)188     private void onPackageEnableChanged(String packageName, boolean enabled) {
189         mListener.onPackageEnableChanged(packageName, enabled);
190         mSelectedPackages.put(packageName, enabled);
191     }
192 
resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName)193     private boolean resolveInfoListHasPackage(List<ResolveInfo> receivers, String packageName) {
194         for (ResolveInfo info : receivers) {
195             if (info.activityInfo.packageName.equals(packageName)) {
196                 return true;
197             }
198         }
199         return false;
200     }
201 
fetchAndMergeApps()202     private List<SelectableAppInfo> fetchAndMergeApps() {
203         List<SelectableAppInfo> visibleApps = new ArrayList<SelectableAppInfo>();
204 
205         // Find all pre-installed input methods that are marked as default and add them to an
206         // exclusion list so that they aren't presented to the user for toggling. Don't add
207         // non-default ones, as they may include other stuff that we don't need to auto-include.
208         final HashSet<String> defaultSystemImes = getDefaultSystemImes();
209 
210         // Add Settings
211         try {
212             visibleApps.add(new SelectableAppInfo(mPackageManager,
213                     mPackageManager.getApplicationInfo(mContext.getPackageName(), 0)));
214         } catch (NameNotFoundException nnfe) {
215             Log.e(TAG, "Couldn't add settings item to list!", nnfe);
216         }
217 
218         // Add leanback launchers
219         Intent leanbackLauncherIntent = new Intent(Intent.ACTION_MAIN);
220         leanbackLauncherIntent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);
221         addSystemApps(visibleApps, leanbackLauncherIntent, defaultSystemImes, mUserId);
222 
223         // Add widgets
224         Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
225         addSystemApps(visibleApps, widgetIntent, defaultSystemImes, mUserId);
226 
227         // Add daydreams
228         Intent daydreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
229         addSystemApps(visibleApps, daydreamIntent, defaultSystemImes, mUserId);
230 
231         List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
232                 PackageManager.GET_UNINSTALLED_PACKAGES);
233         addNonSystemApps(installedApps, true, visibleApps);
234 
235         // Get the list of apps already installed for the user
236         try {
237             List<ApplicationInfo> userApps = mIPackageManager.getInstalledApplications(
238                     PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList();
239             addNonSystemApps(userApps, false, visibleApps);
240         } catch (RemoteException re) {
241         }
242 
243         // Sort the list of visible apps
244         Collections.sort(visibleApps, new AppLabelComparator());
245 
246         // Remove dupes
247         Set<String> dedupPackageSet = new HashSet<String>();
248         for (int i = visibleApps.size() - 1; i >= 0; i--) {
249             SelectableAppInfo info = visibleApps.get(i);
250             if (DEBUG) {
251                 Log.i(TAG, info.toString());
252             }
253             String both = info.packageName + "+" + info.activityName;
254             if (!TextUtils.isEmpty(info.packageName)
255                     && !TextUtils.isEmpty(info.activityName)
256                     && dedupPackageSet.contains(both)) {
257                 if (DEBUG) {
258                     Log.d(TAG, "Removing app: " + info.appName);
259                 }
260                 visibleApps.remove(i);
261             } else {
262                 dedupPackageSet.add(both);
263             }
264         }
265 
266         // Establish master/slave relationship for entries that share a package name
267         HashMap<String, SelectableAppInfo> packageMap = new HashMap<String,
268                 SelectableAppInfo>();
269         for (SelectableAppInfo info : visibleApps) {
270             if (packageMap.containsKey(info.packageName)) {
271                 info.masterEntry = packageMap.get(info.packageName);
272             } else {
273                 packageMap.put(info.packageName, info);
274             }
275         }
276         return visibleApps;
277     }
278 
addNonSystemApps(List<ApplicationInfo> apps, boolean disableSystemApps, List<SelectableAppInfo> visibleApps)279     private void addNonSystemApps(List<ApplicationInfo> apps, boolean disableSystemApps,
280             List<SelectableAppInfo> visibleApps) {
281         if (apps == null) {
282             return;
283         }
284 
285         for (ApplicationInfo app : apps) {
286             // If it's not installed, skip
287             if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
288                 continue;
289             }
290 
291             if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
292                     && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
293                 // Downloaded app
294                 visibleApps.add(new SelectableAppInfo(mPackageManager, app));
295             } else if (disableSystemApps) {
296                 try {
297                     PackageInfo pi = mPackageManager.getPackageInfo(app.packageName, 0);
298                     // If it's a system app that requires an account and doesn't see restricted
299                     // accounts, mark for removal. It might get shown in the UI if it has an
300                     // icon but will still be marked as false and immutable.
301                     if (pi.requiredAccountType != null && pi.restrictedAccountType == null) {
302                         onPackageEnableChanged(app.packageName, false);
303                     }
304                 } catch (NameNotFoundException re) {
305                 }
306             }
307         }
308     }
309 
310     static class SelectableAppInfo {
311         private final String packageName;
312         private final CharSequence appName;
313         private final CharSequence activityName;
314         private final ApplicationInfo info;
315         private final int iconRes;
316         private SelectableAppInfo masterEntry;
317 
SelectableAppInfo(PackageManager packageManager, ResolveInfo resolveInfo)318         SelectableAppInfo(PackageManager packageManager, ResolveInfo resolveInfo) {
319             packageName = resolveInfo.activityInfo.packageName;
320             appName = resolveInfo.activityInfo.applicationInfo.loadLabel(packageManager);
321             CharSequence label = resolveInfo.activityInfo.loadLabel(packageManager);
322             activityName = (label != null) ? label : appName;
323             int activityIconRes = getIconResource(resolveInfo.activityInfo);
324             info = resolveInfo.activityInfo.applicationInfo;
325             iconRes = activityIconRes != 0 ? activityIconRes
326                     : getIconResource(resolveInfo.activityInfo.applicationInfo);
327         }
328 
SelectableAppInfo(PackageManager packageManager, ApplicationInfo applicationInfo)329         SelectableAppInfo(PackageManager packageManager, ApplicationInfo applicationInfo) {
330             packageName = applicationInfo.packageName;
331             appName = applicationInfo.loadLabel(packageManager);
332             activityName = appName;
333             info = applicationInfo;
334             iconRes = getIconResource(applicationInfo);
335         }
336 
337         @Override
toString()338         public String toString() {
339             return packageName + ": appName=" + appName + "; activityName=" + activityName
340                     + "; masterEntry=" + masterEntry;
341         }
342 
getIconResource(PackageItemInfo packageItemInfo)343         private int getIconResource(PackageItemInfo packageItemInfo) {
344             if (packageItemInfo.banner != 0) {
345                 return packageItemInfo.banner;
346             }
347             if (packageItemInfo.logo != 0) {
348                 return packageItemInfo.logo;
349             }
350             return packageItemInfo.icon;
351         }
352     }
353 
354     private static class AppLabelComparator implements Comparator<SelectableAppInfo> {
355 
356         @Override
compare(SelectableAppInfo lhs, SelectableAppInfo rhs)357         public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
358             String lhsLabel = lhs.activityName.toString();
359             String rhsLabel = rhs.activityName.toString();
360             return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
361         }
362     }
363 
364     /**
365      * Find all pre-installed input methods that are marked as default and add them to an exclusion
366      * list so that they aren't presented to the user for toggling. Don't add non-default ones, as
367      * they may include other stuff that we don't need to auto-include.
368      *
369      * @return the set of default system imes
370      */
getDefaultSystemImes()371     private HashSet<String> getDefaultSystemImes() {
372         HashSet<String> defaultSystemImes = new HashSet<String>();
373         InputMethodManager imm = (InputMethodManager)
374                 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
375         List<InputMethodInfo> imis = imm.getInputMethodList();
376         for (InputMethodInfo imi : imis) {
377             try {
378                 if (imi.isDefault(mContext) && isSystemPackage(imi.getPackageName())) {
379                     defaultSystemImes.add(imi.getPackageName());
380                 }
381             } catch (Resources.NotFoundException rnfe) {
382                 // Not default
383             }
384         }
385         return defaultSystemImes;
386     }
387 
isSystemPackage(String packageName)388     private boolean isSystemPackage(String packageName) {
389         try {
390             final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
391             if (pi.applicationInfo == null)
392                 return false;
393             final int flags = pi.applicationInfo.flags;
394             if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
395                     || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
396                 return true;
397             }
398         } catch (NameNotFoundException nnfe) {
399             // Missing package?
400         }
401         return false;
402     }
403 
404     /**
405      * Add system apps that match an intent to the list, excluding any packages in the exclude list.
406      *
407      * @param visibleApps list of apps to append the new list to
408      * @param intent the intent to match
409      * @param excludePackages the set of package names to be excluded, since they're required
410      */
addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent, Set<String> excludePackages, int userId)411     private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent,
412             Set<String> excludePackages, int userId) {
413         final PackageManager pm = mPackageManager;
414         List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent,
415                 PackageManager.GET_DISABLED_COMPONENTS
416                 | PackageManager.GET_UNINSTALLED_PACKAGES);
417         for (ResolveInfo app : launchableApps) {
418             if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
419                 final String packageName = app.activityInfo.packageName;
420                 int flags = app.activityInfo.applicationInfo.flags;
421                 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
422                         || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
423                     if (DEBUG) {
424                         Log.d(TAG, "Found system app: "
425                                 + app.activityInfo.applicationInfo.loadLabel(pm));
426                     }
427                     // System app
428                     // Skip excluded packages
429                     if (excludePackages.contains(packageName)) {
430                         if (DEBUG) {
431                             Log.d(TAG, "App is an excluded ime, not adding: "
432                                     + app.activityInfo.applicationInfo.loadLabel(pm));
433                         }
434                         continue;
435                     }
436                     int enabled = pm.getApplicationEnabledSetting(packageName);
437                     if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
438                             || enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
439                         // Check if the app is already enabled for the target user
440                         ApplicationInfo targetUserAppInfo = getAppInfoForUser(packageName,
441                                 0, userId);
442                         if (targetUserAppInfo == null
443                                 || (targetUserAppInfo.flags & ApplicationInfo.FLAG_INSTALLED)
444                                         == 0) {
445                             if (DEBUG) {
446                                 Log.d(TAG, "App is already something, not adding: "
447                                         + app.activityInfo.applicationInfo.loadLabel(pm));
448                             }
449                             continue;
450                         }
451                     }
452 
453                     if (DEBUG) {
454                         Log.d(TAG, "Adding system app: "
455                                 + app.activityInfo.applicationInfo.loadLabel(pm));
456                     }
457                     visibleApps.add(new SelectableAppInfo(pm, app));
458                 }
459             }
460         }
461     }
462 
getAppInfoForUser(String packageName, int flags, int userId)463     private ApplicationInfo getAppInfoForUser(String packageName, int flags, int userId) {
464         try {
465             ApplicationInfo targetUserAppInfo = mIPackageManager.getApplicationInfo(packageName,
466                     flags,
467                     userId);
468             return targetUserAppInfo;
469         } catch (RemoteException re) {
470             return null;
471         }
472     }
473 
isPlatformSigned(PackageInfo pi)474     private boolean isPlatformSigned(PackageInfo pi) {
475         return (pi != null && pi.signatures != null &&
476                 mSysPackageInfo.signatures[0].equals(pi.signatures[0]));
477     }
478 
isAppEnabledForUser(PackageInfo pi)479     private boolean isAppEnabledForUser(PackageInfo pi) {
480         if (pi == null)
481             return false;
482         final int flags = pi.applicationInfo.flags;
483         final int privateFlags = pi.applicationInfo.privateFlags;
484         // Return true if it is installed and not hidden
485         return ((flags & ApplicationInfo.FLAG_INSTALLED) != 0
486                 && (privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0);
487     }
488 
getAppIconUri(Context context, ApplicationInfo info, int iconRes)489     private static Uri getAppIconUri(Context context, ApplicationInfo info, int iconRes) {
490         String iconUri = null;
491         if (iconRes != 0) {
492             try {
493                 Resources resources = context.getPackageManager()
494                         .getResourcesForApplication(info);
495                 ShortcutIconResource iconResource = new ShortcutIconResource();
496                 iconResource.packageName = info.packageName;
497                 iconResource.resourceName = resources.getResourceName(iconRes);
498                 iconUri = UriUtils.getShortcutIconResourceUri(iconResource).toString();
499             } catch (Exception e1) {
500                 Log.w("AppsBrowseInfo", e1.toString());
501             }
502         } else {
503             iconUri = UriUtils.getAndroidResourceUri(Resources.getSystem(),
504                     com.android.internal.R.drawable.sym_def_app_icon);
505         }
506 
507         if (iconUri == null) {
508             iconUri = UriUtils.getAndroidResourceUri(context.getResources(),
509                     com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
510         }
511         return Uri.parse(iconUri);
512     }
513 }
514