ShortcutUser.java revision 6c1dbd577bcf2b8bccb9a0d04d741ff7337898f2
13da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson/*
23da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * Copyright (C) 2016 The Android Open Source Project
33da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson *
43da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * Licensed under the Apache License, Version 2.0 (the "License");
53da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * you may not use this file except in compliance with the License.
63da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * You may obtain a copy of the License at
73da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson *
83da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson *      http://www.apache.org/licenses/LICENSE-2.0
93da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson *
103da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * Unless required by applicable law or agreed to in writing, software
113da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * distributed under the License is distributed on an "AS IS" BASIS,
123da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * See the License for the specific language governing permissions and
143da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * limitations under the License.
153da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson */
163da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonpackage com.android.server.pm;
173da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
183da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport android.annotation.NonNull;
193da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport android.annotation.UserIdInt;
203da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport android.content.ComponentName;
213da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport android.text.format.Formatter;
223da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport android.util.ArrayMap;
233da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport android.util.Slog;
243da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport android.util.SparseArray;
253da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
263da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport com.android.internal.annotations.VisibleForTesting;
273da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport com.android.internal.util.Preconditions;
283da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
293da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport libcore.util.Objects;
303da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
313da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport org.xmlpull.v1.XmlPullParser;
323da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport org.xmlpull.v1.XmlPullParserException;
333da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport org.xmlpull.v1.XmlSerializer;
343da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
353da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport java.io.File;
363da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport java.io.IOException;
37bae93e8992fef3cfaff681fe85b26fd35e432e31David Gibsonimport java.io.PrintWriter;
383da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonimport java.util.function.Consumer;
393da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
403da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson/**
413da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson * User information used by {@link ShortcutService}.
423da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson */
433da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibsonclass ShortcutUser {
443da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    private static final String TAG = ShortcutService.TAG;
453da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
463da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    static final String TAG_ROOT = "user";
473da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    private static final String TAG_LAUNCHER = "launcher";
483da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
493da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    private static final String ATTR_VALUE = "value";
503da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no";
513da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
523da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    static final class PackageWithUser {
533da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        final int userId;
543da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        final String packageName;
553da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
563da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        private PackageWithUser(int userId, String packageName) {
573da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            this.userId = userId;
583da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            this.packageName = Preconditions.checkNotNull(packageName);
593da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        }
603da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
613da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        public static PackageWithUser of(int userId, String packageName) {
623da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            return new PackageWithUser(userId, packageName);
633da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        }
643da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
653da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        public static PackageWithUser of(ShortcutPackageItem spi) {
663da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
673da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        }
683da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
693da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        @Override
703da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        public int hashCode() {
713da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            return packageName.hashCode() ^ userId;
723da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        }
733da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
743da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        @Override
753da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        public boolean equals(Object obj) {
763da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            if (!(obj instanceof PackageWithUser)) {
773da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson                return false;
783da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            }
793da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            final PackageWithUser that = (PackageWithUser) obj;
803da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
813da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            return userId == that.userId && packageName.equals(that.packageName);
823da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        }
833da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
843da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        @Override
853da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        public String toString() {
863da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson            return String.format("{Package: %d, %s}", userId, packageName);
873da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        }
883da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    }
893da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
903da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    @UserIdInt
913da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    private final int mUserId;
923da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
933da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
943da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
953da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>();
96fd1bf3a5ae46962528ef89a824261a88830758a2David Gibson
97fd1bf3a5ae46962528ef89a824261a88830758a2David Gibson    private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
9873d60926a05814b8864c86c435e272b386513b0eDavid Gibson
993da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    /** Default launcher that can access the launcher apps APIs. */
1009521dc5ecc66c158cd6853cabba2c29f545780f6David Gibson    private ComponentName mLauncherComponent;
1013da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
102bad5b28049e5e0562a8ad91797fb77953a53fa20David Gibson    private long mKnownLocaleChangeSequenceNumber;
1033da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson
1043da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    public ShortcutUser(int userId) {
1053da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        mUserId = userId;
1063da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    }
107a6c76f923dcc42102fac58375eaca28057811c20David Gibson
108a6c76f923dcc42102fac58375eaca28057811c20David Gibson    public int getUserId() {
1099521dc5ecc66c158cd6853cabba2c29f545780f6David Gibson        return mUserId;
1103da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    }
111bad5b28049e5e0562a8ad91797fb77953a53fa20David Gibson
1123da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    // We don't expose this directly to non-test code because only ShortcutUser should add to/
1133da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    // remove from it.
114cbf1410eab4b7ce7be1b15f985ef71bfc1f5886dDavid Gibson    @VisibleForTesting
115cbf1410eab4b7ce7be1b15f985ef71bfc1f5886dDavid Gibson    ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
116bad5b28049e5e0562a8ad91797fb77953a53fa20David Gibson        return mPackages;
117cbf1410eab4b7ce7be1b15f985ef71bfc1f5886dDavid Gibson    }
118cbf1410eab4b7ce7be1b15f985ef71bfc1f5886dDavid Gibson
1197ba551f96629a5273370329658a6e6b3a87068aaDavid Gibson    public boolean hasPackage(@NonNull String packageName) {
1207ba551f96629a5273370329658a6e6b3a87068aaDavid Gibson        return mPackages.containsKey(packageName);
121d2a9da045897c37071597d9aa473964717b14735David Gibson    }
1223c44c87bdeacc66f46c55090d765a9766475ee50David Gibson
1234e6221c171377324cc3e80a9c2260b9788335d87David Gibson    public ShortcutPackage removePackage(@NonNull ShortcutService s, @NonNull String packageName) {
12473d60926a05814b8864c86c435e272b386513b0eDavid Gibson        final ShortcutPackage removed = mPackages.remove(packageName);
125a041dcdc48453f26b76bccdb5e2a1ebb3a0ea987David Gibson
1263da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson        s.cleanupBitmapsForPackage(mUserId, packageName);
12736204fdf742cabc074617648a5b2cf62409dc40bSimon Glass
12836204fdf742cabc074617648a5b2cf62409dc40bSimon Glass        return removed;
1293da0f9a10dfa9b615d06c350c7b9fe29f360a6eDavid Gibson    }
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