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 java.lang.annotation.RetentionPolicy.SOURCE;
20 
21 import android.app.Activity;
22 import android.app.ActivityOptions;
23 import android.car.Car;
24 import android.car.CarNotConnectedException;
25 import android.car.content.pm.CarPackageManager;
26 import android.car.media.CarMediaManager;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.ApplicationInfo;
31 import android.content.pm.LauncherActivityInfo;
32 import android.content.pm.LauncherApps;
33 import android.content.pm.PackageManager;
34 import android.content.pm.ResolveInfo;
35 import android.content.res.Resources;
36 import android.content.res.XmlResourceParser;
37 import android.os.Process;
38 import android.service.media.MediaBrowserService;
39 import android.text.TextUtils;
40 import android.util.ArraySet;
41 import android.util.Log;
42 
43 import androidx.annotation.IntDef;
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 import androidx.annotation.VisibleForTesting;
47 
48 import org.xmlpull.v1.XmlPullParser;
49 import org.xmlpull.v1.XmlPullParserException;
50 
51 import java.io.IOException;
52 import java.lang.annotation.Retention;
53 import java.util.ArrayDeque;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.Comparator;
57 import java.util.HashMap;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Set;
61 import java.util.function.Predicate;
62 
63 /**
64  * Util class that contains helper method used by app launcher classes.
65  */
66 public class AppLauncherUtils {
67     private static final String TAG = "AppLauncherUtils";
68 
69     @Retention(SOURCE)
70     @IntDef({APP_TYPE_LAUNCHABLES, APP_TYPE_MEDIA_SERVICES})
71     @interface AppTypes {}
72     static final int APP_TYPE_LAUNCHABLES = 1;
73     static final int APP_TYPE_MEDIA_SERVICES = 2;
74 
75     private static final String TAG_AUTOMOTIVE_APP = "automotiveApp";
76     private static final String TAG_USES = "uses";
77     private static final String ATTRIBUTE_NAME = "name";
78     private static final String TYPE_VIDEO = "video";
79 
80     // Max no. of uses tags in automotiveApp XML. This is an arbitrary limit to be defensive
81     // to bad input.
82     private static final int MAX_APP_TYPES = 64;
83 
AppLauncherUtils()84     private AppLauncherUtils() {
85     }
86 
87     /**
88      * Comparator for {@link AppMetaData} that sorts the list
89      * by the "displayName" property in ascending order.
90      */
91     static final Comparator<AppMetaData> ALPHABETICAL_COMPARATOR = Comparator
92             .comparing(AppMetaData::getDisplayName, String::compareToIgnoreCase);
93 
94     /**
95      * Helper method that launches the app given the app's AppMetaData.
96      *
97      * @param app the requesting app's AppMetaData
98      */
launchApp(Context context, Intent intent)99     static void launchApp(Context context, Intent intent) {
100         ActivityOptions options = ActivityOptions.makeBasic();
101         options.setLaunchDisplayId(context.getDisplayId());
102         context.startActivity(intent, options.toBundle());
103     }
104 
105     /** Bundles application and services info. */
106     static class LauncherAppsInfo {
107         /*
108          * Map of all car launcher components' (including launcher activities and media services)
109          * metadata keyed by ComponentName.
110          */
111         private final Map<ComponentName, AppMetaData> mLaunchables;
112 
113         /** Map of all the media services keyed by ComponentName. */
114         private final Map<ComponentName, ResolveInfo> mMediaServices;
115 
LauncherAppsInfo(@onNull Map<ComponentName, AppMetaData> launchablesMap, @NonNull Map<ComponentName, ResolveInfo> mediaServices)116         LauncherAppsInfo(@NonNull Map<ComponentName, AppMetaData> launchablesMap,
117                 @NonNull Map<ComponentName, ResolveInfo> mediaServices) {
118             mLaunchables = launchablesMap;
119             mMediaServices = mediaServices;
120         }
121 
122         /** Returns true if all maps are empty. */
isEmpty()123         boolean isEmpty() {
124             return mLaunchables.isEmpty() && mMediaServices.isEmpty();
125         }
126 
127         /**
128          * Returns whether the given componentName is a media service.
129          */
isMediaService(ComponentName componentName)130         boolean isMediaService(ComponentName componentName) {
131             return mMediaServices.containsKey(componentName);
132         }
133 
134         /** Returns the {@link AppMetaData} for the given componentName. */
135         @Nullable
getAppMetaData(ComponentName componentName)136         AppMetaData getAppMetaData(ComponentName componentName) {
137             return mLaunchables.get(componentName);
138         }
139 
140         /** Returns a new list of all launchable components' {@link AppMetaData}. */
141         @NonNull
getLaunchableComponentsList()142         List<AppMetaData> getLaunchableComponentsList() {
143             return new ArrayList<>(mLaunchables.values());
144         }
145     }
146 
147     private final static LauncherAppsInfo EMPTY_APPS_INFO = new LauncherAppsInfo(
148             Collections.emptyMap(), Collections.emptyMap());
149 
150     /*
151      * Gets the media source in a given package. If there are multiple sources in the package,
152      * returns the first one.
153      */
getMediaSource(@onNull PackageManager packageManager, @NonNull String packageName)154     static ComponentName getMediaSource(@NonNull PackageManager packageManager,
155             @NonNull String packageName) {
156         Intent mediaIntent = new Intent();
157         mediaIntent.setPackage(packageName);
158         mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE);
159 
160         List<ResolveInfo> mediaServices = packageManager.queryIntentServices(mediaIntent,
161                 PackageManager.GET_RESOLVED_FILTER);
162 
163         if (mediaServices == null || mediaServices.isEmpty()) {
164             return null;
165         }
166         String defaultService = mediaServices.get(0).serviceInfo.name;
167         if (!TextUtils.isEmpty(defaultService)) {
168             return new ComponentName(packageName, defaultService);
169         }
170         return null;
171     }
172 
173     /**
174      * Gets all the components that we want to see in the launcher in unsorted order, including
175      * launcher activities and media services.
176      *
177      * @param appsToHide            A (possibly empty) list of apps (package names) to hide
178      * @param customMediaComponents A (possibly empty) list of media components (component names)
179      *                              that shouldn't be shown in Launcher because their applications'
180      *                              launcher activities will be shown
181      * @param appTypes              Types of apps to show (e.g.: all, or media sources only)
182      * @param openMediaCenter       Whether launcher should navigate to media center when the
183      *                              user selects a media source.
184      * @param launcherApps          The {@link LauncherApps} system service
185      * @param carPackageManager     The {@link CarPackageManager} system service
186      * @param packageManager        The {@link PackageManager} system service
187      * @param videoAppPredicate     Predicate that checks if a given {@link ResolveInfo} resolves
188      *                              to a video app. See {@link #VideoAppPredicate}. Media-services
189      *                              of such apps are always excluded.
190      * @param carMediaManager       The {@link CarMediaManager} system service
191      * @return a new {@link LauncherAppsInfo}
192      */
193     @NonNull
getLauncherApps( @onNull Set<String> appsToHide, @NonNull Set<String> customMediaComponents, @AppTypes int appTypes, boolean openMediaCenter, LauncherApps launcherApps, CarPackageManager carPackageManager, PackageManager packageManager, @NonNull Predicate<ResolveInfo> videoAppPredicate, CarMediaManager carMediaManager)194     static LauncherAppsInfo getLauncherApps(
195             @NonNull Set<String> appsToHide,
196             @NonNull Set<String> customMediaComponents,
197             @AppTypes int appTypes,
198             boolean openMediaCenter,
199             LauncherApps launcherApps,
200             CarPackageManager carPackageManager,
201             PackageManager packageManager,
202             @NonNull Predicate<ResolveInfo> videoAppPredicate,
203             CarMediaManager carMediaManager) {
204 
205         if (launcherApps == null || carPackageManager == null || packageManager == null
206                 || carMediaManager == null) {
207             return EMPTY_APPS_INFO;
208         }
209 
210         // Useing new list since we require a mutable list to do removeIf.
211         List<ResolveInfo> mediaServices = new ArrayList<>();
212         mediaServices.addAll(
213                 packageManager.queryIntentServices(
214                         new Intent(MediaBrowserService.SERVICE_INTERFACE),
215                         PackageManager.GET_RESOLVED_FILTER));
216         // Exclude Media Services from Video apps from being considered. These apps should offer a
217         // normal Launcher Activity as an entry point.
218         mediaServices.removeIf(videoAppPredicate);
219 
220         List<LauncherActivityInfo> availableActivities =
221                 launcherApps.getActivityList(null, Process.myUserHandle());
222 
223         int launchablesSize = mediaServices.size() + availableActivities.size();
224         Map<ComponentName, AppMetaData> launchablesMap = new HashMap<>(launchablesSize);
225         Map<ComponentName, ResolveInfo> mediaServicesMap = new HashMap<>(mediaServices.size());
226         Set<String> mEnabledPackages = new ArraySet<>(launchablesSize);
227 
228         // Process media services
229         if ((appTypes & APP_TYPE_MEDIA_SERVICES) != 0) {
230             for (ResolveInfo info : mediaServices) {
231                 String packageName = info.serviceInfo.packageName;
232                 String className = info.serviceInfo.name;
233                 ComponentName componentName = new ComponentName(packageName, className);
234                 mediaServicesMap.put(componentName, info);
235                 mEnabledPackages.add(packageName);
236                 if (shouldAddToLaunchables(componentName, appsToHide, customMediaComponents,
237                         appTypes, APP_TYPE_MEDIA_SERVICES)) {
238                     final boolean isDistractionOptimized = true;
239 
240                     Intent intent = new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE);
241                     intent.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, componentName.flattenToString());
242 
243                     AppMetaData appMetaData = new AppMetaData(
244                         info.serviceInfo.loadLabel(packageManager),
245                         componentName,
246                         info.serviceInfo.loadIcon(packageManager),
247                         isDistractionOptimized,
248                         context -> {
249                             if (openMediaCenter) {
250                                 AppLauncherUtils.launchApp(context, intent);
251                             } else {
252                                 selectMediaSourceAndFinish(context, componentName, carMediaManager);
253                             }
254                         },
255                         context -> {
256                             // getLaunchIntentForPackage looks for a main activity in the category
257                             // Intent.CATEGORY_INFO, then Intent.CATEGORY_LAUNCHER, and returns null
258                             // if neither are found
259                             Intent packageLaunchIntent =
260                                     packageManager.getLaunchIntentForPackage(packageName);
261                             AppLauncherUtils.launchApp(context,
262                                     packageLaunchIntent != null ? packageLaunchIntent : intent);
263                         });
264                     launchablesMap.put(componentName, appMetaData);
265                 }
266             }
267         }
268 
269         // Process activities
270         if ((appTypes & APP_TYPE_LAUNCHABLES) != 0) {
271             for (LauncherActivityInfo info : availableActivities) {
272                 ComponentName componentName = info.getComponentName();
273                 String packageName = componentName.getPackageName();
274                 mEnabledPackages.add(packageName);
275                 if (shouldAddToLaunchables(componentName, appsToHide, customMediaComponents,
276                         appTypes, APP_TYPE_LAUNCHABLES)) {
277                     boolean isDistractionOptimized =
278                         isActivityDistractionOptimized(carPackageManager, packageName,
279                             info.getName());
280 
281                     Intent intent = new Intent(Intent.ACTION_MAIN)
282                         .setComponent(componentName)
283                         .addCategory(Intent.CATEGORY_LAUNCHER)
284                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
285 
286                     AppMetaData appMetaData = new AppMetaData(
287                         info.getLabel(),
288                         componentName,
289                         info.getBadgedIcon(0),
290                         isDistractionOptimized,
291                         context -> AppLauncherUtils.launchApp(context, intent),
292                         null);
293                     launchablesMap.put(componentName, appMetaData);
294                 }
295             }
296 
297             List<ResolveInfo> disabledActivities = getDisabledActivities(
298                     packageManager, mEnabledPackages);
299 
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                         context -> {
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(context, intent);
336                         },
337                         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 
489 
getDisabledActivities( PackageManager packageManager, Set<String> enabledPackages)490     private static List<ResolveInfo> getDisabledActivities(
491             PackageManager packageManager, Set<String> enabledPackages) {
492         List<ResolveInfo> allActivities = packageManager.queryIntentActivities(
493                 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER),
494                 PackageManager.GET_RESOLVED_FILTER
495                         | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS);
496 
497         List<ResolveInfo> disabledActivities = new ArrayList<>();
498         for (int i = 0; i < allActivities.size(); ++i) {
499             ResolveInfo info = allActivities.get(i);
500             try {
501                 if (!enabledPackages.contains(info.activityInfo.packageName)
502                         && packageManager.getApplicationEnabledSetting(
503                                 info.activityInfo.packageName)
504                         == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
505                     disabledActivities.add(info);
506                 }
507             } catch (RuntimeException e) {
508                 if (e instanceof IllegalArgumentException) {
509                     /* Don't throw exception when the package is missing, which happens when a
510                      * package is being uninstalled and the internal datastructures are being
511                      * updated.
512                      */
513                     continue;
514                 }
515                 throw e;
516             }
517         }
518         return disabledActivities;
519     }
520 
shouldAddToLaunchables(@onNull ComponentName componentName, @NonNull Set<String> appsToHide, @NonNull Set<String> customMediaComponents, @AppTypes int appTypesToShow, @AppTypes int componentAppType)521     private static boolean shouldAddToLaunchables(@NonNull ComponentName componentName,
522             @NonNull Set<String> appsToHide,
523             @NonNull Set<String> customMediaComponents,
524             @AppTypes int appTypesToShow,
525             @AppTypes int componentAppType) {
526         if (appsToHide.contains(componentName.getPackageName())) {
527             return false;
528         }
529         switch (componentAppType) {
530             // Process media services
531             case APP_TYPE_MEDIA_SERVICES:
532                 // For a media service in customMediaComponents, if its application's launcher
533                 // activity will be shown in the Launcher, don't show the service's icon in the
534                 // Launcher.
535                 if (customMediaComponents.contains(componentName.flattenToString())
536                         && (appTypesToShow & APP_TYPE_LAUNCHABLES) != 0) {
537                     return false;
538                 }
539                 return true;
540             // Process activities
541             case APP_TYPE_LAUNCHABLES:
542                 return true;
543             default:
544                 Log.e(TAG, "Invalid componentAppType : " + componentAppType);
545                 return false;
546         }
547     }
548 
selectMediaSourceAndFinish(Context context, ComponentName componentName, CarMediaManager carMediaManager)549     private static void selectMediaSourceAndFinish(Context context, ComponentName componentName,
550             CarMediaManager carMediaManager) {
551         try {
552             carMediaManager.setMediaSource(componentName, CarMediaManager.MEDIA_SOURCE_MODE_BROWSE);
553             if (context instanceof Activity) {
554                 ((Activity) context).finish();
555             }
556         } catch (CarNotConnectedException e) {
557             Log.e(TAG, "Car not connected", e);
558         }
559     }
560 
561     /**
562      * Gets if an activity is distraction optimized.
563      *
564      * @param carPackageManager The {@link CarPackageManager} system service
565      * @param packageName       The package name of the app
566      * @param activityName      The requested activity name
567      * @return true if the supplied activity is distraction optimized
568      */
isActivityDistractionOptimized( CarPackageManager carPackageManager, String packageName, String activityName)569     static boolean isActivityDistractionOptimized(
570             CarPackageManager carPackageManager, String packageName, String activityName) {
571         boolean isDistractionOptimized = false;
572         // try getting distraction optimization info
573         try {
574             if (carPackageManager != null) {
575                 isDistractionOptimized =
576                         carPackageManager.isActivityDistractionOptimized(packageName, activityName);
577             }
578         } catch (CarNotConnectedException e) {
579             Log.e(TAG, "Car not connected when getting DO info", e);
580         }
581         return isDistractionOptimized;
582     }
583 }
584