Home | History | Annotate | Download | only in shortcutmanager
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.content.pm.cts.shortcutmanager;
     17 
     18 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.*;
     19 
     20 import android.app.Activity;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.ContextWrapper;
     24 import android.content.Intent;
     25 import android.content.pm.LauncherApps;
     26 import android.content.pm.LauncherApps.ShortcutQuery;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ShortcutInfo;
     29 import android.content.pm.ShortcutManager;
     30 import android.content.res.Resources;
     31 import android.graphics.drawable.AdaptiveIconDrawable;
     32 import android.graphics.drawable.Drawable;
     33 import android.graphics.drawable.Icon;
     34 import android.os.Bundle;
     35 import android.os.PersistableBundle;
     36 import android.os.StrictMode;
     37 import android.os.StrictMode.ThreadPolicy;
     38 import android.os.UserHandle;
     39 import androidx.annotation.NonNull;
     40 import android.test.InstrumentationTestCase;
     41 import android.text.TextUtils;
     42 
     43 import java.util.ArrayList;
     44 import java.util.HashMap;
     45 import java.util.List;
     46 import java.util.Map;
     47 import java.util.concurrent.atomic.AtomicReference;
     48 
     49 public abstract class ShortcutManagerCtsTestsBase extends InstrumentationTestCase {
     50     protected static final String TAG = "ShortcutCTS";
     51 
     52     private static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
     53 
     54     /**
     55      * Whether to enable strict mode or not.
     56      *
     57      * TODO Enable it after fixing b/68051728. Somehow violations would happen on the dashboard
     58      * only and can't reproduce it locally.
     59      */
     60     private static final boolean ENABLE_STRICT_MODE = false;
     61 
     62     private static class SpoofingContext extends ContextWrapper {
     63         private final String mPackageName;
     64 
     65         public SpoofingContext(Context base, String packageName) {
     66             super(base);
     67             mPackageName = packageName;
     68         }
     69 
     70         @Override
     71         public String getPackageName() {
     72             return mPackageName;
     73         }
     74     }
     75 
     76     private Context mCurrentCallerPackage;
     77     private int mUserId;
     78     private UserHandle mUserHandle;
     79 
     80     private String mOriginalLauncher;
     81 
     82     protected Context mPackageContext1;
     83     protected Context mPackageContext2;
     84     protected Context mPackageContext3;
     85     protected Context mPackageContext4;
     86 
     87     protected Context mLauncherContext1;
     88     protected Context mLauncherContext2;
     89     protected Context mLauncherContext3;
     90     protected Context mLauncherContext4;
     91 
     92     private LauncherApps mLauncherApps1;
     93     private LauncherApps mLauncherApps2;
     94     private LauncherApps mLauncherApps3;
     95     private LauncherApps mLauncherApps4;
     96 
     97     private Map<Context, ShortcutManager> mManagers = new HashMap<>();
     98     private Map<Context, LauncherApps> mLauncherAppses = new HashMap<>();
     99 
    100     private ShortcutManager mCurrentManager;
    101     private LauncherApps mCurrentLauncherApps;
    102 
    103     private static final String[] ACTIVITIES_WITH_MANIFEST_SHORTCUTS = {
    104             "Launcher_manifest_1",
    105             "Launcher_manifest_2",
    106             "Launcher_manifest_3",
    107             "Launcher_manifest_4a",
    108             "Launcher_manifest_4b",
    109             "Launcher_manifest_error_1",
    110             "Launcher_manifest_error_2",
    111             "Launcher_manifest_error_3"
    112     };
    113 
    114     private ComponentName mTargetActivityOverride;
    115 
    116     private static class ShortcutActivity extends Activity {
    117     }
    118 
    119     @Override
    120     protected void setUp() throws Exception {
    121         super.setUp();
    122 
    123         mUserId = getTestContext().getUserId();
    124         mUserHandle = android.os.Process.myUserHandle();
    125 
    126         resetConfig(getInstrumentation());
    127         final String config = getOverrideConfig();
    128         if (config != null) {
    129             overrideConfig(getInstrumentation(), config);
    130         }
    131         mOriginalLauncher = getDefaultLauncher(getInstrumentation());
    132 
    133         mPackageContext1 = new SpoofingContext(getTestContext(),
    134                 "android.content.pm.cts.shortcutmanager.packages.package1");
    135         mPackageContext2 = new SpoofingContext(getTestContext(),
    136                 "android.content.pm.cts.shortcutmanager.packages.package2");
    137         mPackageContext3 = new SpoofingContext(getTestContext(),
    138                 "android.content.pm.cts.shortcutmanager.packages.package3");
    139         mPackageContext4 = new SpoofingContext(getTestContext(),
    140                 "android.content.pm.cts.shortcutmanager.packages.package4");
    141         mLauncherContext1 = new SpoofingContext(getTestContext(),
    142                 "android.content.pm.cts.shortcutmanager.packages.launcher1");
    143         mLauncherContext2 = new SpoofingContext(getTestContext(),
    144                 "android.content.pm.cts.shortcutmanager.packages.launcher2");
    145         mLauncherContext3 = new SpoofingContext(getTestContext(),
    146                 "android.content.pm.cts.shortcutmanager.packages.launcher3");
    147         mLauncherContext4 = new SpoofingContext(getTestContext(),
    148                 "android.content.pm.cts.shortcutmanager.packages.launcher4");
    149 
    150         mLauncherApps1 = new LauncherApps(mLauncherContext1);
    151         mLauncherApps2 = new LauncherApps(mLauncherContext2);
    152         mLauncherApps3 = new LauncherApps(mLauncherContext3);
    153         mLauncherApps4 = new LauncherApps(mLauncherContext4);
    154 
    155         clearShortcuts(getInstrumentation(), mUserId, mPackageContext1.getPackageName());
    156         clearShortcuts(getInstrumentation(), mUserId, mPackageContext2.getPackageName());
    157         clearShortcuts(getInstrumentation(), mUserId, mPackageContext3.getPackageName());
    158         clearShortcuts(getInstrumentation(), mUserId, mPackageContext4.getPackageName());
    159 
    160         setCurrentCaller(mPackageContext1);
    161 
    162         // Make sure shortcuts are removed.
    163         withCallers(getAllPublishers(), () -> {
    164             // Clear all shortcuts.
    165             clearShortcuts(getInstrumentation(), mUserId, getCurrentCallingPackage());
    166 
    167             disableActivitiesWithManifestShortucts();
    168 
    169             assertEquals("for " + getCurrentCallingPackage(),
    170                     0, getManager().getDynamicShortcuts().size());
    171             assertEquals("for " + getCurrentCallingPackage(),
    172                     0, getManager().getPinnedShortcuts().size());
    173             assertEquals("for " + getCurrentCallingPackage(),
    174                     0, getManager().getManifestShortcuts().size());
    175         });
    176     }
    177 
    178     @Override
    179     protected void tearDown() throws Exception {
    180         if (DUMPSYS_IN_TEARDOWN) {
    181             dumpsysShortcut(getInstrumentation());
    182         }
    183 
    184         withCallers(getAllPublishers(), () -> disableActivitiesWithManifestShortucts());
    185 
    186         resetConfig(getInstrumentation());
    187 
    188         if (!TextUtils.isEmpty(mOriginalLauncher)) {
    189             setDefaultLauncher(getInstrumentation(), mOriginalLauncher);
    190         }
    191 
    192         super.tearDown();
    193     }
    194 
    195     protected Context getTestContext() {
    196         return getInstrumentation().getContext();
    197     }
    198 
    199     protected UserHandle getUserHandle() {
    200         return mUserHandle;
    201     }
    202 
    203     protected List<Context> getAllPublishers() {
    204         // 4 has a different signature, so we can't call for it.
    205         return list(mPackageContext1, mPackageContext2, mPackageContext3);
    206     }
    207 
    208     protected List<Context> getAllLaunchers() {
    209         // 4 has a different signature, so we can't call for it.
    210         return list(mLauncherContext1, mLauncherContext2, mLauncherContext3);
    211     }
    212 
    213     protected List<Context> getAllCallers() {
    214         return list(
    215                 mPackageContext1, mPackageContext2, mPackageContext3, mPackageContext4,
    216                 mLauncherContext1, mLauncherContext2, mLauncherContext3, mLauncherContext4);
    217     }
    218 
    219     protected ComponentName getActivity(String className) {
    220         return new ComponentName(getCurrentCallingPackage(),
    221                 "android.content.pm.cts.shortcutmanager.packages." + className);
    222 
    223     }
    224 
    225     protected void disableActivitiesWithManifestShortucts() {
    226         if (getManager().getManifestShortcuts().size() > 0) {
    227             // Disable DISABLED_ACTIVITIES
    228             for (String className : ACTIVITIES_WITH_MANIFEST_SHORTCUTS) {
    229                 enableManifestActivity(className, false);
    230             }
    231         }
    232     }
    233 
    234     protected void enableManifestActivity(String className, boolean enabled) {
    235         getTestContext().getPackageManager().setComponentEnabledSetting(getActivity(className),
    236                 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
    237                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
    238                 PackageManager.DONT_KILL_APP);
    239     }
    240 
    241     protected void setTargetActivityOverride(String className) {
    242         mTargetActivityOverride = getActivity(className);
    243     }
    244 
    245 
    246     protected void withCallers(List<Context> callers, Runnable r) {
    247         for (Context c : callers) {
    248             runWithCaller(c, r);
    249         }
    250     }
    251 
    252     protected String getOverrideConfig() {
    253         return null;
    254     }
    255 
    256     protected void setCurrentCaller(Context callerContext) {
    257         mCurrentCallerPackage = callerContext;
    258 
    259         if (!mManagers.containsKey(mCurrentCallerPackage)) {
    260             mManagers.put(mCurrentCallerPackage, new ShortcutManager(mCurrentCallerPackage));
    261         }
    262         mCurrentManager = mManagers.get(mCurrentCallerPackage);
    263 
    264         if (!mLauncherAppses.containsKey(mCurrentCallerPackage)) {
    265             mLauncherAppses.put(mCurrentCallerPackage, new LauncherApps(mCurrentCallerPackage));
    266         }
    267         mCurrentLauncherApps = mLauncherAppses.get(mCurrentCallerPackage);
    268 
    269         mTargetActivityOverride = null;
    270     }
    271 
    272     protected Context getCurrentCallerContext() {
    273         return mCurrentCallerPackage;
    274     }
    275 
    276     protected String getCurrentCallingPackage() {
    277         return getCurrentCallerContext().getPackageName();
    278     }
    279 
    280     protected ShortcutManager getManager() {
    281         return mCurrentManager;
    282     }
    283 
    284     protected LauncherApps getLauncherApps() {
    285         return mCurrentLauncherApps;
    286     }
    287 
    288     protected void runWithStrictMode(Runnable r) {
    289         final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
    290         try {
    291             if (ENABLE_STRICT_MODE) {
    292                 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    293                         .detectAll()
    294                         .penaltyDeath()
    295                         .build());
    296             }
    297             r.run();
    298         } finally {
    299             StrictMode.setThreadPolicy(oldPolicy);
    300         }
    301     }
    302 
    303     protected void runWithNoStrictMode(Runnable r) {
    304         final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
    305         try {
    306             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    307                     .permitAll()
    308                     .build());
    309             r.run();
    310         } finally {
    311             StrictMode.setThreadPolicy(oldPolicy);
    312         }
    313     }
    314 
    315     protected void runWithCaller(Context callerContext, Runnable r) {
    316         final Context prev = mCurrentCallerPackage;
    317 
    318         setCurrentCaller(callerContext);
    319 
    320         r.run();
    321 
    322         setCurrentCaller(prev);
    323     }
    324 
    325     protected void runWithCallerWithStrictMode(Context callerContext, Runnable r) {
    326         runWithCaller(callerContext, () -> runWithStrictMode(r));
    327     }
    328 
    329     protected void runWithCallerWithNoStrictMode(Context callerContext, Runnable r) {
    330         runWithCaller(callerContext, () -> runWithNoStrictMode(r));
    331     }
    332 
    333     public static Bundle makeBundle(Object... keysAndValues) {
    334         assertTrue((keysAndValues.length % 2) == 0);
    335 
    336         if (keysAndValues.length == 0) {
    337             return null;
    338         }
    339         final Bundle ret = new Bundle();
    340 
    341         for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
    342             final String key = keysAndValues[i].toString();
    343             final Object value = keysAndValues[i + 1];
    344 
    345             if (value == null) {
    346                 ret.putString(key, null);
    347             } else if (value instanceof Integer) {
    348                 ret.putInt(key, (Integer) value);
    349             } else if (value instanceof String) {
    350                 ret.putString(key, (String) value);
    351             } else if (value instanceof Bundle) {
    352                 ret.putBundle(key, (Bundle) value);
    353             } else {
    354                 fail("Type not supported yet: " + value.getClass().getName());
    355             }
    356         }
    357         return ret;
    358     }
    359 
    360     public static PersistableBundle makePersistableBundle(Object... keysAndValues) {
    361         assertTrue((keysAndValues.length % 2) == 0);
    362 
    363         if (keysAndValues.length == 0) {
    364             return null;
    365         }
    366         final PersistableBundle ret = new PersistableBundle();
    367 
    368         for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
    369             final String key = keysAndValues[i].toString();
    370             final Object value = keysAndValues[i + 1];
    371 
    372             if (value == null) {
    373                 ret.putString(key, null);
    374             } else if (value instanceof Integer) {
    375                 ret.putInt(key, (Integer) value);
    376             } else if (value instanceof String) {
    377                 ret.putString(key, (String) value);
    378             } else if (value instanceof PersistableBundle) {
    379                 ret.putPersistableBundle(key, (PersistableBundle) value);
    380             } else {
    381                 fail("Type not supported yet: " + value.getClass().getName());
    382             }
    383         }
    384         return ret;
    385     }
    386 
    387     /**
    388      * Make a shortcut with an ID.
    389      */
    390     protected ShortcutInfo makeShortcut(String id) {
    391         return makeShortcut(id, "Title-" + id);
    392     }
    393 
    394     protected ShortcutInfo makeShortcutWithRank(String id, int rank) {
    395         return makeShortcut(
    396                 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
    397                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank);
    398     }
    399 
    400     /**
    401      * Make a shortcut with an ID and a title.
    402      */
    403     protected ShortcutInfo makeShortcut(String id, String shortLabel) {
    404         return makeShortcut(
    405                 id, shortLabel, /* activity =*/ null, /* icon =*/ null,
    406                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
    407     }
    408 
    409     protected ShortcutInfo makeShortcut(String id, ComponentName activity) {
    410         return makeShortcut(
    411                 id, "Title-" + id, activity, /* icon =*/ null,
    412                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
    413     }
    414 
    415     /**
    416      * Make a shortcut with an ID and icon.
    417      */
    418     protected ShortcutInfo makeShortcutWithIcon(String id, Icon icon) {
    419         return makeShortcut(
    420                 id, "Title-" + id, /* activity =*/ null, icon,
    421                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
    422     }
    423 
    424     /**
    425      * Make multiple shortcuts with IDs.
    426      */
    427     protected List<ShortcutInfo> makeShortcuts(String... ids) {
    428         final ArrayList<ShortcutInfo> ret = new ArrayList();
    429         for (String id : ids) {
    430             ret.add(makeShortcut(id));
    431         }
    432         return ret;
    433     }
    434 
    435     protected ShortcutInfo.Builder makeShortcutBuilder(String id) {
    436         return new ShortcutInfo.Builder(getCurrentCallerContext(), id);
    437     }
    438 
    439     /**
    440      * Make a shortcut with details.
    441      */
    442     protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
    443             Icon icon, Intent intent, int rank) {
    444         final ShortcutInfo.Builder b = makeShortcutBuilder(id)
    445                 .setShortLabel(shortLabel)
    446                 .setRank(rank)
    447                 .setIntent(intent);
    448         if (activity != null) {
    449             b.setActivity(activity);
    450         } else if (mTargetActivityOverride != null) {
    451             b.setActivity(mTargetActivityOverride);
    452         }
    453         if (icon != null) {
    454             b.setIcon(icon);
    455         }
    456         return b.build();
    457     }
    458 
    459     /**
    460      * Make an intent.
    461      */
    462     protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
    463         final Intent intent = new Intent(action);
    464         intent.setComponent(makeComponent(clazz));
    465         intent.replaceExtras(makeBundle(bundleKeysAndValues));
    466         return intent;
    467     }
    468 
    469     /**
    470      * Make an component name, with the client context.
    471      */
    472     @NonNull
    473     protected ComponentName makeComponent(Class<?> clazz) {
    474         return new ComponentName(getCurrentCallerContext(), clazz);
    475     }
    476 
    477     protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
    478             String shortcutId) {
    479         return getIconAsLauncher(launcherContext, packageName, shortcutId, /* withBadge=*/ true);
    480     }
    481 
    482     protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
    483             String shortcutId, boolean withBadge) {
    484         runWithNoStrictMode(() -> setDefaultLauncher(getInstrumentation(), launcherContext));
    485 
    486         final AtomicReference<Drawable> ret = new AtomicReference<>();
    487 
    488         runWithCallerWithNoStrictMode(launcherContext, () -> {
    489             final ShortcutQuery q = new ShortcutQuery()
    490                     .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
    491                                     | ShortcutQuery.FLAG_MATCH_MANIFEST
    492                                     | ShortcutQuery.FLAG_MATCH_PINNED
    493                                     | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY)
    494                     .setPackage(packageName)
    495                     .setShortcutIds(list(shortcutId));
    496             final List<ShortcutInfo> found = getLauncherApps().getShortcuts(q, getUserHandle());
    497 
    498             assertEquals("Shortcut not found", 1, found.size());
    499 
    500             if (withBadge) {
    501                 ret.set(getLauncherApps().getShortcutBadgedIconDrawable(found.get(0), 0));
    502             } else {
    503                 ret.set(getLauncherApps().getShortcutIconDrawable(found.get(0), 0));
    504             }
    505         });
    506         return ret.get();
    507     }
    508 
    509     protected void assertIconDimensions(Context launcherContext, String packageName,
    510             String shortcutId, Icon expectedIcon) {
    511         final Drawable actual = getIconAsLauncher(launcherContext, packageName, shortcutId);
    512         if (actual == null && expectedIcon == null) {
    513             return; // okay
    514         }
    515         final Drawable expected = expectedIcon.loadDrawable(getTestContext());
    516         assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
    517         assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
    518     }
    519 
    520     protected void assertIconDimensions(Icon expectedIcon, Drawable actual) {
    521         if (actual == null && expectedIcon == null) {
    522             return; // okay
    523         }
    524         final Drawable expected = expectedIcon.loadDrawable(getTestContext());
    525 
    526         if (expected instanceof AdaptiveIconDrawable) {
    527             assertTrue(actual instanceof AdaptiveIconDrawable);
    528         }
    529         assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
    530         assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
    531     }
    532 
    533     protected Icon loadPackageDrawableIcon(Context packageContext, String resName)
    534             throws Exception {
    535         final Resources res = getTestContext().getPackageManager().getResourcesForApplication(
    536                 packageContext.getPackageName());
    537 
    538         // Note the resource package names don't have the numbers.
    539         final int id = res.getIdentifier(resName, "drawable",
    540                 "android.content.pm.cts.shortcutmanager.packages");
    541         if (id == 0) {
    542             fail("Drawable " + resName + " is not found in package "
    543                     + packageContext.getPackageName());
    544         }
    545         return Icon.createWithResource(packageContext, id);
    546     }
    547 
    548     protected Icon loadCallerDrawableIcon(String resName) throws Exception {
    549         return loadPackageDrawableIcon(getCurrentCallerContext(), resName);
    550     }
    551 
    552     protected List<ShortcutInfo> getShortcutsAsLauncher(int flags, String packageName) {
    553         return getShortcutsAsLauncher(flags, packageName, null, 0, null);
    554     }
    555 
    556     protected List<ShortcutInfo> getShortcutsAsLauncher(
    557             int flags, String packageName, String activityName,
    558             long changedSince, List<String> ids) {
    559         final ShortcutQuery q = new ShortcutQuery();
    560         q.setQueryFlags(flags);
    561         if (packageName != null) {
    562             q.setPackage(packageName);
    563             if (activityName != null) {
    564                 q.setActivity(new ComponentName(packageName,
    565                         "android.content.pm.cts.shortcutmanager.packages." + activityName));
    566             }
    567         }
    568         q.setChangedSince(changedSince);
    569         if (ids != null && ids.size() > 0) {
    570             q.setShortcutIds(ids);
    571         }
    572         return getLauncherApps().getShortcuts(q, getUserHandle());
    573     }
    574 }
    575