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