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