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