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