/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.pm;

import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundlesEqual;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.parceled;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;

import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.Manifest.permission;
import android.app.ActivityManager;
import android.app.Person;
import android.content.ComponentName;
import android.content.Intent;
import android.content.LocusId;
import android.content.pm.Capability;
import android.content.pm.CapabilityParams;
import android.content.pm.ShortcutInfo;
import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.test.MoreAsserts;

import androidx.test.filters.SmallTest;

import com.android.frameworks.servicestests.R;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Locale;

/**
 * Tests for ShortcutService and ShortcutManager.
 *
 m FrameworksServicesTests &&
 adb install \
 -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
 adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest2 \
 -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner
 */
@Presubmit
@SmallTest
public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
    // ShortcutInfo tests

    public void testShortcutInfoMissingMandatoryFields() {
        // Disable throttling.
        mService.updateConfigurationLocked(
                ShortcutService.ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
                + ShortcutService.ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
        );

        assertExpectException(
                IllegalArgumentException.class,
                "ID must be provided",
                () -> new ShortcutInfo.Builder(getTestContext()).build());

        assertExpectException(
                RuntimeException.class,
                "id cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), null));

        assertExpectException(
                RuntimeException.class,
                "id cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), ""));

        assertExpectException(
                RuntimeException.class,
                "intents cannot contain null",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(null));

        assertExpectException(
                RuntimeException.class,
                "action must be set",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));

        assertExpectException(
                RuntimeException.class,
                "action must be set",
                () -> new ShortcutInfo.Builder(getTestContext(), "id")
                        .setIntents(new Intent[]{new Intent("action"), new Intent()}));

        assertExpectException(
                RuntimeException.class,
                "activity cannot be null",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setActivity(null));

        assertExpectException(
                RuntimeException.class,
                "shortLabel cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(null));

        assertExpectException(
                RuntimeException.class,
                "shortLabel cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(""));

        assertExpectException(
                RuntimeException.class,
                "longLabel cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(null));

        assertExpectException(
                RuntimeException.class,
                "longLabel cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(""));

        assertExpectException(
                RuntimeException.class,
                "disabledMessage cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(null));

        assertExpectException(
                RuntimeException.class,
                "disabledMessage cannot be empty",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(""));

        assertExpectException(NullPointerException.class, "action must be set",
                () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));

        assertExpectException(
                IllegalArgumentException.class, "Short label must be provided", () -> {
            ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                    .setActivity(new ComponentName(getClientContext().getPackageName(), "s"))
                    .build();
            assertTrue(getManager().setDynamicShortcuts(list(si)));
        });

        // same for add.
        assertExpectException(
                IllegalArgumentException.class, "Short label must be provided", () -> {
            ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                    .setActivity(new ComponentName(getClientContext().getPackageName(), "s"))
                    .build();
            assertTrue(getManager().addDynamicShortcuts(list(si)));
        });

        assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
            ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                    .setActivity(new ComponentName(getClientContext().getPackageName(), "s"))
                    .setShortLabel("x")
                    .build();
            assertTrue(getManager().setDynamicShortcuts(list(si)));
        });

        // same for add.
        assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
            ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                    .setActivity(new ComponentName(getClientContext().getPackageName(), "s"))
                    .setShortLabel("x")
                    .build();
            assertTrue(getManager().addDynamicShortcuts(list(si)));
        });

        assertExpectException(
                IllegalStateException.class, "does not belong to package", () -> {
            ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                    .setActivity(new ComponentName("xxx", "s"))
                    .build();
            assertTrue(getManager().setDynamicShortcuts(list(si)));
        });

        // same for add.
        assertExpectException(
                IllegalStateException.class, "does not belong to package", () -> {
            ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                    .setActivity(new ComponentName("xxx", "s"))
                    .build();
            assertTrue(getManager().addDynamicShortcuts(list(si)));
        });

        // Now all activities are not main.
        mMainActivityChecker = (component, userId) -> false;

        assertExpectException(
                IllegalStateException.class, "is not main", () -> {
                    ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                            .setActivity(new ComponentName(getClientContext(), "s"))
                            .build();
                    assertTrue(getManager().setDynamicShortcuts(list(si)));
                });
        // For add
        assertExpectException(
                IllegalStateException.class, "is not main", () -> {
                    ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                            .setActivity(new ComponentName(getClientContext(), "s"))
                            .build();
                    assertTrue(getManager().addDynamicShortcuts(list(si)));
                });
        // For update
        assertExpectException(
                IllegalStateException.class, "is not main", () -> {
                    ShortcutInfo si = new ShortcutInfo.Builder(getClientContext(), "id")
                            .setActivity(new ComponentName(getClientContext(), "s"))
                            .build();
                    assertTrue(getManager().updateShortcuts(list(si)));
                });
    }

    public void testShortcutIdTruncated() {
        ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(),
                "s".repeat(Short.MAX_VALUE)).build();

        assertTrue(
                "id must be truncated to MAX_ID_LENGTH",
                si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH);
    }

    public void testShortcutInfoParcel() {
        setCaller(CALLING_PACKAGE_1, USER_10);
        ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setTitle("title")
                .setIntent(makeIntent("action", ShortcutActivity.class))
                .build());
        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals(USER_10, si.getUserId());
        assertEquals(HANDLE_USER_10, si.getUserHandle());
        assertEquals("id", si.getId());
        assertEquals("title", si.getTitle());
        assertEquals("action", si.getIntent().getAction());

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);

        si = new ShortcutInfo.Builder(getTestContext())
                .setId("id")
                .setActivity(new ComponentName("a", "b"))
                .setIcon(Icon.createWithResource(mClientContext, 123))
                .setTitle("title")
                .setText("text")
                .setDisabledMessage("dismes")
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setRank(123)
                .setPerson(makePerson("person", "personKey", "personUri"))
                .setLongLived(true)
                .setExtras(pb)
                .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
                .addCapabilityBinding(
                        new Capability.Builder("action.intent.START_EXERCISE").build(),
                        new CapabilityParams.Builder("exercise.type", "running")
                                .addAlias("jogging")
                                .build())
                .addCapabilityBinding(
                        new Capability.Builder("action.intent.START_EXERCISE").build(),
                        new CapabilityParams.Builder("exercise.duration", "10m")
                                .build())
                .build();
        si.addFlags(ShortcutInfo.FLAG_PINNED);
        si.setBitmapPath("abc");
        si.setIconResourceId(456);
        si.setIconUri("test_uri");

        si = parceled(si);

        assertEquals(getTestContext().getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(123, si.getIcon().getResId());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(123, si.getRank());
        assertEquals("person", si.getPersons()[0].getName());
        assertEquals("personKey", si.getPersons()[0].getKey());
        assertEquals("personUri", si.getPersons()[0].getUri());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
        assertEquals("abc", si.getBitmapPath());
        assertEquals(456, si.getIconResourceId());
        assertEquals("test_uri", si.getIconUri());

        assertEquals(0, si.getTitleResId());
        assertEquals(null, si.getTitleResName());
        assertEquals(0, si.getTextResId());
        assertEquals(null, si.getTextResName());
        assertEquals(0, si.getDisabledMessageResourceId());
        assertEquals(null, si.getDisabledMessageResName());
        assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen",
                si.getStartingThemeResName());
        assertEquals(list(new Capability.Builder("action.intent.START_EXERCISE").build()),
                si.getCapabilities());
        assertEquals(list(
                        new CapabilityParams.Builder("exercise.type", "running")
                                .addAlias("jogging").build(),
                        new CapabilityParams.Builder("exercise.duration", "10m").build()),
                si.getCapabilityParams(
                        new Capability.Builder("action.intent.START_EXERCISE").build()));
    }

    public void testShortcutInfoParcel_resId() {
        setCaller(CALLING_PACKAGE_1, USER_10);
        ShortcutInfo si;

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);

        si = new ShortcutInfo.Builder(getTestContext())
                .setId("id")
                .setActivity(new ComponentName("a", "b"))
                .setIcon(Icon.createWithResource(mClientContext, 123))
                .setTitleResId(10)
                .setTextResId(11)
                .setDisabledMessageResId(12)
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setRank(123)
                .setExtras(pb)
                .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
                .build();
        si.addFlags(ShortcutInfo.FLAG_PINNED);
        si.setBitmapPath("abc");
        si.setIconResourceId(456);
        si.setIconUri("test_uri");

        lookupAndFillInResourceNames(si);

        si = parceled(si);

        assertEquals(getTestContext().getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(123, si.getIcon().getResId());
        assertEquals(10, si.getTitleResId());
        assertEquals("r10", si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals("r11", si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals("r12", si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(123, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
        assertEquals("abc", si.getBitmapPath());
        assertEquals(456, si.getIconResourceId());
        assertEquals("string/r456", si.getIconResName());
        assertEquals("test_uri", si.getIconUri());
        assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen",
                si.getStartingThemeResName());
    }

    public void testShortcutInfoClone() {
        setCaller(CALLING_PACKAGE_1, USER_11);

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName("a", "b"))
                .setIcon(Icon.createWithResource(mClientContext, 123))
                .setTitle("title")
                .setText("text")
                .setDisabledMessage("dismes")
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setPerson(makePerson("person", "personKey", "personUri"))
                .setLongLived(true)
                .setExtras(pb)
                .build();
        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
        sorig.setBitmapPath("abc");
        sorig.setIconResourceId(456);
        sorig.setIconUri("test_uri");

        lookupAndFillInResourceNames(sorig);

        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);

        assertEquals(USER_11, si.getUserId());
        assertEquals(HANDLE_USER_11, si.getUserHandle());
        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(123, si.getIcon().getResId());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(123, si.getRank());
        assertEquals("person", si.getPersons()[0].getName());
        assertEquals("personKey", si.getPersons()[0].getKey());
        assertEquals("personUri", si.getPersons()[0].getUri());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
        assertEquals("abc", si.getBitmapPath());
        assertEquals(456, si.getIconResourceId());
        assertEquals("string/r456", si.getIconResName());
        assertEquals("test_uri", si.getIconUri());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);

        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(null, si.getIcon());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(123, si.getRank());
        assertEquals("person", si.getPersons()[0].getName());
        assertEquals("personKey", si.getPersons()[0].getKey());
        assertEquals("personUri", si.getPersons()[0].getUri());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        assertEquals(456, si.getIconResourceId());
        assertEquals(null, si.getIconResName());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);

        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(null, si.getIcon());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals(null, si.getIntent());
        assertEquals(123, si.getRank());
        assertEquals(null, si.getPersons());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        assertEquals(456, si.getIconResourceId());
        assertEquals(null, si.getIconResName());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);

        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(null, si.getIcon());
        assertEquals(null, si.getTitle());
        assertEquals(null, si.getText());
        assertEquals(null, si.getDisabledMessage());
        assertEquals(null, si.getCategories());
        assertEquals(null, si.getIntent());
        assertEquals(0, si.getRank());
        assertEquals(null, si.getPersons());
        assertEquals(null, si.getExtras());

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY
                | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        assertEquals(456, si.getIconResourceId());
        assertEquals(null, si.getIconResName());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);

        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(null, si.getIcon());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(123, si.getRank());
        assertEquals("person", si.getPersons()[0].getName());
        assertEquals("personKey", si.getPersons()[0].getKey());
        assertEquals("personUri", si.getPersons()[0].getUri());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        assertEquals(456, si.getIconResourceId());
        assertEquals(null, si.getIconResName());
    }

    public void testShortcutInfoClone_resId() {
        setCaller(CALLING_PACKAGE_1, USER_11);

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName("a", "b"))
                .setIcon(Icon.createWithResource(mClientContext, 123))
                .setTitleResId(10)
                .setTextResId(11)
                .setDisabledMessageResId(12)
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .build();
        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
        sorig.setBitmapPath("abc");
        sorig.setIconResourceId(456);
        sorig.setIconUri("test_uri");

        lookupAndFillInResourceNames(sorig);

        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);

        assertEquals(USER_11, si.getUserId());
        assertEquals(HANDLE_USER_11, si.getUserHandle());
        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(123, si.getIcon().getResId());
        assertEquals(10, si.getTitleResId());
        assertEquals("r10", si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals("r11", si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals("r12", si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(123, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
        assertEquals("abc", si.getBitmapPath());
        assertEquals(456, si.getIconResourceId());
        assertEquals("string/r456", si.getIconResName());
        assertEquals("test_uri", si.getIconUri());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);

        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(null, si.getIcon());
        assertEquals(10, si.getTitleResId());
        assertEquals(null, si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals(null, si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals(null, si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(123, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        assertEquals(456, si.getIconResourceId());
        assertEquals(null, si.getIconResName());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);

        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(null, si.getIcon());
        assertEquals(10, si.getTitleResId());
        assertEquals(null, si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals(null, si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals(null, si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals(null, si.getIntent());
        assertEquals(123, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        assertEquals(456, si.getIconResourceId());
        assertEquals(null, si.getIconResName());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);

        assertEquals(mClientContext.getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(new ComponentName("a", "b"), si.getActivity());
        assertEquals(null, si.getIcon());
        assertEquals(0, si.getTitleResId());
        assertEquals(null, si.getTitleResName());
        assertEquals(0, si.getTextResId());
        assertEquals(null, si.getTextResName());
        assertEquals(0, si.getDisabledMessageResourceId());
        assertEquals(null, si.getDisabledMessageResName());
        assertEquals(null, si.getCategories());
        assertEquals(null, si.getIntent());
        assertEquals(0, si.getRank());
        assertEquals(null, si.getExtras());

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        assertEquals(456, si.getIconResourceId());
        assertEquals(null, si.getIconResName());
    }

    public void testShortcutInfoClone_minimum() {
        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
                .setId("id")
                .setTitle("title")
                .setIntent(makeIntent("action", ShortcutActivity.class))
                .build();
        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);

        assertEquals(getTestContext().getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals("title", si.getTitle());
        assertEquals("action", si.getIntent().getAction());
        assertEquals(null, si.getCategories());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);

        assertEquals(getTestContext().getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals("title", si.getTitle());
        assertEquals("action", si.getIntent().getAction());
        assertEquals(null, si.getCategories());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);

        assertEquals(getTestContext().getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals("title", si.getTitle());
        assertEquals(null, si.getIntent());
        assertEquals(null, si.getCategories());

        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);

        assertEquals(getTestContext().getPackageName(), si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(null, si.getTitle());
        assertEquals(null, si.getIntent());
        assertEquals(null, si.getCategories());
    }

    public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException {
        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
                .setId("id")
                .setActivity(new ComponentName("a", "b"))
                .setIcon(Icon.createWithResource(mClientContext, 123))
                .setTitle("title")
                .setText("text")
                .setDisabledMessage("dismes")
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .build();
        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
        sorig.setBitmapPath("abc");
        sorig.setIconResourceId(456);
        sorig.setIconUri("test_uri");

        lookupAndFillInResourceNames(sorig);

        ShortcutInfo si;

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setActivity(new ComponentName("x", "y")).build());
        assertEquals("text", si.getText());
        assertEquals(123, si.getRank());
        assertEquals(new ComponentName("x", "y"), si.getActivity());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setIcon(Icon.createWithResource(mClientContext, 456)).build());
        assertEquals("text", si.getText());
        assertEquals(456, si.getIcon().getResId());
        assertEquals(0, si.getIconResourceId());
        assertEquals(null, si.getIconResName());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setTitle("xyz").build());
        assertEquals("text", si.getText());
        assertEquals("xyz", si.getTitle());
        assertEquals(0, si.getTitleResId());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setTitleResId(123).build());
        assertEquals("text", si.getText());
        assertEquals(null, si.getTitle());
        assertEquals(123, si.getTitleResId());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setText("xxx").build());
        assertEquals(123, si.getRank());
        assertEquals("xxx", si.getText());
        assertEquals(0, si.getTextResId());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setTextResId(1111).build());
        assertEquals(123, si.getRank());
        assertEquals(null, si.getText());
        assertEquals(1111, si.getTextResId());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setDisabledMessage("xxx").build());
        assertEquals(123, si.getRank());
        assertEquals("xxx", si.getDisabledMessage());
        assertEquals(0, si.getDisabledMessageResourceId());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setDisabledMessageResId(11111).build());
        assertEquals(123, si.getRank());
        assertEquals(null, si.getDisabledMessage());
        assertEquals(11111, si.getDisabledMessageResourceId());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setCategories(set()).build());
        assertEquals("text", si.getText());
        assertEquals(set(), si.getCategories());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setCategories(set("x")).build());
        assertEquals("text", si.getText());
        assertEquals(set("x"), si.getCategories());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setPerson(makePerson("person", "", "")).build());
        assertEquals("text", si.getText());
        assertEquals("person", si.getPersons()[0].getName());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
        assertEquals("text", si.getText());
        assertEquals("action2", si.getIntent().getAction());
        assertEquals(null, si.getIntent().getStringExtra("key"));

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
        assertEquals("text", si.getText());
        assertEquals("action3", si.getIntent().getAction());
        assertEquals("x", si.getIntent().getStringExtra("key"));

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setRank(999).build());
        assertEquals("text", si.getText());
        assertEquals(999, si.getRank());


        PersistableBundle pb2 = new PersistableBundle();
        pb2.putInt("x", 99);

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setExtras(pb2).build());
        assertEquals("text", si.getText());
        assertEquals(99, si.getExtras().getInt("x"));
    }

    public void testShortcutInfoCopyNonNullFieldsFrom_resId() throws InterruptedException {
        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
                .setId("id")
                .setActivity(new ComponentName("a", "b"))
                .setIcon(Icon.createWithResource(mClientContext, 123))
                .setTitleResId(10)
                .setTextResId(11)
                .setDisabledMessageResId(12)
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .build();
        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
        sorig.setBitmapPath("abc");
        sorig.setIconResourceId(456);
        sorig.setIconUri("test_uri");

        ShortcutInfo si;

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setActivity(new ComponentName("x", "y")).build());
        assertEquals(11, si.getTextResId());
        assertEquals(new ComponentName("x", "y"), si.getActivity());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setIcon(Icon.createWithResource(mClientContext, 456)).build());
        assertEquals(11, si.getTextResId());
        assertEquals(456, si.getIcon().getResId());
        assertEquals(0, si.getIconResourceId());
        assertEquals(null, si.getIconResName());
        assertEquals(null, si.getBitmapPath());
        assertNull(si.getIconUri());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setTitle("xyz").build());
        assertEquals(11, si.getTextResId());
        assertEquals("xyz", si.getTitle());
        assertEquals(0, si.getTitleResId());
        assertEquals(null, si.getTitleResName());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setTitleResId(123).build());
        assertEquals(11, si.getTextResId());
        assertEquals(null, si.getTitle());
        assertEquals(123, si.getTitleResId());
        assertEquals(null, si.getTitleResName());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setText("xxx").build());
        assertEquals(123, si.getRank());
        assertEquals("xxx", si.getText());
        assertEquals(0, si.getTextResId());
        assertEquals(null, si.getTextResName());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setTextResId(1111).build());
        assertEquals(123, si.getRank());
        assertEquals(null, si.getText());
        assertEquals(1111, si.getTextResId());
        assertEquals(null, si.getTextResName());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setDisabledMessage("xxx").build());
        assertEquals(123, si.getRank());
        assertEquals("xxx", si.getDisabledMessage());
        assertEquals(0, si.getDisabledMessageResourceId());
        assertEquals(null, si.getDisabledMessageResName());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setDisabledMessageResId(11111).build());
        assertEquals(123, si.getRank());
        assertEquals(null, si.getDisabledMessage());
        assertEquals(11111, si.getDisabledMessageResourceId());
        assertEquals(null, si.getDisabledMessageResName());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setCategories(set()).build());
        assertEquals(11, si.getTextResId());
        assertEquals(set(), si.getCategories());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setCategories(set("x")).build());
        assertEquals(11, si.getTextResId());
        assertEquals(set("x"), si.getCategories());

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
        assertEquals(11, si.getTextResId());
        assertEquals("action2", si.getIntent().getAction());
        assertEquals(null, si.getIntent().getStringExtra("key"));

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
        assertEquals(11, si.getTextResId());
        assertEquals("action3", si.getIntent().getAction());
        assertEquals("x", si.getIntent().getStringExtra("key"));

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setRank(999).build());
        assertEquals(11, si.getTextResId());
        assertEquals(999, si.getRank());


        PersistableBundle pb2 = new PersistableBundle();
        pb2.putInt("x", 99);

        si = sorig.clone(/* flags=*/ 0);
        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
                .setExtras(pb2).build());
        assertEquals(11, si.getTextResId());
        assertEquals(99, si.getExtras().getInt("x"));
    }

    public void testShortcutInfoSaveAndLoad() throws InterruptedException {
        mRunningUsers.put(USER_10, true);

        setCaller(CALLING_PACKAGE_1, USER_10);

        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                getTestContext().getResources(), R.drawable.black_32x32));

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIcon(bmp32x32)
                .setTitle("title")
                .setText("text")
                .setDisabledMessage("dismes")
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setPerson(makePerson("person", "personKey", "personUri"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .setLocusId(new LocusId("1.2.3.4.5"))
                .addCapabilityBinding(
                        new Capability.Builder("action.intent.START_EXERCISE").build(),
                        new CapabilityParams.Builder("exercise.type", "running")
                                .addAlias("jogging")
                                .build())
                .addCapabilityBinding(
                        new Capability.Builder("action.intent.START_EXERCISE").build(),
                        new CapabilityParams.Builder("exercise.duration", "10m")
                                .build())
                .build();
        sorig.setTimestamp(mInjectedCurrentTimeMillis);

        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
                .setId("id2")
                .setTitle("x")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setPersons(list(makePerson("person1", "personKey1", "personUri1"),
                        makePerson("person2", "personKey2", "personUri2")).toArray(new Person[2]))
                .setRank(456)
                .setLocusId(new LocusId("6.7.8.9"))
                .build();
        sorig2.setTimestamp(mInjectedCurrentTimeMillis);

        mManager.addDynamicShortcuts(list(sorig, sorig2));

        mInjectedCurrentTimeMillis += 1;
        final long now = mInjectedCurrentTimeMillis;
        mInjectedCurrentTimeMillis += 1;

        dumpsysOnLogcat("before save");

        // Save and load.
        mService.saveDirtyInfo();
        initService();
        mService.handleUnlockUser(USER_10);

        dumpUserFile(USER_10);
        dumpsysOnLogcat("after load");

        ShortcutInfo si;
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);

        assertEquals(USER_10, si.getUserId());
        assertEquals(HANDLE_USER_10, si.getUserHandle());
        assertEquals(CALLING_PACKAGE_1, si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
        assertEquals(null, si.getIcon());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(1, si.getPersons().length);
        assertEquals("person", si.getPersons()[0].getName());
        assertEquals("personKey", si.getPersons()[0].getKey());
        assertEquals("personUri", si.getPersons()[0].getUri());
        assertEquals(0, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));
        assertEquals("1.2.3.4.5", si.getLocusId().getId());

        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE
                | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
        assertNotNull(si.getBitmapPath()); // Something should be set.
        assertEquals(0, si.getIconResourceId());
        assertNull(si.getIconUri());
        assertTrue(si.getLastChangedTimestamp() < now);

        assertEquals(list(new Capability.Builder("action.intent.START_EXERCISE").build()),
                si.getCapabilities());
        assertEquals(list(
                        new CapabilityParams.Builder("exercise.type", "running")
                                .addAlias("jogging").build(),
                        new CapabilityParams.Builder("exercise.duration", "10m").build()),
                si.getCapabilityParams(
                        new Capability.Builder("action.intent.START_EXERCISE").build()));

        // Make sure ranks are saved too.  Because of the auto-adjusting, we need two shortcuts
        // to test it.
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
        assertEquals(1, si.getRank());
        assertEquals(2, si.getPersons().length);
        assertEquals("personUri2", si.getPersons()[1].getUri());
        assertEquals("6.7.8.9", si.getLocusId().getId());

        dumpUserFile(USER_10);
    }

    public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
        mRunningUsers.put(USER_10, true);

        setCaller(CALLING_PACKAGE_1, USER_10);

        final Icon bmp32x32 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
            getTestContext().getResources(), R.drawable.black_32x32));

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
            .setId("id")
            .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
            .setIcon(bmp32x32)
            .setTitle("title")
            .setText("text")
            .setDisabledMessage("dismes")
            .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
            .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
            .setRank(123)
            .setExtras(pb)
            .build();
        sorig.setTimestamp(mInjectedCurrentTimeMillis);

        mManager.addDynamicShortcuts(list(sorig));

        mInjectedCurrentTimeMillis += 1;
        final long now = mInjectedCurrentTimeMillis;
        mInjectedCurrentTimeMillis += 1;

        dumpsysOnLogcat("before save");

        // Save and load.
        mService.saveDirtyInfo();
        initService();
        mService.handleUnlockUser(USER_10);

        dumpUserFile(USER_10);
        dumpsysOnLogcat("after load");

        ShortcutInfo si;
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);

        assertEquals(USER_10, si.getUserId());
        assertEquals(HANDLE_USER_10, si.getUserHandle());
        assertEquals(CALLING_PACKAGE_1, si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
        assertEquals(null, si.getIcon());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(0, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE
            | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_ADAPTIVE_BITMAP,
            si.getFlags());
        assertNotNull(si.getBitmapPath()); // Something should be set.
        assertEquals(0, si.getIconResourceId());
        assertNull(si.getIconUri());
        assertTrue(si.getLastChangedTimestamp() < now);

        dumpUserFile(USER_10);
    }

    public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
        mRunningUsers.put(USER_10, true);

        setCaller(CALLING_PACKAGE_1, USER_10);

        final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIcon(res32x32)
                .setTitleResId(10)
                .setTextResId(11)
                .setDisabledMessageResId(12)
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .build();
        sorig.setTimestamp(mInjectedCurrentTimeMillis);

        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
                .setId("id2")
                .setTitle("x")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(456)
                .build();
        sorig2.setTimestamp(mInjectedCurrentTimeMillis);

        mManager.addDynamicShortcuts(list(sorig, sorig2));

        mInjectedCurrentTimeMillis += 1;
        final long now = mInjectedCurrentTimeMillis;
        mInjectedCurrentTimeMillis += 1;

        // Save and load.
        mService.saveDirtyInfo();
        initService();
        mService.handleUnlockUser(USER_10);

        ShortcutInfo si;
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);

        assertEquals(USER_10, si.getUserId());
        assertEquals(HANDLE_USER_10, si.getUserHandle());
        assertEquals(CALLING_PACKAGE_1, si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
        assertEquals(null, si.getIcon());
        assertEquals(10, si.getTitleResId());
        assertEquals("r10", si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals("r11", si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals("r12", si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(0, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_RES
                | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
        assertNull(si.getBitmapPath());
        assertNull(si.getIconUri());
        assertEquals(R.drawable.black_32x32, si.getIconResourceId());
        assertTrue(si.getLastChangedTimestamp() < now);

        // Make sure ranks are saved too.  Because of the auto-adjusting, we need two shortcuts
        // to test it.
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
        assertEquals(1, si.getRank());
    }

    public void testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
        mRunningUsers.put(USER_10, true);

        setCaller(CALLING_PACKAGE_1, USER_10);

        final Icon uriIcon = Icon.createWithContentUri("test_uri");

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIcon(uriIcon)
                .setTitleResId(10)
                .setTextResId(11)
                .setDisabledMessageResId(12)
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .build();
        sorig.setTimestamp(mInjectedCurrentTimeMillis);

        final Icon uriMaskableIcon = Icon.createWithAdaptiveBitmapContentUri("uri_maskable");

        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
                .setId("id2")
                .setTitle("x")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(456)
                .setIcon(uriMaskableIcon)
                .build();
        sorig2.setTimestamp(mInjectedCurrentTimeMillis);

        mManager.addDynamicShortcuts(list(sorig, sorig2));

        mInjectedCurrentTimeMillis += 1;
        final long now = mInjectedCurrentTimeMillis;
        mInjectedCurrentTimeMillis += 1;

        // Save and load.
        mService.saveDirtyInfo();
        initService();
        mService.handleUnlockUser(USER_10);

        ShortcutInfo si;
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);

        assertEquals(USER_10, si.getUserId());
        assertEquals(HANDLE_USER_10, si.getUserHandle());
        assertEquals(CALLING_PACKAGE_1, si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
        assertEquals(null, si.getIcon());
        assertEquals(10, si.getTitleResId());
        assertEquals("r10", si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals("r11", si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals("r12", si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(0, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_URI
                | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
        assertNull(si.getBitmapPath());
        assertNull(si.getIconResName());
        assertEquals(0, si.getIconResourceId());
        assertEquals("test_uri", si.getIconUri());
        assertTrue(si.getLastChangedTimestamp() < now);

        // Make sure ranks are saved too.  Because of the auto-adjusting, we need two shortcuts
        // to test it.
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
        assertEquals(1, si.getRank());
        assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_URI
                | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_ADAPTIVE_BITMAP,
                si.getFlags());
        assertNull(si.getBitmapPath());
        assertNull(si.getIconResName());
        assertEquals(0, si.getIconResourceId());
        assertEquals("uri_maskable", si.getIconUri());
    }

    public void testShortcutInfoSaveAndLoad_forBackup() {
        setCaller(CALLING_PACKAGE_1, USER_0);

        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                getTestContext().getResources(), R.drawable.black_32x32));

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIcon(bmp32x32)
                .setTitle("title")
                .setText("text")
                .setDisabledMessage("dismes")
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setPerson(makePerson("person", "personKey", "personUri"))
                .setRank(123)
                .setExtras(pb)
                .build();

        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
                .setId("id2")
                .setTitle("x")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(456)
                .build();

        mManager.addDynamicShortcuts(list(sorig, sorig2));

        // Dynamic shortcuts won't be backed up, so we need to pin it.
        setCaller(LAUNCHER_1, USER_0);
        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);

        // Do backup & restore.
        backupAndRestore();

        mService.handleUnlockUser(USER_0); // Load user-0.

        ShortcutInfo si;
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);

        assertEquals(CALLING_PACKAGE_1, si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
        assertEquals(null, si.getIcon());
        assertEquals("title", si.getTitle());
        assertEquals("text", si.getText());
        assertEquals("dismes", si.getDisabledMessage());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(0, si.getPersons().length); // Don't backup the persons field
        assertEquals(0, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
                | ShortcutInfo.FLAG_SHADOW , si.getFlags());
        assertNull(si.getBitmapPath()); // No icon.
        assertEquals(0, si.getIconResourceId());
        assertNull(si.getIconUri());

        // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
        assertEquals(0, si.getRank());
    }

    public void testShortcutInfoSaveAndLoad_forBackup_resId() {
        setCaller(CALLING_PACKAGE_1, USER_0);

        final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIcon(res32x32)
                .setTitleResId(10)
                .setTextResId(11)
                .setDisabledMessageResId(12)
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .build();

        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
                .setId("id2")
                .setTitle("x")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(456)
                .build();

        mManager.addDynamicShortcuts(list(sorig, sorig2));

        // Dynamic shortcuts won't be backed up, so we need to pin it.
        setCaller(LAUNCHER_1, USER_0);
        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);

        // Do backup & restore.
        backupAndRestore();

        mService.handleUnlockUser(USER_0); // Load user-0.

        ShortcutInfo si;
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);

        assertEquals(CALLING_PACKAGE_1, si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
        assertEquals(null, si.getIcon());
        assertEquals(10, si.getTitleResId());
        assertEquals("r10", si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals("r11", si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals("r12", si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(0, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
                | ShortcutInfo.FLAG_SHADOW , si.getFlags());
        assertNull(si.getBitmapPath()); // No icon.
        assertEquals(0, si.getIconResourceId());
        assertEquals(null, si.getIconResName());
        assertNull(si.getIconUri());

        // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
        assertEquals(0, si.getRank());
    }

    public void testShortcutInfoSaveAndLoad_forBackup_uri() {
        setCaller(CALLING_PACKAGE_1, USER_0);

        final Icon uriIcon = Icon.createWithContentUri("test_uri");

        PersistableBundle pb = new PersistableBundle();
        pb.putInt("k", 1);
        ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
                .setId("id")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIcon(uriIcon)
                .setTitleResId(10)
                .setTextResId(11)
                .setDisabledMessageResId(12)
                .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(123)
                .setExtras(pb)
                .build();

        ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
                .setId("id2")
                .setTitle("x")
                .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
                .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
                .setRank(456)
                .build();

        mManager.addDynamicShortcuts(list(sorig, sorig2));

        // Dynamic shortcuts won't be backed up, so we need to pin it.
        setCaller(LAUNCHER_1, USER_0);
        mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);

        // Do backup & restore.
        backupAndRestore();

        mService.handleUnlockUser(USER_0); // Load user-0.

        ShortcutInfo si;
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);

        assertEquals(CALLING_PACKAGE_1, si.getPackage());
        assertEquals("id", si.getId());
        assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
        assertEquals(null, si.getIcon());
        assertEquals(10, si.getTitleResId());
        assertEquals("r10", si.getTitleResName());
        assertEquals(11, si.getTextResId());
        assertEquals("r11", si.getTextResName());
        assertEquals(12, si.getDisabledMessageResourceId());
        assertEquals("r12", si.getDisabledMessageResName());
        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
        assertEquals("action", si.getIntent().getAction());
        assertEquals("val", si.getIntent().getStringExtra("key"));
        assertEquals(0, si.getRank());
        assertEquals(1, si.getExtras().getInt("k"));

        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
                | ShortcutInfo.FLAG_SHADOW , si.getFlags());
        assertNull(si.getBitmapPath()); // No icon.
        assertEquals(0, si.getIconResourceId());
        assertEquals(null, si.getIconResName());
        assertNull(si.getIconUri());

        // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
        si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
        assertEquals(0, si.getRank());
    }

    private void checkShortcutInfoSaveAndLoad_intents(Intent intent) {
        assertTrue(mManager.setDynamicShortcuts(list(
                makeShortcutWithIntent("s1", intent))));
        initService();
        mService.handleUnlockUser(USER_0);

        assertWith(getCallerShortcuts())
                .haveIds("s1")
                .forShortcutWithId("s1", si -> {
                    assertEquals(intent.getAction(), si.getIntent().getAction());
                    assertEquals(intent.getData(), si.getIntent().getData());
                    assertEquals(intent.getComponent(), si.getIntent().getComponent());
                    assertBundlesEqual(intent.getExtras(), si.getIntent().getExtras());
                });
    }

    private void checkShortcutInfoSaveAndLoad_intents(Intent... intents) {
        assertTrue(mManager.setDynamicShortcuts(list(
                makeShortcutWithIntents("s1", intents))));
        initService();
        mService.handleUnlockUser(USER_0);

        assertWith(getCallerShortcuts())
                .haveIds("s1")
                .forShortcutWithId("s1", si -> {

                    final Intent[] actual = si.getIntents();
                    assertEquals(intents.length, actual.length);

                    for (int i = 0; i < intents.length; i++) {
                        assertEquals(intents[i].getAction(), actual[i].getAction());
                        assertEquals(intents[i].getData(), actual[i].getData());
                        assertEquals(intents[i].getComponent(), actual[i].getComponent());
                        assertEquals(intents[i].getFlags(), actual[i].getFlags());
                        assertBundlesEqual(intents[i].getExtras(), actual[i].getExtras());
                    }
                });
    }

    public void testShortcutInfoSaveAndLoad_intents() {
        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.

        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_MAIN));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.

        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW,
                Uri.parse("http://www.example.com/")));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.

        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_MAIN,
                Uri.parse("http://www.example.com/")));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.

        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW)
                .setComponent(new ComponentName("a", "b")));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.

        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_MAIN)
                .setComponent(new ComponentName("a", "b")));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.

        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW)
                .putExtras(makeBundle("a", "b")));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.


        checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_MAIN)
                .putExtras(makeBundle("a", "b")));

        mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.

        // Multi-intents
        checkShortcutInfoSaveAndLoad_intents(
                new Intent(Intent.ACTION_MAIN).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK),
                new Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
        );

        checkShortcutInfoSaveAndLoad_intents(
                new Intent(Intent.ACTION_MAIN).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                        .setComponent(new ComponentName("a", "b")),
                new Intent(Intent.ACTION_VIEW)
                        .setComponent(new ComponentName("a", "b"))
                );

        checkShortcutInfoSaveAndLoad_intents(
                new Intent(Intent.ACTION_MAIN)
                        .setComponent(new ComponentName("a", "b")),
                new Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                        .setComponent(new ComponentName("a", "b")),
                new Intent("xyz").setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
                        | Intent.FILL_IN_COMPONENT)
                        .setComponent(new ComponentName("a", "b")).putExtras(
                        makeBundle("xx", "yy"))
                );
    }

    public void testThrottling() {
        final ShortcutInfo si1 = makeShortcut("shortcut1");

        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(2, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(1, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(0, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        // Reached the max

        mInjectedCurrentTimeMillis++;
        assertFalse(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(0, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        // Still throttled
        mInjectedCurrentTimeMillis = START_TIME + INTERVAL - 1;
        assertFalse(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(0, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        // Now it should work.
        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1))); // fail
        assertEquals(2, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(1, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(0, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis++;
        assertFalse(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(0, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());

        // 4 hours later...
        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(2, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(1, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());

        // Make sure getRemainingCallCount() itself gets reset without calling setDynamicShortcuts().
        mInjectedCurrentTimeMillis = START_TIME + 8 * INTERVAL;
        assertEquals(3, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(2, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
    }

    public void testThrottling_rewind() {
        final ShortcutInfo si1 = makeShortcut("shortcut1");

        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(2, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        mInjectedCurrentTimeMillis = 12345; // Clock reset!

        // Since the clock looks invalid, the counter shouldn't have reset.
        assertEquals(2, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        // Forward again.  Still haven't reset yet.
        mInjectedCurrentTimeMillis = START_TIME + INTERVAL - 1;
        assertEquals(2, mManager.getRemainingCallCount());
        assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());

        // Now rewind -- this will reset the counters.
        mInjectedCurrentTimeMillis = START_TIME - 100000;
        assertEquals(3, mManager.getRemainingCallCount());

        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(2, mManager.getRemainingCallCount());

        // Forward again, should be reset now.
        mInjectedCurrentTimeMillis += INTERVAL;
        assertEquals(3, mManager.getRemainingCallCount());
    }

    public void testThrottling_perPackage() {
        final ShortcutInfo si1 = makeShortcut("shortcut1");

        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(2, mManager.getRemainingCallCount());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(1, mManager.getRemainingCallCount());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(0, mManager.getRemainingCallCount());

        // Reached the max

        mInjectedCurrentTimeMillis++;
        assertFalse(mManager.setDynamicShortcuts(list(si1)));

        // Try from a different caller.
        mInjectedClientPackage = CALLING_PACKAGE_2;
        mInjectedCallingUid = CALLING_UID_2;

        // Need to create a new one wit the updated package name.
        final ShortcutInfo si2 = makeShortcut("shortcut1");

        assertEquals(3, mManager.getRemainingCallCount());

        assertTrue(mManager.setDynamicShortcuts(list(si2)));
        assertEquals(2, mManager.getRemainingCallCount());

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si2)));
        assertEquals(1, mManager.getRemainingCallCount());

        // Back to the original caller, still throttled.
        mInjectedClientPackage = CALLING_PACKAGE_1;
        mInjectedCallingUid = CALLING_UID_1;

        mInjectedCurrentTimeMillis = START_TIME + INTERVAL - 1;
        assertEquals(0, mManager.getRemainingCallCount());
        assertFalse(mManager.setDynamicShortcuts(list(si1)));
        assertEquals(0, mManager.getRemainingCallCount());

        // Now it should work.
        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));

        mInjectedCurrentTimeMillis++;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));

        mInjectedCurrentTimeMillis++;
        assertFalse(mManager.setDynamicShortcuts(list(si1)));

        mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL;
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertTrue(mManager.setDynamicShortcuts(list(si1)));
        assertFalse(mManager.setDynamicShortcuts(list(si1)));

        mInjectedClientPackage = CALLING_PACKAGE_2;
        mInjectedCallingUid = CALLING_UID_2;

        assertEquals(3, mManager.getRemainingCallCount());

        assertTrue(mManager.setDynamicShortcuts(list(si2)));
        assertTrue(mManager.setDynamicShortcuts(list(si2)));
        assertTrue(mManager.setDynamicShortcuts(list(si2)));
        assertFalse(mManager.setDynamicShortcuts(list(si2)));
    }

    public void testThrottling_localeChanges() {
        prepareCrossProfileDataSet();

        dumpsysOnLogcat("Before save & load");

        mService.saveDirtyInfo();
        initService();

        mInjectedLocale = Locale.CHINA;
        mService.mReceiver.onReceive(mServiceContext, new Intent(Intent.ACTION_LOCALE_CHANGED));

        // Note at this point only user-0 is loaded, and the counters are reset for this user,
        // but it will work for other users too because we check the locale change at any
        // API entry point.

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });

        // Make sure even if we receive ACTION_LOCALE_CHANGED, if the locale hasn't actually
        // changed, we don't reset throttling.
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            mManager.updateShortcuts(list());
            assertEquals(2, mManager.getRemainingCallCount());
        });

        mService.mReceiver.onReceive(mServiceContext, new Intent(Intent.ACTION_LOCALE_CHANGED));

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(2, mManager.getRemainingCallCount()); // Still 2.
        });

        mService.saveDirtyInfo();
        initService();

        // The locale should be persisted, so it still shouldn't reset throttling.
        mService.mReceiver.onReceive(mServiceContext, new Intent(Intent.ACTION_LOCALE_CHANGED));

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(2, mManager.getRemainingCallCount()); // Still 2.
        });
    }

    public void testThrottling_foreground() throws Exception {
        prepareCrossProfileDataSet();

        dumpsysOnLogcat("Before save & load");

        mService.saveDirtyInfo();
        initService();

        // We need to update the current time from time to time, since some of the internal checks
        // rely on the time being correctly incremented.
        mInjectedCurrentTimeMillis++;

        // First, all packages have less than 3 (== initial value) remaining calls.

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        mInjectedCurrentTimeMillis++;

        // State changed, but not foreground, so no resetting.
        mService.mUidObserver.onUidStateChanged(
                CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        mInjectedCurrentTimeMillis++;

        // State changed, package1 foreground, reset.
        mService.mUidObserver.onUidStateChanged(
                CALLING_UID_1, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        mService.mUidObserver.onUidStateChanged(
                CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);

        mInjectedCurrentTimeMillis++;

        // Different app comes to foreground briefly, and goes back to background.
        // Now, make sure package 2's counter is reset, even in this case.
        mService.mUidObserver.onUidStateChanged(
                CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);
        mService.mUidObserver.onUidStateChanged(
                CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        mInjectedCurrentTimeMillis++;

        // Do the same thing one more time.  This would catch the bug with mixuing up
        // the current time and the elapsed time.
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            mManager.updateShortcuts(list(makeShortcut("s")));
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        mService.mUidObserver.onUidStateChanged(
                CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);
        mService.mUidObserver.onUidStateChanged(
                CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        mInjectedCurrentTimeMillis++;

        // Package 1 on user-10 comes to foreground.
        // Now, also try calling some APIs and make sure foreground apps don't get throttled.
        mService.mUidObserver.onUidStateChanged(
                UserHandle.getUid(USER_10, CALLING_UID_1),
                ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0,
                ActivityManager.PROCESS_CAPABILITY_NONE);
        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
            assertFalse(mManager.isRateLimitingActive());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(2, mManager.getRemainingCallCount());
            assertFalse(mManager.isRateLimitingActive());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(1, mManager.getRemainingCallCount());
            assertFalse(mManager.isRateLimitingActive());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(0, mManager.getRemainingCallCount());
            assertTrue(mManager.isRateLimitingActive());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(0, mManager.getRemainingCallCount());
            assertTrue(mManager.isRateLimitingActive());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(0, mManager.getRemainingCallCount());
            assertTrue(mManager.isRateLimitingActive());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(0, mManager.getRemainingCallCount());
            assertTrue(mManager.isRateLimitingActive());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(0, mManager.getRemainingCallCount());
            assertTrue(mManager.isRateLimitingActive());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            assertEquals(3, mManager.getRemainingCallCount());

            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));
            mManager.setDynamicShortcuts(list(makeShortcut("s")));

            assertEquals(3, mManager.getRemainingCallCount()); // Still 3!
            assertFalse(mManager.isRateLimitingActive());
        });
    }


    public void testThrottling_resetByInternalCall() throws Exception {
        prepareCrossProfileDataSet();

        dumpsysOnLogcat("Before save & load");

        mService.saveDirtyInfo();
        initService();

        // First, all packages have less than 3 (== initial value) remaining calls.

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        // Simulate a call from sys UI.
        mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING);
        mManager.onApplicationActive(CALLING_PACKAGE_1, USER_0);

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        mManager.onApplicationActive(CALLING_PACKAGE_3, USER_0);

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });

        mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10);

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
            MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            assertEquals(3, mManager.getRemainingCallCount());
        });
    }

    public void testReportShortcutUsed() {
        mRunningUsers.put(USER_10, true);

        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            reset(mMockUsageStatsManagerInternal);

            // Report with an nonexistent shortcut.
            mManager.reportShortcutUsed("s1");
            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
                    anyString(), anyString(), anyInt());

            // Publish s2, but s1 still doesn't exist.
            mManager.setDynamicShortcuts(list(makeShortcut("s2")));
            mManager.reportShortcutUsed("s1");
            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
                    anyString(), anyString(), anyInt());

            mManager.reportShortcutUsed("s2");
            verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
                    eq(CALLING_PACKAGE_1), eq("s2"), eq(USER_10));
        });
        runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
            // Try with a different package.
            reset(mMockUsageStatsManagerInternal);

            // Report with an nonexistent shortcut.
            mManager.reportShortcutUsed("s2");
            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
                    anyString(), anyString(), anyInt());

            // Publish s2, but s1 still doesn't exist.
            mManager.setDynamicShortcuts(list(makeShortcut("s3")));
            mManager.reportShortcutUsed("s2");
            verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
                    anyString(), anyString(), anyInt());

            mManager.reportShortcutUsed("s3");
            verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
                    eq(CALLING_PACKAGE_2), eq("s3"), eq(USER_10));
        });
    }

    // Test for a ShortcutInfo method.
    public void testGetResourcePackageName() {
        assertEquals(null, ShortcutInfo.getResourcePackageName(""));
        assertEquals(null, ShortcutInfo.getResourcePackageName("abc"));
        assertEquals("p", ShortcutInfo.getResourcePackageName("p:"));
        assertEquals("p", ShortcutInfo.getResourcePackageName("p:xx"));
        assertEquals("pac", ShortcutInfo.getResourcePackageName("pac:"));
    }

    // Test for a ShortcutInfo method.
    public void testGetResourceTypeName() {
        assertEquals(null, ShortcutInfo.getResourceTypeName(""));
        assertEquals(null, ShortcutInfo.getResourceTypeName(":"));
        assertEquals(null, ShortcutInfo.getResourceTypeName("/"));
        assertEquals(null, ShortcutInfo.getResourceTypeName("/:"));
        assertEquals("a", ShortcutInfo.getResourceTypeName(":a/"));
        assertEquals("type", ShortcutInfo.getResourceTypeName("xxx:type/yyy"));
    }

    // Test for a ShortcutInfo method.
    public void testGetResourceTypeAndEntryName() {
        assertEquals(null, ShortcutInfo.getResourceTypeAndEntryName(""));
        assertEquals(null, ShortcutInfo.getResourceTypeAndEntryName("abc"));
        assertEquals("", ShortcutInfo.getResourceTypeAndEntryName("p:"));
        assertEquals("x", ShortcutInfo.getResourceTypeAndEntryName(":x"));
        assertEquals("x", ShortcutInfo.getResourceTypeAndEntryName("p:x"));
        assertEquals("xyz", ShortcutInfo.getResourceTypeAndEntryName("pac:xyz"));
    }

    // Test for a ShortcutInfo method.
    public void testGetResourceEntryName() {
        assertEquals(null, ShortcutInfo.getResourceEntryName(""));
        assertEquals(null, ShortcutInfo.getResourceEntryName("ab:"));
        assertEquals("", ShortcutInfo.getResourceEntryName("/"));
        assertEquals("abc", ShortcutInfo.getResourceEntryName("/abc"));
        assertEquals("abc", ShortcutInfo.getResourceEntryName("xyz/abc"));
    }

    // Test for a ShortcutInfo method.
    public void testLookUpResourceName_systemResources() {
        // For android system resources, lookUpResourceName will simply return the value as a
        // string, regardless of "withType".
        final Resources res = getTestContext().getResources();

        assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res,
                android.R.string.cancel, true, getTestContext().getPackageName()));
        assertEquals("" + android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceName(res,
                android.R.drawable.alert_dark_frame, true, getTestContext().getPackageName()));
        assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res,
                android.R.string.cancel, false, getTestContext().getPackageName()));
        assertEquals("" + android.R.style.Theme_Black_NoTitleBar_Fullscreen,
                ShortcutInfo.lookUpResourceName(
                        res, android.R.style.Theme_Black_NoTitleBar_Fullscreen, true,
                        getTestContext().getPackageName()));
    }

    public void testLookUpResourceName_appResources() {
        final Resources res = getTestContext().getResources();

        assertEquals("shortcut_text1", ShortcutInfo.lookUpResourceName(res,
                R.string.shortcut_text1, false, getTestContext().getPackageName()));
        assertEquals("string/shortcut_text1", ShortcutInfo.lookUpResourceName(res,
                R.string.shortcut_text1, true, getTestContext().getPackageName()));

        assertEquals("black_16x64", ShortcutInfo.lookUpResourceName(res,
                R.drawable.black_16x64, false, getTestContext().getPackageName()));
        assertEquals("drawable/black_16x64", ShortcutInfo.lookUpResourceName(res,
                R.drawable.black_16x64, true, getTestContext().getPackageName()));
    }

    // Test for a ShortcutInfo method.
    public void testLookUpResourceId_systemResources() {
        final Resources res = getTestContext().getResources();

        assertEquals(android.R.string.cancel, ShortcutInfo.lookUpResourceId(res,
                "" + android.R.string.cancel, null,
                getTestContext().getPackageName()));
        assertEquals(android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceId(res,
                "" + android.R.drawable.alert_dark_frame, null,
                getTestContext().getPackageName()));
        assertEquals(android.R.style.Theme_Black_NoTitleBar_Fullscreen,
                ShortcutInfo.lookUpResourceId(
                        res, "" + android.R.style.Theme_Black_NoTitleBar_Fullscreen,
                        null, getTestContext().getPackageName()));
    }

    // Test for a ShortcutInfo method.
    public void testLookUpResourceId_appResources() {
        final Resources res = getTestContext().getResources();

        assertEquals(R.string.shortcut_text1,
                ShortcutInfo.lookUpResourceId(res, "shortcut_text1", "string",
                        getTestContext().getPackageName()));

        assertEquals(R.string.shortcut_text1,
                ShortcutInfo.lookUpResourceId(res, "string/shortcut_text1", null,
                        getTestContext().getPackageName()));

        assertEquals(R.drawable.black_16x64,
                ShortcutInfo.lookUpResourceId(res, "black_16x64", "drawable",
                        getTestContext().getPackageName()));

        assertEquals(R.drawable.black_16x64,
                ShortcutInfo.lookUpResourceId(res, "drawable/black_16x64", null,
                        getTestContext().getPackageName()));
    }

    public void testDumpCheckin() throws IOException {
        prepareCrossProfileDataSet();

        // prepareCrossProfileDataSet() doesn't set any icons, so do set here.
        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
        final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
        final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                getTestContext().getResources(), R.drawable.black_32x32));
        final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                getTestContext().getResources(), R.drawable.black_64x64));

        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            assertTrue(mManager.setDynamicShortcuts(list(
                    makeShortcutWithIcon("res32x32", res32x32),
                    makeShortcutWithIcon("res64x64", res64x64),
                    makeShortcutWithIcon("bmp32x32", bmp32x32),
                    makeShortcutWithIcon("bmp64x64", bmp64x64))));
        });

        // We can't predict the compressed bitmap sizes, so get the real sizes here.
        final long bitmapTotal =
                new File(getBitmapAbsPath(USER_0, CALLING_PACKAGE_2, "bmp32x32")).length() +
                new File(getBitmapAbsPath(USER_0, CALLING_PACKAGE_2, "bmp64x64")).length();

        // Read the expected output and inject the bitmap size.
        final String expected = readTestAsset("shortcut/dumpsys_expected.txt")
                .replace("***BITMAP_SIZE***", String.valueOf(bitmapTotal));

        assertEquals(expected, dumpCheckin());
    }

    /**
     * Make sure the legacy file format that only supported a single intent per shortcut
     * can still be read.
     */
    public void testLoadLegacySavedFile() throws Exception {
        final File path = mService.getUserFile(USER_0).getBaseFile();
        path.getParentFile().mkdirs();
        try (Writer w = new FileWriter(path)) {
            w.write(readTestAsset("shortcut/shortcut_legacy_file.xml"));
        };
        initService();
        mService.handleUnlockUser(USER_0);

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertWith(getCallerShortcuts())
                    .haveIds("manifest-shortcut-storage")
                    .forShortcutWithId("manifest-shortcut-storage", si -> {
                        assertEquals("android.settings.INTERNAL_STORAGE_SETTINGS",
                                si.getIntent().getAction());
                        assertEquals(12345, si.getIntent().getIntExtra("key", 0));
                    });
        });
    }

    public void testIsUserUnlocked() {
        mRunningUsers.clear();
        mUnlockedUsers.clear();

        assertFalse(mService.isUserUnlockedL(USER_0));
        assertFalse(mService.isUserUnlockedL(USER_10));

        // Start user 0, still locked.
        mRunningUsers.put(USER_0, true);
        assertFalse(mService.isUserUnlockedL(USER_0));
        assertFalse(mService.isUserUnlockedL(USER_10));

        // Unlock user.
        mUnlockedUsers.put(USER_0, true);
        assertTrue(mService.isUserUnlockedL(USER_0));
        assertFalse(mService.isUserUnlockedL(USER_10));

        // Clear again.
        mRunningUsers.clear();
        mUnlockedUsers.clear();

        // Directly call the lifecycle event.  Now also locked.
        mService.handleUnlockUser(USER_0);
        assertTrue(mService.isUserUnlockedL(USER_0));
        assertFalse(mService.isUserUnlockedL(USER_10));

        // Directly call the stop lifecycle event.  Goes back to the initial state.
        mService.handleStopUser(USER_0);
        assertFalse(mService.isUserUnlockedL(USER_0));
        assertFalse(mService.isUserUnlockedL(USER_10));
    }

    public void testEphemeralApp() {
        mRunningUsers.put(USER_10, true); // this test needs user 10.

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertWith(mManager.getDynamicShortcuts()).isEmpty();
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            assertWith(mManager.getDynamicShortcuts()).isEmpty();
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            assertWith(mManager.getDynamicShortcuts()).isEmpty();
        });
        // Make package 1 ephemeral.
        mEphemeralPackages.add(UserPackage.of(USER_0, CALLING_PACKAGE_1));

        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
            assertExpectException(IllegalStateException.class, "Ephemeral apps", () -> {
                mManager.getDynamicShortcuts();
            });
        });
        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
            assertWith(mManager.getDynamicShortcuts()).isEmpty();
        });
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            assertWith(mManager.getDynamicShortcuts()).isEmpty();
        });
    }
}
