ShortcutUser.java revision 6c1dbd577bcf2b8bccb9a0d04d741ff7337898f2
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 */
16package com.android.server.pm;
17
18import android.annotation.NonNull;
19import android.annotation.UserIdInt;
20import android.content.ComponentName;
21import android.text.format.Formatter;
22import android.util.ArrayMap;
23import android.util.Slog;
24import android.util.SparseArray;
25
26import com.android.internal.annotations.VisibleForTesting;
27import com.android.internal.util.Preconditions;
28
29import libcore.util.Objects;
30
31import org.xmlpull.v1.XmlPullParser;
32import org.xmlpull.v1.XmlPullParserException;
33import org.xmlpull.v1.XmlSerializer;
34
35import java.io.File;
36import java.io.IOException;
37import java.io.PrintWriter;
38import java.util.function.Consumer;
39
40/**
41 * User information used by {@link ShortcutService}.
42 */
43class ShortcutUser {
44    private static final String TAG = ShortcutService.TAG;
45
46    static final String TAG_ROOT = "user";
47    private static final String TAG_LAUNCHER = "launcher";
48
49    private static final String ATTR_VALUE = "value";
50    private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no";
51
52    static final class PackageWithUser {
53        final int userId;
54        final String packageName;
55
56        private PackageWithUser(int userId, String packageName) {
57            this.userId = userId;
58            this.packageName = Preconditions.checkNotNull(packageName);
59        }
60
61        public static PackageWithUser of(int userId, String packageName) {
62            return new PackageWithUser(userId, packageName);
63        }
64
65        public static PackageWithUser of(ShortcutPackageItem spi) {
66            return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
67        }
68
69        @Override
70        public int hashCode() {
71            return packageName.hashCode() ^ userId;
72        }
73
74        @Override
75        public boolean equals(Object obj) {
76            if (!(obj instanceof PackageWithUser)) {
77                return false;
78            }
79            final PackageWithUser that = (PackageWithUser) obj;
80
81            return userId == that.userId && packageName.equals(that.packageName);
82        }
83
84        @Override
85        public String toString() {
86            return String.format("{Package: %d, %s}", userId, packageName);
87        }
88    }
89
90    @UserIdInt
91    private final int mUserId;
92
93    private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
94
95    private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>();
96
97    private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
98
99    /** Default launcher that can access the launcher apps APIs. */
100    private ComponentName mLauncherComponent;
101
102    private long mKnownLocaleChangeSequenceNumber;
103
104    public ShortcutUser(int userId) {
105        mUserId = userId;
106    }
107
108    public int getUserId() {
109        return mUserId;
110    }
111
112    // We don't expose this directly to non-test code because only ShortcutUser should add to/
113    // remove from it.
114    @VisibleForTesting
115    ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
116        return mPackages;
117    }
118
119    public boolean hasPackage(@NonNull String packageName) {
120        return mPackages.containsKey(packageName);
121    }
122
123    public ShortcutPackage removePackage(@NonNull ShortcutService s, @NonNull String packageName) {
124        final ShortcutPackage removed = mPackages.remove(packageName);
125
126        s.cleanupBitmapsForPackage(mUserId, packageName);
127
128        return removed;
129    }
130
131    // We don't expose this directly to non-test code because only ShortcutUser should add to/
132    // remove from it.
133    @VisibleForTesting
134    ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
135        return mLaunchers;
136    }
137
138    public void addLauncher(ShortcutLauncher launcher) {
139        mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
140                launcher.getPackageName()), launcher);
141    }
142
143    public ShortcutLauncher removeLauncher(
144            @UserIdInt int packageUserId, @NonNull String packageName) {
145        return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
146    }
147
148    public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
149        ShortcutPackage ret = mPackages.get(packageName);
150        if (ret == null) {
151            ret = new ShortcutPackage(s, this, mUserId, packageName);
152            mPackages.put(packageName, ret);
153        } else {
154            ret.attemptToRestoreIfNeededAndSave(s);
155        }
156        return ret;
157    }
158
159    public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
160            @UserIdInt int launcherUserId) {
161        final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
162        ShortcutLauncher ret = mLaunchers.get(key);
163        if (ret == null) {
164            ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
165            mLaunchers.put(key, ret);
166        } else {
167            ret.attemptToRestoreIfNeededAndSave(s);
168        }
169        return ret;
170    }
171
172    public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
173        final int size = mPackages.size();
174        for (int i = 0; i < size; i++) {
175            callback.accept(mPackages.valueAt(i));
176        }
177    }
178
179    public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
180        final int size = mLaunchers.size();
181        for (int i = 0; i < size; i++) {
182            callback.accept(mLaunchers.valueAt(i));
183        }
184    }
185
186    public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
187        forAllLaunchers(callback);
188        forAllPackages(callback);
189    }
190
191    public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
192            Consumer<ShortcutPackageItem> callback) {
193        forAllPackageItems(spi -> {
194            if ((spi.getPackageUserId() == packageUserId)
195                    && spi.getPackageName().equals(packageName)) {
196                callback.accept(spi);
197            }
198        });
199    }
200
201    /**
202     * Reset all throttling counters for all packages, if there has been a system locale change.
203     */
204    public void resetThrottlingIfNeeded(ShortcutService s) {
205        final long currentNo = s.getLocaleChangeSequenceNumber();
206        if (mKnownLocaleChangeSequenceNumber < currentNo) {
207            if (ShortcutService.DEBUG) {
208                Slog.d(TAG, "LocaleChange detected for user " + mUserId);
209            }
210
211            mKnownLocaleChangeSequenceNumber = currentNo;
212
213            forAllPackages(p -> p.resetRateLimiting(s));
214
215            s.scheduleSaveUser(mUserId);
216        }
217    }
218
219    /**
220     * Called when a package is updated.
221     */
222    public void handlePackageUpdated(ShortcutService s, @NonNull String packageName,
223            int newVersionCode) {
224        if (!mPackages.containsKey(packageName)) {
225            return;
226        }
227        getPackageShortcuts(s, packageName).handlePackageUpdated(s, newVersionCode);
228    }
229
230    public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
231            @UserIdInt int packageUserId) {
232        forPackageItem(packageName, packageUserId, spi -> {
233            spi.attemptToRestoreIfNeededAndSave(s);
234        });
235    }
236
237    public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
238            throws IOException, XmlPullParserException {
239        out.startTag(null, TAG_ROOT);
240
241        ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER,
242                mKnownLocaleChangeSequenceNumber);
243
244        ShortcutService.writeTagValue(out, TAG_LAUNCHER,
245                mLauncherComponent);
246
247        // Can't use forEachPackageItem due to the checked exceptions.
248        {
249            final int size = mLaunchers.size();
250            for (int i = 0; i < size; i++) {
251                saveShortcutPackageItem(s, out, mLaunchers.valueAt(i), forBackup);
252            }
253        }
254        {
255            final int size = mPackages.size();
256            for (int i = 0; i < size; i++) {
257                saveShortcutPackageItem(s, out, mPackages.valueAt(i), forBackup);
258            }
259        }
260
261        out.endTag(null, TAG_ROOT);
262    }
263
264    private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out,
265            ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
266        if (forBackup) {
267            if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
268                return; // Don't save.
269            }
270            if (spi.getPackageUserId() != spi.getOwnerUserId()) {
271                return; // Don't save cross-user information.
272            }
273        }
274        spi.saveToXml(out, forBackup);
275    }
276
277    public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
278            boolean fromBackup) throws IOException, XmlPullParserException {
279        final ShortcutUser ret = new ShortcutUser(userId);
280
281        ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
282                ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
283
284        final int outerDepth = parser.getDepth();
285        int type;
286        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
287                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
288            if (type != XmlPullParser.START_TAG) {
289                continue;
290            }
291            final int depth = parser.getDepth();
292            final String tag = parser.getName();
293
294            if (depth == outerDepth + 1) {
295                switch (tag) {
296                    case TAG_LAUNCHER: {
297                        ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
298                                parser, ATTR_VALUE);
299                        continue;
300                    }
301                    case ShortcutPackage.TAG_ROOT: {
302                        final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
303                                s, ret, parser, fromBackup);
304
305                        // Don't use addShortcut(), we don't need to save the icon.
306                        ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
307                        continue;
308                    }
309
310                    case ShortcutLauncher.TAG_ROOT: {
311                        ret.addLauncher(
312                                ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
313                        continue;
314                    }
315                }
316            }
317            ShortcutService.warnForInvalidTag(depth, tag);
318        }
319        return ret;
320    }
321
322    public ComponentName getLauncherComponent() {
323        return mLauncherComponent;
324    }
325
326    public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
327        if (Objects.equal(mLauncherComponent, launcherComponent)) {
328            return;
329        }
330        mLauncherComponent = launcherComponent;
331        s.scheduleSaveUser(mUserId);
332    }
333
334    public void resetThrottling() {
335        for (int i = mPackages.size() - 1; i >= 0; i--) {
336            mPackages.valueAt(i).resetThrottling();
337        }
338    }
339
340    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
341        pw.print(prefix);
342        pw.print("User: ");
343        pw.print(mUserId);
344        pw.print("  Known locale seq#: ");
345        pw.print(mKnownLocaleChangeSequenceNumber);
346        pw.println();
347
348        prefix += prefix + "  ";
349
350        pw.print(prefix);
351        pw.print("Default launcher: ");
352        pw.print(mLauncherComponent);
353        pw.println();
354
355        for (int i = 0; i < mLaunchers.size(); i++) {
356            mLaunchers.valueAt(i).dump(s, pw, prefix);
357        }
358
359        for (int i = 0; i < mPackages.size(); i++) {
360            mPackages.valueAt(i).dump(s, pw, prefix);
361        }
362
363        pw.println();
364        pw.print(prefix);
365        pw.println("Bitmap directories: ");
366        dumpDirectorySize(s, pw, prefix + "  ", s.getUserBitmapFilePath(mUserId));
367    }
368
369    private void dumpDirectorySize(@NonNull ShortcutService s, @NonNull PrintWriter pw,
370            @NonNull String prefix, File path) {
371        int numFiles = 0;
372        long size = 0;
373        final File[] children = path.listFiles();
374        if (children != null) {
375            for (File child : path.listFiles()) {
376                if (child.isFile()) {
377                    numFiles++;
378                    size += child.length();
379                } else if (child.isDirectory()) {
380                    dumpDirectorySize(s, pw, prefix + "  ", child);
381                }
382            }
383        }
384        pw.print(prefix);
385        pw.print("Path: ");
386        pw.print(path.getName());
387        pw.print("/ has ");
388        pw.print(numFiles);
389        pw.print(" files, size=");
390        pw.print(size);
391        pw.print(" (");
392        pw.print(Formatter.formatFileSize(s.mContext, size));
393        pw.println(")");
394    }
395}
396