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 android.annotation.Nullable; 20 import android.app.ActivityOptions; 21 import android.car.Car; 22 import android.car.CarNotConnectedException; 23 import android.car.content.pm.CarPackageManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.LauncherActivityInfo; 27 import android.content.pm.LauncherApps; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.os.Process; 31 import android.service.media.MediaBrowserService; 32 import android.util.Log; 33 34 import androidx.annotation.NonNull; 35 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** 45 * Util class that contains helper method used by app launcher classes. 46 */ 47 class AppLauncherUtils { 48 49 private static final String TAG = "AppLauncherUtils"; 50 AppLauncherUtils()51 private AppLauncherUtils() { 52 } 53 54 /** 55 * Comparator for {@link AppMetaData} that sorts the list 56 * by the "displayName" property in ascending order. 57 */ 58 static final Comparator<AppMetaData> ALPHABETICAL_COMPARATOR = Comparator 59 .comparing(AppMetaData::getDisplayName, String::compareToIgnoreCase); 60 61 /** 62 * Helper method that launches the app given the app's AppMetaData. 63 * 64 * @param app the requesting app's AppMetaData 65 */ launchApp(Context context, AppMetaData app)66 static void launchApp(Context context, AppMetaData app) { 67 ActivityOptions options = ActivityOptions.makeBasic(); 68 options.setLaunchDisplayId(context.getDisplayId()); 69 context.startActivity(app.getMainLaunchIntent(), options.toBundle()); 70 } 71 72 /** Bundles application and services info. */ 73 static class LauncherAppsInfo { 74 /** Map of all apps' metadata keyed by package name. */ 75 private final Map<String, AppMetaData> mApplications; 76 77 /** Map of all the media services keyed by package name. */ 78 private final Map<String, ResolveInfo> mMediaServices; 79 LauncherAppsInfo(@onNull Map<String, AppMetaData> apps, @NonNull Map<String, ResolveInfo> mediaServices)80 LauncherAppsInfo(@NonNull Map<String, AppMetaData> apps, 81 @NonNull Map<String, ResolveInfo> mediaServices) { 82 mApplications = apps; 83 mMediaServices = mediaServices; 84 } 85 86 /** Returns true if all maps are empty. */ isEmpty()87 boolean isEmpty() { 88 return mApplications.isEmpty() && mMediaServices.isEmpty(); 89 } 90 91 /** Returns whether the given package name is a media service. */ isMediaService(String packageName)92 boolean isMediaService(String packageName) { 93 return mMediaServices.containsKey(packageName); 94 } 95 96 /** Returns the {@link AppMetaData} for the given package name. */ 97 @Nullable getAppMetaData(String packageName)98 AppMetaData getAppMetaData(String packageName) { 99 return mApplications.get(packageName); 100 } 101 102 /** Returns a new list of the applications' {@link AppMetaData}. */ 103 @NonNull getApplicationsList()104 List<AppMetaData> getApplicationsList() { 105 return new ArrayList<>(mApplications.values()); 106 } 107 } 108 109 private final static LauncherAppsInfo EMPTY_APPS_INFO = new LauncherAppsInfo( 110 Collections.emptyMap(), Collections.emptyMap()); 111 112 /** 113 * Gets all the apps that we want to see in the launcher in unsorted order. Includes media 114 * services without launcher activities. 115 * 116 * @param blackList A (possibly empty) list of apps to hide 117 * @param launcherApps The {@link LauncherApps} system service 118 * @param carPackageManager The {@link CarPackageManager} system service 119 * @param packageManager The {@link PackageManager} system service 120 * @return a new {@link LauncherAppsInfo} 121 */ 122 @NonNull getAllLauncherApps( @onNull Set<String> blackList, LauncherApps launcherApps, CarPackageManager carPackageManager, PackageManager packageManager)123 static LauncherAppsInfo getAllLauncherApps( 124 @NonNull Set<String> blackList, 125 LauncherApps launcherApps, 126 CarPackageManager carPackageManager, 127 PackageManager packageManager) { 128 129 if (launcherApps == null || carPackageManager == null || packageManager == null) { 130 return EMPTY_APPS_INFO; 131 } 132 133 List<ResolveInfo> mediaServices = packageManager.queryIntentServices( 134 new Intent(MediaBrowserService.SERVICE_INTERFACE), 135 PackageManager.GET_RESOLVED_FILTER); 136 List<LauncherActivityInfo> availableActivities = 137 launcherApps.getActivityList(null, Process.myUserHandle()); 138 139 Map<String, AppMetaData> apps = new HashMap<>( 140 mediaServices.size() + availableActivities.size()); 141 Map<String, ResolveInfo> mediaServicesMap = new HashMap<>(mediaServices.size()); 142 143 // Process media services 144 for (ResolveInfo info : mediaServices) { 145 String packageName = info.serviceInfo.packageName; 146 mediaServicesMap.put(packageName, info); 147 if (shouldAdd(packageName, apps, blackList)) { 148 final boolean isDistractionOptimized = true; 149 150 Intent intent = new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE); 151 intent.putExtra(Car.CAR_EXTRA_MEDIA_PACKAGE, packageName); 152 153 AppMetaData appMetaData = new AppMetaData( 154 info.serviceInfo.loadLabel(packageManager), 155 packageName, 156 info.serviceInfo.loadIcon(packageManager), 157 isDistractionOptimized, 158 intent, 159 packageManager.getLaunchIntentForPackage(packageName)); 160 apps.put(packageName, appMetaData); 161 } 162 } 163 164 // Process activities 165 for (LauncherActivityInfo info : availableActivities) { 166 String packageName = info.getComponentName().getPackageName(); 167 if (shouldAdd(packageName, apps, blackList)) { 168 boolean isDistractionOptimized = 169 isActivityDistractionOptimized(carPackageManager, packageName, 170 info.getName()); 171 172 AppMetaData appMetaData = new AppMetaData( 173 info.getLabel(), 174 packageName, 175 info.getBadgedIcon(0), 176 isDistractionOptimized, 177 packageManager.getLaunchIntentForPackage(packageName), 178 null); 179 apps.put(packageName, appMetaData); 180 } 181 } 182 183 return new LauncherAppsInfo(apps, mediaServicesMap); 184 } 185 shouldAdd(String packageName, Map<String, AppMetaData> apps, @NonNull Set<String> blackList)186 private static boolean shouldAdd(String packageName, Map<String, AppMetaData> apps, 187 @NonNull Set<String> blackList) { 188 return !apps.containsKey(packageName) && !blackList.contains(packageName); 189 } 190 191 /** 192 * Gets if an activity is distraction optimized. 193 * 194 * @param carPackageManager The {@link CarPackageManager} system service 195 * @param packageName The package name of the app 196 * @param activityName The requested activity name 197 * @return true if the supplied activity is distraction optimized 198 */ isActivityDistractionOptimized( CarPackageManager carPackageManager, String packageName, String activityName)199 static boolean isActivityDistractionOptimized( 200 CarPackageManager carPackageManager, String packageName, String activityName) { 201 boolean isDistractionOptimized = false; 202 // try getting distraction optimization info 203 try { 204 if (carPackageManager != null) { 205 isDistractionOptimized = 206 carPackageManager.isActivityDistractionOptimized(packageName, activityName); 207 } 208 } catch (CarNotConnectedException e) { 209 Log.e(TAG, "Car not connected when getting DO info", e); 210 } 211 return isDistractionOptimized; 212 } 213 } 214