1 /* 2 * Copyright (C) 2018 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.car.settings.CarSettings.Secure.KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE; 20 21 import static java.lang.annotation.RetentionPolicy.SOURCE; 22 23 import android.app.Activity; 24 import android.app.ActivityManager; 25 import android.app.ActivityOptions; 26 import android.app.admin.DevicePolicyManager; 27 import android.car.Car; 28 import android.car.CarNotConnectedException; 29 import android.car.content.pm.CarPackageManager; 30 import android.car.media.CarMediaManager; 31 import android.content.ComponentName; 32 import android.content.ContentResolver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.LauncherActivityInfo; 37 import android.content.pm.LauncherApps; 38 import android.content.pm.PackageManager; 39 import android.content.pm.ResolveInfo; 40 import android.net.Uri; 41 import android.os.Bundle; 42 import android.os.Process; 43 import android.os.UserHandle; 44 import android.os.UserManager; 45 import android.provider.Settings; 46 import android.service.media.MediaBrowserService; 47 import android.text.TextUtils; 48 import android.util.ArraySet; 49 import android.util.Log; 50 import android.util.Pair; 51 import android.view.View; 52 53 import androidx.annotation.IntDef; 54 import androidx.annotation.NonNull; 55 import androidx.annotation.Nullable; 56 57 import com.android.car.ui.shortcutspopup.CarUiShortcutsPopup; 58 59 import com.google.common.collect.Sets; 60 61 import java.lang.annotation.Retention; 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.Collections; 65 import java.util.Comparator; 66 import java.util.HashMap; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.Objects; 70 import java.util.Set; 71 import java.util.function.Consumer; 72 73 /** 74 * Util class that contains helper method used by app launcher classes. 75 */ 76 public class AppLauncherUtils { 77 private static final String TAG = "AppLauncherUtils"; 78 private static final String ANDROIDX_CAR_APP_LAUNCHABLE = "androidx.car.app.launchable"; 79 80 @Retention(SOURCE) 81 @IntDef({APP_TYPE_LAUNCHABLES, APP_TYPE_MEDIA_SERVICES}) 82 @interface AppTypes {} 83 84 static final int APP_TYPE_LAUNCHABLES = 1; 85 static final int APP_TYPE_MEDIA_SERVICES = 2; 86 87 static final String PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR = ";"; 88 89 // Max no. of uses tags in automotiveApp XML. This is an arbitrary limit to be defensive 90 // to bad input. 91 private static final int MAX_APP_TYPES = 64; 92 private static final String PACKAGE_URI_PREFIX = "package:"; 93 AppLauncherUtils()94 private AppLauncherUtils() { 95 } 96 97 /** 98 * Comparator for {@link AppMetaData} that sorts the list 99 * by the "displayName" property in ascending order. 100 */ 101 static final Comparator<AppMetaData> ALPHABETICAL_COMPARATOR = Comparator 102 .comparing(AppMetaData::getDisplayName, String::compareToIgnoreCase); 103 104 /** 105 * Helper method that launches the app given the app's AppMetaData. 106 * 107 * @param app the requesting app's AppMetaData 108 */ launchApp(Context context, Intent intent)109 static void launchApp(Context context, Intent intent) { 110 ActivityOptions options = ActivityOptions.makeBasic(); 111 options.setLaunchDisplayId(context.getDisplayId()); 112 context.startActivity(intent, options.toBundle()); 113 } 114 115 /** Bundles application and services info. */ 116 static class LauncherAppsInfo { 117 /* 118 * Map of all car launcher components' (including launcher activities and media services) 119 * metadata keyed by ComponentName. 120 */ 121 private final Map<ComponentName, AppMetaData> mLaunchables; 122 123 /** Map of all the media services keyed by ComponentName. */ 124 private final Map<ComponentName, ResolveInfo> mMediaServices; 125 LauncherAppsInfo(@onNull Map<ComponentName, AppMetaData> launchablesMap, @NonNull Map<ComponentName, ResolveInfo> mediaServices)126 LauncherAppsInfo(@NonNull Map<ComponentName, AppMetaData> launchablesMap, 127 @NonNull Map<ComponentName, ResolveInfo> mediaServices) { 128 mLaunchables = launchablesMap; 129 mMediaServices = mediaServices; 130 } 131 132 /** Returns true if all maps are empty. */ isEmpty()133 boolean isEmpty() { 134 return mLaunchables.isEmpty() && mMediaServices.isEmpty(); 135 } 136 137 /** 138 * Returns whether the given componentName is a media service. 139 */ isMediaService(ComponentName componentName)140 boolean isMediaService(ComponentName componentName) { 141 return mMediaServices.containsKey(componentName); 142 } 143 144 /** Returns the {@link AppMetaData} for the given componentName. */ 145 @Nullable getAppMetaData(ComponentName componentName)146 AppMetaData getAppMetaData(ComponentName componentName) { 147 return mLaunchables.get(componentName); 148 } 149 150 /** Returns a new list of all launchable components' {@link AppMetaData}. */ 151 @NonNull getLaunchableComponentsList()152 List<AppMetaData> getLaunchableComponentsList() { 153 return new ArrayList<>(mLaunchables.values()); 154 } 155 156 /** Returns list of Media Services for the launcher **/ 157 @NonNull getMediaServices()158 Map<ComponentName, ResolveInfo> getMediaServices() { 159 return mMediaServices; 160 } 161 } 162 163 private final static LauncherAppsInfo EMPTY_APPS_INFO = new LauncherAppsInfo( 164 Collections.emptyMap(), Collections.emptyMap()); 165 166 /* 167 * Gets the media source in a given package. If there are multiple sources in the package, 168 * returns the first one. 169 */ getMediaSource(@onNull PackageManager packageManager, @NonNull String packageName)170 static ComponentName getMediaSource(@NonNull PackageManager packageManager, 171 @NonNull String packageName) { 172 Intent mediaIntent = new Intent(); 173 mediaIntent.setPackage(packageName); 174 mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE); 175 176 List<ResolveInfo> mediaServices = packageManager.queryIntentServices(mediaIntent, 177 PackageManager.GET_RESOLVED_FILTER); 178 179 if (mediaServices == null || mediaServices.isEmpty()) { 180 return null; 181 } 182 String defaultService = mediaServices.get(0).serviceInfo.name; 183 if (!TextUtils.isEmpty(defaultService)) { 184 return new ComponentName(packageName, defaultService); 185 } 186 return null; 187 } 188 189 /** 190 * Gets all the components that we want to see in the launcher in unsorted order, including 191 * launcher activities and media services. 192 * 193 * @param appsToHide A (possibly empty) list of apps (package names) to hide 194 * @param appTypes Types of apps to show (e.g.: all, or media sources only) 195 * @param openMediaCenter Whether launcher should navigate to media center when the 196 * user selects a media source. 197 * @param launcherApps The {@link LauncherApps} system service 198 * @param carPackageManager The {@link CarPackageManager} system service 199 * @param packageManager The {@link PackageManager} system service 200 * of such apps are always excluded. 201 * @param carMediaManager The {@link CarMediaManager} system service 202 * @return a new {@link LauncherAppsInfo} 203 */ 204 @NonNull getLauncherApps( Context context, @NonNull Set<String> appsToHide, @AppTypes int appTypes, boolean openMediaCenter, LauncherApps launcherApps, CarPackageManager carPackageManager, PackageManager packageManager, CarMediaManager carMediaManager, ShortcutsListener shortcutsListener, String mirroringAppPkgName, Intent mirroringAppRedirect)205 static LauncherAppsInfo getLauncherApps( 206 Context context, 207 @NonNull Set<String> appsToHide, 208 @AppTypes int appTypes, 209 boolean openMediaCenter, 210 LauncherApps launcherApps, 211 CarPackageManager carPackageManager, 212 PackageManager packageManager, 213 CarMediaManager carMediaManager, 214 ShortcutsListener shortcutsListener, 215 String mirroringAppPkgName, 216 Intent mirroringAppRedirect) { 217 218 if (launcherApps == null || carPackageManager == null || packageManager == null 219 || carMediaManager == null) { 220 return EMPTY_APPS_INFO; 221 } 222 223 // Using new list since we require a mutable list to do removeIf. 224 List<ResolveInfo> mediaServices = new ArrayList<>(); 225 mediaServices.addAll( 226 packageManager.queryIntentServices( 227 new Intent(MediaBrowserService.SERVICE_INTERFACE), 228 PackageManager.GET_RESOLVED_FILTER)); 229 230 List<LauncherActivityInfo> availableActivities = 231 launcherApps.getActivityList(null, Process.myUserHandle()); 232 233 int launchablesSize = mediaServices.size() + availableActivities.size(); 234 Map<ComponentName, AppMetaData> launchablesMap = new HashMap<>(launchablesSize); 235 Map<ComponentName, ResolveInfo> mediaServicesMap = new HashMap<>(mediaServices.size()); 236 Set<String> mEnabledPackages = new ArraySet<>(launchablesSize); 237 238 Set<String> customMediaComponents = Sets.newHashSet( 239 context.getResources().getStringArray( 240 com.android.car.media.common.R.array.custom_media_packages)); 241 242 // Process media services 243 if ((appTypes & APP_TYPE_MEDIA_SERVICES) != 0) { 244 for (ResolveInfo info : mediaServices) { 245 String packageName = info.serviceInfo.packageName; 246 String className = info.serviceInfo.name; 247 ComponentName componentName = new ComponentName(packageName, className); 248 mediaServicesMap.put(componentName, info); 249 mEnabledPackages.add(packageName); 250 if (shouldAddToLaunchables(packageManager, componentName, appsToHide, 251 customMediaComponents, appTypes, APP_TYPE_MEDIA_SERVICES)) { 252 final boolean isDistractionOptimized = true; 253 254 Intent intent = new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE); 255 intent.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, componentName.flattenToString()); 256 257 CharSequence displayName = info.serviceInfo.loadLabel(packageManager); 258 AppMetaData appMetaData = new AppMetaData( 259 displayName, 260 componentName, 261 info.serviceInfo.loadIcon(packageManager), 262 isDistractionOptimized, 263 /* isMirroring = */ false, 264 contextArg -> { 265 if (openMediaCenter) { 266 AppLauncherUtils.launchApp(contextArg, intent); 267 } else { 268 selectMediaSourceAndFinish(contextArg, componentName, 269 carMediaManager); 270 } 271 }, 272 buildShortcuts(packageName, displayName, shortcutsListener)); 273 launchablesMap.put(componentName, appMetaData); 274 } 275 } 276 } 277 278 // Process activities 279 if ((appTypes & APP_TYPE_LAUNCHABLES) != 0) { 280 for (LauncherActivityInfo info : availableActivities) { 281 ComponentName componentName = info.getComponentName(); 282 String packageName = componentName.getPackageName(); 283 mEnabledPackages.add(packageName); 284 if (shouldAddToLaunchables(packageManager, componentName, appsToHide, 285 customMediaComponents, appTypes, APP_TYPE_LAUNCHABLES)) { 286 boolean isDistractionOptimized = 287 isActivityDistractionOptimized(carPackageManager, packageName, 288 info.getName()); 289 290 Intent intent = new Intent(Intent.ACTION_MAIN) 291 .setComponent(componentName) 292 .addCategory(Intent.CATEGORY_LAUNCHER) 293 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 294 295 CharSequence displayName = info.getLabel(); 296 boolean isMirroring = packageName.equals(mirroringAppPkgName); 297 AppMetaData appMetaData = new AppMetaData( 298 displayName, 299 componentName, 300 info.getBadgedIcon(0), 301 isDistractionOptimized, 302 isMirroring, 303 contextArg -> { 304 if (packageName.equals(mirroringAppPkgName)) { 305 Log.d(TAG, "non-media service package name " 306 + "equals mirroring pkg name"); 307 } 308 AppLauncherUtils.launchApp(contextArg, 309 isMirroring ? mirroringAppRedirect : intent); 310 }, 311 buildShortcuts(packageName, displayName, shortcutsListener)); 312 launchablesMap.put(componentName, appMetaData); 313 } 314 } 315 316 List<ResolveInfo> disabledActivities = getDisabledActivities(context, packageManager, 317 mEnabledPackages); 318 for (ResolveInfo info : disabledActivities) { 319 String packageName = info.activityInfo.packageName; 320 String className = info.activityInfo.name; 321 ComponentName componentName = new ComponentName(packageName, className); 322 if (!shouldAddToLaunchables(packageManager, componentName, appsToHide, 323 customMediaComponents, appTypes, APP_TYPE_LAUNCHABLES)) { 324 continue; 325 } 326 boolean isDistractionOptimized = 327 isActivityDistractionOptimized(carPackageManager, packageName, className); 328 329 Intent intent = new Intent(Intent.ACTION_MAIN) 330 .setComponent(componentName) 331 .addCategory(Intent.CATEGORY_LAUNCHER) 332 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 333 334 CharSequence displayName = info.activityInfo.loadLabel(packageManager); 335 AppMetaData appMetaData = new AppMetaData( 336 displayName, 337 componentName, 338 info.activityInfo.loadIcon(packageManager), 339 isDistractionOptimized, 340 /* isMirroring = */ false, 341 contextArg -> { 342 packageManager.setApplicationEnabledSetting(packageName, 343 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); 344 /* Fetch the current enabled setting to make sure the setting is synced 345 * before launching the activity. Otherwise, the activity may not 346 * launch. 347 */ 348 if (packageManager.getApplicationEnabledSetting(packageName) 349 != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 350 throw new IllegalStateException( 351 "Failed to enable the disabled package [" + packageName 352 + "]"); 353 } 354 Log.i(TAG, "Successfully enabled package [" + packageName + "]"); 355 AppLauncherUtils.launchApp(contextArg, intent); 356 }, 357 buildShortcuts(packageName, displayName, shortcutsListener)); 358 launchablesMap.put(componentName, appMetaData); 359 } 360 } 361 362 return new LauncherAppsInfo(launchablesMap, mediaServicesMap); 363 } 364 365 /** 366 * Determine if the given media browse service is supported through media templates 367 */ isMediaTemplate(PackageManager pm, ComponentName mbsComponentName)368 public static boolean isMediaTemplate(PackageManager pm, ComponentName mbsComponentName) { 369 Bundle metaData = null; 370 try { 371 metaData = pm.getServiceInfo( 372 mbsComponentName, 373 PackageManager.GET_META_DATA) 374 .metaData; 375 } catch (PackageManager.NameNotFoundException e) { 376 Log.e(TAG, "Service for Component " + mbsComponentName + " was not found"); 377 return false; 378 } 379 380 if (metaData != null && metaData.containsKey(ANDROIDX_CAR_APP_LAUNCHABLE)) { 381 boolean launchable = metaData.getBoolean(ANDROIDX_CAR_APP_LAUNCHABLE); 382 if (Log.isLoggable(TAG, Log.DEBUG)) { 383 Log.d(TAG, "MBS for " + mbsComponentName 384 + " is opted " + (launchable ? "in" : "out")); 385 } 386 return launchable; 387 } 388 389 // No explicit declaration. For backward compatibility, keep MBS only for media apps 390 String packageName = mbsComponentName.getPackageName(); 391 try { 392 if (isLegacyMediaApp(pm, packageName)) { 393 Log.d(TAG, "Including " + mbsComponentName + " for media app " + packageName); 394 return true; 395 } 396 } catch (PackageManager.NameNotFoundException e) { 397 Log.e(TAG, "Package " + packageName + " was not found"); 398 } 399 Log.d(TAG, "Skipping MBS for " + mbsComponentName 400 + " belonging to non media app " + packageName); 401 return false; 402 } 403 404 /** Determine if it's a legacy media app that doesn't have a launcher activity*/ isLegacyMediaApp( PackageManager pm, String packageName)405 private static boolean isLegacyMediaApp( 406 PackageManager pm, String packageName) throws PackageManager.NameNotFoundException { 407 // a media app doesn't have a launcher activity 408 return pm.getLaunchIntentForPackage(packageName) == null; 409 } 410 buildShortcuts(String packageName, CharSequence displayName, ShortcutsListener shortcutsListener)411 private static Consumer<Pair<Context, View>> buildShortcuts(String packageName, 412 CharSequence displayName, ShortcutsListener shortcutsListener) { 413 return pair -> { 414 CarUiShortcutsPopup carUiShortcutsPopup = new CarUiShortcutsPopup.Builder() 415 .addShortcut( 416 buildForceStopShortcut(packageName, displayName, pair.first, 417 shortcutsListener) 418 ) 419 .addShortcut(buildAppInfoShortcut(packageName, pair.first)) 420 .build(pair.first, 421 pair.second 422 ); 423 424 carUiShortcutsPopup.show(); 425 shortcutsListener.onShortcutsShow(carUiShortcutsPopup); 426 }; 427 } 428 buildForceStopShortcut(String packageName, CharSequence displayName, Context context, ShortcutsListener shortcutsListener)429 private static CarUiShortcutsPopup.ShortcutItem buildForceStopShortcut(String packageName, 430 CharSequence displayName, 431 Context context, 432 ShortcutsListener shortcutsListener) { 433 return new CarUiShortcutsPopup.ShortcutItem() { 434 @Override 435 public CarUiShortcutsPopup.ItemData data() { 436 return new CarUiShortcutsPopup.ItemData( 437 R.drawable.ic_force_stop_caution_icon, 438 context.getResources().getString( 439 R.string.app_launcher_stop_app_action)); 440 } 441 442 @Override 443 public boolean onClick() { 444 shortcutsListener.onShortcutsItemClick(packageName, displayName, 445 /* allowStopApp= */ true); 446 return true; 447 } 448 449 @Override 450 public boolean isEnabled() { 451 return shouldAllowStopApp(packageName, context); 452 } 453 }; 454 } 455 456 private static CarUiShortcutsPopup.ShortcutItem buildAppInfoShortcut(String packageName, 457 Context context) { 458 return new CarUiShortcutsPopup.ShortcutItem() { 459 @Override 460 public CarUiShortcutsPopup.ItemData data() { 461 return new CarUiShortcutsPopup.ItemData( 462 R.drawable.ic_app_info, 463 context.getResources().getString( 464 R.string.app_launcher_app_info_action)); 465 } 466 467 @Override 468 public boolean onClick() { 469 Uri packageURI = Uri.parse(PACKAGE_URI_PREFIX + packageName); 470 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 471 packageURI); 472 context.startActivity(intent); 473 return true; 474 } 475 476 @Override 477 public boolean isEnabled() { 478 return true; 479 } 480 }; 481 } 482 483 /** 484 * Force stops an app 485 * <p>Note: Uses hidden apis<p/> 486 */ 487 public static void forceStop(String packageName, Context context, CharSequence displayName, 488 CarMediaManager carMediaManager, Map<ComponentName, ResolveInfo> mediaServices, 489 ShortcutsListener listener) { 490 ActivityManager activityManager = context.getSystemService(ActivityManager.class); 491 if (activityManager != null) { 492 maybeReplaceMediaSource(carMediaManager, packageName, mediaServices, 493 CarMediaManager.MEDIA_SOURCE_MODE_BROWSE); 494 maybeReplaceMediaSource(carMediaManager, packageName, mediaServices, 495 CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK); 496 activityManager.forceStopPackage(packageName); 497 String message = context.getResources() 498 .getString(R.string.app_launcher_stop_app_success_toast_text, displayName); 499 listener.onStopAppSuccess(message); 500 } 501 } 502 503 private static boolean isCurrentMediaSource(CarMediaManager carMediaManager, 504 String packageName, @CarMediaManager.MediaSourceMode int mode) { 505 ComponentName componentName = carMediaManager.getMediaSource(mode); 506 if (componentName == null) { 507 //There is no current media source. 508 return false; 509 } 510 return Objects.equals(componentName.getPackageName(), packageName); 511 } 512 513 /*** 514 * Updates the MediaSource to second most recent if {@code packageName} is current media source 515 * Sets to MediaSource to null if no previous MediaSource exists. 516 */ 517 private static void maybeReplaceMediaSource(CarMediaManager carMediaManager, String packageName, 518 Map<ComponentName, ResolveInfo> allMediaServices, 519 @CarMediaManager.MediaSourceMode int mode) { 520 if (!isCurrentMediaSource(carMediaManager, packageName, mode)) { 521 return; 522 } 523 //find the most recent source from history not equal to force-stopping package. 524 List<ComponentName> mediaSources = carMediaManager.getLastMediaSources(mode); 525 ComponentName componentName = mediaSources.stream().filter(c-> (!c.getPackageName() 526 .equals(packageName))).findFirst().orElse(null); 527 if (componentName == null) { 528 //no recent package found, find from all available media services. 529 componentName = allMediaServices.keySet().stream().filter( 530 c -> (!c.getPackageName().equals(packageName))).findFirst().orElse(null); 531 if (componentName == null) { 532 Log.e(TAG, "Stop-app, no alternative media service found"); 533 } 534 } 535 carMediaManager.setMediaSource(componentName, mode); 536 } 537 538 /** 539 * <p>Note: Uses hidden apis<p/> 540 * @return true if the user has restrictions to force stop an app with {@code appInfo} 541 */ 542 private static boolean hasUserRestriction(ApplicationInfo appInfo, Context context) { 543 String restriction = UserManager.DISALLOW_APPS_CONTROL; 544 UserManager userManager = context.getSystemService(UserManager.class); 545 if (userManager == null) { 546 Log.e(TAG, " Disabled because , UserManager is null"); 547 return true; 548 } 549 if (!userManager.hasUserRestriction(restriction)) { 550 return false; 551 } 552 UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); 553 if (userManager.hasBaseUserRestriction(restriction, user)) { 554 Log.d(TAG, " Disabled because " + user + " has " + restriction 555 + " restriction"); 556 return true; 557 } 558 // Not disabled for this User 559 return false; 560 } 561 562 /** 563 * <p>Note: uses hidden apis</p> 564 * 565 * @param packageName name of the package to stop the app 566 * @param context app context 567 * @return true if an app should show the Stop app action 568 */ 569 private static boolean shouldAllowStopApp(String packageName, Context context) { 570 DevicePolicyManager dm = context.getSystemService(DevicePolicyManager.class); 571 if (dm == null || dm.packageHasActiveAdmins(packageName)) { 572 return false; 573 } 574 try { 575 ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName, 576 PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA)); 577 // Show only if the User has no restrictions to force stop this app 578 if (hasUserRestriction(appInfo, context)) { 579 return false; 580 } 581 // Show only if the app is running 582 if ((appInfo.flags & ApplicationInfo.FLAG_STOPPED) == 0) { 583 return true; 584 } 585 } catch (PackageManager.NameNotFoundException e) { 586 Log.d(TAG, "shouldAllowStopApp() Package " + packageName + " was not found"); 587 } 588 return false; 589 } 590 591 private static List<ResolveInfo> getDisabledActivities(Context context, 592 PackageManager packageManager, Set<String> enabledPackages) { 593 ContentResolver contentResolverForUser = context.createContextAsUser( 594 UserHandle.getUserHandleForUid(Process.myUid()), /* flags= */ 0) 595 .getContentResolver(); 596 String settingsValue = Settings.Secure.getString(contentResolverForUser, 597 KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE); 598 Set<String> disabledPackages = TextUtils.isEmpty(settingsValue) ? new ArraySet<>() 599 : new ArraySet<>(Arrays.asList(settingsValue.split( 600 PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR))); 601 if (disabledPackages.isEmpty()) { 602 return Collections.emptyList(); 603 } 604 605 List<ResolveInfo> allActivities = packageManager.queryIntentActivities( 606 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER), 607 PackageManager.ResolveInfoFlags.of(PackageManager.GET_RESOLVED_FILTER 608 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS)); 609 610 List<ResolveInfo> disabledActivities = new ArrayList<>(); 611 for (int i = 0; i < allActivities.size(); ++i) { 612 ResolveInfo info = allActivities.get(i); 613 if (!enabledPackages.contains(info.activityInfo.packageName) 614 && disabledPackages.contains(info.activityInfo.packageName)) { 615 disabledActivities.add(info); 616 } 617 } 618 return disabledActivities; 619 } 620 621 private static boolean shouldAddToLaunchables(PackageManager packageManager, 622 @NonNull ComponentName componentName, 623 @NonNull Set<String> appsToHide, 624 @NonNull Set<String> customMediaComponents, 625 @AppTypes int appTypesToShow, 626 @AppTypes int componentAppType) { 627 if (appsToHide.contains(componentName.getPackageName())) { 628 return false; 629 } 630 switch (componentAppType) { 631 // Process media services 632 case APP_TYPE_MEDIA_SERVICES: 633 // For a media service in customMediaComponents, if its application's launcher 634 // activity will be shown in the Launcher, don't show the service's icon in the 635 // Launcher. 636 if (customMediaComponents.contains(componentName.flattenToString())) { 637 if ((appTypesToShow & APP_TYPE_LAUNCHABLES) != 0) { 638 if (Log.isLoggable(TAG, Log.DEBUG)) { 639 Log.d(TAG, "MBS for custom media app " + componentName 640 + " is skipped in app launcher"); 641 } 642 return false; 643 } 644 // Media switcher use case should still show 645 if (Log.isLoggable(TAG, Log.DEBUG)) { 646 Log.d(TAG, "MBS for custom media app " + componentName 647 + " is included in media switcher"); 648 } 649 return true; 650 } 651 // Only Keep MBS that is a media template 652 return isMediaTemplate(packageManager, componentName); 653 // Process activities 654 case APP_TYPE_LAUNCHABLES: 655 return true; 656 default: 657 Log.e(TAG, "Invalid componentAppType : " + componentAppType); 658 return false; 659 } 660 } 661 662 private static void selectMediaSourceAndFinish(Context context, ComponentName componentName, 663 CarMediaManager carMediaManager) { 664 try { 665 carMediaManager.setMediaSource(componentName, CarMediaManager.MEDIA_SOURCE_MODE_BROWSE); 666 if (context instanceof Activity) { 667 ((Activity) context).finish(); 668 } 669 } catch (CarNotConnectedException e) { 670 Log.e(TAG, "Car not connected", e); 671 } 672 } 673 674 /** 675 * Gets if an activity is distraction optimized. 676 * 677 * @param carPackageManager The {@link CarPackageManager} system service 678 * @param packageName The package name of the app 679 * @param activityName The requested activity name 680 * @return true if the supplied activity is distraction optimized 681 */ 682 static boolean isActivityDistractionOptimized( 683 CarPackageManager carPackageManager, String packageName, String activityName) { 684 boolean isDistractionOptimized = false; 685 // try getting distraction optimization info 686 try { 687 if (carPackageManager != null) { 688 isDistractionOptimized = 689 carPackageManager.isActivityDistractionOptimized(packageName, activityName); 690 } 691 } catch (CarNotConnectedException e) { 692 Log.e(TAG, "Car not connected when getting DO info", e); 693 } 694 return isDistractionOptimized; 695 } 696 697 /** 698 * Callback when a ShortcutsPopup View is shown 699 */ 700 protected interface ShortcutsListener { 701 702 void onShortcutsShow(CarUiShortcutsPopup carUiShortcutsPopup); 703 704 void onShortcutsItemClick(String packageName, CharSequence displayName, 705 boolean allowStopApp); 706 707 void onStopAppSuccess(String message); 708 } 709 } 710