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 ShortcutPackage removePackage(@NonNull ShortcutService s, @NonNull String packageName) {
120        final ShortcutPackage removed = mPackages.remove(packageName);
121
122        s.cleanupBitmapsForPackage(mUserId, packageName);
123
124        return removed;
125    }
126
127    // We don't expose this directly to non-test code because only ShortcutUser should add to/
128    // remove from it.
129    @VisibleForTesting
130    ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
131        return mLaunchers;
132    }
133
134    public void addLauncher(ShortcutLauncher launcher) {
135        mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
136                launcher.getPackageName()), launcher);
137    }
138
139    public ShortcutLauncher removeLauncher(
140            @UserIdInt int packageUserId, @NonNull String packageName) {
141        return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
142    }
143
144    public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
145        ShortcutPackage ret = mPackages.get(packageName);
146        if (ret == null) {
147            ret = new ShortcutPackage(s, this, mUserId, packageName);
148            mPackages.put(packageName, ret);
149        } else {
150            ret.attemptToRestoreIfNeededAndSave(s);
151        }
152        return ret;
153    }
154
155    public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
156            @UserIdInt int launcherUserId) {
157        final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
158        ShortcutLauncher ret = mLaunchers.get(key);
159        if (ret == null) {
160            ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
161            mLaunchers.put(key, ret);
162        } else {
163            ret.attemptToRestoreIfNeededAndSave(s);
164        }
165        return ret;
166    }
167
168    public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
169        final int size = mPackages.size();
170        for (int i = 0; i < size; i++) {
171            callback.accept(mPackages.valueAt(i));
172        }
173    }
174
175    public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
176        final int size = mLaunchers.size();
177        for (int i = 0; i < size; i++) {
178            callback.accept(mLaunchers.valueAt(i));
179        }
180    }
181
182    public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
183        forAllLaunchers(callback);
184        forAllPackages(callback);
185    }
186
187    public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
188            Consumer<ShortcutPackageItem> callback) {
189        forAllPackageItems(spi -> {
190            if ((spi.getPackageUserId() == packageUserId)
191                    && spi.getPackageName().equals(packageName)) {
192                callback.accept(spi);
193            }
194        });
195    }
196
197    /**
198     * Reset all throttling counters for all packages, if there has been a system locale change.
199     */
200    public void resetThrottlingIfNeeded(ShortcutService s) {
201        final long currentNo = s.getLocaleChangeSequenceNumber();
202        if (mKnownLocaleChangeSequenceNumber < currentNo) {
203            if (ShortcutService.DEBUG) {
204                Slog.d(TAG, "LocaleChange detected for user " + mUserId);
205            }
206
207            mKnownLocaleChangeSequenceNumber = currentNo;
208
209            forAllPackages(p -> p.resetRateLimiting(s));
210
211            s.scheduleSaveUser(mUserId);
212        }
213    }
214
215    /**
216     * Called when a package is updated.
217     */
218    public void handlePackageUpdated(ShortcutService s, @NonNull String packageName,
219            int newVersionCode) {
220        if (!mPackages.containsKey(packageName)) {
221            return;
222        }
223        getPackageShortcuts(s, packageName).handlePackageUpdated(s, newVersionCode);
224    }
225
226    public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
227            @UserIdInt int packageUserId) {
228        forPackageItem(packageName, packageUserId, spi -> {
229            spi.attemptToRestoreIfNeededAndSave(s);
230        });
231    }
232
233    public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
234            throws IOException, XmlPullParserException {
235        out.startTag(null, TAG_ROOT);
236
237        ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER,
238                mKnownLocaleChangeSequenceNumber);
239
240        ShortcutService.writeTagValue(out, TAG_LAUNCHER,
241                mLauncherComponent);
242
243        // Can't use forEachPackageItem due to the checked exceptions.
244        {
245            final int size = mLaunchers.size();
246            for (int i = 0; i < size; i++) {
247                saveShortcutPackageItem(s, out, mLaunchers.valueAt(i), forBackup);
248            }
249        }
250        {
251            final int size = mPackages.size();
252            for (int i = 0; i < size; i++) {
253                saveShortcutPackageItem(s, out, mPackages.valueAt(i), forBackup);
254            }
255        }
256
257        out.endTag(null, TAG_ROOT);
258    }
259
260    private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out,
261            ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
262        if (forBackup) {
263            if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
264                return; // Don't save.
265            }
266            if (spi.getPackageUserId() != spi.getOwnerUserId()) {
267                return; // Don't save cross-user information.
268            }
269        }
270        spi.saveToXml(out, forBackup);
271    }
272
273    public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
274            boolean fromBackup) throws IOException, XmlPullParserException {
275        final ShortcutUser ret = new ShortcutUser(userId);
276
277        ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
278                ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
279
280        final int outerDepth = parser.getDepth();
281        int type;
282        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
283                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
284            if (type != XmlPullParser.START_TAG) {
285                continue;
286            }
287            final int depth = parser.getDepth();
288            final String tag = parser.getName();
289
290            if (depth == outerDepth + 1) {
291                switch (tag) {
292                    case TAG_LAUNCHER: {
293                        ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
294                                parser, ATTR_VALUE);
295                        continue;
296                    }
297                    case ShortcutPackage.TAG_ROOT: {
298                        final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
299                                s, ret, parser, fromBackup);
300
301                        // Don't use addShortcut(), we don't need to save the icon.
302                        ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
303                        continue;
304                    }
305
306                    case ShortcutLauncher.TAG_ROOT: {
307                        ret.addLauncher(
308                                ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
309                        continue;
310                    }
311                }
312            }
313            ShortcutService.warnForInvalidTag(depth, tag);
314        }
315        return ret;
316    }
317
318    public ComponentName getLauncherComponent() {
319        return mLauncherComponent;
320    }
321
322    public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
323        if (Objects.equal(mLauncherComponent, launcherComponent)) {
324            return;
325        }
326        mLauncherComponent = launcherComponent;
327        s.scheduleSaveUser(mUserId);
328    }
329
330    public void resetThrottling() {
331        for (int i = mPackages.size() - 1; i >= 0; i--) {
332            mPackages.valueAt(i).resetThrottling();
333        }
334    }
335
336    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
337        pw.print(prefix);
338        pw.print("User: ");
339        pw.print(mUserId);
340        pw.print("  Known locale seq#: ");
341        pw.print(mKnownLocaleChangeSequenceNumber);
342        pw.println();
343
344        prefix += prefix + "  ";
345
346        pw.print(prefix);
347        pw.print("Default launcher: ");
348        pw.print(mLauncherComponent);
349        pw.println();
350
351        for (int i = 0; i < mLaunchers.size(); i++) {
352            mLaunchers.valueAt(i).dump(s, pw, prefix);
353        }
354
355        for (int i = 0; i < mPackages.size(); i++) {
356            mPackages.valueAt(i).dump(s, pw, prefix);
357        }
358
359        pw.println();
360        pw.print(prefix);
361        pw.println("Bitmap directories: ");
362        dumpDirectorySize(s, pw, prefix + "  ", s.getUserBitmapFilePath(mUserId));
363    }
364
365    private void dumpDirectorySize(@NonNull ShortcutService s, @NonNull PrintWriter pw,
366            @NonNull String prefix, File path) {
367        int numFiles = 0;
368        long size = 0;
369        final File[] children = path.listFiles();
370        if (children != null) {
371            for (File child : path.listFiles()) {
372                if (child.isFile()) {
373                    numFiles++;
374                    size += child.length();
375                } else if (child.isDirectory()) {
376                    dumpDirectorySize(s, pw, prefix + "  ", child);
377                }
378            }
379        }
380        pw.print(prefix);
381        pw.print("Path: ");
382        pw.print(path.getName());
383        pw.print("/ has ");
384        pw.print(numFiles);
385        pw.print(" files, size=");
386        pw.print(size);
387        pw.print(" (");
388        pw.print(Formatter.formatFileSize(s.mContext, size));
389        pw.println(")");
390    }
391}
392