1 /* 2 * Copyright (C) 2021 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 17 package com.android.car.carlauncher; 18 19 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 20 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 21 import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 22 23 import static com.android.car.carlauncher.AppLauncherUtils.APP_TYPE_LAUNCHABLES; 24 import static com.android.car.carlauncher.AppLauncherUtils.APP_TYPE_MEDIA_SERVICES; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 import static org.mockito.ArgumentMatchers.any; 30 import static org.mockito.ArgumentMatchers.anyInt; 31 import static org.mockito.ArgumentMatchers.anyString; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.never; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.car.Car; 39 import android.car.content.pm.CarPackageManager; 40 import android.car.media.CarMediaManager; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.pm.ActivityInfo; 44 import android.content.pm.ApplicationInfo; 45 import android.content.pm.LauncherApps; 46 import android.content.pm.PackageManager; 47 import android.content.pm.ResolveInfo; 48 import android.content.pm.ServiceInfo; 49 import android.service.media.MediaBrowserService; 50 import android.util.ArraySet; 51 52 import androidx.test.ext.junit.runners.AndroidJUnit4; 53 import androidx.test.filters.SmallTest; 54 import androidx.test.platform.app.InstrumentationRegistry; 55 56 import org.junit.Before; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.mockito.Mock; 60 import org.mockito.MockitoAnnotations; 61 import org.xmlpull.v1.XmlPullParser; 62 import org.xmlpull.v1.XmlPullParserException; 63 import org.xmlpull.v1.XmlPullParserFactory; 64 65 import java.io.StringReader; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.Collections; 69 import java.util.List; 70 import java.util.function.Consumer; 71 import java.util.function.Predicate; 72 73 @RunWith(AndroidJUnit4.class) 74 @SmallTest 75 public final class AppLauncherUtilsTest { 76 private static final String TEST_DISABLED_APP_1 = "com.android.car.test.disabled1"; 77 private static final String TEST_DISABLED_APP_2 = "com.android.car.test.disabled2"; 78 private static final String TEST_ENABLED_APP = "com.android.car.test.enabled"; 79 private static final String TEST_VIDEO_APP = "com.android.car.test.video"; 80 81 private static final Predicate<ResolveInfo> MATCH_NO_APP = (resolveInfo) -> false; 82 83 @Mock 84 private Context mMockContext; 85 @Mock 86 private LauncherApps mMockLauncherApps; 87 @Mock 88 private PackageManager mMockPackageManager; 89 90 private CarMediaManager mCarMediaManager; 91 private CarPackageManager mCarPackageManager; 92 93 private XmlPullParserFactory mParserFactory; 94 95 @Before setUp()96 public void setUp() throws XmlPullParserException { 97 MockitoAnnotations.initMocks(this); 98 Car car = Car.createCar(mMockContext); 99 mCarPackageManager = (CarPackageManager) car.getCarManager(Car.PACKAGE_SERVICE); 100 mCarMediaManager = (CarMediaManager) car.getCarManager(Car.CAR_MEDIA_SERVICE); 101 102 mParserFactory = XmlPullParserFactory.newInstance(); 103 mParserFactory.setNamespaceAware(true); 104 } 105 106 @Test testGetLauncherAppsWithEnableAndLaunchDisabledApps()107 public void testGetLauncherAppsWithEnableAndLaunchDisabledApps() { 108 mockPackageManagerQueries(); 109 injectApplicationEnabledSetting(COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); 110 111 AppLauncherUtils.LauncherAppsInfo launcherAppsInfo = AppLauncherUtils.getLauncherApps( 112 /* appsToHide= */ new ArraySet<>(), /* customMediaComponents= */ new ArraySet<>(), 113 /* appTypes= */ APP_TYPE_LAUNCHABLES + APP_TYPE_MEDIA_SERVICES, 114 /* openMediaCenter= */ false, mMockLauncherApps, mCarPackageManager, 115 mMockPackageManager, MATCH_NO_APP, mCarMediaManager); 116 117 List<AppMetaData> appMetaData = launcherAppsInfo.getLaunchableComponentsList(); 118 119 // mMockLauncherApps is never stubbed, only services & disabled activities are expected. 120 assertEquals(3, appMetaData.size()); 121 122 injectApplicationEnabledSetting(COMPONENT_ENABLED_STATE_ENABLED); 123 launchAllApps(appMetaData); 124 125 verify(mMockPackageManager).setApplicationEnabledSetting( 126 eq(TEST_DISABLED_APP_1), eq(COMPONENT_ENABLED_STATE_ENABLED), eq(0)); 127 verify(mMockPackageManager, times(2)).getApplicationEnabledSetting(eq(TEST_DISABLED_APP_1)); 128 129 verify(mMockPackageManager).setApplicationEnabledSetting( 130 eq(TEST_DISABLED_APP_2), eq(COMPONENT_ENABLED_STATE_ENABLED), eq(0)); 131 verify(mMockPackageManager, times(2)).getApplicationEnabledSetting(eq(TEST_DISABLED_APP_2)); 132 133 verify(mMockContext, times(2)).startActivity(any(), any()); 134 } 135 136 @Test testGetLauncherAppsWithNotEnablingEnabledApps()137 public void testGetLauncherAppsWithNotEnablingEnabledApps() { 138 mockPackageManagerQueries(); 139 injectApplicationEnabledSetting(COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); 140 141 AppLauncherUtils.LauncherAppsInfo launcherAppsInfo = AppLauncherUtils.getLauncherApps( 142 /* appsToHide= */ new ArraySet<>(), /* customMediaComponents= */ new ArraySet<>(), 143 /* appTypes= */ APP_TYPE_LAUNCHABLES + APP_TYPE_MEDIA_SERVICES, 144 /* openMediaCenter= */ false, mMockLauncherApps, mCarPackageManager, 145 mMockPackageManager, MATCH_NO_APP, mCarMediaManager); 146 147 List<AppMetaData> appMetaData = launcherAppsInfo.getLaunchableComponentsList(); 148 149 // mMockLauncherApps is never stubbed, only services & disabled activities are expected. 150 assertEquals(3, appMetaData.size()); 151 152 injectApplicationEnabledSetting(COMPONENT_ENABLED_STATE_ENABLED); 153 launchAllApps(appMetaData); 154 155 verify(mMockPackageManager, never()).setApplicationEnabledSetting( 156 eq(TEST_ENABLED_APP), anyInt(), eq(0)); 157 verify(mMockPackageManager, never()).getApplicationEnabledSetting(eq(TEST_ENABLED_APP)); 158 } 159 160 @Test testGetLauncherAppsWithEnableAndVideoApps()161 public void testGetLauncherAppsWithEnableAndVideoApps() { 162 mockPackageManagerQueriesForVideo(); 163 injectApplicationEnabledSetting(COMPONENT_ENABLED_STATE_ENABLED); 164 165 AppLauncherUtils.LauncherAppsInfo launcherAppsInfo = AppLauncherUtils.getLauncherApps( 166 /* appsToHide= */ new ArraySet<>(), /* customMediaComponents= */ new ArraySet<>(), 167 /* appTypes= */ APP_TYPE_LAUNCHABLES + APP_TYPE_MEDIA_SERVICES, 168 /* openMediaCenter= */ false, mMockLauncherApps, mCarPackageManager, 169 mMockPackageManager, new TestVideoAppPredicate(), mCarMediaManager); 170 171 // mMockLauncherApps is never stubbed, only services & disabled activities are expected. 172 List<AppMetaData> appMetaData = launcherAppsInfo.getLaunchableComponentsList(); 173 174 // TEST_VIDEO_APP should be filtered by TestVideoAppPredicate above. 175 assertEquals(1, appMetaData.size()); 176 assertEquals(TEST_ENABLED_APP, appMetaData.get(0).getPackageName()); 177 } 178 179 180 @Test getAutomotiveAppTypes()181 public void getAutomotiveAppTypes() { 182 // This test relies on test app's manifest & xml resources. 183 Context testContext = 184 InstrumentationRegistry.getInstrumentation().getContext(); 185 assertEquals( 186 Arrays.asList("video", "media"), 187 AppLauncherUtils.getAutomotiveAppTypes( 188 testContext.getPackageManager(), 189 testContext.getPackageName())); 190 } 191 192 @Test videoAppPredicate()193 public void videoAppPredicate() { 194 // This test relies on test app's manifest & xml resources. 195 Context testContext = 196 InstrumentationRegistry.getInstrumentation().getContext(); 197 Predicate<ResolveInfo> predicate = 198 new AppLauncherUtils.VideoAppPredicate(testContext.getPackageManager()); 199 200 assertTrue(predicate.test(constructServiceResolveInfo(testContext.getPackageName()))); 201 } 202 203 @Test invalidAutomotiveXml()204 public void invalidAutomotiveXml() { 205 StringBuilder hugeInvalidXml = new StringBuilder("<automotiveApp>"); 206 for (int i = 0; i < 65; i++) { 207 hugeInvalidXml.append("<uses name=\"video\"/>"); 208 } 209 hugeInvalidXml.append("</automotiveApp>"); 210 211 String[] invalidXmls = { 212 "NoTagsHere", 213 // Unknown tag. 214 "<foo/>", 215 // Manifest tag not expected. 216 "<automotiveApp><manifest/></automotiveApp>", 217 // Uses tag has missing name attribute. 218 "<automotiveApp><uses/></automotiveApp>", 219 // Uses tag has empty name attribute. 220 "<automotiveApp><uses name=\"\"/></automotiveApp>", 221 // Uses tag nested inside uses tag. 222 "<automotiveApp><uses name=\"video\"><uses name=\"media\"/></uses></automotiveApp>", 223 // Too many uses tags 224 hugeInvalidXml.toString() 225 }; 226 227 for (String invalidXml : invalidXmls) { 228 List<String> appTypes = 229 AppLauncherUtils.parseAutomotiveAppTypes(createPullParser(invalidXml)); 230 assertEquals(0, appTypes.size()); 231 } 232 } 233 createPullParser(String xmlText)234 private XmlPullParser createPullParser(String xmlText) { 235 try { 236 XmlPullParser parser = mParserFactory.newPullParser(); 237 parser.setInput(new StringReader(xmlText)); 238 return parser; 239 } catch (XmlPullParserException e) { 240 fail("Unexpected failure"); 241 return null; 242 } 243 } 244 mockPackageManagerQueriesForVideo()245 private void mockPackageManagerQueriesForVideo() { 246 when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenAnswer(args -> { 247 Intent intent = args.getArgument(0); 248 if (intent.getAction().equals(MediaBrowserService.SERVICE_INTERFACE)) { 249 return Arrays.asList( 250 constructServiceResolveInfo(TEST_ENABLED_APP), 251 constructServiceResolveInfo(TEST_VIDEO_APP)); 252 } 253 return new ArrayList<>(); 254 }); 255 } 256 mockPackageManagerQueries()257 private void mockPackageManagerQueries() { 258 when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenAnswer(args -> { 259 Intent intent = args.getArgument(0); 260 if (intent.getAction().equals(MediaBrowserService.SERVICE_INTERFACE)) { 261 return Collections.singletonList(constructServiceResolveInfo(TEST_ENABLED_APP)); 262 } 263 return new ArrayList<>(); 264 }); 265 when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenAnswer(args -> { 266 Intent intent = args.getArgument(0); 267 int flags = args.getArgument(1); 268 List<ResolveInfo> resolveInfoList = new ArrayList<>(); 269 if (intent.getAction().equals(Intent.ACTION_MAIN)) { 270 if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) != 0) { 271 resolveInfoList.add(constructActivityResolveInfo(TEST_DISABLED_APP_1)); 272 resolveInfoList.add(constructActivityResolveInfo(TEST_DISABLED_APP_2)); 273 } 274 resolveInfoList.add(constructActivityResolveInfo(TEST_ENABLED_APP)); 275 } 276 return resolveInfoList; 277 }); 278 } 279 injectApplicationEnabledSetting(int enabledState)280 private void injectApplicationEnabledSetting(int enabledState) { 281 when(mMockPackageManager.getApplicationEnabledSetting(anyString())) 282 .thenReturn(enabledState); 283 } 284 launchAllApps(List<AppMetaData> appMetaData)285 private void launchAllApps(List<AppMetaData> appMetaData) { 286 for (AppMetaData meta : appMetaData) { 287 Consumer<Context> launchCallback = meta.getLaunchCallback(); 288 launchCallback.accept(mMockContext); 289 } 290 } 291 constructActivityResolveInfo(String packageName)292 private static ResolveInfo constructActivityResolveInfo(String packageName) { 293 ResolveInfo info = new ResolveInfo(); 294 info.activityInfo = new ActivityInfo(); 295 info.activityInfo.packageName = packageName; 296 info.activityInfo.name = packageName + ".activity"; 297 info.activityInfo.applicationInfo = new ApplicationInfo(); 298 return info; 299 } 300 constructServiceResolveInfo(String packageName)301 private static ResolveInfo constructServiceResolveInfo(String packageName) { 302 ResolveInfo info = new ResolveInfo(); 303 info.serviceInfo = new ServiceInfo(); 304 info.serviceInfo.packageName = packageName; 305 info.serviceInfo.name = packageName + ".service"; 306 info.serviceInfo.applicationInfo = new ApplicationInfo(); 307 return info; 308 } 309 310 /** Test sub-class of VideoAppPredicate that only matches TEST_VIDEO_APP package-name */ 311 static class TestVideoAppPredicate extends AppLauncherUtils.VideoAppPredicate { TestVideoAppPredicate()312 TestVideoAppPredicate() { 313 super(/* packageManager= */ null); 314 } 315 316 @Override test(ResolveInfo resolveInfo)317 public boolean test(ResolveInfo resolveInfo) { 318 return TEST_VIDEO_APP.equals(super.getPackageName(resolveInfo)); 319 } 320 } 321 } 322