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.pm.PackageInfo; 22 import android.content.pm.ShortcutInfo; 23 import android.content.pm.UserPackage; 24 import android.util.ArrayMap; 25 import android.util.ArraySet; 26 import android.util.Slog; 27 import android.util.Xml; 28 29 import com.android.internal.annotations.GuardedBy; 30 import com.android.modules.utils.TypedXmlPullParser; 31 import com.android.modules.utils.TypedXmlSerializer; 32 import com.android.server.pm.ShortcutService.DumpFilter; 33 34 import org.json.JSONException; 35 import org.json.JSONObject; 36 import org.xmlpull.v1.XmlPullParser; 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.File; 40 import java.io.FileInputStream; 41 import java.io.IOException; 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 /** 47 * Launcher information used by {@link ShortcutService}. 48 * 49 * All methods should be guarded by {@code ShortcutPackageItem#mPackageItemLock}. 50 */ 51 class ShortcutLauncher extends ShortcutPackageItem { 52 private static final String TAG = ShortcutService.TAG; 53 54 static final String TAG_ROOT = "launcher-pins"; 55 56 private static final String TAG_PACKAGE = "package"; 57 private static final String TAG_PIN = "pin"; 58 59 private static final String ATTR_LAUNCHER_USER_ID = "launcher-user"; 60 private static final String ATTR_VALUE = "value"; 61 private static final String ATTR_PACKAGE_NAME = "package-name"; 62 private static final String ATTR_PACKAGE_USER_ID = "package-user"; 63 64 private final int mOwnerUserId; 65 66 /** 67 * Package name -> IDs. 68 */ 69 @GuardedBy("mPackageItemLock") 70 private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>(); 71 ShortcutLauncher(@onNull ShortcutUser shortcutUser, @UserIdInt int ownerUserId, @NonNull String packageName, @UserIdInt int launcherUserId, ShortcutPackageInfo spi)72 private ShortcutLauncher(@NonNull ShortcutUser shortcutUser, 73 @UserIdInt int ownerUserId, @NonNull String packageName, 74 @UserIdInt int launcherUserId, ShortcutPackageInfo spi) { 75 super(shortcutUser, launcherUserId, packageName, 76 spi != null ? spi : ShortcutPackageInfo.newEmpty()); 77 mOwnerUserId = ownerUserId; 78 } 79 ShortcutLauncher(@onNull ShortcutUser shortcutUser, @UserIdInt int ownerUserId, @NonNull String packageName, @UserIdInt int launcherUserId)80 public ShortcutLauncher(@NonNull ShortcutUser shortcutUser, 81 @UserIdInt int ownerUserId, @NonNull String packageName, 82 @UserIdInt int launcherUserId) { 83 this(shortcutUser, ownerUserId, packageName, launcherUserId, null); 84 } 85 86 @Override getOwnerUserId()87 public int getOwnerUserId() { 88 return mOwnerUserId; 89 } 90 91 @Override canRestoreAnyVersion()92 protected boolean canRestoreAnyVersion() { 93 // Launcher's pinned shortcuts can be restored to an older version. 94 return true; 95 } 96 97 /** 98 * Called when the new package can't receive the backup, due to signature or version mismatch. 99 */ onRestoreBlocked()100 private void onRestoreBlocked() { 101 final ArrayList<UserPackage> pinnedPackages; 102 synchronized (mPackageItemLock) { 103 pinnedPackages = new ArrayList<>(mPinnedShortcuts.keySet()); 104 mPinnedShortcuts.clear(); 105 } 106 for (int i = pinnedPackages.size() - 1; i >= 0; i--) { 107 final UserPackage up = pinnedPackages.get(i); 108 final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(up.packageName); 109 if (p != null) { 110 p.refreshPinnedFlags(); 111 } 112 } 113 } 114 115 @Override onRestored(int restoreBlockReason)116 protected void onRestored(int restoreBlockReason) { 117 // For launcher, possible reasons here are DISABLED_REASON_SIGNATURE_MISMATCH or 118 // DISABLED_REASON_BACKUP_NOT_SUPPORTED. 119 // DISABLED_REASON_VERSION_LOWER will NOT happen because we don't check version 120 // code for launchers. 121 if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { 122 onRestoreBlocked(); 123 } 124 } 125 126 /** 127 * Pin the given shortcuts, replacing the current pinned ones. 128 */ pinShortcuts(@serIdInt int packageUserId, @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest)129 public void pinShortcuts(@UserIdInt int packageUserId, 130 @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) { 131 final ShortcutPackage packageShortcuts = 132 mShortcutUser.getPackageShortcutsIfExists(packageName); 133 if (packageShortcuts == null) { 134 return; // No need to instantiate. 135 } 136 137 final UserPackage up = UserPackage.of(packageUserId, packageName); 138 139 final int idSize = ids.size(); 140 if (idSize == 0) { 141 synchronized (mPackageItemLock) { 142 mPinnedShortcuts.remove(up); 143 } 144 } else { 145 // Actually pin shortcuts. 146 // This logic here is to make sure a launcher cannot pin a shortcut that is not 147 // dynamic nor long-lived nor manifest but is pinned. 148 // In this case, technically the shortcut doesn't exist to this launcher, so it 149 // can't pin it. 150 // (Maybe unnecessarily strict...) 151 final ArraySet<String> floatingSet = new ArraySet<>(); 152 final ArraySet<String> newSet = new ArraySet<>(); 153 154 for (int i = 0; i < idSize; i++) { 155 final String id = ids.get(i); 156 final ShortcutInfo si = packageShortcuts.findShortcutById(id); 157 if (si == null) { 158 continue; 159 } 160 if (si.isDynamic() || si.isLongLived() 161 || si.isManifestShortcut() 162 || forPinRequest) { 163 newSet.add(id); 164 } else { 165 floatingSet.add(id); 166 } 167 } 168 synchronized (mPackageItemLock) { 169 final ArraySet<String> prevSet = mPinnedShortcuts.get(up); 170 if (prevSet != null) { 171 for (String id : floatingSet) { 172 if (prevSet.contains(id)) { 173 newSet.add(id); 174 } 175 } 176 } 177 mPinnedShortcuts.put(up, newSet); 178 } 179 } 180 181 packageShortcuts.refreshPinnedFlags(); 182 } 183 184 /** 185 * Return the pinned shortcut IDs for the publisher package. 186 */ 187 @Nullable getPinnedShortcutIds(@onNull String packageName, @UserIdInt int packageUserId)188 public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName, 189 @UserIdInt int packageUserId) { 190 synchronized (mPackageItemLock) { 191 final ArraySet<String> pinnedShortcuts = mPinnedShortcuts.get( 192 UserPackage.of(packageUserId, packageName)); 193 return pinnedShortcuts == null ? null : new ArraySet<>(pinnedShortcuts); 194 } 195 } 196 197 /** 198 * Return true if the given shortcut is pinned by this launcher.<code></code> 199 */ hasPinned(ShortcutInfo shortcut)200 public boolean hasPinned(ShortcutInfo shortcut) { 201 synchronized (mPackageItemLock) { 202 final ArraySet<String> pinned = mPinnedShortcuts.get( 203 UserPackage.of(shortcut.getUserId(), shortcut.getPackage())); 204 return (pinned != null) && pinned.contains(shortcut.getId()); 205 } 206 } 207 208 /** 209 * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List, boolean)} 210 */ addPinnedShortcut(@onNull String packageName, @UserIdInt int packageUserId, String id, boolean forPinRequest)211 public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId, 212 String id, boolean forPinRequest) { 213 final ArrayList<String> pinnedList; 214 synchronized (mPackageItemLock) { 215 final ArraySet<String> pinnedSet = mPinnedShortcuts.get( 216 UserPackage.of(packageUserId, packageName)); 217 if (pinnedSet != null) { 218 pinnedList = new ArrayList<>(pinnedSet.size() + 1); 219 pinnedList.addAll(pinnedSet); 220 } else { 221 pinnedList = new ArrayList<>(1); 222 } 223 } 224 pinnedList.add(id); 225 226 pinShortcuts(packageUserId, packageName, pinnedList, forPinRequest); 227 } 228 cleanUpPackage(String packageName, @UserIdInt int packageUserId)229 boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) { 230 synchronized (mPackageItemLock) { 231 return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null; 232 } 233 } 234 ensurePackageInfo()235 public void ensurePackageInfo() { 236 final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures( 237 getPackageName(), getPackageUserId()); 238 if (pi == null) { 239 Slog.w(TAG, "Package not found: " + getPackageName()); 240 return; 241 } 242 getPackageInfo().updateFromPackageInfo(pi); 243 } 244 245 /** 246 * Persist. 247 */ 248 @Override saveToXml(TypedXmlSerializer out, boolean forBackup)249 public void saveToXml(TypedXmlSerializer out, boolean forBackup) 250 throws IOException { 251 if (forBackup && !getPackageInfo().isBackupAllowed()) { 252 // If an launcher app doesn't support backup&restore, then nothing to do. 253 return; 254 } 255 final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts; 256 synchronized (mPackageItemLock) { 257 pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts); 258 } 259 final int size = pinnedShortcuts.size(); 260 if (size == 0) { 261 return; // Nothing to write. 262 } 263 264 out.startTag(null, TAG_ROOT); 265 ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName()); 266 ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId()); 267 getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup); 268 269 for (int i = 0; i < size; i++) { 270 final UserPackage up = pinnedShortcuts.keyAt(i); 271 272 if (forBackup && (up.userId != getOwnerUserId())) { 273 continue; // Target package on a different user, skip. (i.e. work profile) 274 } 275 276 out.startTag(null, TAG_PACKAGE); 277 ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, up.packageName); 278 ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, up.userId); 279 280 final ArraySet<String> ids = pinnedShortcuts.valueAt(i); 281 final int idSize = ids.size(); 282 for (int j = 0; j < idSize; j++) { 283 ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j)); 284 } 285 out.endTag(null, TAG_PACKAGE); 286 } 287 288 out.endTag(null, TAG_ROOT); 289 } 290 loadFromFile(File path, ShortcutUser shortcutUser, int ownerUserId, boolean fromBackup)291 public static ShortcutLauncher loadFromFile(File path, ShortcutUser shortcutUser, 292 int ownerUserId, boolean fromBackup) { 293 try (ResilientAtomicFile file = getResilientFile(path)) { 294 FileInputStream in = null; 295 try { 296 in = file.openRead(); 297 if (in == null) { 298 Slog.d(TAG, "Not found " + path); 299 return null; 300 } 301 302 ShortcutLauncher ret = null; 303 TypedXmlPullParser parser = Xml.resolvePullParser(in); 304 305 int type; 306 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 307 if (type != XmlPullParser.START_TAG) { 308 continue; 309 } 310 final int depth = parser.getDepth(); 311 312 final String tag = parser.getName(); 313 if (ShortcutService.DEBUG_LOAD) { 314 Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag)); 315 } 316 if ((depth == 1) && TAG_ROOT.equals(tag)) { 317 ret = loadFromXml(parser, shortcutUser, ownerUserId, fromBackup); 318 continue; 319 } 320 ShortcutService.throwForInvalidTag(depth, tag); 321 } 322 return ret; 323 } catch (Exception e) { 324 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 325 file.failRead(in, e); 326 return loadFromFile(path, shortcutUser, ownerUserId, fromBackup); 327 } 328 } 329 } 330 331 /** 332 * Load. 333 */ loadFromXml(TypedXmlPullParser parser, ShortcutUser shortcutUser, int ownerUserId, boolean fromBackup)334 public static ShortcutLauncher loadFromXml(TypedXmlPullParser parser, ShortcutUser shortcutUser, 335 int ownerUserId, boolean fromBackup) throws IOException, XmlPullParserException { 336 final String launcherPackageName = ShortcutService.parseStringAttribute(parser, 337 ATTR_PACKAGE_NAME); 338 339 // If restoring, just use the real user ID. 340 final int launcherUserId = 341 fromBackup ? ownerUserId 342 : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId); 343 344 final ShortcutLauncher ret = new ShortcutLauncher(shortcutUser, ownerUserId, 345 launcherPackageName, launcherUserId); 346 347 ArraySet<String> ids = null; 348 final int outerDepth = parser.getDepth(); 349 int type; 350 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 351 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 352 if (type != XmlPullParser.START_TAG) { 353 continue; 354 } 355 final int depth = parser.getDepth(); 356 final String tag = parser.getName(); 357 if (depth == outerDepth + 1) { 358 switch (tag) { 359 case ShortcutPackageInfo.TAG_ROOT: 360 ret.getPackageInfo().loadFromXml(parser, fromBackup); 361 continue; 362 case TAG_PACKAGE: { 363 final String packageName = ShortcutService.parseStringAttribute(parser, 364 ATTR_PACKAGE_NAME); 365 final int packageUserId = fromBackup ? ownerUserId 366 : ShortcutService.parseIntAttribute(parser, 367 ATTR_PACKAGE_USER_ID, ownerUserId); 368 ids = new ArraySet<>(); 369 synchronized (ret.mPackageItemLock) { 370 ret.mPinnedShortcuts.put( 371 UserPackage.of(packageUserId, packageName), ids); 372 } 373 continue; 374 } 375 } 376 } 377 if (depth == outerDepth + 2) { 378 switch (tag) { 379 case TAG_PIN: { 380 if (ids == null) { 381 Slog.w(TAG, TAG_PIN + " in invalid place"); 382 } else { 383 ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE)); 384 } 385 continue; 386 } 387 } 388 } 389 ShortcutService.warnForInvalidTag(depth, tag); 390 } 391 return ret; 392 } 393 dump(@onNull PrintWriter pw, @NonNull String prefix, DumpFilter filter)394 public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { 395 pw.println(); 396 397 pw.print(prefix); 398 pw.print("Launcher: "); 399 pw.print(getPackageName()); 400 pw.print(" Package user: "); 401 pw.print(getPackageUserId()); 402 pw.print(" Owner user: "); 403 pw.print(getOwnerUserId()); 404 pw.println(); 405 406 getPackageInfo().dump(pw, prefix + " "); 407 pw.println(); 408 409 final ArrayMap<UserPackage, ArraySet<String>> pinnedShortcuts; 410 synchronized (mPackageItemLock) { 411 pinnedShortcuts = new ArrayMap<>(mPinnedShortcuts); 412 } 413 final int size = pinnedShortcuts.size(); 414 for (int i = 0; i < size; i++) { 415 pw.println(); 416 417 final UserPackage up = pinnedShortcuts.keyAt(i); 418 419 pw.print(prefix); 420 pw.print(" "); 421 pw.print("Package: "); 422 pw.print(up.packageName); 423 pw.print(" User: "); 424 pw.println(up.userId); 425 426 final ArraySet<String> ids = pinnedShortcuts.valueAt(i); 427 final int idSize = ids.size(); 428 429 for (int j = 0; j < idSize; j++) { 430 pw.print(prefix); 431 pw.print(" Pinned: "); 432 pw.print(ids.valueAt(j)); 433 pw.println(); 434 } 435 } 436 } 437 438 @Override dumpCheckin(boolean clear)439 public JSONObject dumpCheckin(boolean clear) throws JSONException { 440 final JSONObject result = super.dumpCheckin(clear); 441 442 // Nothing really interesting to dump. 443 444 return result; 445 } 446 447 @Override getShortcutPackageItemFile()448 protected File getShortcutPackageItemFile() { 449 final File path = new File(mShortcutUser.mService.injectUserDataPath( 450 mShortcutUser.getUserId()), ShortcutUser.DIRECTORY_LUANCHERS); 451 // Package user id and owner id can have different values for ShortcutLaunchers. Adding 452 // user Id to the file name to create a unique path. Owner id is used in the root path. 453 final String fileName = getPackageName() + getPackageUserId() + ".xml"; 454 return new File(path, fileName); 455 } 456 } 457