blob: 5c1e7a8dde9bd1f563bd3e1ccd281700952b85c3 [file] [log] [blame]
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001/*
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 */
16package com.android.server.pm;
17
18import android.annotation.NonNull;
19import android.annotation.Nullable;
20import android.annotation.UserIdInt;
Makoto Onuki55046222016-03-08 10:49:47 -080021import android.app.ActivityManager;
Makoto Onuki0acbb142016-03-22 17:02:57 -070022import android.app.AppGlobals;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080023import android.content.ComponentName;
Makoto Onuki55046222016-03-08 10:49:47 -080024import android.content.ContentProvider;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080025import android.content.Context;
26import android.content.Intent;
Makoto Onuki0acbb142016-03-22 17:02:57 -070027import android.content.pm.ApplicationInfo;
28import android.content.pm.IPackageManager;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080029import android.content.pm.IShortcutService;
30import android.content.pm.LauncherApps;
31import android.content.pm.LauncherApps.ShortcutQuery;
Makoto Onuki0acbb142016-03-22 17:02:57 -070032import android.content.pm.PackageInfo;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080033import android.content.pm.PackageManager;
Makoto Onuki2d5b4652016-03-11 16:09:54 -080034import android.content.pm.PackageManagerInternal;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080035import android.content.pm.ParceledListSlice;
Makoto Onuki2d5b4652016-03-11 16:09:54 -080036import android.content.pm.ResolveInfo;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080037import android.content.pm.ShortcutInfo;
38import android.content.pm.ShortcutServiceInternal;
39import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
Makoto Onuki55046222016-03-08 10:49:47 -080040import android.graphics.Bitmap;
41import android.graphics.Bitmap.CompressFormat;
42import android.graphics.BitmapFactory;
43import android.graphics.Canvas;
44import android.graphics.RectF;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080045import android.graphics.drawable.Icon;
Makoto Onuki55046222016-03-08 10:49:47 -080046import android.net.Uri;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080047import android.os.Binder;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080048import android.os.Environment;
Makoto Onuki2e210c42016-03-30 08:30:36 -070049import android.os.FileUtils;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080050import android.os.Handler;
Makoto Onukiaa8b94a2016-03-17 13:14:05 -070051import android.os.Looper;
Makoto Onuki55046222016-03-08 10:49:47 -080052import android.os.ParcelFileDescriptor;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080053import android.os.PersistableBundle;
54import android.os.Process;
55import android.os.RemoteException;
56import android.os.ResultReceiver;
Makoto Onuki55046222016-03-08 10:49:47 -080057import android.os.SELinux;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080058import android.os.ShellCommand;
59import android.os.UserHandle;
Makoto Onukicdc78f72016-03-21 15:47:52 -070060import android.os.UserManager;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080061import android.text.TextUtils;
62import android.text.format.Time;
63import android.util.ArrayMap;
64import android.util.ArraySet;
65import android.util.AtomicFile;
Makoto Onuki4362a662016-03-08 18:59:09 -080066import android.util.KeyValueListParser;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080067import android.util.Slog;
68import android.util.SparseArray;
Makoto Onuki55046222016-03-08 10:49:47 -080069import android.util.TypedValue;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080070import android.util.Xml;
71
72import com.android.internal.annotations.GuardedBy;
73import com.android.internal.annotations.VisibleForTesting;
Makoto Onukicdc78f72016-03-21 15:47:52 -070074import com.android.internal.content.PackageMonitor;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080075import com.android.internal.os.BackgroundThread;
76import com.android.internal.util.FastXmlSerializer;
77import com.android.internal.util.Preconditions;
78import com.android.server.LocalServices;
79import com.android.server.SystemService;
Makoto Onukid99c6f02016-03-28 11:02:54 -070080import com.android.server.pm.ShortcutUser.PackageWithUser;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080081
82import libcore.io.IoUtils;
83
84import org.xmlpull.v1.XmlPullParser;
85import org.xmlpull.v1.XmlPullParserException;
86import org.xmlpull.v1.XmlSerializer;
87
Makoto Onuki9da23fc2016-03-29 11:14:42 -070088import java.io.BufferedInputStream;
89import java.io.BufferedOutputStream;
90import java.io.ByteArrayInputStream;
91import java.io.ByteArrayOutputStream;
Makoto Onuki6f7362d92016-03-04 13:39:41 -080092import java.io.File;
93import java.io.FileDescriptor;
94import java.io.FileInputStream;
95import java.io.FileNotFoundException;
96import java.io.FileOutputStream;
97import java.io.IOException;
Makoto Onuki55046222016-03-08 10:49:47 -080098import java.io.InputStream;
Makoto Onuki9da23fc2016-03-29 11:14:42 -070099import java.io.OutputStream;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800100import java.io.PrintWriter;
101import java.net.URISyntaxException;
102import java.nio.charset.StandardCharsets;
103import java.util.ArrayList;
104import java.util.List;
Makoto Onuki2e210c42016-03-30 08:30:36 -0700105import java.util.function.Consumer;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800106import java.util.function.Predicate;
107
108/**
109 * TODO:
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800110 *
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700111 * - Default launcher check does take a few ms. Worth caching.
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800112 *
Makoto Onukid99c6f02016-03-28 11:02:54 -0700113 * - Clear data -> remove all dynamic? but not the pinned?
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800114 *
Makoto Onuki55046222016-03-08 10:49:47 -0800115 * - Scan and remove orphan bitmaps (just in case).
116 *
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700117 * - Detect when already registered instances are passed to APIs again, which might break
118 * internal bitmap handling.
Makoto Onuki2e210c42016-03-30 08:30:36 -0700119 *
120 * - Add more call stats.
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800121 */
122public class ShortcutService extends IShortcutService.Stub {
Makoto Onuki55046222016-03-08 10:49:47 -0800123 static final String TAG = "ShortcutService";
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800124
Makoto Onuki4554d0e2016-03-14 15:51:41 -0700125 static final boolean DEBUG = false; // STOPSHIP if true
Makoto Onuki41066a62016-03-09 16:18:44 -0800126 static final boolean DEBUG_LOAD = false; // STOPSHIP if true
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800127
Makoto Onuki4362a662016-03-08 18:59:09 -0800128 @VisibleForTesting
129 static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
130
131 @VisibleForTesting
132 static final int DEFAULT_MAX_DAILY_UPDATES = 10;
133
134 @VisibleForTesting
135 static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
136
137 @VisibleForTesting
138 static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
139
140 @VisibleForTesting
141 static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
142
143 @VisibleForTesting
144 static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
145
146 @VisibleForTesting
147 static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800148
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700149 @VisibleForTesting
150 static final int DEFAULT_SAVE_DELAY_MS = 3000;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800151
152 @VisibleForTesting
153 static final String FILENAME_BASE_STATE = "shortcut_service.xml";
154
155 @VisibleForTesting
156 static final String DIRECTORY_PER_USER = "shortcut_service";
157
158 @VisibleForTesting
159 static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
160
Makoto Onuki55046222016-03-08 10:49:47 -0800161 static final String DIRECTORY_BITMAPS = "bitmaps";
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800162
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700163 private static final String TAG_ROOT = "root";
164 private static final String TAG_LAST_RESET_TIME = "last_reset_time";
Makoto Onuki55046222016-03-08 10:49:47 -0800165
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700166 private static final String ATTR_VALUE = "value";
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800167
Makoto Onuki4362a662016-03-08 18:59:09 -0800168 @VisibleForTesting
169 interface ConfigConstants {
170 /**
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700171 * Key name for the save delay, in milliseconds. (int)
172 */
173 String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
174
175 /**
Makoto Onuki4362a662016-03-08 18:59:09 -0800176 * Key name for the throttling reset interval, in seconds. (long)
177 */
178 String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
179
180 /**
181 * Key name for the max number of modifying API calls per app for every interval. (int)
182 */
183 String KEY_MAX_DAILY_UPDATES = "max_daily_updates";
184
185 /**
186 * Key name for the max icon dimensions in DP, for non-low-memory devices.
187 */
188 String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
189
190 /**
191 * Key name for the max icon dimensions in DP, for low-memory devices.
192 */
193 String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
194
195 /**
196 * Key name for the max dynamic shortcuts per app. (int)
197 */
198 String KEY_MAX_SHORTCUTS = "max_shortcuts";
199
200 /**
Makoto Onuki41066a62016-03-09 16:18:44 -0800201 * Key name for icon compression quality, 0-100.
Makoto Onuki4362a662016-03-08 18:59:09 -0800202 */
203 String KEY_ICON_QUALITY = "icon_quality";
204
205 /**
206 * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
207 */
208 String KEY_ICON_FORMAT = "icon_format";
209 }
210
Makoto Onuki41066a62016-03-09 16:18:44 -0800211 final Context mContext;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800212
213 private final Object mLock = new Object();
214
215 private final Handler mHandler;
216
217 @GuardedBy("mLock")
218 private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
219
220 @GuardedBy("mLock")
221 private long mRawLastResetTime;
222
223 /**
Makoto Onuki3f4b1ca2016-03-11 13:44:32 -0800224 * User ID -> UserShortcuts
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800225 */
226 @GuardedBy("mLock")
Makoto Onuki31459242016-03-22 11:12:18 -0700227 private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800228
229 /**
230 * Max number of dynamic shortcuts that each application can have at a time.
231 */
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800232 private int mMaxDynamicShortcuts;
233
234 /**
235 * Max number of updating API calls that each application can make a day.
236 */
Makoto Onuki41066a62016-03-09 16:18:44 -0800237 int mMaxDailyUpdates;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800238
239 /**
240 * Actual throttling-reset interval. By default it's a day.
241 */
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800242 private long mResetInterval;
243
Makoto Onuki55046222016-03-08 10:49:47 -0800244 /**
245 * Icon max width/height in pixels.
246 */
247 private int mMaxIconDimension;
248
Makoto Onuki4362a662016-03-08 18:59:09 -0800249 private CompressFormat mIconPersistFormat;
250 private int mIconPersistQuality;
Makoto Onuki55046222016-03-08 10:49:47 -0800251
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700252 private int mSaveDelayMillis;
253
Makoto Onuki0acbb142016-03-22 17:02:57 -0700254 private final IPackageManager mIPackageManager;
Makoto Onuki2d5b4652016-03-11 16:09:54 -0800255 private final PackageManagerInternal mPackageManagerInternal;
Makoto Onukicdc78f72016-03-21 15:47:52 -0700256 private final UserManager mUserManager;
Makoto Onuki2d5b4652016-03-11 16:09:54 -0800257
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700258 @GuardedBy("mLock")
259 private List<Integer> mDirtyUserIds = new ArrayList<>();
260
Makoto Onuki905e8852016-03-28 10:40:58 -0700261 private static final int PACKAGE_MATCH_FLAGS =
262 PackageManager.MATCH_DIRECT_BOOT_AWARE
263 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
264 | PackageManager.MATCH_UNINSTALLED_PACKAGES;
265
Makoto Onuki2e210c42016-03-30 08:30:36 -0700266 // Stats
267 @VisibleForTesting
268 interface Stats {
269 int GET_DEFAULT_HOME = 0;
270 int GET_PACKAGE_INFO = 1;
271 int GET_PACKAGE_INFO_WITH_SIG = 2;
272 int GET_APPLICATION_INFO = 3;
273 int LAUNCHER_PERMISSION_CHECK = 4;
274
275 int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
276 }
277
278 final Object mStatLock = new Object();
279
280 @GuardedBy("mStatLock")
281 private final int[] mCountStats = new int[Stats.COUNT];
282
283 @GuardedBy("mStatLock")
284 private final long[] mDurationStats = new long[Stats.COUNT];
285
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800286 public ShortcutService(Context context) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700287 this(context, BackgroundThread.get().getLooper());
288 }
289
290 @VisibleForTesting
291 ShortcutService(Context context, Looper looper) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800292 mContext = Preconditions.checkNotNull(context);
293 LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700294 mHandler = new Handler(looper);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700295 mIPackageManager = AppGlobals.getPackageManager();
Makoto Onuki2d5b4652016-03-11 16:09:54 -0800296 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
Makoto Onukicdc78f72016-03-21 15:47:52 -0700297 mUserManager = context.getSystemService(UserManager.class);
298
299 mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800300 }
301
Makoto Onuki2e210c42016-03-30 08:30:36 -0700302 void logDurationStat(int statId, long start) {
303 synchronized (mStatLock) {
304 mCountStats[statId]++;
305 mDurationStats[statId] += (System.currentTimeMillis() - start);
306 }
307 }
308
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800309 /**
310 * System service lifecycle.
311 */
312 public static final class Lifecycle extends SystemService {
313 final ShortcutService mService;
314
315 public Lifecycle(Context context) {
316 super(context);
317 mService = new ShortcutService(context);
318 }
319
320 @Override
321 public void onStart() {
322 publishBinderService(Context.SHORTCUT_SERVICE, mService);
323 }
324
325 @Override
326 public void onBootPhase(int phase) {
327 mService.onBootPhase(phase);
328 }
329
330 @Override
331 public void onCleanupUser(int userHandle) {
Makoto Onukicdc78f72016-03-21 15:47:52 -0700332 mService.handleCleanupUser(userHandle);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800333 }
334
335 @Override
Makoto Onukif3a572b2016-03-10 12:28:38 -0800336 public void onUnlockUser(int userId) {
Makoto Onukicdc78f72016-03-21 15:47:52 -0700337 mService.handleUnlockUser(userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800338 }
339 }
340
341 /** lifecycle event */
342 void onBootPhase(int phase) {
343 if (DEBUG) {
344 Slog.d(TAG, "onBootPhase: " + phase);
345 }
346 switch (phase) {
347 case SystemService.PHASE_LOCK_SETTINGS_READY:
348 initialize();
349 break;
350 }
351 }
352
353 /** lifecycle event */
Makoto Onukicdc78f72016-03-21 15:47:52 -0700354 void handleUnlockUser(int userId) {
355 synchronized (mLock) {
356 // Preload
357 getUserShortcutsLocked(userId);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700358
359 cleanupGonePackages(userId);
Makoto Onukicdc78f72016-03-21 15:47:52 -0700360 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800361 }
362
363 /** lifecycle event */
Makoto Onukicdc78f72016-03-21 15:47:52 -0700364 void handleCleanupUser(int userId) {
365 synchronized (mLock) {
366 unloadUserLocked(userId);
367 }
368 }
369
370 private void unloadUserLocked(int userId) {
371 if (DEBUG) {
372 Slog.d(TAG, "unloadUserLocked: user=" + userId);
373 }
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700374 // Save all dirty information.
375 saveDirtyInfo();
376
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800377 // Unload
Makoto Onuki3f4b1ca2016-03-11 13:44:32 -0800378 mUsers.delete(userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800379 }
380
381 /** Return the base state file name */
382 private AtomicFile getBaseStateFile() {
383 final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
384 path.mkdirs();
385 return new AtomicFile(path);
386 }
387
388 /**
389 * Init the instance. (load the state file, etc)
390 */
391 private void initialize() {
392 synchronized (mLock) {
Makoto Onuki4362a662016-03-08 18:59:09 -0800393 loadConfigurationLocked();
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800394 loadBaseStateLocked();
395 }
396 }
397
Makoto Onuki4362a662016-03-08 18:59:09 -0800398 /**
399 * Load the configuration from Settings.
400 */
401 private void loadConfigurationLocked() {
402 updateConfigurationLocked(injectShortcutManagerConstants());
403 }
Makoto Onuki55046222016-03-08 10:49:47 -0800404
Makoto Onuki4362a662016-03-08 18:59:09 -0800405 /**
406 * Load the configuration from Settings.
407 */
408 @VisibleForTesting
409 boolean updateConfigurationLocked(String config) {
410 boolean result = true;
411
412 final KeyValueListParser parser = new KeyValueListParser(',');
413 try {
414 parser.setString(config);
415 } catch (IllegalArgumentException e) {
416 // Failed to parse the settings string, log this and move on
417 // with defaults.
418 Slog.e(TAG, "Bad shortcut manager settings", e);
419 result = false;
420 }
421
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700422 mSaveDelayMillis = (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
423 DEFAULT_SAVE_DELAY_MS);
424
Makoto Onuki4362a662016-03-08 18:59:09 -0800425 mResetInterval = parser.getLong(
426 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
427 * 1000L;
428
429 mMaxDailyUpdates = (int) parser.getLong(
430 ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES);
431
432 mMaxDynamicShortcuts = (int) parser.getLong(
433 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP);
434
435 final int iconDimensionDp = injectIsLowRamDevice()
436 ? (int) parser.getLong(
437 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
438 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
439 : (int) parser.getLong(
440 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
441 DEFAULT_MAX_ICON_DIMENSION_DP);
442
443 mMaxIconDimension = injectDipToPixel(iconDimensionDp);
444
445 mIconPersistFormat = CompressFormat.valueOf(
446 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
447
448 mIconPersistQuality = (int) parser.getLong(
449 ConfigConstants.KEY_ICON_QUALITY,
450 DEFAULT_ICON_PERSIST_QUALITY);
451
452 return result;
453 }
454
455 @VisibleForTesting
456 String injectShortcutManagerConstants() {
457 return android.provider.Settings.Global.getString(
458 mContext.getContentResolver(),
459 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
460 }
461
462 @VisibleForTesting
463 int injectDipToPixel(int dip) {
464 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
465 mContext.getResources().getDisplayMetrics());
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800466 }
467
Makoto Onuki55046222016-03-08 10:49:47 -0800468 // === Persisting ===
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800469
470 @Nullable
Makoto Onuki41066a62016-03-09 16:18:44 -0800471 static String parseStringAttribute(XmlPullParser parser, String attribute) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800472 return parser.getAttributeValue(null, attribute);
473 }
474
Makoto Onuki0acbb142016-03-22 17:02:57 -0700475 static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
476 return parseLongAttribute(parser, attribute) == 1;
477 }
478
Makoto Onuki41066a62016-03-09 16:18:44 -0800479 static int parseIntAttribute(XmlPullParser parser, String attribute) {
480 return (int) parseLongAttribute(parser, attribute);
481 }
482
Makoto Onukid99c6f02016-03-28 11:02:54 -0700483 static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
484 return (int) parseLongAttribute(parser, attribute, def);
485 }
486
Makoto Onuki41066a62016-03-09 16:18:44 -0800487 static long parseLongAttribute(XmlPullParser parser, String attribute) {
Makoto Onukid99c6f02016-03-28 11:02:54 -0700488 return parseLongAttribute(parser, attribute, 0);
489 }
490
491 static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800492 final String value = parseStringAttribute(parser, attribute);
493 if (TextUtils.isEmpty(value)) {
Makoto Onukid99c6f02016-03-28 11:02:54 -0700494 return def;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800495 }
496 try {
497 return Long.parseLong(value);
498 } catch (NumberFormatException e) {
499 Slog.e(TAG, "Error parsing long " + value);
Makoto Onukid99c6f02016-03-28 11:02:54 -0700500 return def;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800501 }
502 }
503
504 @Nullable
Makoto Onuki41066a62016-03-09 16:18:44 -0800505 static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800506 final String value = parseStringAttribute(parser, attribute);
507 if (TextUtils.isEmpty(value)) {
508 return null;
509 }
510 return ComponentName.unflattenFromString(value);
511 }
512
513 @Nullable
Makoto Onuki41066a62016-03-09 16:18:44 -0800514 static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800515 final String value = parseStringAttribute(parser, attribute);
516 if (TextUtils.isEmpty(value)) {
517 return null;
518 }
519 try {
520 return Intent.parseUri(value, /* flags =*/ 0);
521 } catch (URISyntaxException e) {
522 Slog.e(TAG, "Error parsing intent", e);
523 return null;
524 }
525 }
526
Makoto Onuki41066a62016-03-09 16:18:44 -0800527 static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800528 if (TextUtils.isEmpty(value)) return;
529
530 out.startTag(null, tag);
531 out.attribute(null, ATTR_VALUE, value);
532 out.endTag(null, tag);
533 }
534
Makoto Onuki41066a62016-03-09 16:18:44 -0800535 static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800536 writeTagValue(out, tag, Long.toString(value));
537 }
538
Makoto Onuki2d5b4652016-03-11 16:09:54 -0800539 static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
540 if (name == null) return;
541 writeTagValue(out, tag, name.flattenToString());
542 }
543
Makoto Onuki41066a62016-03-09 16:18:44 -0800544 static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800545 throws IOException, XmlPullParserException {
546 if (bundle == null) return;
547
548 out.startTag(null, tag);
549 bundle.saveToXml(out);
550 out.endTag(null, tag);
551 }
552
Makoto Onuki41066a62016-03-09 16:18:44 -0800553 static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800554 if (TextUtils.isEmpty(value)) return;
555
556 out.attribute(null, name, value);
557 }
558
Makoto Onuki41066a62016-03-09 16:18:44 -0800559 static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800560 writeAttr(out, name, String.valueOf(value));
561 }
562
Makoto Onuki0acbb142016-03-22 17:02:57 -0700563 static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
564 if (value) {
565 writeAttr(out, name, "1");
566 }
567 }
568
Makoto Onuki41066a62016-03-09 16:18:44 -0800569 static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800570 if (comp == null) return;
571 writeAttr(out, name, comp.flattenToString());
572 }
573
Makoto Onuki41066a62016-03-09 16:18:44 -0800574 static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800575 if (intent == null) return;
576
577 writeAttr(out, name, intent.toUri(/* flags =*/ 0));
578 }
579
580 @VisibleForTesting
581 void saveBaseStateLocked() {
582 final AtomicFile file = getBaseStateFile();
583 if (DEBUG) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700584 Slog.d(TAG, "Saving to " + file.getBaseFile());
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800585 }
586
587 FileOutputStream outs = null;
588 try {
589 outs = file.startWrite();
590
591 // Write to XML
592 XmlSerializer out = new FastXmlSerializer();
593 out.setOutput(outs, StandardCharsets.UTF_8.name());
594 out.startDocument(null, true);
595 out.startTag(null, TAG_ROOT);
596
597 // Body.
598 writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
599
600 // Epilogue.
601 out.endTag(null, TAG_ROOT);
602 out.endDocument();
603
604 // Close.
605 file.finishWrite(outs);
606 } catch (IOException e) {
607 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
608 file.failWrite(outs);
609 }
610 }
611
612 private void loadBaseStateLocked() {
613 mRawLastResetTime = 0;
614
615 final AtomicFile file = getBaseStateFile();
616 if (DEBUG) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700617 Slog.d(TAG, "Loading from " + file.getBaseFile());
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800618 }
619 try (FileInputStream in = file.openRead()) {
620 XmlPullParser parser = Xml.newPullParser();
621 parser.setInput(in, StandardCharsets.UTF_8.name());
622
623 int type;
624 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
625 if (type != XmlPullParser.START_TAG) {
626 continue;
627 }
628 final int depth = parser.getDepth();
629 // Check the root tag
630 final String tag = parser.getName();
631 if (depth == 1) {
632 if (!TAG_ROOT.equals(tag)) {
633 Slog.e(TAG, "Invalid root tag: " + tag);
634 return;
635 }
636 continue;
637 }
638 // Assume depth == 2
639 switch (tag) {
640 case TAG_LAST_RESET_TIME:
641 mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
642 break;
643 default:
644 Slog.e(TAG, "Invalid tag: " + tag);
645 break;
646 }
647 }
648 } catch (FileNotFoundException e) {
649 // Use the default
650 } catch (IOException|XmlPullParserException e) {
651 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
652
653 mRawLastResetTime = 0;
654 }
655 // Adjust the last reset time.
656 getLastResetTimeLocked();
657 }
658
659 private void saveUserLocked(@UserIdInt int userId) {
660 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
661 if (DEBUG) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700662 Slog.d(TAG, "Saving to " + path);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800663 }
664 path.mkdirs();
665 final AtomicFile file = new AtomicFile(path);
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700666 FileOutputStream os = null;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800667 try {
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700668 os = file.startWrite();
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800669
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700670 saveUserInternalLocked(userId, os, /* forBackup= */ false);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800671
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700672 file.finishWrite(os);
673 } catch (XmlPullParserException|IOException e) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800674 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700675 file.failWrite(os);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800676 }
677 }
678
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700679 private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
680 boolean forBackup) throws IOException, XmlPullParserException {
681
682 final BufferedOutputStream bos = new BufferedOutputStream(os);
683
684 // Write to XML
685 XmlSerializer out = new FastXmlSerializer();
686 out.setOutput(bos, StandardCharsets.UTF_8.name());
687 out.startDocument(null, true);
688
689 getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
690
691 out.endDocument();
692
693 bos.flush();
694 os.flush();
695 }
696
Makoto Onuki41066a62016-03-09 16:18:44 -0800697 static IOException throwForInvalidTag(int depth, String tag) throws IOException {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800698 throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
699 }
700
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700701 static void warnForInvalidTag(int depth, String tag) throws IOException {
702 Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
703 }
704
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800705 @Nullable
Makoto Onuki31459242016-03-22 11:12:18 -0700706 private ShortcutUser loadUserLocked(@UserIdInt int userId) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800707 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
708 if (DEBUG) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700709 Slog.d(TAG, "Loading from " + path);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800710 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800711 final AtomicFile file = new AtomicFile(path);
712
713 final FileInputStream in;
714 try {
715 in = file.openRead();
716 } catch (FileNotFoundException e) {
717 if (DEBUG) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700718 Slog.d(TAG, "Not found " + path);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800719 }
720 return null;
721 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800722 try {
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700723 return loadUserInternal(userId, in, /* forBackup= */ false);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800724 } catch (IOException|XmlPullParserException e) {
725 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
726 return null;
727 } finally {
728 IoUtils.closeQuietly(in);
729 }
730 }
731
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700732 private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
733 boolean fromBackup) throws XmlPullParserException, IOException {
734
735 final BufferedInputStream bis = new BufferedInputStream(is);
736
737 ShortcutUser ret = null;
738 XmlPullParser parser = Xml.newPullParser();
739 parser.setInput(bis, StandardCharsets.UTF_8.name());
740
741 int type;
742 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
743 if (type != XmlPullParser.START_TAG) {
744 continue;
745 }
746 final int depth = parser.getDepth();
747
748 final String tag = parser.getName();
749 if (DEBUG_LOAD) {
750 Slog.d(TAG, String.format("depth=%d type=%d name=%s",
751 depth, type, tag));
752 }
753 if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
754 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
755 continue;
756 }
757 throwForInvalidTag(depth, tag);
758 }
759 return ret;
760 }
761
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800762 private void scheduleSaveBaseState() {
Makoto Onuki0acbb142016-03-22 17:02:57 -0700763 scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800764 }
765
Makoto Onuki2d5b4652016-03-11 16:09:54 -0800766 void scheduleSaveUser(@UserIdInt int userId) {
Makoto Onuki0acbb142016-03-22 17:02:57 -0700767 scheduleSaveInner(userId);
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700768 }
769
770 // In order to re-schedule, we need to reuse the same instance, so keep it in final.
771 private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
772
Makoto Onuki0acbb142016-03-22 17:02:57 -0700773 private void scheduleSaveInner(@UserIdInt int userId) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700774 if (DEBUG) {
775 Slog.d(TAG, "Scheduling to save for " + userId);
776 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800777 synchronized (mLock) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700778 if (!mDirtyUserIds.contains(userId)) {
779 mDirtyUserIds.add(userId);
780 }
781 }
782 // If already scheduled, remove that and re-schedule in N seconds.
783 mHandler.removeCallbacks(mSaveDirtyInfoRunner);
784 mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
785 }
786
787 @VisibleForTesting
788 void saveDirtyInfo() {
789 if (DEBUG) {
790 Slog.d(TAG, "saveDirtyInfo");
791 }
792 synchronized (mLock) {
793 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
794 final int userId = mDirtyUserIds.get(i);
795 if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
796 saveBaseStateLocked();
797 } else {
798 saveUserLocked(userId);
799 }
800 }
801 mDirtyUserIds.clear();
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800802 }
803 }
804
805 /** Return the last reset time. */
806 long getLastResetTimeLocked() {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700807 updateTimesLocked();
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800808 return mRawLastResetTime;
809 }
810
811 /** Return the next reset time. */
812 long getNextResetTimeLocked() {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700813 updateTimesLocked();
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800814 return mRawLastResetTime + mResetInterval;
815 }
816
Makoto Onuki4554d0e2016-03-14 15:51:41 -0700817 static boolean isClockValid(long time) {
818 return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
819 }
820
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800821 /**
822 * Update the last reset time.
823 */
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700824 private void updateTimesLocked() {
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800825
826 final long now = injectCurrentTimeMillis();
827
828 final long prevLastResetTime = mRawLastResetTime;
829
830 if (mRawLastResetTime == 0) { // first launch.
831 // TODO Randomize??
832 mRawLastResetTime = now;
833 } else if (now < mRawLastResetTime) {
834 // Clock rewound.
Makoto Onuki4554d0e2016-03-14 15:51:41 -0700835 if (isClockValid(now)) {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700836 Slog.w(TAG, "Clock rewound");
Makoto Onuki4554d0e2016-03-14 15:51:41 -0700837 // TODO Randomize??
838 mRawLastResetTime = now;
839 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800840 } else {
Makoto Onukiaa8b94a2016-03-17 13:14:05 -0700841 if ((mRawLastResetTime + mResetInterval) <= now) {
842 final long offset = mRawLastResetTime % mResetInterval;
843 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800844 }
845 }
846 if (prevLastResetTime != mRawLastResetTime) {
847 scheduleSaveBaseState();
848 }
849 }
850
Makoto Onukicdc78f72016-03-21 15:47:52 -0700851 @GuardedBy("mLock")
852 @NonNull
Makoto Onuki2e210c42016-03-30 08:30:36 -0700853 private boolean isUserLoadedLocked(@UserIdInt int userId) {
Makoto Onukicdc78f72016-03-21 15:47:52 -0700854 return mUsers.get(userId) != null;
855 }
856
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800857 /** Return the per-user state. */
858 @GuardedBy("mLock")
859 @NonNull
Makoto Onuki31459242016-03-22 11:12:18 -0700860 ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
861 ShortcutUser userPackages = mUsers.get(userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800862 if (userPackages == null) {
863 userPackages = loadUserLocked(userId);
864 if (userPackages == null) {
Makoto Onuki31459242016-03-22 11:12:18 -0700865 userPackages = new ShortcutUser(userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800866 }
Makoto Onuki3f4b1ca2016-03-11 13:44:32 -0800867 mUsers.put(userId, userPackages);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800868 }
869 return userPackages;
870 }
871
Makoto Onuki2e210c42016-03-30 08:30:36 -0700872 void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
873 for (int i = mUsers.size() - 1; i >= 0; i--) {
874 c.accept(mUsers.valueAt(i));
875 }
876 }
877
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800878 /** Return the per-user per-package state. */
879 @GuardedBy("mLock")
880 @NonNull
Makoto Onuki31459242016-03-22 11:12:18 -0700881 ShortcutPackage getPackageShortcutsLocked(
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800882 @NonNull String packageName, @UserIdInt int userId) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700883 return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
Makoto Onukide667372016-03-15 14:29:20 -0700884 }
885
886 @GuardedBy("mLock")
887 @NonNull
Makoto Onuki2e210c42016-03-30 08:30:36 -0700888 ShortcutLauncher getLauncherShortcutsLocked(
889 @NonNull String packageName, @UserIdInt int ownerUserId,
890 @UserIdInt int launcherUserId) {
891 return getUserShortcutsLocked(ownerUserId)
892 .getLauncherShortcuts(this, packageName, launcherUserId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -0800893 }
894
895 // === Caller validation ===
896
Makoto Onuki55046222016-03-08 10:49:47 -0800897 void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
898 if (shortcut.getBitmapPath() != null) {
899 if (DEBUG) {
900 Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
901 }
902 new File(shortcut.getBitmapPath()).delete();
903
904 shortcut.setBitmapPath(null);
905 shortcut.setIconResourceId(0);
906 shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
907 }
908 }
909
910 @VisibleForTesting
911 static class FileOutputStreamWithPath extends FileOutputStream {
912 private final File mFile;
913
914 public FileOutputStreamWithPath(File file) throws FileNotFoundException {
915 super(file);
916 mFile = file;
917 }
918
919 public File getFile() {
920 return mFile;
921 }
922 }
923
924 /**
925 * Build the cached bitmap filename for a shortcut icon.
926 *
927 * The filename will be based on the ID, except certain characters will be escaped.
928 */
929 @VisibleForTesting
930 FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
931 throws IOException {
932 final File packagePath = new File(getUserBitmapFilePath(userId),
933 shortcut.getPackageName());
934 if (!packagePath.isDirectory()) {
935 packagePath.mkdirs();
936 if (!packagePath.isDirectory()) {
937 throw new IOException("Unable to create directory " + packagePath);
938 }
939 SELinux.restorecon(packagePath);
940 }
941
942 final String baseName = String.valueOf(injectCurrentTimeMillis());
943 for (int suffix = 0;; suffix++) {
944 final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
945 final File file = new File(packagePath, filename);
946 if (!file.exists()) {
947 if (DEBUG) {
948 Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
949 }
950 return new FileOutputStreamWithPath(file);
951 }
952 }
953 }
954
955 void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
956 if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
957 return;
958 }
959
Makoto Onuki4dbe0de2016-03-14 17:31:49 -0700960 final long token = injectClearCallingIdentity();
Makoto Onuki55046222016-03-08 10:49:47 -0800961 try {
962 // Clear icon info on the shortcut.
963 shortcut.setIconResourceId(0);
964 shortcut.setBitmapPath(null);
965
966 final Icon icon = shortcut.getIcon();
967 if (icon == null) {
968 return; // has no icon
969 }
970
971 Bitmap bitmap = null;
972 try {
973 switch (icon.getType()) {
974 case Icon.TYPE_RESOURCE: {
975 injectValidateIconResPackage(shortcut, icon);
976
977 shortcut.setIconResourceId(icon.getResId());
978 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
979 return;
980 }
981 case Icon.TYPE_BITMAP: {
982 bitmap = icon.getBitmap();
983 break;
984 }
985 case Icon.TYPE_URI: {
986 final Uri uri = ContentProvider.maybeAddUserId(icon.getUri(), userId);
987
988 try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
989
990 bitmap = BitmapFactory.decodeStream(is);
991
992 } catch (IOException e) {
993 Slog.e(TAG, "Unable to load icon from " + uri);
994 return;
995 }
996 break;
997 }
998 default:
999 // This shouldn't happen because we've already validated the icon, but
1000 // just in case.
1001 throw ShortcutInfo.getInvalidIconException();
1002 }
1003 if (bitmap == null) {
1004 Slog.e(TAG, "Null bitmap detected");
1005 return;
1006 }
1007 // Shrink and write to the file.
1008 File path = null;
1009 try {
1010 final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
1011 try {
1012 path = out.getFile();
1013
1014 shrinkBitmap(bitmap, mMaxIconDimension)
1015 .compress(mIconPersistFormat, mIconPersistQuality, out);
1016
1017 shortcut.setBitmapPath(out.getFile().getAbsolutePath());
1018 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
1019 } finally {
1020 IoUtils.closeQuietly(out);
1021 }
1022 } catch (IOException|RuntimeException e) {
1023 // STOPSHIP Change wtf to e
1024 Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
1025 if (path != null && path.exists()) {
1026 path.delete();
1027 }
1028 }
1029 } finally {
1030 if (bitmap != null) {
1031 bitmap.recycle();
1032 }
1033 // Once saved, we won't use the original icon information, so null it out.
1034 shortcut.clearIcon();
1035 }
1036 } finally {
Makoto Onuki4dbe0de2016-03-14 17:31:49 -07001037 injectRestoreCallingIdentity(token);
Makoto Onuki55046222016-03-08 10:49:47 -08001038 }
1039 }
1040
1041 // Unfortunately we can't do this check in unit tests because we fake creator package names,
1042 // so override in unit tests.
1043 // TODO CTS this case.
1044 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
1045 if (!shortcut.getPackageName().equals(icon.getResPackage())) {
1046 throw new IllegalArgumentException(
1047 "Icon resource must reside in shortcut owner package");
1048 }
1049 }
1050
1051 @VisibleForTesting
1052 static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
1053 // Original width/height.
1054 final int ow = in.getWidth();
1055 final int oh = in.getHeight();
1056 if ((ow <= maxSize) && (oh <= maxSize)) {
1057 if (DEBUG) {
1058 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
1059 }
1060 return in;
1061 }
1062 final int longerDimension = Math.max(ow, oh);
1063
1064 // New width and height.
1065 final int nw = ow * maxSize / longerDimension;
1066 final int nh = oh * maxSize / longerDimension;
1067 if (DEBUG) {
1068 Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
1069 ow, oh, nw, nh));
1070 }
1071
1072 final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
1073 final Canvas c = new Canvas(scaledBitmap);
1074
1075 final RectF dst = new RectF(0, 0, nw, nh);
1076
1077 c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
1078
1079 in.recycle();
1080
1081 return scaledBitmap;
1082 }
1083
1084 // === Caller validation ===
1085
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001086 private boolean isCallerSystem() {
1087 final int callingUid = injectBinderCallingUid();
1088 return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1089 }
1090
1091 private boolean isCallerShell() {
1092 final int callingUid = injectBinderCallingUid();
1093 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1094 }
1095
1096 private void enforceSystemOrShell() {
1097 Preconditions.checkState(isCallerSystem() || isCallerShell(),
1098 "Caller must be system or shell");
1099 }
1100
1101 private void enforceShell() {
1102 Preconditions.checkState(isCallerShell(), "Caller must be shell");
1103 }
1104
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001105 private void enforceSystem() {
1106 Preconditions.checkState(isCallerSystem(), "Caller must be system");
1107 }
1108
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001109 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1110 Preconditions.checkStringNotEmpty(packageName, "packageName");
1111
1112 if (isCallerSystem()) {
1113 return; // no check
1114 }
1115
1116 final int callingUid = injectBinderCallingUid();
1117
1118 // Otherwise, make sure the arguments are valid.
1119 if (UserHandle.getUserId(callingUid) != userId) {
1120 throw new SecurityException("Invalid user-ID");
1121 }
Makoto Onuki55046222016-03-08 10:49:47 -08001122 if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001123 return; // Caller is valid.
1124 }
1125 throw new SecurityException("Caller UID= doesn't own " + packageName);
1126 }
1127
Makoto Onuki4dbe0de2016-03-14 17:31:49 -07001128 void postToHandler(Runnable r) {
1129 mHandler.post(r);
1130 }
1131
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001132 /**
1133 * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
1134 */
1135 void enforceMaxDynamicShortcuts(int numShortcuts) {
1136 if (numShortcuts > mMaxDynamicShortcuts) {
1137 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1138 }
1139 }
1140
1141 /**
1142 * - Sends a notification to LauncherApps
1143 * - Write to file
1144 */
1145 private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) {
1146 notifyListeners(packageName, userId);
1147 scheduleSaveUser(userId);
1148 }
1149
1150 private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
Makoto Onukicdc78f72016-03-21 15:47:52 -07001151 if (!mUserManager.isUserRunning(userId)) {
1152 return;
1153 }
Makoto Onuki4dbe0de2016-03-14 17:31:49 -07001154 postToHandler(() -> {
1155 final ArrayList<ShortcutChangeListener> copy;
1156 synchronized (mLock) {
1157 copy = new ArrayList<>(mListeners);
1158 }
1159 // Note onShortcutChanged() needs to be called with the system service permissions.
1160 for (int i = copy.size() - 1; i >= 0; i--) {
1161 copy.get(i).onShortcutChanged(packageName, userId);
1162 }
1163 });
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001164 }
1165
1166 /**
1167 * Clean up / validate an incoming shortcut.
1168 * - Make sure all mandatory fields are set.
1169 * - Make sure the intent's extras are persistable, and them to set
1170 * {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras.
1171 * - Clear flags.
Makoto Onuki55046222016-03-08 10:49:47 -08001172 *
1173 * TODO Detailed unit tests
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001174 */
Makoto Onuki55046222016-03-08 10:49:47 -08001175 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001176 Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1177 if (shortcut.getActivityComponent() != null) {
1178 Preconditions.checkState(
1179 shortcut.getPackageName().equals(
1180 shortcut.getActivityComponent().getPackageName()),
1181 "Activity package name mismatch");
1182 }
1183
Makoto Onuki55046222016-03-08 10:49:47 -08001184 if (!forUpdate) {
1185 shortcut.enforceMandatoryFields();
1186 }
1187 if (shortcut.getIcon() != null) {
1188 ShortcutInfo.validateIcon(shortcut.getIcon());
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001189 }
1190
Makoto Onuki55046222016-03-08 10:49:47 -08001191 validateForXml(shortcut.getId());
1192 validateForXml(shortcut.getTitle());
1193 validatePersistableBundleForXml(shortcut.getIntentPersistableExtras());
1194 validatePersistableBundleForXml(shortcut.getExtras());
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001195
Makoto Onukide667372016-03-15 14:29:20 -07001196 shortcut.replaceFlags(0);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001197 }
1198
Makoto Onuki55046222016-03-08 10:49:47 -08001199 // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those
1200 // characters.
1201
1202 private static void validatePersistableBundleForXml(PersistableBundle b) {
1203 if (b == null || b.size() == 0) {
1204 return;
1205 }
1206 for (String key : b.keySet()) {
1207 validateForXml(key);
1208 final Object value = b.get(key);
1209 if (value == null) {
1210 continue;
1211 } else if (value instanceof String) {
1212 validateForXml((String) value);
1213 } else if (value instanceof String[]) {
1214 for (String v : (String[]) value) {
1215 validateForXml(v);
1216 }
1217 } else if (value instanceof PersistableBundle) {
1218 validatePersistableBundleForXml((PersistableBundle) value);
1219 }
1220 }
1221 }
1222
1223 private static void validateForXml(String s) {
1224 if (TextUtils.isEmpty(s)) {
1225 return;
1226 }
1227 for (int i = s.length() - 1; i >= 0; i--) {
1228 if (!isAllowedInXml(s.charAt(i))) {
1229 throw new IllegalArgumentException("Unsupported character detected in: " + s);
1230 }
1231 }
1232 }
1233
1234 private static boolean isAllowedInXml(char c) {
1235 return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
1236 }
1237
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001238 // === APIs ===
1239
1240 @Override
1241 public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1242 @UserIdInt int userId) {
1243 verifyCaller(packageName, userId);
1244
1245 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1246 final int size = newShortcuts.size();
1247
1248 synchronized (mLock) {
Makoto Onuki31459242016-03-22 11:12:18 -07001249 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001250
1251 // Throttling.
1252 if (!ps.tryApiCall(this)) {
1253 return false;
1254 }
1255 enforceMaxDynamicShortcuts(size);
1256
1257 // Validate the shortcuts.
1258 for (int i = 0; i < size; i++) {
Makoto Onuki55046222016-03-08 10:49:47 -08001259 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001260 }
1261
1262 // First, remove all un-pinned; dynamic shortcuts
Makoto Onuki55046222016-03-08 10:49:47 -08001263 ps.deleteAllDynamicShortcuts(this);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001264
1265 // Then, add/update all. We need to make sure to take over "pinned" flag.
1266 for (int i = 0; i < size; i++) {
1267 final ShortcutInfo newShortcut = newShortcuts.get(i);
Makoto Onukide667372016-03-15 14:29:20 -07001268 ps.addDynamicShortcut(this, newShortcut);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001269 }
1270 }
1271 userPackageChanged(packageName, userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001272 return true;
1273 }
1274
1275 @Override
1276 public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1277 @UserIdInt int userId) {
1278 verifyCaller(packageName, userId);
1279
1280 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
Makoto Onuki55046222016-03-08 10:49:47 -08001281 final int size = newShortcuts.size();
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001282
1283 synchronized (mLock) {
Makoto Onuki31459242016-03-22 11:12:18 -07001284 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001285
Makoto Onuki55046222016-03-08 10:49:47 -08001286 // Throttling.
1287 if (!ps.tryApiCall(this)) {
1288 return false;
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001289 }
1290
Makoto Onuki55046222016-03-08 10:49:47 -08001291 for (int i = 0; i < size; i++) {
1292 final ShortcutInfo source = newShortcuts.get(i);
1293 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1294
1295 final ShortcutInfo target = ps.findShortcutById(source.getId());
1296 if (target != null) {
1297 final boolean replacingIcon = (source.getIcon() != null);
1298 if (replacingIcon) {
1299 removeIcon(userId, target);
1300 }
1301
1302 target.copyNonNullFieldsFrom(source);
1303
1304 if (replacingIcon) {
1305 saveIconAndFixUpShortcut(userId, target);
1306 }
1307 }
1308 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001309 }
1310 userPackageChanged(packageName, userId);
1311
1312 return true;
1313 }
1314
1315 @Override
1316 public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut,
1317 @UserIdInt int userId) {
1318 verifyCaller(packageName, userId);
1319
1320 synchronized (mLock) {
Makoto Onuki31459242016-03-22 11:12:18 -07001321 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001322
1323 // Throttling.
1324 if (!ps.tryApiCall(this)) {
1325 return false;
1326 }
1327
1328 // Validate the shortcut.
Makoto Onuki55046222016-03-08 10:49:47 -08001329 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001330
1331 // Add it.
Makoto Onukide667372016-03-15 14:29:20 -07001332 ps.addDynamicShortcut(this, newShortcut);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001333 }
1334 userPackageChanged(packageName, userId);
1335
1336 return true;
1337 }
1338
1339 @Override
1340 public void deleteDynamicShortcut(String packageName, String shortcutId,
1341 @UserIdInt int userId) {
1342 verifyCaller(packageName, userId);
1343 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided");
1344
1345 synchronized (mLock) {
Makoto Onuki55046222016-03-08 10:49:47 -08001346 getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this, shortcutId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001347 }
1348 userPackageChanged(packageName, userId);
1349 }
1350
1351 @Override
1352 public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1353 verifyCaller(packageName, userId);
1354
1355 synchronized (mLock) {
Makoto Onuki55046222016-03-08 10:49:47 -08001356 getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001357 }
1358 userPackageChanged(packageName, userId);
1359 }
1360
1361 @Override
1362 public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1363 @UserIdInt int userId) {
1364 verifyCaller(packageName, userId);
1365 synchronized (mLock) {
1366 return getShortcutsWithQueryLocked(
1367 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1368 ShortcutInfo::isDynamic);
1369 }
1370 }
1371
1372 @Override
1373 public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
1374 @UserIdInt int userId) {
1375 verifyCaller(packageName, userId);
1376 synchronized (mLock) {
1377 return getShortcutsWithQueryLocked(
1378 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1379 ShortcutInfo::isPinned);
1380 }
1381 }
1382
1383 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
1384 @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
1385
1386 final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1387
Makoto Onukid99c6f02016-03-28 11:02:54 -07001388 getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001389
1390 return new ParceledListSlice<>(ret);
1391 }
1392
1393 @Override
1394 public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId)
1395 throws RemoteException {
1396 verifyCaller(packageName, userId);
1397
1398 return mMaxDynamicShortcuts;
1399 }
1400
1401 @Override
1402 public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
1403 verifyCaller(packageName, userId);
1404
1405 synchronized (mLock) {
1406 return mMaxDailyUpdates
1407 - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
1408 }
1409 }
1410
1411 @Override
1412 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
1413 verifyCaller(packageName, userId);
1414
1415 synchronized (mLock) {
1416 return getNextResetTimeLocked();
1417 }
1418 }
1419
Makoto Onuki55046222016-03-08 10:49:47 -08001420 @Override
1421 public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
1422 synchronized (mLock) {
1423 return mMaxIconDimension;
1424 }
1425 }
1426
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001427 /**
1428 * Reset all throttling, for developer options and command line. Only system/shell can call it.
1429 */
1430 @Override
1431 public void resetThrottling() {
1432 enforceSystemOrShell();
1433
Makoto Onuki4554d0e2016-03-14 15:51:41 -07001434 resetThrottlingInner(getCallingUserId());
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001435 }
1436
Makoto Onuki4554d0e2016-03-14 15:51:41 -07001437 void resetThrottlingInner(@UserIdInt int userId) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001438 synchronized (mLock) {
Makoto Onuki4554d0e2016-03-14 15:51:41 -07001439 getUserShortcutsLocked(userId).resetThrottling();
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001440 }
Makoto Onuki4554d0e2016-03-14 15:51:41 -07001441 scheduleSaveUser(userId);
Makoto Onuki55046222016-03-08 10:49:47 -08001442 Slog.i(TAG, "ShortcutManager: throttling counter reset");
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001443 }
1444
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001445 // We override this method in unit tests to do a simpler check.
1446 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1447 return hasShortcutHostPermissionInner(callingPackage, userId);
1448 }
1449
1450 // This method is extracted so we can directly call this method from unit tests,
1451 // even when hasShortcutPermission() is overridden.
1452 @VisibleForTesting
1453 boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
1454 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001455 final long start = System.currentTimeMillis();
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001456
Makoto Onuki31459242016-03-22 11:12:18 -07001457 final ShortcutUser user = getUserShortcutsLocked(userId);
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001458
1459 final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
1460
1461 // Default launcher from package manager.
Makoto Onuki2e210c42016-03-30 08:30:36 -07001462 final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001463 final ComponentName defaultLauncher = injectPackageManagerInternal()
1464 .getHomeActivitiesAsUser(allHomeCandidates, userId);
Makoto Onuki2e210c42016-03-30 08:30:36 -07001465 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001466
1467 ComponentName detected;
1468 if (defaultLauncher != null) {
1469 detected = defaultLauncher;
1470 if (DEBUG) {
1471 Slog.v(TAG, "Default launcher from PM: " + detected);
1472 }
1473 } else {
1474 detected = user.getLauncherComponent();
1475
1476 // TODO: Make sure it's still enabled.
1477 if (DEBUG) {
1478 Slog.v(TAG, "Cached launcher: " + detected);
1479 }
1480 }
1481
1482 if (detected == null) {
1483 // If we reach here, that means it's the first check since the user was created,
1484 // and there's already multiple launchers and there's no default set.
1485 // Find the system one with the highest priority.
1486 // (We need to check the priority too because of FallbackHome in Settings.)
1487 // If there's no system launcher yet, then no one can access shortcuts, until
1488 // the user explicitly
1489 final int size = allHomeCandidates.size();
1490
1491 int lastPriority = Integer.MIN_VALUE;
1492 for (int i = 0; i < size; i++) {
1493 final ResolveInfo ri = allHomeCandidates.get(i);
1494 if (!ri.activityInfo.applicationInfo.isSystemApp()) {
1495 continue;
1496 }
1497 if (DEBUG) {
1498 Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
1499 ri.activityInfo.getComponentName(), ri.priority));
1500 }
1501 if (ri.priority < lastPriority) {
1502 continue;
1503 }
1504 detected = ri.activityInfo.getComponentName();
1505 lastPriority = ri.priority;
1506 }
1507 }
Makoto Onuki2e210c42016-03-30 08:30:36 -07001508 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
1509
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001510 if (detected != null) {
1511 if (DEBUG) {
1512 Slog.v(TAG, "Detected launcher: " + detected);
1513 }
1514 user.setLauncherComponent(this, detected);
1515 return detected.getPackageName().equals(callingPackage);
1516 } else {
1517 // Default launcher not found.
1518 return false;
1519 }
1520 }
1521 }
1522
Makoto Onukicdc78f72016-03-21 15:47:52 -07001523 // === House keeping ===
1524
Makoto Onuki2e210c42016-03-30 08:30:36 -07001525 /**
1526 * Remove all the information associated with a package. This will really remove all the
1527 * information, including the restore information (i.e. it'll remove packages even if they're
1528 * shadow).
1529 */
Makoto Onukicdc78f72016-03-21 15:47:52 -07001530 @VisibleForTesting
Makoto Onukid99c6f02016-03-28 11:02:54 -07001531 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001532 if (isPackageInstalled(packageName, packageUserId)) {
1533 wtf("Package " + packageName + " is still installed for user " + packageUserId);
1534 return;
1535 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001536
Makoto Onukid99c6f02016-03-28 11:02:54 -07001537 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001538
Makoto Onukid99c6f02016-03-28 11:02:54 -07001539 final ShortcutUser mUser = getUserShortcutsLocked(owningUserId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001540 boolean doNotify = false;
1541
1542 // First, remove the package from the package list (if the package is a publisher).
Makoto Onukid99c6f02016-03-28 11:02:54 -07001543 if (packageUserId == owningUserId) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001544 if (mUser.removePackage(packageName) != null) {
Makoto Onukid99c6f02016-03-28 11:02:54 -07001545 doNotify = true;
1546 }
Makoto Onukicdc78f72016-03-21 15:47:52 -07001547 }
Makoto Onukid99c6f02016-03-28 11:02:54 -07001548
Makoto Onukicdc78f72016-03-21 15:47:52 -07001549 // Also remove from the launcher list (if the package is a launcher).
Makoto Onukid99c6f02016-03-28 11:02:54 -07001550 mUser.removeLauncher(packageUserId, packageName);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001551
1552 // Then remove pinned shortcuts from all launchers.
Makoto Onukid99c6f02016-03-28 11:02:54 -07001553 final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers();
1554 for (int i = launchers.size() - 1; i >= 0; i--) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001555 launchers.valueAt(i).cleanUpPackage(packageName, packageUserId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001556 }
1557 // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
1558 // step. Remove them too.
Makoto Onuki2e210c42016-03-30 08:30:36 -07001559 for (int i = mUser.getAllPackages().size() - 1; i >= 0; i--) {
1560 mUser.getAllPackages().valueAt(i).refreshPinnedFlags(this);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001561 }
1562
Makoto Onukid99c6f02016-03-28 11:02:54 -07001563 scheduleSaveUser(owningUserId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001564
1565 if (doNotify) {
Makoto Onukid99c6f02016-03-28 11:02:54 -07001566 notifyListeners(packageName, owningUserId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001567 }
1568
1569 if (!wasUserLoaded) {
1570 // Note this will execute the scheduled save.
Makoto Onukid99c6f02016-03-28 11:02:54 -07001571 unloadUserLocked(owningUserId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001572 }
1573 }
1574
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001575 /**
1576 * Entry point from {@link LauncherApps}.
1577 */
1578 private class LocalService extends ShortcutServiceInternal {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001579
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001580 @Override
Makoto Onukid99c6f02016-03-28 11:02:54 -07001581 public List<ShortcutInfo> getShortcuts(int launcherUserId,
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001582 @NonNull String callingPackage, long changedSince,
1583 @Nullable String packageName, @Nullable ComponentName componentName,
1584 int queryFlags, int userId) {
1585 final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1586 final int cloneFlag =
1587 ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
1588 ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
1589 : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
1590
1591 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001592 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1593 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1594
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001595 if (packageName != null) {
Makoto Onukid99c6f02016-03-28 11:02:54 -07001596 getShortcutsInnerLocked(launcherUserId,
Makoto Onukide667372016-03-15 14:29:20 -07001597 callingPackage, packageName, changedSince,
1598 componentName, queryFlags, userId, ret, cloneFlag);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001599 } else {
Makoto Onuki31459242016-03-22 11:12:18 -07001600 final ArrayMap<String, ShortcutPackage> packages =
Makoto Onuki2e210c42016-03-30 08:30:36 -07001601 getUserShortcutsLocked(userId).getAllPackages();
Makoto Onuki55046222016-03-08 10:49:47 -08001602 for (int i = packages.size() - 1; i >= 0; i--) {
Makoto Onukid99c6f02016-03-28 11:02:54 -07001603 getShortcutsInnerLocked(launcherUserId,
Makoto Onukide667372016-03-15 14:29:20 -07001604 callingPackage, packages.keyAt(i), changedSince,
1605 componentName, queryFlags, userId, ret, cloneFlag);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001606 }
1607 }
1608 }
1609 return ret;
1610 }
1611
Makoto Onukid99c6f02016-03-28 11:02:54 -07001612 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
Makoto Onukide667372016-03-15 14:29:20 -07001613 @Nullable String packageName,long changedSince,
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001614 @Nullable ComponentName componentName, int queryFlags,
1615 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
Makoto Onukide667372016-03-15 14:29:20 -07001616 getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001617 (ShortcutInfo si) -> {
1618 if (si.getLastChangedTimestamp() < changedSince) {
1619 return false;
1620 }
1621 if (componentName != null
1622 && !componentName.equals(si.getActivityComponent())) {
1623 return false;
1624 }
1625 final boolean matchDynamic =
1626 ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
Makoto Onukide667372016-03-15 14:29:20 -07001627 && si.isDynamic();
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001628 final boolean matchPinned =
1629 ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
1630 && si.isPinned();
1631 return matchDynamic || matchPinned;
Makoto Onukid99c6f02016-03-28 11:02:54 -07001632 }, cloneFlag, callingPackage, launcherUserId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001633 }
1634
1635 @Override
Makoto Onukid99c6f02016-03-28 11:02:54 -07001636 public List<ShortcutInfo> getShortcutInfo(int launcherUserId,
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001637 @NonNull String callingPackage,
1638 @NonNull String packageName, @Nullable List<String> ids, int userId) {
1639 // Calling permission must be checked by LauncherAppsImpl.
1640 Preconditions.checkStringNotEmpty(packageName, "packageName");
1641
1642 final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
1643 final ArraySet<String> idSet = new ArraySet<>(ids);
1644 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001645 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1646 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1647
Makoto Onukide667372016-03-15 14:29:20 -07001648 getPackageShortcutsLocked(packageName, userId).findAll(
1649 ShortcutService.this, ret,
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001650 (ShortcutInfo si) -> idSet.contains(si.getId()),
Makoto Onukid99c6f02016-03-28 11:02:54 -07001651 ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage, launcherUserId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001652 }
1653 return ret;
1654 }
1655
1656 @Override
Makoto Onukid99c6f02016-03-28 11:02:54 -07001657 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
1658 @NonNull String packageName, @NonNull String shortcutId, int userId) {
1659 Preconditions.checkStringNotEmpty(packageName, "packageName");
1660 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
1661
1662 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001663 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1664 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1665
Makoto Onukid99c6f02016-03-28 11:02:54 -07001666 final ShortcutInfo si = getShortcutInfoLocked(
1667 launcherUserId, callingPackage, packageName, shortcutId, userId);
1668 return si != null && si.isPinned();
1669 }
1670 }
1671
Makoto Onuki2e210c42016-03-30 08:30:36 -07001672 private ShortcutInfo getShortcutInfoLocked(
Makoto Onukid99c6f02016-03-28 11:02:54 -07001673 int launcherUserId, @NonNull String callingPackage,
1674 @NonNull String packageName, @NonNull String shortcutId, int userId) {
1675 Preconditions.checkStringNotEmpty(packageName, "packageName");
1676 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
1677
1678 final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
1679 getPackageShortcutsLocked(packageName, userId).findAll(
1680 ShortcutService.this, list,
1681 (ShortcutInfo si) -> shortcutId.equals(si.getId()),
1682 /* clone flags=*/ 0, callingPackage, launcherUserId);
1683 return list.size() == 0 ? null : list.get(0);
1684 }
1685
1686 @Override
1687 public void pinShortcuts(int launcherUserId,
1688 @NonNull String callingPackage, @NonNull String packageName,
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001689 @NonNull List<String> shortcutIds, int userId) {
1690 // Calling permission must be checked by LauncherAppsImpl.
1691 Preconditions.checkStringNotEmpty(packageName, "packageName");
1692 Preconditions.checkNotNull(shortcutIds, "shortcutIds");
1693
1694 synchronized (mLock) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001695 final ShortcutLauncher launcher =
Makoto Onuki2e210c42016-03-30 08:30:36 -07001696 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
1697 launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001698
1699 launcher.pinShortcuts(
1700 ShortcutService.this, userId, packageName, shortcutIds);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001701 }
1702 userPackageChanged(packageName, userId);
1703 }
1704
1705 @Override
Makoto Onukid99c6f02016-03-28 11:02:54 -07001706 public Intent createShortcutIntent(int launcherUserId,
1707 @NonNull String callingPackage,
Makoto Onuki43204b82016-03-08 16:16:44 -08001708 @NonNull String packageName, @NonNull String shortcutId, int userId) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001709 // Calling permission must be checked by LauncherAppsImpl.
Makoto Onuki43204b82016-03-08 16:16:44 -08001710 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
1711 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001712
1713 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001714 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1715 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1716
Makoto Onukid99c6f02016-03-28 11:02:54 -07001717 // Make sure the shortcut is actually visible to the launcher.
1718 final ShortcutInfo si = getShortcutInfoLocked(
1719 launcherUserId, callingPackage, packageName, shortcutId, userId);
1720 // "si == null" should suffice here, but check the flags too just to make sure.
1721 if (si == null || !(si.isDynamic() || si.isPinned())) {
1722 return null;
1723 }
1724 return si.getIntent();
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001725 }
1726 }
1727
1728 @Override
1729 public void addListener(@NonNull ShortcutChangeListener listener) {
1730 synchronized (mLock) {
1731 mListeners.add(Preconditions.checkNotNull(listener));
1732 }
1733 }
Makoto Onuki55046222016-03-08 10:49:47 -08001734
1735 @Override
Makoto Onukid99c6f02016-03-28 11:02:54 -07001736 public int getShortcutIconResId(int launcherUserId,
1737 @NonNull String callingPackage,
Makoto Onuki55046222016-03-08 10:49:47 -08001738 @NonNull ShortcutInfo shortcut, int userId) {
1739 Preconditions.checkNotNull(shortcut, "shortcut");
1740
1741 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001742 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1743 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1744
Makoto Onuki55046222016-03-08 10:49:47 -08001745 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1746 shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
1747 return (shortcutInfo != null && shortcutInfo.hasIconResource())
1748 ? shortcutInfo.getIconResourceId() : 0;
1749 }
1750 }
1751
1752 @Override
Makoto Onukid99c6f02016-03-28 11:02:54 -07001753 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
1754 @NonNull String callingPackage,
Makoto Onuki34d1c912016-03-10 14:24:58 -08001755 @NonNull ShortcutInfo shortcutIn, int userId) {
1756 Preconditions.checkNotNull(shortcutIn, "shortcut");
Makoto Onuki55046222016-03-08 10:49:47 -08001757
1758 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001759 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1760 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1761
Makoto Onuki55046222016-03-08 10:49:47 -08001762 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
Makoto Onuki34d1c912016-03-10 14:24:58 -08001763 shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
Makoto Onuki55046222016-03-08 10:49:47 -08001764 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
1765 return null;
1766 }
1767 try {
Makoto Onuki34d1c912016-03-10 14:24:58 -08001768 if (shortcutInfo.getBitmapPath() == null) {
1769 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
1770 return null;
1771 }
Makoto Onuki55046222016-03-08 10:49:47 -08001772 return ParcelFileDescriptor.open(
1773 new File(shortcutInfo.getBitmapPath()),
1774 ParcelFileDescriptor.MODE_READ_ONLY);
1775 } catch (FileNotFoundException e) {
1776 Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath());
1777 return null;
1778 }
1779 }
1780 }
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001781
1782 @Override
Makoto Onukid99c6f02016-03-28 11:02:54 -07001783 public boolean hasShortcutHostPermission(int launcherUserId,
1784 @NonNull String callingPackage) {
1785 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
Makoto Onuki2d5b4652016-03-11 16:09:54 -08001786 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -08001787 }
1788
Makoto Onuki0acbb142016-03-22 17:02:57 -07001789 /**
1790 * Package event callbacks.
1791 */
1792 @VisibleForTesting
1793 final PackageMonitor mPackageMonitor = new PackageMonitor() {
1794 @Override
1795 public void onPackageAdded(String packageName, int uid) {
1796 handlePackageAdded(packageName, getChangingUserId());
1797 }
1798
Makoto Onukicdc78f72016-03-21 15:47:52 -07001799 @Override
1800 public void onPackageUpdateFinished(String packageName, int uid) {
1801 handlePackageUpdateFinished(packageName, getChangingUserId());
1802 }
1803
1804 @Override
1805 public void onPackageRemoved(String packageName, int uid) {
1806 handlePackageRemoved(packageName, getChangingUserId());
1807 }
Makoto Onukicdc78f72016-03-21 15:47:52 -07001808 };
1809
Makoto Onuki0acbb142016-03-22 17:02:57 -07001810 /**
1811 * Called when a user is unlocked. Check all known packages still exist, and otherwise
1812 * perform cleanup.
1813 */
Makoto Onukid99c6f02016-03-28 11:02:54 -07001814 @VisibleForTesting
Makoto Onuki2e210c42016-03-30 08:30:36 -07001815 void cleanupGonePackages(@UserIdInt int ownerUserId) {
Makoto Onukicdc78f72016-03-21 15:47:52 -07001816 if (DEBUG) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001817 Slog.d(TAG, "cleanupGonePackages() ownerUserId=" + ownerUserId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07001818 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001819 final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
Makoto Onuki0acbb142016-03-22 17:02:57 -07001820
Makoto Onukid99c6f02016-03-28 11:02:54 -07001821 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001822 final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001823
1824 user.forAllPackageItems(spi -> {
1825 if (spi.getPackageInfo().isShadow()) {
1826 return; // Don't delete shadow information.
Makoto Onukid99c6f02016-03-28 11:02:54 -07001827 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001828 if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001829 return; // Package not gone.
Makoto Onukid99c6f02016-03-28 11:02:54 -07001830 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001831 gonePackages.add(PackageWithUser.of(spi));
1832 });
1833 if (gonePackages.size() > 0) {
Makoto Onuki905e8852016-03-28 10:40:58 -07001834 for (int i = gonePackages.size() - 1; i >= 0; i--) {
Makoto Onukid99c6f02016-03-28 11:02:54 -07001835 final PackageWithUser pu = gonePackages.get(i);
Makoto Onuki2e210c42016-03-30 08:30:36 -07001836 cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
Makoto Onuki905e8852016-03-28 10:40:58 -07001837 }
Makoto Onuki0acbb142016-03-22 17:02:57 -07001838 }
1839 }
Makoto Onukicdc78f72016-03-21 15:47:52 -07001840 }
1841
Makoto Onuki0acbb142016-03-22 17:02:57 -07001842 private void handlePackageAdded(String packageName, @UserIdInt int userId) {
Makoto Onukicdc78f72016-03-21 15:47:52 -07001843 if (DEBUG) {
Makoto Onuki0acbb142016-03-22 17:02:57 -07001844 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
1845 }
1846 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001847 forEachLoadedUserLocked(user ->
1848 user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
Makoto Onuki0acbb142016-03-22 17:02:57 -07001849 }
1850 }
1851
1852 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
Makoto Onuki905e8852016-03-28 10:40:58 -07001853 if (DEBUG) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001854 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
1855 packageName, userId));
Makoto Onuki0acbb142016-03-22 17:02:57 -07001856 }
1857 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001858 forEachLoadedUserLocked(user ->
1859 user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
Makoto Onuki0acbb142016-03-22 17:02:57 -07001860 }
1861 }
1862
Makoto Onuki2e210c42016-03-30 08:30:36 -07001863 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
Makoto Onuki0acbb142016-03-22 17:02:57 -07001864 if (DEBUG) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001865 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
1866 packageUserId));
Makoto Onukicdc78f72016-03-21 15:47:52 -07001867 }
1868 synchronized (mLock) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001869 forEachLoadedUserLocked(user ->
1870 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
Makoto Onukicdc78f72016-03-21 15:47:52 -07001871 }
1872 }
1873
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001874 // === PackageManager interaction ===
Makoto Onuki0acbb142016-03-22 17:02:57 -07001875
Makoto Onuki905e8852016-03-28 10:40:58 -07001876 PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
1877 return injectPackageInfo(packageName, userId, true);
Makoto Onuki0acbb142016-03-22 17:02:57 -07001878 }
1879
Makoto Onuki905e8852016-03-28 10:40:58 -07001880 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001881 final long token = injectClearCallingIdentity();
Makoto Onuki905e8852016-03-28 10:40:58 -07001882 try {
1883 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
1884 , userId);
1885 } catch (RemoteException e) {
1886 // Shouldn't happen.
1887 Slog.wtf(TAG, "RemoteException", e);
1888 return -1;
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001889 } finally {
1890 injectRestoreCallingIdentity(token);
Makoto Onuki905e8852016-03-28 10:40:58 -07001891 }
Makoto Onuki0acbb142016-03-22 17:02:57 -07001892 }
1893
1894 @VisibleForTesting
1895 PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
1896 boolean getSignatures) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001897 final long start = System.currentTimeMillis();
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001898 final long token = injectClearCallingIdentity();
Makoto Onuki0acbb142016-03-22 17:02:57 -07001899 try {
Makoto Onuki905e8852016-03-28 10:40:58 -07001900 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
Makoto Onuki0acbb142016-03-22 17:02:57 -07001901 | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
1902 , userId);
1903 } catch (RemoteException e) {
1904 // Shouldn't happen.
1905 Slog.wtf(TAG, "RemoteException", e);
1906 return null;
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001907 } finally {
1908 injectRestoreCallingIdentity(token);
Makoto Onuki2e210c42016-03-30 08:30:36 -07001909
1910 logDurationStat(
1911 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
1912 start);
Makoto Onuki0acbb142016-03-22 17:02:57 -07001913 }
1914 }
1915
Makoto Onuki905e8852016-03-28 10:40:58 -07001916 @VisibleForTesting
1917 ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07001918 final long start = System.currentTimeMillis();
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001919 final long token = injectClearCallingIdentity();
Makoto Onuki905e8852016-03-28 10:40:58 -07001920 try {
1921 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
1922 } catch (RemoteException e) {
1923 // Shouldn't happen.
1924 Slog.wtf(TAG, "RemoteException", e);
1925 return null;
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001926 } finally {
1927 injectRestoreCallingIdentity(token);
Makoto Onuki2e210c42016-03-30 08:30:36 -07001928
1929 logDurationStat(Stats.GET_APPLICATION_INFO, start);
Makoto Onuki905e8852016-03-28 10:40:58 -07001930 }
1931 }
1932
1933 private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
1934 final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
1935 return (ai != null) && ((ai.flags & flags) == flags);
1936 }
1937
Makoto Onuki2e210c42016-03-30 08:30:36 -07001938 boolean isPackageInstalled(String packageName, int userId) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001939 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
1940 }
1941
1942 // === Backup & restore ===
1943
Makoto Onuki0acbb142016-03-22 17:02:57 -07001944 boolean shouldBackupApp(String packageName, int userId) {
Makoto Onuki905e8852016-03-28 10:40:58 -07001945 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
Makoto Onuki0acbb142016-03-22 17:02:57 -07001946 }
1947
Makoto Onuki2e210c42016-03-30 08:30:36 -07001948 boolean shouldBackupApp(PackageInfo pi) {
1949 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
1950 }
1951
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001952 @Override
Makoto Onuki2e210c42016-03-30 08:30:36 -07001953 public byte[] getBackupPayload(@UserIdInt int userId) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001954 enforceSystem();
1955 if (DEBUG) {
1956 Slog.d(TAG, "Backing up user " + userId);
1957 }
1958 synchronized (mLock) {
1959 final ShortcutUser user = getUserShortcutsLocked(userId);
1960 if (user == null) {
1961 Slog.w(TAG, "Can't backup: user not found: id=" + userId);
1962 return null;
1963 }
1964
1965 user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
1966
1967 // Then save.
1968 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
1969 try {
1970 saveUserInternalLocked(userId, os, /* forBackup */ true);
1971 } catch (XmlPullParserException|IOException e) {
1972 // Shouldn't happen.
1973 Slog.w(TAG, "Backup failed.", e);
1974 return null;
1975 }
1976 return os.toByteArray();
1977 }
1978 }
1979
1980 @Override
Makoto Onuki2e210c42016-03-30 08:30:36 -07001981 public void applyRestore(byte[] payload, @UserIdInt int userId) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001982 enforceSystem();
1983 if (DEBUG) {
1984 Slog.d(TAG, "Restoring user " + userId);
1985 }
1986 final ShortcutUser user;
1987 final ByteArrayInputStream is = new ByteArrayInputStream(payload);
1988 try {
1989 user = loadUserInternal(userId, is, /* fromBackup */ true);
1990 } catch (XmlPullParserException|IOException e) {
1991 Slog.w(TAG, "Restoration failed.", e);
1992 return;
1993 }
1994 synchronized (mLock) {
1995 mUsers.put(userId, user);
Makoto Onuki2e210c42016-03-30 08:30:36 -07001996
1997 // Then purge all the save images.
1998 final File bitmapPath = getUserBitmapFilePath(userId);
1999 final boolean success = FileUtils.deleteContents(bitmapPath);
2000 if (!success) {
2001 Slog.w(TAG, "Failed to delete " + bitmapPath);
2002 }
2003
2004 saveUserLocked(userId);
Makoto Onuki9da23fc2016-03-29 11:14:42 -07002005 }
Makoto Onukicdc78f72016-03-21 15:47:52 -07002006 }
2007
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002008 // === Dump ===
2009
2010 @Override
2011 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2012 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2013 != PackageManager.PERMISSION_GRANTED) {
2014 pw.println("Permission Denial: can't dump UserManager from from pid="
2015 + Binder.getCallingPid()
2016 + ", uid=" + Binder.getCallingUid()
2017 + " without permission "
2018 + android.Manifest.permission.DUMP);
2019 return;
2020 }
2021 dumpInner(pw);
2022 }
2023
2024 @VisibleForTesting
2025 void dumpInner(PrintWriter pw) {
2026 synchronized (mLock) {
2027 final long now = injectCurrentTimeMillis();
2028 pw.print("Now: [");
2029 pw.print(now);
2030 pw.print("] ");
2031 pw.print(formatTime(now));
Makoto Onuki55046222016-03-08 10:49:47 -08002032
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002033 pw.print(" Raw last reset: [");
2034 pw.print(mRawLastResetTime);
2035 pw.print("] ");
2036 pw.print(formatTime(mRawLastResetTime));
2037
2038 final long last = getLastResetTimeLocked();
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002039 pw.print(" Last reset: [");
2040 pw.print(last);
2041 pw.print("] ");
2042 pw.print(formatTime(last));
2043
Makoto Onuki55046222016-03-08 10:49:47 -08002044 final long next = getNextResetTimeLocked();
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002045 pw.print(" Next reset: [");
2046 pw.print(next);
2047 pw.print("] ");
2048 pw.print(formatTime(next));
2049 pw.println();
2050
Makoto Onuki55046222016-03-08 10:49:47 -08002051 pw.print(" Max icon dim: ");
2052 pw.print(mMaxIconDimension);
2053 pw.print(" Icon format: ");
2054 pw.print(mIconPersistFormat);
2055 pw.print(" Icon quality: ");
Makoto Onuki2e210c42016-03-30 08:30:36 -07002056 pw.println(mIconPersistQuality);
Makoto Onuki55046222016-03-08 10:49:47 -08002057 pw.println();
2058
Makoto Onuki2e210c42016-03-30 08:30:36 -07002059 pw.println(" Stats:");
2060 synchronized (mStatLock) {
2061 final String p = " ";
2062 dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()");
2063 dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check");
2064
2065 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
2066 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
2067 dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
2068 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002069
Makoto Onuki3f4b1ca2016-03-11 13:44:32 -08002070 for (int i = 0; i < mUsers.size(); i++) {
2071 pw.println();
2072 mUsers.valueAt(i).dump(this, pw, " ");
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002073 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002074 }
2075 }
2076
Makoto Onuki41066a62016-03-09 16:18:44 -08002077 static String formatTime(long time) {
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002078 Time tobj = new Time();
2079 tobj.set(time);
2080 return tobj.format("%Y-%m-%d %H:%M:%S");
2081 }
2082
Makoto Onuki2e210c42016-03-30 08:30:36 -07002083 private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
2084 pw.print(prefix);
2085 final int count = mCountStats[statId];
2086 final long dur = mDurationStats[statId];
2087 pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
2088 label, count, dur,
2089 (count == 0 ? 0 : ((double) dur) / count)));
2090 }
2091
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002092 // === Shell support ===
2093
2094 @Override
2095 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
2096 String[] args, ResultReceiver resultReceiver) throws RemoteException {
2097
2098 enforceShell();
2099
2100 (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
2101 }
2102
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002103 static class CommandException extends Exception {
2104 public CommandException(String message) {
2105 super(message);
2106 }
2107 }
2108
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002109 /**
2110 * Handle "adb shell cmd".
2111 */
2112 private class MyShellCommand extends ShellCommand {
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002113
2114 private int mUserId = UserHandle.USER_SYSTEM;
2115
2116 private void parseOptions(boolean takeUser)
2117 throws CommandException {
2118 String opt;
2119 while ((opt = getNextOption()) != null) {
2120 switch (opt) {
2121 case "--user":
2122 if (takeUser) {
2123 mUserId = UserHandle.parseUserArg(getNextArgRequired());
2124 break;
2125 }
2126 // fallthrough
2127 default:
2128 throw new CommandException("Unknown option: " + opt);
2129 }
2130 }
2131 }
2132
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002133 @Override
2134 public int onCommand(String cmd) {
2135 if (cmd == null) {
2136 return handleDefaultCommands(cmd);
2137 }
2138 final PrintWriter pw = getOutPrintWriter();
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002139 try {
2140 switch (cmd) {
2141 case "reset-package-throttling":
2142 handleResetPackageThrottling();
2143 break;
2144 case "reset-throttling":
2145 handleResetThrottling();
2146 break;
2147 case "override-config":
2148 handleOverrideConfig();
2149 break;
2150 case "reset-config":
2151 handleResetConfig();
2152 break;
2153 case "clear-default-launcher":
2154 handleClearDefaultLauncher();
2155 break;
2156 case "get-default-launcher":
2157 handleGetDefaultLauncher();
2158 break;
2159 case "refresh-default-launcher":
2160 handleRefreshDefaultLauncher();
2161 break;
Makoto Onukiac214972016-04-04 10:19:45 -07002162 case "unload-user":
2163 handleUnloadUser();
2164 break;
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002165 default:
2166 return handleDefaultCommands(cmd);
2167 }
2168 } catch (CommandException e) {
2169 pw.println("Error: " + e.getMessage());
2170 return 1;
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002171 }
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002172 pw.println("Success");
2173 return 0;
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002174 }
2175
2176 @Override
2177 public void onHelp() {
2178 final PrintWriter pw = getOutPrintWriter();
2179 pw.println("Usage: cmd shortcut COMMAND [options ...]");
2180 pw.println();
2181 pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
2182 pw.println(" Reset throttling for a package");
2183 pw.println();
2184 pw.println("cmd shortcut reset-throttling");
2185 pw.println(" Reset throttling for all packages and users");
2186 pw.println();
Makoto Onuki4362a662016-03-08 18:59:09 -08002187 pw.println("cmd shortcut override-config CONFIG");
2188 pw.println(" Override the configuration for testing (will last until reboot)");
2189 pw.println();
2190 pw.println("cmd shortcut reset-config");
2191 pw.println(" Reset the configuration set with \"update-config\"");
2192 pw.println();
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002193 pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
2194 pw.println(" Clear the cached default launcher");
2195 pw.println();
2196 pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
2197 pw.println(" Show the cached default launcher");
2198 pw.println();
2199 pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
2200 pw.println(" Refresh the cached default launcher");
2201 pw.println();
Makoto Onukiac214972016-04-04 10:19:45 -07002202 pw.println("cmd shortcut unload-user [--user USER_ID]");
2203 pw.println(" Unload a user from the memory");
2204 pw.println(" (This should not affect any observable behavior)");
2205 pw.println();
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002206 }
2207
Makoto Onuki4554d0e2016-03-14 15:51:41 -07002208 private int handleResetThrottling() throws CommandException {
2209 parseOptions(/* takeUser =*/ true);
2210
2211 resetThrottlingInner(mUserId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002212 return 0;
2213 }
2214
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002215 private void handleResetPackageThrottling() throws CommandException {
2216 parseOptions(/* takeUser =*/ true);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002217
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002218 final String packageName = getNextArgRequired();
2219
2220 synchronized (mLock) {
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002221 getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine();
2222 saveUserLocked(mUserId);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002223 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002224 }
Makoto Onuki4362a662016-03-08 18:59:09 -08002225
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002226 private void handleOverrideConfig() throws CommandException {
Makoto Onuki4362a662016-03-08 18:59:09 -08002227 final String config = getNextArgRequired();
2228
2229 synchronized (mLock) {
2230 if (!updateConfigurationLocked(config)) {
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002231 throw new CommandException("override-config failed. See logcat for details.");
Makoto Onuki4362a662016-03-08 18:59:09 -08002232 }
2233 }
Makoto Onuki4362a662016-03-08 18:59:09 -08002234 }
2235
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002236 private void handleResetConfig() {
Makoto Onuki4362a662016-03-08 18:59:09 -08002237 synchronized (mLock) {
2238 loadConfigurationLocked();
2239 }
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002240 }
2241
2242 private void clearLauncher() {
2243 synchronized (mLock) {
2244 getUserShortcutsLocked(mUserId).setLauncherComponent(
2245 ShortcutService.this, null);
2246 }
2247 }
2248
2249 private void showLauncher() {
2250 synchronized (mLock) {
2251 // This ensures to set the cached launcher. Package name doesn't matter.
2252 hasShortcutHostPermissionInner("-", mUserId);
2253
2254 getOutPrintWriter().println("Launcher: "
2255 + getUserShortcutsLocked(mUserId).getLauncherComponent());
2256 }
2257 }
2258
2259 private void handleClearDefaultLauncher() throws CommandException {
2260 parseOptions(/* takeUser =*/ true);
2261
2262 clearLauncher();
2263 }
2264
2265 private void handleGetDefaultLauncher() throws CommandException {
2266 parseOptions(/* takeUser =*/ true);
2267
2268 showLauncher();
2269 }
2270
2271 private void handleRefreshDefaultLauncher() throws CommandException {
2272 parseOptions(/* takeUser =*/ true);
2273
2274 clearLauncher();
2275 showLauncher();
Makoto Onuki4362a662016-03-08 18:59:09 -08002276 }
Makoto Onukiac214972016-04-04 10:19:45 -07002277
2278 private void handleUnloadUser() throws CommandException {
2279 parseOptions(/* takeUser =*/ true);
2280
2281 ShortcutService.this.handleCleanupUser(mUserId);
2282 }
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002283 }
2284
2285 // === Unit test support ===
2286
2287 // Injection point.
Makoto Onuki31459242016-03-22 11:12:18 -07002288 @VisibleForTesting
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002289 long injectCurrentTimeMillis() {
2290 return System.currentTimeMillis();
2291 }
2292
2293 // Injection point.
Makoto Onuki31459242016-03-22 11:12:18 -07002294 @VisibleForTesting
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002295 int injectBinderCallingUid() {
2296 return getCallingUid();
2297 }
2298
Makoto Onuki31459242016-03-22 11:12:18 -07002299 private int getCallingUserId() {
Makoto Onuki4554d0e2016-03-14 15:51:41 -07002300 return UserHandle.getUserId(injectBinderCallingUid());
2301 }
2302
Makoto Onuki4dbe0de2016-03-14 17:31:49 -07002303 // Injection point.
Makoto Onuki31459242016-03-22 11:12:18 -07002304 @VisibleForTesting
Makoto Onuki4dbe0de2016-03-14 17:31:49 -07002305 long injectClearCallingIdentity() {
2306 return Binder.clearCallingIdentity();
2307 }
2308
2309 // Injection point.
Makoto Onuki31459242016-03-22 11:12:18 -07002310 @VisibleForTesting
Makoto Onuki4dbe0de2016-03-14 17:31:49 -07002311 void injectRestoreCallingIdentity(long token) {
2312 Binder.restoreCallingIdentity(token);
2313 }
2314
Makoto Onukide667372016-03-15 14:29:20 -07002315 final void wtf(String message) {
Makoto Onuki2e210c42016-03-30 08:30:36 -07002316 wtf( message, /* exception= */ null);
Makoto Onukide667372016-03-15 14:29:20 -07002317 }
2318
Makoto Onuki2e210c42016-03-30 08:30:36 -07002319 // Injection point.
Makoto Onukide667372016-03-15 14:29:20 -07002320 void wtf(String message, Exception e) {
2321 Slog.wtf(TAG, message, e);
2322 }
2323
Makoto Onuki31459242016-03-22 11:12:18 -07002324 @VisibleForTesting
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002325 File injectSystemDataPath() {
2326 return Environment.getDataSystemDirectory();
2327 }
2328
Makoto Onuki31459242016-03-22 11:12:18 -07002329 @VisibleForTesting
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002330 File injectUserDataPath(@UserIdInt int userId) {
Makoto Onuki55046222016-03-08 10:49:47 -08002331 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
2332 }
2333
Makoto Onuki4362a662016-03-08 18:59:09 -08002334 @VisibleForTesting
Makoto Onuki55046222016-03-08 10:49:47 -08002335 boolean injectIsLowRamDevice() {
2336 return ActivityManager.isLowRamDeviceStatic();
2337 }
2338
Makoto Onuki31459242016-03-22 11:12:18 -07002339 @VisibleForTesting
Makoto Onuki2d5b4652016-03-11 16:09:54 -08002340 PackageManagerInternal injectPackageManagerInternal() {
2341 return mPackageManagerInternal;
2342 }
2343
Makoto Onuki31459242016-03-22 11:12:18 -07002344 @VisibleForTesting
Makoto Onuki55046222016-03-08 10:49:47 -08002345 File getUserBitmapFilePath(@UserIdInt int userId) {
2346 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002347 }
2348
2349 @VisibleForTesting
Makoto Onuki31459242016-03-22 11:12:18 -07002350 SparseArray<ShortcutUser> getShortcutsForTest() {
Makoto Onuki3f4b1ca2016-03-11 13:44:32 -08002351 return mUsers;
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002352 }
2353
2354 @VisibleForTesting
Makoto Onuki4362a662016-03-08 18:59:09 -08002355 int getMaxDynamicShortcutsForTest() {
2356 return mMaxDynamicShortcuts;
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002357 }
2358
2359 @VisibleForTesting
Makoto Onuki4362a662016-03-08 18:59:09 -08002360 int getMaxDailyUpdatesForTest() {
2361 return mMaxDailyUpdates;
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002362 }
2363
2364 @VisibleForTesting
Makoto Onuki4362a662016-03-08 18:59:09 -08002365 long getResetIntervalForTest() {
2366 return mResetInterval;
Makoto Onuki55046222016-03-08 10:49:47 -08002367 }
2368
2369 @VisibleForTesting
Makoto Onuki4362a662016-03-08 18:59:09 -08002370 int getMaxIconDimensionForTest() {
2371 return mMaxIconDimension;
2372 }
2373
2374 @VisibleForTesting
2375 CompressFormat getIconPersistFormatForTest() {
2376 return mIconPersistFormat;
2377 }
2378
2379 @VisibleForTesting
2380 int getIconPersistQualityForTest() {
2381 return mIconPersistQuality;
Makoto Onuki6f7362d92016-03-04 13:39:41 -08002382 }
Makoto Onuki41066a62016-03-09 16:18:44 -08002383
2384 @VisibleForTesting
2385 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
2386 synchronized (mLock) {
Makoto Onuki31459242016-03-22 11:12:18 -07002387 final ShortcutUser user = mUsers.get(userId);
Makoto Onukicdc78f72016-03-21 15:47:52 -07002388 if (user == null) return null;
2389
Makoto Onuki2e210c42016-03-30 08:30:36 -07002390 final ShortcutPackage pkg = user.getAllPackages().get(packageName);
Makoto Onukicdc78f72016-03-21 15:47:52 -07002391 if (pkg == null) return null;
2392
2393 return pkg.findShortcutById(shortcutId);
Makoto Onuki41066a62016-03-09 16:18:44 -08002394 }
2395 }
2396}