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