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