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(ShortcutService s) {
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            s.getPackageShortcutsLocked(pu.packageName, pu.userId)
90                    .refreshPinnedFlags(s);
91        }
92    }
93
94    @Override
95    protected void onRestored(ShortcutService s) {
96        // Nothing to do.
97    }
98
99    public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
100            @NonNull String packageName, @NonNull List<String> ids) {
101        final ShortcutPackage packageShortcuts =
102                s.getPackageShortcutsLocked(packageName, packageUserId);
103
104        final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
105
106        final int idSize = ids.size();
107        if (idSize == 0) {
108            mPinnedShortcuts.remove(pu);
109        } else {
110            final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
111
112            // Pin shortcuts.  Make sure only pin the ones that were visible to the caller.
113            // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
114
115            final ArraySet<String> newSet = new ArraySet<>();
116
117            for (int i = 0; i < idSize; i++) {
118                final String id = ids.get(i);
119                final ShortcutInfo si = packageShortcuts.findShortcutById(id);
120                if (si == null) {
121                    continue;
122                }
123                if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
124                    newSet.add(id);
125                }
126            }
127            mPinnedShortcuts.put(pu, newSet);
128        }
129        packageShortcuts.refreshPinnedFlags(s);
130    }
131
132    /**
133     * Return the pinned shortcut IDs for the publisher package.
134     */
135    public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
136            @UserIdInt int packageUserId) {
137        return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
138    }
139
140    boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
141        return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
142    }
143
144    /**
145     * Persist.
146     */
147    @Override
148    public void saveToXml(XmlSerializer out, boolean forBackup)
149            throws IOException {
150        final int size = mPinnedShortcuts.size();
151        if (size == 0) {
152            return; // Nothing to write.
153        }
154
155        out.startTag(null, TAG_ROOT);
156        ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
157        ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
158        getPackageInfo().saveToXml(out);
159
160        for (int i = 0; i < size; i++) {
161            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
162
163            if (forBackup && (pu.userId != getOwnerUserId())) {
164                continue; // Target package on a different user, skip. (i.e. work profile)
165            }
166
167            out.startTag(null, TAG_PACKAGE);
168            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
169            ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
170
171            final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
172            final int idSize = ids.size();
173            for (int j = 0; j < idSize; j++) {
174                ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
175            }
176            out.endTag(null, TAG_PACKAGE);
177        }
178
179        out.endTag(null, TAG_ROOT);
180    }
181
182    /**
183     * Load.
184     */
185    public static ShortcutLauncher loadFromXml(XmlPullParser parser, ShortcutUser shortcutUser,
186            int ownerUserId, boolean fromBackup) throws IOException, XmlPullParserException {
187        final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
188                ATTR_PACKAGE_NAME);
189
190        // If restoring, just use the real user ID.
191        final int launcherUserId =
192                fromBackup ? ownerUserId
193                : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId);
194
195        final ShortcutLauncher ret = new ShortcutLauncher(shortcutUser, launcherUserId,
196                launcherPackageName, launcherUserId);
197
198        ArraySet<String> ids = null;
199        final int outerDepth = parser.getDepth();
200        int type;
201        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
202                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
203            if (type != XmlPullParser.START_TAG) {
204                continue;
205            }
206            final int depth = parser.getDepth();
207            final String tag = parser.getName();
208            if (depth == outerDepth + 1) {
209                switch (tag) {
210                    case ShortcutPackageInfo.TAG_ROOT:
211                        ret.getPackageInfo().loadFromXml(parser, fromBackup);
212                        continue;
213                    case TAG_PACKAGE: {
214                        final String packageName = ShortcutService.parseStringAttribute(parser,
215                                ATTR_PACKAGE_NAME);
216                        final int packageUserId = fromBackup ? ownerUserId
217                                : ShortcutService.parseIntAttribute(parser,
218                                ATTR_PACKAGE_USER_ID, ownerUserId);
219                        ids = new ArraySet<>();
220                        ret.mPinnedShortcuts.put(
221                                PackageWithUser.of(packageUserId, packageName), ids);
222                        continue;
223                    }
224                }
225            }
226            if (depth == outerDepth + 2) {
227                switch (tag) {
228                    case TAG_PIN: {
229                        if (ids == null) {
230                            Slog.w(TAG, TAG_PIN + " in invalid place");
231                        } else {
232                            ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE));
233                        }
234                        continue;
235                    }
236                }
237            }
238            ShortcutService.warnForInvalidTag(depth, tag);
239        }
240        return ret;
241    }
242
243    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
244        pw.println();
245
246        pw.print(prefix);
247        pw.print("Launcher: ");
248        pw.print(getPackageName());
249        pw.print("  Package user: ");
250        pw.print(getPackageUserId());
251        pw.print("  Owner user: ");
252        pw.print(getOwnerUserId());
253        pw.println();
254
255        getPackageInfo().dump(s, pw, prefix + "  ");
256        pw.println();
257
258        final int size = mPinnedShortcuts.size();
259        for (int i = 0; i < size; i++) {
260            pw.println();
261
262            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
263
264            pw.print(prefix);
265            pw.print("  ");
266            pw.print("Package: ");
267            pw.print(pu.packageName);
268            pw.print("  User: ");
269            pw.println(pu.userId);
270
271            final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
272            final int idSize = ids.size();
273
274            for (int j = 0; j < idSize; j++) {
275                pw.print(prefix);
276                pw.print("    Pinned: ");
277                pw.print(ids.valueAt(j));
278                pw.println();
279            }
280        }
281    }
282
283    @VisibleForTesting
284    ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
285        return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
286    }
287}
288