• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.graphics.drawable.Drawable;
28 import android.os.RemoteException;
29 import android.os.UserHandle;
30 import android.text.TextUtils;
31 import android.util.ArraySet;
32 import android.util.Log;
33 
34 import androidx.annotation.VisibleForTesting;
35 
36 import java.util.ArrayList;
37 import java.util.Comparator;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Set;
41 
42 /**
43  * Helper for {@link com.android.settings.users.AppCopyFragment}, for keeping track of which
44  * packages a user has chosen to copy to a second user and fulfilling that installation.
45  *
46  * To test, use
47  *   atest SettingsLibTests:com.android.settingslib.users.AppCopyingHelperTest
48  */
49 public class AppCopyHelper {
50     private static final boolean DEBUG = false;
51     private static final String TAG = "AppCopyHelper";
52 
53     private final PackageManager mPackageManager;
54     private final IPackageManager mIPm;
55     private final UserHandle mUser;
56     private boolean mLeanback;
57 
58     /** Set of packages to be installed. */
59     private final ArraySet<String> mSelectedPackages = new ArraySet<>();
60     /** List of installable packages from which the user can choose. */
61     private List<SelectableAppInfo> mVisibleApps;
62 
AppCopyHelper(Context context, UserHandle user)63     public AppCopyHelper(Context context, UserHandle user) {
64         this(new Injector(context, user));
65     }
66 
67     @VisibleForTesting
AppCopyHelper(Injector injector)68     AppCopyHelper(Injector injector) {
69         mPackageManager = injector.getPackageManager();
70         mIPm = injector.getIPackageManager();
71         mUser = injector.getUser();
72     }
73 
74     /** Toggles whether the package has been selected. */
setPackageSelected(String packageName, boolean selected)75     public void setPackageSelected(String packageName, boolean selected) {
76         if (selected) {
77             mSelectedPackages.add(packageName);
78         } else {
79             mSelectedPackages.remove(packageName);
80         }
81     }
82 
83     /** Resets all packages as unselected. */
resetSelectedPackages()84     public void resetSelectedPackages() {
85         mSelectedPackages.clear();
86     }
87 
setLeanback(boolean isLeanback)88     public void setLeanback(boolean isLeanback) {
89         mLeanback = isLeanback;
90     }
91 
92     /** List of installable packages from which the user can choose. */
getVisibleApps()93     public List<SelectableAppInfo> getVisibleApps() {
94         return mVisibleApps;
95     }
96 
97     /** Installs the packages that have been selected using {@link #setPackageSelected} */
installSelectedApps()98     public void installSelectedApps() {
99         for (int i = 0; i < mSelectedPackages.size(); i++) {
100             final String packageName = mSelectedPackages.valueAt(i);
101             installSelectedApp(packageName);
102         }
103     }
104 
installSelectedApp(String packageName)105     private void installSelectedApp(String packageName) {
106         final int userId = mUser.getIdentifier();
107         try {
108             final ApplicationInfo info = mIPm.getApplicationInfo(packageName,
109                     PackageManager.MATCH_ANY_USER, userId);
110             if (info == null || !info.enabled
111                     || (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
112                 Log.i(TAG, "Installing " + packageName);
113                 mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier(),
114                         PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
115                         PackageManager.INSTALL_REASON_UNKNOWN, null);
116             }
117             if (info != null && (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN) != 0
118                     && (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
119                 Log.i(TAG, "Unhiding " + packageName);
120                 mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId);
121             }
122         } catch (RemoteException re) {
123             // Ignore
124         }
125     }
126 
127     /**
128      * Fetches the list of installable packages to display.
129      * This list can be obtained from {@link #getVisibleApps}.
130      */
fetchAndMergeApps()131     public void fetchAndMergeApps() {
132         mVisibleApps = new ArrayList<>();
133         addCurrentUsersApps();
134         removeSecondUsersApp();
135     }
136 
137     /**
138      * Adds to {@link #mVisibleApps} packages from the current user:
139      *  (1) All downloaded apps and
140      *  (2) all system apps that have launcher or widgets.
141      */
addCurrentUsersApps()142     private void addCurrentUsersApps() {
143         // Add system package launchers of the current user
144         final Intent launcherIntent = new Intent(Intent.ACTION_MAIN).addCategory(
145                 mLeanback ? Intent.CATEGORY_LEANBACK_LAUNCHER : Intent.CATEGORY_LAUNCHER);
146         addSystemApps(mVisibleApps, launcherIntent);
147 
148         // Add system package widgets of the current user
149         final Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
150         addSystemApps(mVisibleApps, widgetIntent);
151 
152         // Add all downloaded apps of the current user
153         final List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(0);
154         for (ApplicationInfo app : installedApps) {
155             // If it's not installed, skip
156             if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
157 
158             if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0
159                     && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
160                 // Downloaded app
161                 final SelectableAppInfo info = new SelectableAppInfo();
162                 info.packageName = app.packageName;
163                 info.appName = app.loadLabel(mPackageManager);
164                 info.icon = app.loadIcon(mPackageManager);
165                 mVisibleApps.add(info);
166             }
167         }
168 
169         // Remove dupes
170         final Set<String> dedupPackages = new HashSet<>();
171         for (int i = mVisibleApps.size() - 1; i >= 0; i--) {
172             final SelectableAppInfo info = mVisibleApps.get(i);
173             if (DEBUG) Log.i(TAG, info.toString());
174             if (!TextUtils.isEmpty(info.packageName) && dedupPackages.contains(info.packageName)) {
175                 mVisibleApps.remove(i);
176             } else {
177                 dedupPackages.add(info.packageName);
178             }
179         }
180 
181         // Sort the list of visible apps
182         mVisibleApps.sort(new AppLabelComparator());
183     }
184 
185     /** Removes from {@link #mVisibleApps} all packages already installed on mUser. */
removeSecondUsersApp()186     private void removeSecondUsersApp() {
187         // Get the set of apps already installed for mUser
188         final Set<String> userPackages = new HashSet<>();
189         final List<ApplicationInfo> userAppInfos = mPackageManager.getInstalledApplicationsAsUser(
190                     PackageManager.MATCH_UNINSTALLED_PACKAGES, mUser.getIdentifier());
191         for (int i = userAppInfos.size() - 1; i >= 0; i--) {
192             final ApplicationInfo app = userAppInfos.get(i);
193             if ((app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) continue;
194             userPackages.add(app.packageName);
195         }
196 
197         for (int i = mVisibleApps.size() - 1; i >= 0; i--) {
198             final SelectableAppInfo info = mVisibleApps.get(i);
199             if (DEBUG) Log.i(TAG, info.toString());
200             if (!TextUtils.isEmpty(info.packageName) && userPackages.contains(info.packageName)) {
201                 mVisibleApps.remove(i);
202             }
203         }
204     }
205 
206     /**
207      * Add system apps that match an intent to the list.
208      * @param visibleApps list of apps to append the new list to
209      * @param intent the intent to match
210      */
addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent)211     private void addSystemApps(List<SelectableAppInfo> visibleApps, Intent intent) {
212         final List<ResolveInfo> intentApps = mPackageManager.queryIntentActivities(intent, 0);
213         for (ResolveInfo app : intentApps) {
214             if (app.activityInfo != null && app.activityInfo.applicationInfo != null) {
215                 final int flags = app.activityInfo.applicationInfo.flags;
216                 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0
217                         || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
218 
219                     final SelectableAppInfo info = new SelectableAppInfo();
220                     info.packageName = app.activityInfo.packageName;
221                     info.appName = app.activityInfo.applicationInfo.loadLabel(mPackageManager);
222                     info.icon = app.activityInfo.loadIcon(mPackageManager);
223 
224                     visibleApps.add(info);
225                 }
226             }
227         }
228     }
229 
230     /** Container for a package, its name, and icon. */
231     public static class SelectableAppInfo {
232         public String packageName;
233         public CharSequence appName;
234         public Drawable icon;
235 
236         @Override
toString()237         public String toString() {
238             return packageName + ": appName=" + appName + "; icon=" + icon;
239         }
240     }
241 
242     private static class AppLabelComparator implements Comparator<SelectableAppInfo> {
243         @Override
compare(SelectableAppInfo lhs, SelectableAppInfo rhs)244         public int compare(SelectableAppInfo lhs, SelectableAppInfo rhs) {
245             String lhsLabel = lhs.appName.toString();
246             String rhsLabel = rhs.appName.toString();
247             return lhsLabel.toLowerCase().compareTo(rhsLabel.toLowerCase());
248         }
249     }
250 
251     /**
252      * Unit test will subclass it to inject mocks.
253      */
254     @VisibleForTesting
255     static class Injector {
256         private final Context mContext;
257         private final UserHandle mUser;
258 
Injector(Context context, UserHandle user)259         Injector(Context context, UserHandle user) {
260             mContext = context;
261             mUser = user;
262         }
263 
getUser()264         UserHandle getUser() {
265             return mUser;
266         }
267 
getPackageManager()268         PackageManager getPackageManager() {
269             return mContext.getPackageManager();
270         }
271 
getIPackageManager()272         IPackageManager getIPackageManager() {
273             return AppGlobals.getPackageManager();
274         }
275     }
276 }
277