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