blob: ffffa65b56eef343baaeab4b9bd85bc05f7da5e0 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.carlauncher;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.Nullable;
import android.app.Activity;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.content.pm.CarPackageManager;
import android.car.media.CarMediaManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Process;
import android.service.media.MediaBrowserService;
import android.text.TextUtils;
import android.util.Log;
import com.android.car.media.common.source.MediaSourceViewModel;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Util class that contains helper method used by app launcher classes.
*/
class AppLauncherUtils {
private static final String TAG = "AppLauncherUtils";
@Retention(SOURCE)
@IntDef({APP_TYPE_LAUNCHABLES, APP_TYPE_MEDIA_SERVICES})
@interface AppTypes {}
static final int APP_TYPE_LAUNCHABLES = 1;
static final int APP_TYPE_MEDIA_SERVICES = 2;
private AppLauncherUtils() {
}
/**
* Comparator for {@link AppMetaData} that sorts the list
* by the "displayName" property in ascending order.
*/
static final Comparator<AppMetaData> ALPHABETICAL_COMPARATOR = Comparator
.comparing(AppMetaData::getDisplayName, String::compareToIgnoreCase);
/** Bundles application and services info. */
static class LauncherAppsInfo {
/*
* Map of all car launcher components' (including launcher activities and media services)
* metadata keyed by ComponentName.
*/
private final Map<ComponentName, AppMetaData> mLaunchables;
/** Map of all the media services keyed by ComponentName. */
private final Map<ComponentName, ResolveInfo> mMediaServices;
LauncherAppsInfo(@NonNull Map<ComponentName, AppMetaData> components,
@NonNull Map<ComponentName, ResolveInfo> mediaServices) {
mLaunchables = components;
mMediaServices = mediaServices;
}
/** Returns true if all maps are empty. */
boolean isEmpty() {
return mLaunchables.isEmpty() && mMediaServices.isEmpty();
}
/**
* Returns whether the given componentName is a media service.
*/
boolean isMediaService(ComponentName componentName) {
return mMediaServices.containsKey(componentName);
}
/** Returns the {@link AppMetaData} for the given componentName. */
@Nullable
AppMetaData getAppMetaData(ComponentName componentName) {
return mLaunchables.get(componentName);
}
/** Returns a new list of all launchable components' {@link AppMetaData}. */
@NonNull
List<AppMetaData> getLaunchableComponentsList() {
return new ArrayList<>(mLaunchables.values());
}
}
private final static LauncherAppsInfo EMPTY_APPS_INFO = new LauncherAppsInfo(
Collections.emptyMap(), Collections.emptyMap());
/*
* Gets the media source in a given package. If there are multiple sources in the package,
* returns the first one.
*/
static ComponentName getMediaSource(@NonNull PackageManager packageManager,
@NonNull String packageName) {
Intent mediaIntent = new Intent();
mediaIntent.setPackage(packageName);
mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE);
List<ResolveInfo> mediaServices = packageManager.queryIntentServices(mediaIntent,
PackageManager.GET_RESOLVED_FILTER);
if (mediaServices == null || mediaServices.isEmpty()) {
return null;
}
String defaultService = mediaServices.get(0).serviceInfo.name;
if (!TextUtils.isEmpty(defaultService)) {
return new ComponentName(packageName, defaultService);
}
return null;
}
/**
* Gets all the components that we want to see in the launcher in unsorted order, including
* launcher activities and media services.
*
* @param blackList A (possibly empty) list of apps (package names) to hide
* @param appTypes Types of apps to show (e.g.: all, or media sources only)
* @param openMediaCenter Whether launcher should navigate to media center when the user
* selects a media source.
* @param launcherApps The {@link LauncherApps} system service
* @param carPackageManager The {@link CarPackageManager} system service
* @param packageManager The {@link PackageManager} system service
* @return a new {@link LauncherAppsInfo}
*/
@NonNull
static LauncherAppsInfo getLauncherApps(
@NonNull Set<String> blackList,
@AppTypes int appTypes,
boolean openMediaCenter,
LauncherApps launcherApps,
CarPackageManager carPackageManager,
PackageManager packageManager) {
if (launcherApps == null || carPackageManager == null || packageManager == null) {
return EMPTY_APPS_INFO;
}
List<ResolveInfo> mediaServices = packageManager.queryIntentServices(
new Intent(MediaBrowserService.SERVICE_INTERFACE),
PackageManager.GET_RESOLVED_FILTER);
List<LauncherActivityInfo> availableActivities =
launcherApps.getActivityList(null, Process.myUserHandle());
Map<ComponentName, AppMetaData> components = new HashMap<>(
mediaServices.size() + availableActivities.size());
Map<ComponentName, ResolveInfo> mediaServicesMap = new HashMap<>(mediaServices.size());
Set<String> mediaPackages = new HashSet<>();
// Process media services
if ((appTypes & APP_TYPE_MEDIA_SERVICES) != 0) {
for (ResolveInfo info : mediaServices) {
String packageName = info.serviceInfo.packageName;
String className = info.serviceInfo.name;
ComponentName componentName = new ComponentName(packageName, className);
mediaServicesMap.put(componentName, info);
if (shouldAdd(componentName, components, blackList)) {
mediaPackages.add(packageName);
final boolean isDistractionOptimized = true;
Intent intent = new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE);
intent.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, componentName.flattenToString());
AppMetaData appMetaData = new AppMetaData(
info.serviceInfo.loadLabel(packageManager),
componentName,
info.serviceInfo.loadIcon(packageManager),
isDistractionOptimized,
context -> {
if (openMediaCenter) {
context.startActivity(intent);
} else {
selectMediaSourceAndFinish(context, componentName);
}
},
context -> context.startActivity(
packageManager.getLaunchIntentForPackage(packageName)));
components.put(componentName, appMetaData);
}
}
}
// Process activities
if ((appTypes & APP_TYPE_LAUNCHABLES) != 0) {
for (LauncherActivityInfo info : availableActivities) {
ComponentName componentName = info.getComponentName();
String packageName = componentName.getPackageName();
if (shouldAdd(componentName, components, blackList)) {
boolean isDistractionOptimized =
isActivityDistractionOptimized(carPackageManager, packageName,
info.getName());
Intent intent = new Intent(Intent.ACTION_MAIN)
.setComponent(componentName)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
AppMetaData appMetaData = new AppMetaData(
info.getLabel(),
componentName,
info.getBadgedIcon(0),
isDistractionOptimized,
context -> context.startActivity(intent),
null);
components.put(componentName, appMetaData);
}
}
}
return new LauncherAppsInfo(components, mediaServicesMap);
}
private static boolean shouldAdd(ComponentName componentName,
Map<ComponentName, AppMetaData> components,
@NonNull Set<String> blackList) {
return !components.containsKey(componentName) && !blackList.contains(
componentName.getPackageName());
}
private static void selectMediaSourceAndFinish(Context context, ComponentName componentName) {
try {
Car carApi = Car.createCar(context);
CarMediaManager manager = (CarMediaManager) carApi
.getCarManager(Car.CAR_MEDIA_SERVICE);
manager.setMediaSource(componentName);
if (context instanceof Activity) {
((Activity) context).finish();
}
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car not connected", e);
}
}
/**
* Gets if an activity is distraction optimized.
*
* @param carPackageManager The {@link CarPackageManager} system service
* @param packageName The package name of the app
* @param activityName The requested activity name
* @return true if the supplied activity is distraction optimized
*/
static boolean isActivityDistractionOptimized(
CarPackageManager carPackageManager, String packageName, String activityName) {
boolean isDistractionOptimized = false;
// try getting distraction optimization info
try {
if (carPackageManager != null) {
isDistractionOptimized =
carPackageManager.isActivityDistractionOptimized(packageName, activityName);
}
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car not connected when getting DO info", e);
}
return isDistractionOptimized;
}
}