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