import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.ShortcutService.ShortcutOperation;
+import com.android.server.pm.ShortcutService.Stats;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
* locale changes.
*/
public int getApiCallCount() {
- mShortcutUser.resetThrottlingIfNeeded();
-
final ShortcutService s = mShortcutUser.mService;
// Reset the counter if:
}
/**
- * Called when the package is updated or added.
+ * @return false if any of the target activities are no longer enabled.
+ */
+ private boolean areAllActivitiesStillEnabled() {
+ if (mShortcuts.size() == 0) {
+ return true;
+ }
+ final ShortcutService s = mShortcutUser.mService;
+
+ // Normally the number of target activities is 1 or so, so no need to use a complex
+ // structure like a set.
+ final ArrayList<ComponentName> checked = new ArrayList<>(4);
+
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ final ComponentName activity = si.getActivity();
+
+ if (checked.contains(activity)) {
+ continue; // Already checked.
+ }
+ checked.add(activity);
+
+ if (!s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Called when the package may be added or updated, or its activities may be disabled, and
+ * if so, rescan the package and do the necessary stuff.
*
* Add case:
* - Publish manifest shortcuts.
* Update case:
* - Re-publish manifest shortcuts.
* - If there are shortcuts with resources (icons or strings), update their timestamps.
+ * - Disable shortcuts whose target activities are disabled.
*
* @return TRUE if any shortcuts have been changed.
*/
- public boolean handlePackageAddedOrUpdated(boolean isNewApp, boolean forceRescan) {
- final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
- getPackageName(), getPackageUserId());
- if (pi == null) {
- return false; // Shouldn't happen.
- }
+ public boolean rescanPackageIfNeeded(boolean isNewApp, boolean forceRescan) {
+ final ShortcutService s = mShortcutUser.mService;
+ final long start = s.injectElapsedRealtime();
- if (!isNewApp && !forceRescan) {
- // Make sure the version code or last update time has changed.
- // Otherwise, nothing to do.
- if (getPackageInfo().getVersionCode() >= pi.versionCode
- && getPackageInfo().getLastUpdateTime() >= pi.lastUpdateTime) {
- return false;
+ final PackageInfo pi;
+ try {
+ pi = mShortcutUser.mService.getPackageInfo(
+ getPackageName(), getPackageUserId());
+ if (pi == null) {
+ return false; // Shouldn't happen.
+ }
+
+ if (!isNewApp && !forceRescan) {
+ // Return if the package hasn't changed, ie:
+ // - version code hasn't change
+ // - lastUpdateTime hasn't change
+ // - all target activities are still enabled.
+ if ((getPackageInfo().getVersionCode() >= pi.versionCode)
+ && (getPackageInfo().getLastUpdateTime() >= pi.lastUpdateTime)
+ && areAllActivitiesStillEnabled()) {
+ return false;
+ }
}
+ } finally {
+ s.logDurationStat(Stats.PACKAGE_UPDATE_CHECK, start);
}
// Now prepare to publish manifest shortcuts.
getPackageInfo().updateVersionInfo(pi);
- final ShortcutService s = mShortcutUser.mService;
-
boolean changed = false;
// For existing shortcuts, update timestamps if they have any resources.
}
}
if (changed) {
- s.scheduleSaveUser(getPackageUserId());
+ s.packageShortcutsChanged(getPackageName(), getPackageUserId());
}
}
import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.drawable.Icon;
+import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.LocaleList;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* TODO:
- * - Deal with the async nature of PACKAGE_ADD. Basically when a publisher does anything after
- * it's upgraded, the manager should make sure the upgrade process has been executed.
- *
* - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
* -> But TypedValue.applyDimension() doesn't differentiate x and y..?
*
private static final String TAG_ROOT = "root";
private static final String TAG_LAST_RESET_TIME = "last_reset_time";
- private static final String TAG_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale_seq_no";
private static final String ATTR_VALUE = "value";
@GuardedBy("mLock")
private List<Integer> mDirtyUserIds = new ArrayList<>();
- /**
- * A counter that increments every time the system locale changes. We keep track of it to
- * reset
- * throttling counters on the first call from each package after the last locale change.
- *
- * We need this mechanism because we can't do much in the locale change callback, which is
- * {@link ShortcutServiceInternal#onSystemLocaleChangedNoLock()}.
- */
- private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
-
private final AtomicBoolean mBootCompleted = new AtomicBoolean();
private static final int PACKAGE_MATCH_FLAGS =
int GET_LAUNCHER_ACTIVITY = 11;
int CHECK_LAUNCHER_ACTIVITY = 12;
int IS_ACTIVITY_ENABLED = 13;
+ int PACKAGE_UPDATE_CHECK = 14;
- int COUNT = IS_ACTIVITY_ENABLED + 1;
+ int COUNT = PACKAGE_UPDATE_CHECK + 1;
}
final Object mStatLock = new Object();
return; // Don't do anything further. For unit tests only.
}
- mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
+ // Register receivers.
+
+ // We need to set a priority, so let's just not use PackageMonitor for now.
+ // TODO Refactor PackageMonitor to support priorities.
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ packageFilter.addDataScheme("package");
+ packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL,
+ packageFilter, null, mHandler);
+
+ final IntentFilter localeFilter = new IntentFilter();
+ localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ localeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL,
+ localeFilter, null, mHandler);
injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
| ActivityManager.UID_OBSERVER_GONE);
}
}
- public long getLocaleChangeSequenceNumber() {
- return mLocaleChangeSequenceNumber.get();
+ public String injectGetLocaleTagsForUser(@UserIdInt int userId) {
+ // TODO This should get the per-user locale. b/30123329 b/30119489
+ return LocaleList.getDefault().toLanguageTags();
}
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
Slog.d(TAG, "handleUnlockUser: user=" + userId);
}
synchronized (mLock) {
- // Preload
- getUserShortcutsLocked(userId);
+ // Preload the user's shortcuts.
+ // Also see if the locale has changed.
+ // Note as of nyc, the locale is per-user, so the locale shouldn't change
+ // when the user is locked. However due to b/30119489 it still happens.
+ getUserShortcutsLocked(userId).detectLocaleChange();
checkPackageChanges(userId);
}
// Body.
writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
- writeTagValue(out, TAG_LOCALE_CHANGE_SEQUENCE_NUMBER,
- mLocaleChangeSequenceNumber.get());
// Epilogue.
out.endTag(null, TAG_ROOT);
case TAG_LAST_RESET_TIME:
mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
break;
- case TAG_LOCALE_CHANGE_SEQUENCE_NUMBER:
- mLocaleChangeSequenceNumber.set(parseLongAttribute(parser, ATTR_VALUE));
- break;
default:
Slog.e(TAG, "Invalid tag: " + tag);
break;
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
verifyCaller(packageName, userId);
synchronized (mLock) {
- getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
+ ps.deleteAllDynamicShortcuts();
}
packageShortcutsChanged(packageName, userId);
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
- getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags);
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
+ ps.findAll(ret, query, cloneFlags);
return new ParceledListSlice<>(ret);
}
verifyCaller(packageName, userId);
synchronized (mLock) {
- return mMaxUpdatesPerInterval
- - getPackageShortcutsLocked(packageName, userId).getApiCallCount();
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
+ return mMaxUpdatesPerInterval - ps.getApiCallCount();
}
}
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.getUser().onCalledByPublisher(packageName);
+
if (ps.findShortcutById(shortcutId) == null) {
Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
packageName, shortcutId));
if (appStillExists && (packageUserId == owningUserId)) {
// This will do the notification and save when needed, so do it after the above
// notifyListeners.
- user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
+ user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
}
if (!wasUserLoaded) {
@NonNull String callingPackage) {
return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
}
+ }
- /**
- * Called by AM when the system locale changes *within the AM lock. ABSOLUTELY do not take
- * any locks in this method.
- */
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
- public void onSystemLocaleChangedNoLock() {
- // DO NOT HOLD ANY LOCKS HERE.
-
- // We want to reset throttling for all packages for all users. But we can't just do so
- // here because:
- // - We can't load/save users that are locked.
- // - Even for loaded users, resetting the counters would require us to hold mLock.
- //
- // So we use a "pull" model instead. In here, we just increment the "locale change
- // sequence number". Each ShortcutUser has the "last known locale change sequence".
- //
- // This allows ShortcutUser's to detect the system locale change, so they can reset
- // counters.
-
- // Ignore all callback during system boot.
- if (mBootCompleted.get()) {
- mLocaleChangeSequenceNumber.incrementAndGet();
- if (DEBUG) {
- Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get());
- }
- injectPostToHandler(() -> handleLocaleChanged());
+ public void onReceive(Context context, Intent intent) {
+ if (!mBootCompleted.get()) {
+ return; // Boot not completed, ignore the broadcast.
+ }
+ if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+ handleLocaleChanged();
}
}
- }
+ };
void handleLocaleChanged() {
if (DEBUG) {
final long token = injectClearCallingIdentity();
try {
- forEachLoadedUserLocked(u -> u.forAllPackages(p -> p.resolveResourceStrings()));
+ forEachLoadedUserLocked(user -> user.detectLocaleChange());
} finally {
injectRestoreCallingIdentity(token);
}
* Package event callbacks.
*/
@VisibleForTesting
- final PackageMonitor mPackageMonitor = new PackageMonitor() {
-
- private boolean isUserUnlocked() {
- return mUserManager.isUserUnlocked(getChangingUserId());
- }
-
+ final BroadcastReceiver mPackageMonitor = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- // clearCallingIdentity is not needed normally, but need to do it for the unit test.
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId == UserHandle.USER_NULL) {
+ Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
+ return;
+ }
+
+ final String action = intent.getAction();
+
+ // This is normally called on Handler, so clearCallingIdentity() isn't needed,
+ // but we still check it in unit tests.
final long token = injectClearCallingIdentity();
try {
- super.onReceive(context, intent);
- } finally {
- injectRestoreCallingIdentity(token);
- }
- }
- @Override
- public void onPackageAdded(String packageName, int uid) {
- if (!isUserUnlocked()) return;
- handlePackageAdded(packageName, getChangingUserId());
- }
+ if (!mUserManager.isUserUnlocked(userId)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring package broadcast " + action
+ + " for locked/stopped user " + userId);
+ }
+ return;
+ }
- @Override
- public void onPackageUpdateFinished(String packageName, int uid) {
- if (!isUserUnlocked()) return;
- handlePackageUpdateFinished(packageName, getChangingUserId());
- }
+ final Uri intentUri = intent.getData();
+ final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart()
+ : null;
+ if (packageName == null) {
+ Slog.w(TAG, "Intent broadcast does not contain package name: " + intent);
+ return;
+ }
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- if (!isUserUnlocked()) return;
- handlePackageRemoved(packageName, getChangingUserId());
- }
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
- @Override
- public void onPackageDataCleared(String packageName, int uid) {
- if (!isUserUnlocked()) return;
- handlePackageDataCleared(packageName, getChangingUserId());
- }
+ switch (action) {
+ case Intent.ACTION_PACKAGE_ADDED:
+ if (replacing) {
+ handlePackageUpdateFinished(packageName, userId);
+ } else {
+ handlePackageAdded(packageName, userId);
+ }
+ break;
+ case Intent.ACTION_PACKAGE_REMOVED:
+ if (!replacing) {
+ handlePackageRemoved(packageName, userId);
+ }
+ break;
+ case Intent.ACTION_PACKAGE_CHANGED:
+ handlePackageChanged(packageName, userId);
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- if (!isUserUnlocked()) return false;
- handlePackageChanged(packageName, getChangingUserId());
- return false; // We don't need to receive onSomePackagesChanged(), so just false.
+ break;
+ case Intent.ACTION_PACKAGE_DATA_CLEARED:
+ handlePackageDataCleared(packageName, userId);
+ break;
+ }
+ } finally {
+ injectRestoreCallingIdentity(token);
+ }
}
};
// Then for each installed app, publish manifest shortcuts when needed.
forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
- user.handlePackageAddedOrUpdated(ai.packageName, /* forceRescan=*/ false);
+ user.rescanPackageIfNeeded(ai.packageName, /* forceRescan=*/ false);
});
// Write the time just before the scan, because there may be apps that have just
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
- user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
+ user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ false);
}
verifyStates();
}
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
if (isPackageInstalled(packageName, userId)) {
- user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
+ user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ false);
}
}
verifyStates();
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(packageUserId);
- user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
+ user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
}
verifyStates();
pw.print("] ");
pw.print(formatTime(next));
- pw.print(" Locale change seq#: ");
- pw.print(mLocaleChangeSequenceNumber.get());
- pw.println();
-
pw.print(" Config:");
pw.print(" Max icon dim: ");
pw.println(mMaxIconDimension);
dumpStatLS(pw, p, Stats.GET_LAUNCHER_ACTIVITY, "getLauncherActivity");
dumpStatLS(pw, p, Stats.CHECK_LAUNCHER_ACTIVITY, "checkLauncherActivity");
dumpStatLS(pw, p, Stats.IS_ACTIVITY_ENABLED, "isActivityEnabled");
+ dumpStatLS(pw, p, Stats.PACKAGE_UPDATE_CHECK, "packageUpdateCheck");
}
pw.println();
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
+import android.content.pm.ShortcutManager;
+import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.Slog;
private static final String TAG_LAUNCHER = "launcher";
private static final String ATTR_VALUE = "value";
- private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no";
+ private static final String ATTR_KNOWN_LOCALES = "locales";
private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time";
static final class PackageWithUser {
/** Default launcher that can access the launcher apps APIs. */
private ComponentName mDefaultLauncherComponent;
- private long mKnownLocaleChangeSequenceNumber;
+ private String mKnownLocales;
private long mLastAppScanTime;
}
/**
- * Reset all throttling counters for all packages, if there has been a system locale change.
+ * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the
+ * information on the package is up-to-date.
+ *
+ * We use broadcasts to handle locale changes and package changes, but because broadcasts
+ * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event
+ * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast.
+ *
+ * So we call this method at all entry points from publishers to make sure we update all
+ * relevant information.
+ *
+ * Similar inconsistencies can happen when the launcher fetches shortcut information, but
+ * that's a less of an issue because for the launcher we report shortcut changes with
+ * callbacks.
*/
- public void resetThrottlingIfNeeded() {
- final long currentNo = mService.getLocaleChangeSequenceNumber();
- if (mKnownLocaleChangeSequenceNumber < currentNo) {
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, "LocaleChange detected for user " + mUserId);
- }
-
- mKnownLocaleChangeSequenceNumber = currentNo;
-
- forAllPackages(p -> p.resetRateLimiting());
+ public void onCalledByPublisher(@NonNull String packageName) {
+ detectLocaleChange();
+ rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
+ }
+ private String getKnownLocales() {
+ if (TextUtils.isEmpty(mKnownLocales)) {
+ mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId);
mService.scheduleSaveUser(mUserId);
}
+ return mKnownLocales;
+ }
+
+ /**
+ * Check to see if the system locale has changed, and if so, reset throttling
+ * and update resource strings.
+ */
+ public void detectLocaleChange() {
+ final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
+ if (getKnownLocales().equals(currentLocales)) {
+ return;
+ }
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales
+ + " for user " + mUserId);
+ }
+ mKnownLocales = currentLocales;
+
+ forAllPackages(pkg -> {
+ pkg.resetRateLimiting();
+ pkg.resolveResourceStrings();
+ });
+
+ mService.scheduleSaveUser(mUserId);
}
- public void handlePackageAddedOrUpdated(@NonNull String packageName, boolean forceRescan) {
+ public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
final boolean isNewApp = !mPackages.containsKey(packageName);
final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
- if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) {
+ if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
if (isNewApp) {
mPackages.remove(packageName);
}
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER,
- mKnownLocaleChangeSequenceNumber);
+ ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
mLastAppScanTime);
boolean fromBackup) throws IOException, XmlPullParserException {
final ShortcutUser ret = new ShortcutUser(s, userId);
- ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
- ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
+ ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
+ ATTR_KNOWN_LOCALES);
// If lastAppScanTime is in the future, that means the clock went backwards.
// Just scan all apps again.
pw.print(prefix);
pw.print("User: ");
pw.print(mUserId);
- pw.print(" Known locale seq#: ");
- pw.print(mKnownLocaleChangeSequenceNumber);
+ pw.print(" Known locales: ");
+ pw.print(mKnownLocales);
pw.print(" Last app scan: [");
pw.print(mLastAppScanTime);
pw.print("] ");