ShortcutLauncher.java revision c51b2876ec5c0af449469a0f76bb38c51cfcff04
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.pm.ShortcutInfo;
21import android.util.ArrayMap;
22import android.util.ArraySet;
23import android.util.Slog;
24
25import com.android.internal.annotations.VisibleForTesting;
26import com.android.server.pm.ShortcutUser.PackageWithUser;
27
28import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30import org.xmlpull.v1.XmlSerializer;
31
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.List;
36
37/**
38 * Launcher information used by {@link ShortcutService}.
39 */
40class ShortcutLauncher extends ShortcutPackageItem {
41    private static final String TAG = ShortcutService.TAG;
42
43    static final String TAG_ROOT = "launcher-pins";
44
45    private static final String TAG_PACKAGE = "package";
46    private static final String TAG_PIN = "pin";
47
48    private static final String ATTR_LAUNCHER_USER_ID = "launcher-user";
49    private static final String ATTR_VALUE = "value";
50    private static final String ATTR_PACKAGE_NAME = "package-name";
51    private static final String ATTR_PACKAGE_USER_ID = "package-user";
52
53    private final int mOwnerUserId;
54
55    /**
56     * Package name -> IDs.
57     */
58    final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
59
60    private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
61            @UserIdInt int ownerUserId, @NonNull String packageName,
62            @UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
63        super(shortcutUser, launcherUserId, packageName,
64                spi != null ? spi : ShortcutPackageInfo.newEmpty());
65        mOwnerUserId = ownerUserId;
66    }
67
68    public ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
69            @UserIdInt int ownerUserId, @NonNull String packageName,
70            @UserIdInt int launcherUserId) {
71        this(shortcutUser, ownerUserId, packageName, launcherUserId, null);
72    }
73
74    @Override
75    public int getOwnerUserId() {
76        return mOwnerUserId;
77    }
78
79    /**
80     * Called when the new package can't receive the backup, due to signature or version mismatch.
81     */
82    @Override
83    protected void onRestoreBlocked() {
84        final ArrayList<PackageWithUser> pinnedPackages =
85                new ArrayList<>(mPinnedShortcuts.keySet());
86        mPinnedShortcuts.clear();
87        for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
88            final PackageWithUser pu = pinnedPackages.get(i);
89            final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName);
90            if (p != null) {
91                p.refreshPinnedFlags();
92            }
93        }
94    }
95
96    @Override
97    protected void onRestored() {
98        // Nothing to do.
99    }
100
101    public void pinShortcuts(@UserIdInt int packageUserId,
102            @NonNull String packageName, @NonNull List<String> ids) {
103        final ShortcutPackage packageShortcuts =
104                mShortcutUser.getPackageShortcutsIfExists(packageName);
105        if (packageShortcuts == null) {
106            return; // No need to instantiate.
107        }
108
109        final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
110
111        final int idSize = ids.size();
112        if (idSize == 0) {
113            mPinnedShortcuts.remove(pu);
114        } else {
115            final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
116
117            // Pin shortcuts.  Make sure only pin the ones that were visible to the caller.
118            // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
119
120            final ArraySet<String> newSet = new ArraySet<>();
121
122            for (int i = 0; i < idSize; i++) {
123                final String id = ids.get(i);
124                final ShortcutInfo si = packageShortcuts.findShortcutById(id);
125                if (si == null) {
126                    continue;
127                }
128                if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
129                    newSet.add(id);
130                }
131            }
132            mPinnedShortcuts.put(pu, newSet);
133        }
134        packageShortcuts.refreshPinnedFlags();
135    }
136
137    /**
138     * Return the pinned shortcut IDs for the publisher package.
139     */
140    public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
141            @UserIdInt int packageUserId) {
142        return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
143    }
144
145    boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
146        return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
147    }
148
149    /**
150     * Persist.
151     */
152    @Override
153    public void saveToXml(XmlSerializer out, boolean forBackup)
154            throws IOException {
155        final int size = mPinnedShortcuts.size();
156        if (size == 0) {
157            return; // Nothing to write.
158        }
159
160        out.startTag(null, TAG_ROOT);
161        ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
162        ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
163        getPackageInfo().saveToXml(out);
164
165        for (int i = 0; i < size; i++) {
166            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
167
168            if (forBackup && (pu.userId != getOwnerUserId())) {
169                continue; // Target package on a different user, skip. (i.e. work profile)
170            }
171
172            out.startTag(null, TAG_PACKAGE);
173            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
174            ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
175
176            final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
177            final int idSize = ids.size();
178            for (int j = 0; j < idSize; j++) {
179                ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
180            }
181            out.endTag(null, TAG_PACKAGE);
182        }
183
184        out.endTag(null, TAG_ROOT);
185    }
186
187    /**
188     * Load.
189     */
190    public static ShortcutLauncher loadFromXml(XmlPullParser parser, ShortcutUser shortcutUser,
191            int ownerUserId, boolean fromBackup) throws IOException, XmlPullParserException {
192        final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
193                ATTR_PACKAGE_NAME);
194
195        // If restoring, just use the real user ID.
196        final int launcherUserId =
197                fromBackup ? ownerUserId
198                : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId);
199
200        final ShortcutLauncher ret = new ShortcutLauncher(shortcutUser, launcherUserId,
201                launcherPackageName, launcherUserId);
202
203        ArraySet<String> ids = null;
204        final int outerDepth = parser.getDepth();
205        int type;
206        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
207                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
208            if (type != XmlPullParser.START_TAG) {
209                continue;
210            }
211            final int depth = parser.getDepth();
212            final String tag = parser.getName();
213            if (depth == outerDepth + 1) {
214                switch (tag) {
215                    case ShortcutPackageInfo.TAG_ROOT:
216                        ret.getPackageInfo().loadFromXml(parser, fromBackup);
217                        continue;
218                    case TAG_PACKAGE: {
219                        final String packageName = ShortcutService.parseStringAttribute(parser,
220                                ATTR_PACKAGE_NAME);
221                        final int packageUserId = fromBackup ? ownerUserId
222                                : ShortcutService.parseIntAttribute(parser,
223                                ATTR_PACKAGE_USER_ID, ownerUserId);
224                        ids = new ArraySet<>();
225                        ret.mPinnedShortcuts.put(
226                                PackageWithUser.of(packageUserId, packageName), ids);
227                        continue;
228                    }
229                }
230            }
231            if (depth == outerDepth + 2) {
232                switch (tag) {
233                    case TAG_PIN: {
234                        if (ids == null) {
235                            Slog.w(TAG, TAG_PIN + " in invalid place");
236                        } else {
237                            ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE));
238                        }
239                        continue;
240                    }
241                }
242            }
243            ShortcutService.warnForInvalidTag(depth, tag);
244        }
245        return ret;
246    }
247
248    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
249        pw.println();
250
251        pw.print(prefix);
252        pw.print("Launcher: ");
253        pw.print(getPackageName());
254        pw.print("  Package user: ");
255        pw.print(getPackageUserId());
256        pw.print("  Owner user: ");
257        pw.print(getOwnerUserId());
258        pw.println();
259
260        getPackageInfo().dump(pw, prefix + "  ");
261        pw.println();
262
263        final int size = mPinnedShortcuts.size();
264        for (int i = 0; i < size; i++) {
265            pw.println();
266
267            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
268
269            pw.print(prefix);
270            pw.print("  ");
271            pw.print("Package: ");
272            pw.print(pu.packageName);
273            pw.print("  User: ");
274            pw.println(pu.userId);
275
276            final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
277            final int idSize = ids.size();
278
279            for (int j = 0; j < idSize; j++) {
280                pw.print(prefix);
281                pw.print("    Pinned: ");
282                pw.print(ids.valueAt(j));
283                pw.println();
284            }
285        }
286    }
287
288    @VisibleForTesting
289    ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
290        return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
291    }
292}
293