1/*
2 * Copyright (C) 2008 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
17package com.android.launcher3;
18
19import android.content.ComponentName;
20import android.content.Context;
21
22import com.android.launcher3.compat.LauncherActivityInfoCompat;
23import com.android.launcher3.compat.LauncherAppsCompat;
24import com.android.launcher3.compat.UserHandleCompat;
25import com.android.launcher3.util.FlagOp;
26import com.android.launcher3.util.StringFilter;
27
28import java.util.ArrayList;
29import java.util.HashSet;
30import java.util.List;
31
32
33/**
34 * Stores the list of all applications for the all apps view.
35 */
36class AllAppsList {
37    public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
38
39    /** The list off all apps. */
40    public ArrayList<AppInfo> data =
41            new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
42    /** The list of apps that have been added since the last notify() call. */
43    public ArrayList<AppInfo> added =
44            new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
45    /** The list of apps that have been removed since the last notify() call. */
46    public ArrayList<AppInfo> removed = new ArrayList<AppInfo>();
47    /** The list of apps that have been modified since the last notify() call. */
48    public ArrayList<AppInfo> modified = new ArrayList<AppInfo>();
49
50    private IconCache mIconCache;
51
52    private AppFilter mAppFilter;
53
54    /**
55     * Boring constructor.
56     */
57    public AllAppsList(IconCache iconCache, AppFilter appFilter) {
58        mIconCache = iconCache;
59        mAppFilter = appFilter;
60    }
61
62    /**
63     * Add the supplied ApplicationInfo objects to the list, and enqueue it into the
64     * list to broadcast when notify() is called.
65     *
66     * If the app is already in the list, doesn't add it.
67     */
68    public void add(AppInfo info) {
69        if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
70            return;
71        }
72        if (findActivity(data, info.componentName, info.user)) {
73            return;
74        }
75        data.add(info);
76        added.add(info);
77    }
78
79    public void clear() {
80        data.clear();
81        // TODO: do we clear these too?
82        added.clear();
83        removed.clear();
84        modified.clear();
85    }
86
87    public int size() {
88        return data.size();
89    }
90
91    public AppInfo get(int index) {
92        return data.get(index);
93    }
94
95    /**
96     * Add the icons for the supplied apk called packageName.
97     */
98    public void addPackage(Context context, String packageName, UserHandleCompat user) {
99        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
100        final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
101                user);
102
103        for (LauncherActivityInfoCompat info : matches) {
104            add(new AppInfo(context, info, user, mIconCache));
105        }
106    }
107
108    /**
109     * Remove the apps for the given apk identified by packageName.
110     */
111    public void removePackage(String packageName, UserHandleCompat user) {
112        final List<AppInfo> data = this.data;
113        for (int i = data.size() - 1; i >= 0; i--) {
114            AppInfo info = data.get(i);
115            final ComponentName component = info.intent.getComponent();
116            if (info.user.equals(user) && packageName.equals(component.getPackageName())) {
117                removed.add(info);
118                data.remove(i);
119            }
120        }
121    }
122
123    /**
124     * Updates the apps for the given packageName and user based on {@param op}.
125     */
126    public void updatePackageFlags(StringFilter pkgFilter, UserHandleCompat user, FlagOp op) {
127        final List<AppInfo> data = this.data;
128        for (int i = data.size() - 1; i >= 0; i--) {
129            AppInfo info = data.get(i);
130            final ComponentName component = info.intent.getComponent();
131            if (info.user.equals(user) && pkgFilter.matches(component.getPackageName())) {
132                info.isDisabled = op.apply(info.isDisabled);
133                modified.add(info);
134            }
135        }
136    }
137
138    public void updateIconsAndLabels(HashSet<String> packages, UserHandleCompat user,
139            ArrayList<AppInfo> outUpdates) {
140        for (AppInfo info : data) {
141            if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
142                mIconCache.updateTitleAndIcon(info);
143                outUpdates.add(info);
144            }
145        }
146    }
147
148    /**
149     * Add and remove icons for this package which has been updated.
150     */
151    public void updatePackage(Context context, String packageName, UserHandleCompat user) {
152        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
153        final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
154                user);
155        if (matches.size() > 0) {
156            // Find disabled/removed activities and remove them from data and add them
157            // to the removed list.
158            for (int i = data.size() - 1; i >= 0; i--) {
159                final AppInfo applicationInfo = data.get(i);
160                final ComponentName component = applicationInfo.intent.getComponent();
161                if (user.equals(applicationInfo.user)
162                        && packageName.equals(component.getPackageName())) {
163                    if (!findActivity(matches, component)) {
164                        removed.add(applicationInfo);
165                        data.remove(i);
166                    }
167                }
168            }
169
170            // Find enabled activities and add them to the adapter
171            // Also updates existing activities with new labels/icons
172            for (final LauncherActivityInfoCompat info : matches) {
173                AppInfo applicationInfo = findApplicationInfoLocked(
174                        info.getComponentName().getPackageName(), user,
175                        info.getComponentName().getClassName());
176                if (applicationInfo == null) {
177                    add(new AppInfo(context, info, user, mIconCache));
178                } else {
179                    mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
180                    modified.add(applicationInfo);
181                }
182            }
183        } else {
184            // Remove all data for this package.
185            for (int i = data.size() - 1; i >= 0; i--) {
186                final AppInfo applicationInfo = data.get(i);
187                final ComponentName component = applicationInfo.intent.getComponent();
188                if (user.equals(applicationInfo.user)
189                        && packageName.equals(component.getPackageName())) {
190                    removed.add(applicationInfo);
191                    mIconCache.remove(component, user);
192                    data.remove(i);
193                }
194            }
195        }
196    }
197
198
199    /**
200     * Returns whether <em>apps</em> contains <em>component</em>.
201     */
202    private static boolean findActivity(List<LauncherActivityInfoCompat> apps,
203            ComponentName component) {
204        for (LauncherActivityInfoCompat info : apps) {
205            if (info.getComponentName().equals(component)) {
206                return true;
207            }
208        }
209        return false;
210    }
211
212    /**
213     * Query the launcher apps service for whether the supplied package has
214     * MAIN/LAUNCHER activities in the supplied package.
215     */
216    static boolean packageHasActivities(Context context, String packageName,
217            UserHandleCompat user) {
218        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
219        return launcherApps.getActivityList(packageName, user).size() > 0;
220    }
221
222    /**
223     * Returns whether <em>apps</em> contains <em>component</em>.
224     */
225    private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component,
226            UserHandleCompat user) {
227        final int N = apps.size();
228        for (int i = 0; i < N; i++) {
229            final AppInfo info = apps.get(i);
230            if (info.user.equals(user) && info.componentName.equals(component)) {
231                return true;
232            }
233        }
234        return false;
235    }
236
237    /**
238     * Find an ApplicationInfo object for the given packageName and className.
239     */
240    private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
241            String className) {
242        for (AppInfo info: data) {
243            final ComponentName component = info.intent.getComponent();
244            if (user.equals(info.user) && packageName.equals(component.getPackageName())
245                    && className.equals(component.getClassName())) {
246                return info;
247            }
248        }
249        return null;
250    }
251}
252