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