OSDN Git Service

ShortcutManager: rescan all apps after next OTA
[android-x86/frameworks-base.git] / services / core / java / com / android / server / pm / ShortcutUser.java
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  */
16 package com.android.server.pm;
17
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.UserIdInt;
21 import android.content.ComponentName;
22 import android.content.pm.ShortcutManager;
23 import android.text.TextUtils;
24 import android.text.format.Formatter;
25 import android.util.ArrayMap;
26 import android.util.Slog;
27 import android.util.SparseArray;
28
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.util.Preconditions;
32
33 import libcore.util.Objects;
34
35 import org.json.JSONArray;
36 import org.json.JSONException;
37 import org.json.JSONObject;
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 import org.xmlpull.v1.XmlSerializer;
41
42 import java.io.File;
43 import java.io.IOException;
44 import java.io.PrintWriter;
45 import java.util.function.Consumer;
46
47 /**
48  * User information used by {@link ShortcutService}.
49  *
50  * All methods should be guarded by {@code #mService.mLock}.
51  */
52 class ShortcutUser {
53     private static final String TAG = ShortcutService.TAG;
54
55     static final String TAG_ROOT = "user";
56     private static final String TAG_LAUNCHER = "launcher";
57
58     private static final String ATTR_VALUE = "value";
59     private static final String ATTR_KNOWN_LOCALES = "locales";
60
61     // Suffix "2" was added to force rescan all packages after the next OTA.
62     private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2";
63     private static final String KEY_USER_ID = "userId";
64     private static final String KEY_LAUNCHERS = "launchers";
65     private static final String KEY_PACKAGES = "packages";
66
67     static final class PackageWithUser {
68         final int userId;
69         final String packageName;
70
71         private PackageWithUser(int userId, String packageName) {
72             this.userId = userId;
73             this.packageName = Preconditions.checkNotNull(packageName);
74         }
75
76         public static PackageWithUser of(int userId, String packageName) {
77             return new PackageWithUser(userId, packageName);
78         }
79
80         public static PackageWithUser of(ShortcutPackageItem spi) {
81             return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
82         }
83
84         @Override
85         public int hashCode() {
86             return packageName.hashCode() ^ userId;
87         }
88
89         @Override
90         public boolean equals(Object obj) {
91             if (!(obj instanceof PackageWithUser)) {
92                 return false;
93             }
94             final PackageWithUser that = (PackageWithUser) obj;
95
96             return userId == that.userId && packageName.equals(that.packageName);
97         }
98
99         @Override
100         public String toString() {
101             return String.format("[Package: %d, %s]", userId, packageName);
102         }
103     }
104
105     final ShortcutService mService;
106
107     @UserIdInt
108     private final int mUserId;
109
110     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
111
112     private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
113
114     /**
115      * Last known launcher.  It's used when the default launcher isn't set in PM -- i.e.
116      * when getHomeActivitiesAsUser() return null.  We need it so that in this situation the
117      * previously default launcher can still access shortcuts.
118      */
119     private ComponentName mLastKnownLauncher;
120
121     /** In-memory-cached default launcher. */
122     private ComponentName mCachedLauncher;
123
124     private String mKnownLocales;
125
126     private long mLastAppScanTime;
127
128     public ShortcutUser(ShortcutService service, int userId) {
129         mService = service;
130         mUserId = userId;
131     }
132
133     public int getUserId() {
134         return mUserId;
135     }
136
137     public long getLastAppScanTime() {
138         return mLastAppScanTime;
139     }
140
141     public void setLastAppScanTime(long lastAppScanTime) {
142         mLastAppScanTime = lastAppScanTime;
143     }
144
145     // We don't expose this directly to non-test code because only ShortcutUser should add to/
146     // remove from it.
147     @VisibleForTesting
148     ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
149         return mPackages;
150     }
151
152     public boolean hasPackage(@NonNull String packageName) {
153         return mPackages.containsKey(packageName);
154     }
155
156     public ShortcutPackage removePackage(@NonNull String packageName) {
157         final ShortcutPackage removed = mPackages.remove(packageName);
158
159         mService.cleanupBitmapsForPackage(mUserId, packageName);
160
161         return removed;
162     }
163
164     // We don't expose this directly to non-test code because only ShortcutUser should add to/
165     // remove from it.
166     @VisibleForTesting
167     ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
168         return mLaunchers;
169     }
170
171     public void addLauncher(ShortcutLauncher launcher) {
172         mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
173                 launcher.getPackageName()), launcher);
174     }
175
176     @Nullable
177     public ShortcutLauncher removeLauncher(
178             @UserIdInt int packageUserId, @NonNull String packageName) {
179         return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
180     }
181
182     @Nullable
183     public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
184         final ShortcutPackage ret = mPackages.get(packageName);
185         if (ret != null) {
186             ret.attemptToRestoreIfNeededAndSave();
187         }
188         return ret;
189     }
190
191     @NonNull
192     public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
193         ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
194         if (ret == null) {
195             ret = new ShortcutPackage(this, mUserId, packageName);
196             mPackages.put(packageName, ret);
197         }
198         return ret;
199     }
200
201     @NonNull
202     public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
203             @UserIdInt int launcherUserId) {
204         final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
205         ShortcutLauncher ret = mLaunchers.get(key);
206         if (ret == null) {
207             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
208             mLaunchers.put(key, ret);
209         } else {
210             ret.attemptToRestoreIfNeededAndSave();
211         }
212         return ret;
213     }
214
215     public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
216         final int size = mPackages.size();
217         for (int i = 0; i < size; i++) {
218             callback.accept(mPackages.valueAt(i));
219         }
220     }
221
222     public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
223         final int size = mLaunchers.size();
224         for (int i = 0; i < size; i++) {
225             callback.accept(mLaunchers.valueAt(i));
226         }
227     }
228
229     public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
230         forAllLaunchers(callback);
231         forAllPackages(callback);
232     }
233
234     public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
235             Consumer<ShortcutPackageItem> callback) {
236         forAllPackageItems(spi -> {
237             if ((spi.getPackageUserId() == packageUserId)
238                     && spi.getPackageName().equals(packageName)) {
239                 callback.accept(spi);
240             }
241         });
242     }
243
244     /**
245      * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the
246      * information on the package is up-to-date.
247      *
248      * We use broadcasts to handle locale changes and package changes, but because broadcasts
249      * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event
250      * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast.
251      *
252      * So we call this method at all entry points from publishers to make sure we update all
253      * relevant information.
254      *
255      * Similar inconsistencies can happen when the launcher fetches shortcut information, but
256      * that's a less of an issue because for the launcher we report shortcut changes with
257      * callbacks.
258      */
259     public void onCalledByPublisher(@NonNull String packageName) {
260         detectLocaleChange();
261         rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
262     }
263
264     private String getKnownLocales() {
265         if (TextUtils.isEmpty(mKnownLocales)) {
266             mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId);
267             mService.scheduleSaveUser(mUserId);
268         }
269         return mKnownLocales;
270     }
271
272     /**
273      * Check to see if the system locale has changed, and if so, reset throttling
274      * and update resource strings.
275      */
276     public void detectLocaleChange() {
277         final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
278         if (getKnownLocales().equals(currentLocales)) {
279             return;
280         }
281         if (ShortcutService.DEBUG) {
282             Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales
283                     + " for user " + mUserId);
284         }
285         mKnownLocales = currentLocales;
286
287         forAllPackages(pkg -> {
288             pkg.resetRateLimiting();
289             pkg.resolveResourceStrings();
290         });
291
292         mService.scheduleSaveUser(mUserId);
293     }
294
295     public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
296         final boolean isNewApp = !mPackages.containsKey(packageName);
297
298         final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
299
300         if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
301             if (isNewApp) {
302                 mPackages.remove(packageName);
303             }
304         }
305     }
306
307     public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
308             @UserIdInt int packageUserId) {
309         forPackageItem(packageName, packageUserId, spi -> {
310             spi.attemptToRestoreIfNeededAndSave();
311         });
312     }
313
314     public void saveToXml(XmlSerializer out, boolean forBackup)
315             throws IOException, XmlPullParserException {
316         out.startTag(null, TAG_ROOT);
317
318         ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
319         ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
320                 mLastAppScanTime);
321
322         ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
323
324         // Can't use forEachPackageItem due to the checked exceptions.
325         {
326             final int size = mLaunchers.size();
327             for (int i = 0; i < size; i++) {
328                 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
329             }
330         }
331         {
332             final int size = mPackages.size();
333             for (int i = 0; i < size; i++) {
334                 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
335             }
336         }
337
338         out.endTag(null, TAG_ROOT);
339     }
340
341     private void saveShortcutPackageItem(XmlSerializer out,
342             ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
343         if (forBackup) {
344             if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
345                 return; // Don't save.
346             }
347             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
348                 return; // Don't save cross-user information.
349             }
350         }
351         spi.saveToXml(out, forBackup);
352     }
353
354     public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
355             boolean fromBackup) throws IOException, XmlPullParserException {
356         final ShortcutUser ret = new ShortcutUser(s, userId);
357
358         ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
359                 ATTR_KNOWN_LOCALES);
360
361         // If lastAppScanTime is in the future, that means the clock went backwards.
362         // Just scan all apps again.
363         final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
364                 ATTR_LAST_APP_SCAN_TIME);
365         final long currentTime = s.injectCurrentTimeMillis();
366         ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
367
368         final int outerDepth = parser.getDepth();
369         int type;
370         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
371                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
372             if (type != XmlPullParser.START_TAG) {
373                 continue;
374             }
375             final int depth = parser.getDepth();
376             final String tag = parser.getName();
377
378             if (depth == outerDepth + 1) {
379                 switch (tag) {
380                     case TAG_LAUNCHER: {
381                         ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
382                                 parser, ATTR_VALUE);
383                         continue;
384                     }
385                     case ShortcutPackage.TAG_ROOT: {
386                         final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
387                                 s, ret, parser, fromBackup);
388
389                         // Don't use addShortcut(), we don't need to save the icon.
390                         ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
391                         continue;
392                     }
393
394                     case ShortcutLauncher.TAG_ROOT: {
395                         ret.addLauncher(
396                                 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
397                         continue;
398                     }
399                 }
400             }
401             ShortcutService.warnForInvalidTag(depth, tag);
402         }
403         return ret;
404     }
405
406     public ComponentName getLastKnownLauncher() {
407         return mLastKnownLauncher;
408     }
409
410     public void setLauncher(ComponentName launcherComponent) {
411         setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
412     }
413
414     /** Clears the launcher information without clearing the last known one */
415     public void clearLauncher() {
416         setLauncher(null);
417     }
418
419     /**
420      * Clears the launcher information *with(* clearing the last known one; we do this witl
421      * "cmd shortcut clear-default-launcher".
422      */
423     public void forceClearLauncher() {
424         setLauncher(null, /* allowPurgeLastKnown */ true);
425     }
426
427     private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) {
428         mCachedLauncher = launcherComponent; // Always update the in-memory cache.
429
430         if (Objects.equal(mLastKnownLauncher, launcherComponent)) {
431             return;
432         }
433         if (!allowPurgeLastKnown && launcherComponent == null) {
434             return;
435         }
436         mLastKnownLauncher = launcherComponent;
437         mService.scheduleSaveUser(mUserId);
438     }
439
440     public ComponentName getCachedLauncher() {
441         return mCachedLauncher;
442     }
443
444     public void resetThrottling() {
445         for (int i = mPackages.size() - 1; i >= 0; i--) {
446             mPackages.valueAt(i).resetThrottling();
447         }
448     }
449
450     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
451         pw.print(prefix);
452         pw.print("User: ");
453         pw.print(mUserId);
454         pw.print("  Known locales: ");
455         pw.print(mKnownLocales);
456         pw.print("  Last app scan: [");
457         pw.print(mLastAppScanTime);
458         pw.print("] ");
459         pw.print(ShortcutService.formatTime(mLastAppScanTime));
460         pw.println();
461
462         prefix += prefix + "  ";
463
464         pw.print(prefix);
465         pw.print("Cached launcher: ");
466         pw.print(mCachedLauncher);
467         pw.println();
468
469         pw.print(prefix);
470         pw.print("Last known launcher: ");
471         pw.print(mLastKnownLauncher);
472         pw.println();
473
474         for (int i = 0; i < mLaunchers.size(); i++) {
475             mLaunchers.valueAt(i).dump(pw, prefix);
476         }
477
478         for (int i = 0; i < mPackages.size(); i++) {
479             mPackages.valueAt(i).dump(pw, prefix);
480         }
481
482         pw.println();
483         pw.print(prefix);
484         pw.println("Bitmap directories: ");
485         dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
486     }
487
488     private void dumpDirectorySize(@NonNull PrintWriter pw,
489             @NonNull String prefix, File path) {
490         int numFiles = 0;
491         long size = 0;
492         final File[] children = path.listFiles();
493         if (children != null) {
494             for (File child : path.listFiles()) {
495                 if (child.isFile()) {
496                     numFiles++;
497                     size += child.length();
498                 } else if (child.isDirectory()) {
499                     dumpDirectorySize(pw, prefix + "  ", child);
500                 }
501             }
502         }
503         pw.print(prefix);
504         pw.print("Path: ");
505         pw.print(path.getName());
506         pw.print("/ has ");
507         pw.print(numFiles);
508         pw.print(" files, size=");
509         pw.print(size);
510         pw.print(" (");
511         pw.print(Formatter.formatFileSize(mService.mContext, size));
512         pw.println(")");
513     }
514
515     public JSONObject dumpCheckin(boolean clear) throws JSONException {
516         final JSONObject result = new JSONObject();
517
518         result.put(KEY_USER_ID, mUserId);
519
520         {
521             final JSONArray launchers = new JSONArray();
522             for (int i = 0; i < mLaunchers.size(); i++) {
523                 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear));
524             }
525             result.put(KEY_LAUNCHERS, launchers);
526         }
527
528         {
529             final JSONArray packages = new JSONArray();
530             for (int i = 0; i < mPackages.size(); i++) {
531                 packages.put(mPackages.valueAt(i).dumpCheckin(clear));
532             }
533             result.put(KEY_PACKAGES, packages);
534         }
535
536         return result;
537     }
538 }