• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ActivityOptions;
25 import android.car.Car;
26 import android.car.CarNotConnectedException;
27 import android.car.content.pm.CarPackageManager;
28 import android.car.media.CarMediaManager;
29 import android.content.ComponentName;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.LauncherActivityInfo;
35 import android.content.pm.LauncherApps;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Resources;
39 import android.content.res.XmlResourceParser;
40 import android.os.Process;
41 import android.os.UserHandle;
42 import android.provider.Settings;
43 import android.service.media.MediaBrowserService;
44 import android.text.TextUtils;
45 import android.util.ArraySet;
46 import android.util.Log;
47 
48 import androidx.annotation.IntDef;
49 import androidx.annotation.NonNull;
50 import androidx.annotation.Nullable;
51 import androidx.annotation.VisibleForTesting;
52 
53 import org.xmlpull.v1.XmlPullParser;
54 import org.xmlpull.v1.XmlPullParserException;
55 
56 import java.io.IOException;
57 import java.lang.annotation.Retention;
58 import java.util.ArrayDeque;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collections;
62 import java.util.Comparator;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.function.Predicate;
68 
69 /**
70  * Util class that contains helper method used by app launcher classes.
71  */
72 public class AppLauncherUtils {
73     private static final String TAG = "AppLauncherUtils";
74 
75     @Retention(SOURCE)
76     @IntDef({APP_TYPE_LAUNCHABLES, APP_TYPE_MEDIA_SERVICES})
77     @interface AppTypes {}
78     static final int APP_TYPE_LAUNCHABLES = 1;
79     static final int APP_TYPE_MEDIA_SERVICES = 2;
80 
81     private static final String TAG_AUTOMOTIVE_APP = "automotiveApp";
82     private static final String TAG_USES = "uses";
83     private static final String ATTRIBUTE_NAME = "name";
84     private static final String TYPE_VIDEO = "video";
85     static final String PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR = ";";
86 
87     // Max no. of uses tags in automotiveApp XML. This is an arbitrary limit to be defensive
88     // to bad input.
89     private static final int MAX_APP_TYPES = 64;
90 
AppLauncherUtils()91     private AppLauncherUtils() {
92     }
93 
94     /**
95      * Comparator for {@link AppMetaData} that sorts the list
96      * by the "displayName" property in ascending order.
97      */
98     static final Comparator<AppMetaData> ALPHABETICAL_COMPARATOR = Comparator
99             .comparing(AppMetaData::getDisplayName, String::compareToIgnoreCase);
100 
101     /**
102      * Helper method that launches the app given the app's AppMetaData.
103      *
104      * @param app the requesting app's AppMetaData
105      */
launchApp(Context context, Intent intent)106     static void launchApp(Context context, Intent intent) {
107         ActivityOptions options = ActivityOptions.makeBasic();
108         options.setLaunchDisplayId(context.getDisplayId());
109         context.startActivity(intent, options.toBundle());
110     }
111 
112     /** Bundles application and services info. */
113     static class LauncherAppsInfo {
114         /*
115          * Map of all car launcher components' (including launcher activities and media services)
116          * metadata keyed by ComponentName.
117          */
118         private final Map<ComponentName, AppMetaData> mLaunchables;
119 
120         /** Map of all the media services keyed by ComponentName. */
121         private final Map<ComponentName, ResolveInfo> mMediaServices;
122 
LauncherAppsInfo(@onNull Map<ComponentName, AppMetaData> launchablesMap, @NonNull Map<ComponentName, ResolveInfo> mediaServices)123         LauncherAppsInfo(@NonNull Map<ComponentName, AppMetaData> launchablesMap,
124                 @NonNull Map<ComponentName, ResolveInfo> mediaServices) {
125             mLaunchables = launchablesMap;
126             mMediaServices = mediaServices;
127         }
128 
129         /** Returns true if all maps are empty. */
isEmpty()130         boolean isEmpty() {
131             return mLaunchables.isEmpty() && mMediaServices.isEmpty();
132         }
133 
134         /**
135          * Returns whether the given componentName is a media service.
136          */
isMediaService(ComponentName componentName)137         boolean isMediaService(ComponentName componentName) {
138             return mMediaServices.containsKey(componentName);
139         }
140 
141         /** Returns the {@link AppMetaData} for the given componentName. */
142         @Nullable
getAppMetaData(ComponentName componentName)143         AppMetaData getAppMetaData(ComponentName componentName) {
144             return mLaunchables.get(componentName);
145         }
146 
147         /** Returns a new list of all launchable components' {@link AppMetaData}. */
148         @NonNull
getLaunchableComponentsList()149         List<AppMetaData> getLaunchableComponentsList() {
150             return new ArrayList<>(mLaunchables.values());
151         }
152     }
153 
154     private final static LauncherAppsInfo EMPTY_APPS_INFO = new LauncherAppsInfo(
155             Collections.emptyMap(), Collections.emptyMap());
156 
157     /*
158      * Gets the media source in a given package. If there are multiple sources in the package,
159      * returns the first one.
160      */
getMediaSource(@onNull PackageManager packageManager, @NonNull String packageName)161     static ComponentName getMediaSource(@NonNull PackageManager packageManager,
162             @NonNull String packageName) {
163         Intent mediaIntent = new Intent();
164         mediaIntent.setPackage(packageName);
165         mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE);
166 
167         List<ResolveInfo> mediaServices = packageManager.queryIntentServices(mediaIntent,
168                 PackageManager.GET_RESOLVED_FILTER);
169 
170         if (mediaServices == null || mediaServices.isEmpty()) {
171             return null;
172         }
173         String defaultService = mediaServices.get(0).serviceInfo.name;
174         if (!TextUtils.isEmpty(defaultService)) {
175             return new ComponentName(packageName, defaultService);
176         }
177         return null;
178     }
179 
180     /**
181      * Gets all the components that we want to see in the launcher in unsorted order, including
182      * launcher activities and media services.
183      *
184      * @param appsToHide            A (possibly empty) list of apps (package names) to hide
185      * @param customMediaComponents A (possibly empty) list of media components (component names)
186      *                              that shouldn't be shown in Launcher because their applications'
187      *                              launcher activities will be shown
188      * @param appTypes              Types of apps to show (e.g.: all, or media sources only)
189      * @param openMediaCenter       Whether launcher should navigate to media center when the
190      *                              user selects a media source.
191      * @param launcherApps          The {@link LauncherApps} system service
192      * @param carPackageManager     The {@link CarPackageManager} system service
193      * @param packageManager        The {@link PackageManager} system service
194      * @param videoAppPredicate     Predicate that checks if a given {@link ResolveInfo} resolves
195      *                              to a video app. See {@link #VideoAppPredicate}. Media-services
196      *                              of such apps are always excluded.
197      * @param carMediaManager       The {@link CarMediaManager} system service
198      * @return a new {@link LauncherAppsInfo}
199      */
200     @NonNull
getLauncherApps( Context context, @NonNull Set<String> appsToHide, @NonNull Set<String> customMediaComponents, @AppTypes int appTypes, boolean openMediaCenter, LauncherApps launcherApps, CarPackageManager carPackageManager, PackageManager packageManager, @NonNull Predicate<ResolveInfo> videoAppPredicate, CarMediaManager carMediaManager)201     static LauncherAppsInfo getLauncherApps(
202             Context context,
203             @NonNull Set<String> appsToHide,
204             @NonNull Set<String> customMediaComponents,
205             @AppTypes int appTypes,
206             boolean openMediaCenter,
207             LauncherApps launcherApps,
208             CarPackageManager carPackageManager,
209             PackageManager packageManager,
210             @NonNull Predicate<ResolveInfo> videoAppPredicate,
211             CarMediaManager carMediaManager) {
212 
213         if (launcherApps == null || carPackageManager == null || packageManager == null
214                 || carMediaManager == null) {
215             return EMPTY_APPS_INFO;
216         }
217 
218         // Using new list since we require a mutable list to do removeIf.
219         List<ResolveInfo> mediaServices = new ArrayList<>();
220         mediaServices.addAll(
221                 packageManager.queryIntentServices(
222                         new Intent(MediaBrowserService.SERVICE_INTERFACE),
223                         PackageManager.GET_RESOLVED_FILTER));
224         // Exclude Media Services from Video apps from being considered. These apps should offer a
225         // normal Launcher Activity as an entry point.
226         mediaServices.removeIf(videoAppPredicate);
227 
228         List<LauncherActivityInfo> availableActivities =
229                 launcherApps.getActivityList(null, Process.myUserHandle());
230 
231         int launchablesSize = mediaServices.size() + availableActivities.size();
232         Map<ComponentName, AppMetaData> launchablesMap = new HashMap<>(launchablesSize);
233         Map<ComponentName, ResolveInfo> mediaServicesMap = new HashMap<>(mediaServices.size());
234         Set<String> mEnabledPackages = new ArraySet<>(launchablesSize);
235 
236         // Process media services
237         if ((appTypes & APP_TYPE_MEDIA_SERVICES) != 0) {
238             for (ResolveInfo info : mediaServices) {
239                 String packageName = info.serviceInfo.packageName;
240                 String className = info.serviceInfo.name;
241                 ComponentName componentName = new ComponentName(packageName, className);
242                 mediaServicesMap.put(componentName, info);
243                 mEnabledPackages.add(packageName);
244                 if (shouldAddToLaunchables(componentName, appsToHide, customMediaComponents,
245                         appTypes, APP_TYPE_MEDIA_SERVICES)) {
246                     final boolean isDistractionOptimized = true;
247 
248                     Intent intent = new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE);
249                     intent.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, componentName.flattenToString());
250 
251                     AppMetaData appMetaData = new AppMetaData(
252                         info.serviceInfo.loadLabel(packageManager),
253                         componentName,
254                         info.serviceInfo.loadIcon(packageManager),
255                         isDistractionOptimized,
256                         contextArg -> {
257                             if (openMediaCenter) {
258                                 AppLauncherUtils.launchApp(contextArg, intent);
259                             } else {
260                                 selectMediaSourceAndFinish(contextArg, componentName,
261                                         carMediaManager);
262                             }
263                         },
264                         /* alternateLaunchCallback */ null);
265                     launchablesMap.put(componentName, appMetaData);
266                 }
267             }
268         }
269 
270         // Process activities
271         if ((appTypes & APP_TYPE_LAUNCHABLES) != 0) {
272             for (LauncherActivityInfo info : availableActivities) {
273                 ComponentName componentName = info.getComponentName();
274                 String packageName = componentName.getPackageName();
275                 mEnabledPackages.add(packageName);
276                 if (shouldAddToLaunchables(componentName, appsToHide, customMediaComponents,
277                         appTypes, APP_TYPE_LAUNCHABLES)) {
278                     boolean isDistractionOptimized =
279                         isActivityDistractionOptimized(carPackageManager, packageName,
280                             info.getName());
281 
282                     Intent intent = new Intent(Intent.ACTION_MAIN)
283                         .setComponent(componentName)
284                         .addCategory(Intent.CATEGORY_LAUNCHER)
285                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
286 
287                     AppMetaData appMetaData = new AppMetaData(
288                         info.getLabel(),
289                         componentName,
290                         info.getBadgedIcon(0),
291                         isDistractionOptimized,
292                         contextArg -> AppLauncherUtils.launchApp(contextArg, intent),
293                         /* alternateLaunchCallback */ null);
294                     launchablesMap.put(componentName, appMetaData);
295                 }
296             }
297 
298             List<ResolveInfo> disabledActivities = getDisabledActivities(context, packageManager,
299                     mEnabledPackages);
300             for (ResolveInfo info : disabledActivities) {
301                 String packageName = info.activityInfo.packageName;
302                 String className = info.activityInfo.name;
303                 ComponentName componentName = new ComponentName(packageName, className);
304                 if (!shouldAddToLaunchables(componentName, appsToHide, customMediaComponents,
305                         appTypes, APP_TYPE_LAUNCHABLES)) {
306                     continue;
307                 }
308                 boolean isDistractionOptimized =
309                         isActivityDistractionOptimized(carPackageManager, packageName, className);
310 
311                 Intent intent = new Intent(Intent.ACTION_MAIN)
312                         .setComponent(componentName)
313                         .addCategory(Intent.CATEGORY_LAUNCHER)
314                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
315 
316                 AppMetaData appMetaData = new AppMetaData(
317                         info.activityInfo.loadLabel(packageManager),
318                         componentName,
319                         info.activityInfo.loadIcon(packageManager),
320                         isDistractionOptimized,
321                         contextArg -> {
322                             packageManager.setApplicationEnabledSetting(packageName,
323                                     PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
324                             /* Fetch the current enabled setting to make sure the setting is synced
325                              * before launching the activity. Otherwise, the activity may not
326                              * launch.
327                              */
328                             if (packageManager.getApplicationEnabledSetting(packageName)
329                                     != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
330                                 throw new IllegalStateException(
331                                         "Failed to enable the disabled package [" + packageName
332                                                 + "]");
333                             }
334                             Log.i(TAG, "Successfully enabled package [" + packageName + "]");
335                             AppLauncherUtils.launchApp(contextArg, intent);
336                         },
337                         /* alternateLaunchCallback */ null);
338                 launchablesMap.put(componentName, appMetaData);
339             }
340         }
341 
342         return new LauncherAppsInfo(launchablesMap, mediaServicesMap);
343     }
344 
345     /**
346      * Predicate that can be used to check if a given {@link ResolveInfo} resolves to a Video app.
347      */
348     static class VideoAppPredicate implements Predicate<ResolveInfo> {
349         private final PackageManager mPackageManager;
350 
VideoAppPredicate(PackageManager packageManager)351         VideoAppPredicate(PackageManager packageManager) {
352             mPackageManager = packageManager;
353         }
354 
355         @Override
test(ResolveInfo resolveInfo)356         public boolean test(ResolveInfo resolveInfo) {
357             String packageName = resolveInfo != null ? getPackageName(resolveInfo) : null;
358             if (packageName == null) {
359                 Log.w(TAG, "Unable to determine packageName from resolveInfo");
360                 return false;
361             }
362             List<String> automotiveAppTypes =
363                     getAutomotiveAppTypes(mPackageManager, getPackageName(resolveInfo));
364             return automotiveAppTypes.contains(TYPE_VIDEO);
365         }
366 
getPackageName(ResolveInfo resolveInfo)367         protected String getPackageName(ResolveInfo resolveInfo) {
368             // A valid ResolveInfo should have exactly one of these set.
369             if (resolveInfo.activityInfo != null) {
370                 return resolveInfo.activityInfo.packageName;
371             }
372             if (resolveInfo.serviceInfo != null) {
373                 return resolveInfo.serviceInfo.packageName;
374             }
375             if (resolveInfo.providerInfo != null) {
376                 return resolveInfo.providerInfo.packageName;
377             }
378             // Unexpected case.
379             return null;
380         }
381     }
382 
383 
384     /**
385      * Returns whether app identified by {@code packageName} declares itself as a video app.
386      */
isVideoApp(PackageManager packageManager, String packageName)387     public static boolean isVideoApp(PackageManager packageManager, String packageName) {
388         return getAutomotiveAppTypes(packageManager, packageName).contains(TYPE_VIDEO);
389     }
390 
391     /**
392      * Queries an app manifest and resources to determine the types of AAOS app it declares itself
393      * as.
394      *
395      * @param packageManager {@link PackageManager} to query.
396      * @param packageName App package.
397      * @return List of AAOS app-types from XML resources.
398      */
getAutomotiveAppTypes(PackageManager packageManager, String packageName)399     public static List<String> getAutomotiveAppTypes(PackageManager packageManager,
400             String packageName) {
401         ApplicationInfo appInfo;
402         Resources appResources;
403         try {
404             appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
405             appResources = packageManager.getResourcesForApplication(appInfo);
406         } catch (PackageManager.NameNotFoundException e) {
407             Log.w(TAG, "Unexpected package not found for: " + packageName, e);
408             return new ArrayList<>();
409         }
410 
411         int resourceId =
412                 appInfo.metaData != null
413                         ? appInfo.metaData.getInt("com.android.automotive", -1) : -1;
414         if (resourceId == -1) {
415             return new ArrayList<>();
416         }
417         try (XmlResourceParser parser = appResources.getXml(resourceId)) {
418             return parseAutomotiveAppTypes(parser);
419         }
420     }
421 
422     @VisibleForTesting
parseAutomotiveAppTypes(XmlPullParser parser)423     static List<String> parseAutomotiveAppTypes(XmlPullParser parser) {
424         try {
425             // This pattern for parsing can be seen in Javadocs for XmlPullParser.
426             List<String> appTypes = new ArrayList<>();
427             ArrayDeque<String> tagStack = new ArrayDeque<>();
428             int eventType = parser.getEventType();
429             while (eventType != XmlPullParser.END_DOCUMENT) {
430                 if (eventType == XmlPullParser.START_TAG) {
431                     String tag = parser.getName();
432                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
433                         Log.v(TAG, "Start tag " + tag);
434                     }
435                     tagStack.addFirst(tag);
436                     if (!validTagStack(tagStack)) {
437                         Log.w(TAG, "Invalid XML; tagStack: " + tagStack);
438                         return new ArrayList<>();
439                     }
440                     if (TAG_USES.equals(tag)) {
441                         String nameValue =
442                                 parser.getAttributeValue(/* namespace= */ null , ATTRIBUTE_NAME);
443                         if (TextUtils.isEmpty(nameValue)) {
444                             Log.w(TAG, "Invalid XML; uses tag with missing/empty name attribute");
445                             return new ArrayList<>();
446                         }
447                         appTypes.add(nameValue);
448                         if (appTypes.size() > MAX_APP_TYPES) {
449                             Log.w(TAG, "Too many uses tags in automotiveApp tag");
450                             return new ArrayList<>();
451                         }
452                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
453                             Log.v(TAG, "Found appType: " + nameValue);
454                         }
455                     }
456                 } else if (eventType == XmlPullParser.END_TAG) {
457                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
458                         Log.v(TAG, "End tag " + parser.getName());
459                     }
460                     tagStack.removeFirst();
461                 }
462                 eventType = parser.next();
463             }
464             return appTypes;
465         } catch (XmlPullParserException | IOException e) {
466             Log.w(TAG, "Unexpected exception whiling parsing XML resource", e);
467             return new ArrayList<>();
468         }
469     }
470 
validTagStack(ArrayDeque<String> tagStack)471     private static boolean validTagStack(ArrayDeque<String> tagStack) {
472         // Expected to be called after a new tag is pushed on this stack.
473         // Ensures that XML is of form:
474         // <automotiveApp>
475         //     <uses/>
476         //     <uses/>
477         //     ....
478         // </automotiveApp>
479         switch (tagStack.size()) {
480             case 1:
481                 return TAG_AUTOMOTIVE_APP.equals(tagStack.peekFirst());
482             case 2:
483                 return TAG_USES.equals(tagStack.peekFirst());
484             default:
485                 return false;
486         }
487     }
488 
getDisabledActivities(Context context, PackageManager packageManager, Set<String> enabledPackages)489     private static List<ResolveInfo> getDisabledActivities(Context context,
490             PackageManager packageManager, Set<String> enabledPackages) {
491         ContentResolver contentResolverForUser = context.createContextAsUser(
492                 UserHandle.getUserHandleForUid(Process.myUid()), /* flags= */ 0)
493                 .getContentResolver();
494         String settingsValue = Settings.Secure.getString(contentResolverForUser,
495                 KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE);
496         Set<String> disabledPackages = TextUtils.isEmpty(settingsValue) ? new ArraySet<>()
497                 : new ArraySet<>(Arrays.asList(settingsValue.split(
498                         PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR)));
499         if (disabledPackages.isEmpty()) {
500             return Collections.emptyList();
501         }
502 
503         List<ResolveInfo> allActivities = packageManager.queryIntentActivities(
504                 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER),
505                 PackageManager.ResolveInfoFlags.of(PackageManager.GET_RESOLVED_FILTER
506                         | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS));
507 
508         List<ResolveInfo> disabledActivities = new ArrayList<>();
509         for (int i = 0; i < allActivities.size(); ++i) {
510             ResolveInfo info = allActivities.get(i);
511             if (!enabledPackages.contains(info.activityInfo.packageName)
512                     && disabledPackages.contains(info.activityInfo.packageName)) {
513                 disabledActivities.add(info);
514             }
515         }
516         return disabledActivities;
517     }
518 
shouldAddToLaunchables(@onNull ComponentName componentName, @NonNull Set<String> appsToHide, @NonNull Set<String> customMediaComponents, @AppTypes int appTypesToShow, @AppTypes int componentAppType)519     private static boolean shouldAddToLaunchables(@NonNull ComponentName componentName,
520             @NonNull Set<String> appsToHide,
521             @NonNull Set<String> customMediaComponents,
522             @AppTypes int appTypesToShow,
523             @AppTypes int componentAppType) {
524         if (appsToHide.contains(componentName.getPackageName())) {
525             return false;
526         }
527         switch (componentAppType) {
528             // Process media services
529             case APP_TYPE_MEDIA_SERVICES:
530                 // For a media service in customMediaComponents, if its application's launcher
531                 // activity will be shown in the Launcher, don't show the service's icon in the
532                 // Launcher.
533                 if (customMediaComponents.contains(componentName.flattenToString())
534                         && (appTypesToShow & APP_TYPE_LAUNCHABLES) != 0) {
535                     return false;
536                 }
537                 return true;
538             // Process activities
539             case APP_TYPE_LAUNCHABLES:
540                 return true;
541             default:
542                 Log.e(TAG, "Invalid componentAppType : " + componentAppType);
543                 return false;
544         }
545     }
546 
selectMediaSourceAndFinish(Context context, ComponentName componentName, CarMediaManager carMediaManager)547     private static void selectMediaSourceAndFinish(Context context, ComponentName componentName,
548             CarMediaManager carMediaManager) {
549         try {
550             carMediaManager.setMediaSource(componentName, CarMediaManager.MEDIA_SOURCE_MODE_BROWSE);
551             if (context instanceof Activity) {
552                 ((Activity) context).finish();
553             }
554         } catch (CarNotConnectedException e) {
555             Log.e(TAG, "Car not connected", e);
556         }
557     }
558 
559     /**
560      * Gets if an activity is distraction optimized.
561      *
562      * @param carPackageManager The {@link CarPackageManager} system service
563      * @param packageName       The package name of the app
564      * @param activityName      The requested activity name
565      * @return true if the supplied activity is distraction optimized
566      */
isActivityDistractionOptimized( CarPackageManager carPackageManager, String packageName, String activityName)567     static boolean isActivityDistractionOptimized(
568             CarPackageManager carPackageManager, String packageName, String activityName) {
569         boolean isDistractionOptimized = false;
570         // try getting distraction optimization info
571         try {
572             if (carPackageManager != null) {
573                 isDistractionOptimized =
574                         carPackageManager.isActivityDistractionOptimized(packageName, activityName);
575             }
576         } catch (CarNotConnectedException e) {
577             Log.e(TAG, "Car not connected when getting DO info", e);
578         }
579         return isDistractionOptimized;
580     }
581 }
582